ここまでの 7 回で、Git の地図はひととおり見てきました。コミットの鎖、3 つの領域、ブランチとマージ、取り消しとやり直し、チームでの協働、そして worktree。仕組みは分かった。でも、読んで分かったことと、自分の手で動かせることの間には、まだ距離があります。
その距離を埋めるのが、この実戦編です。小さな TODO アプリを 1 つ、ゼロから育てながら、これまで学んだ操作を全部使い切ります。しかも、コードは 1 行も自分で書きません。
AI に「こう作って」とひと言頼む。私たちの仕事は、技術者になることではなく、技術の分かるマネージャーとして AI に的確に頼み、結果を Git で守ることです。初回の今日は、アプリをゼロから作り、最初の記録を残し、GitHub で世界に公開するところまでを一気に通します。
TL;DR
- 実戦編は、1 つの TODO アプリを育てながら本編で学んだ Git 操作を全部使います。初回はゼロから公開まで一本道で通します。
- アプリは AI にひと言で作らせます。素の HTML・CSS・JavaScript だけなので、インストールもビルドも要りません。
- プロジェクトは 最初に
.gitignoreを置く のが習慣です。そのあとgit init→add→commitで最初の記録を残し、gh repo create一発で公開します。 - AI に頼むと、結果は「こちらが指定した値」と「AI が勝手に決めた値」に分かれます。この線引きを毎回はっきりさせるのが、実戦編の主役です。
この連載で作るもの
これから育てるのは、ブラウザだけで動くシンプルな TODO アプリ です。最終的には、タスクの追加・完了・削除、残り件数の表示、ブラウザを閉じても消えない保存、絞り込みフィルタ、ダークモード、並び替えまでを備え、GitHub Pages で公開し、push するだけで自動デプロイされる状態まで持っていきます。
大事なのは、機能を 1 つ足すたびに、本編で見た Git 操作とちょうど 1 対 1 で対応させていくことです。
- 今回 (第 8 回): ゼロからアプリを作り、
git init→最初のコミット→gh repo createで公開 - 第9回: 完了チェックと削除を 別のブランチ で足し、わざとコンフリクトを起こして解決する
- 第10回: 保存機能を入れながら
revert/reset/reflogで取り消しとやり直しを体験する - 第11回: 絞り込みフィルタを プルリクエスト で取り込む
- 第12回: ダークモードと並び替えを worktree で並行開発する
- 第13回: GitHub Pages で公開し、
v1.0のタグを打つ - 第14回: GitHub Actions で push したら自動でデプロイされる仕組みを作る
本編が「Git の仕組みを内側から理解する」回だったとすれば、実戦編は「その仕組みを実際の開発で使う」回です。仕組みの説明は本編に譲り、手を動かすことに集中します。
今日のゴールまでの流れです。
flowchart TD
L["手元のフォルダ<br/>index.html を作る"] -->|"git init → add → commit"| G["Git の履歴<br/>最初の記録を残す"]
G -->|"gh repo create --push"| H["GitHub<br/>世界に公開する"]手元でファイルを作り、Git で記録し、GitHub へ送る。この左から右への一本道を、今日のうちに最後まで通します。
まず AI に「最小の TODO」を作らせる
何から始めればいいか。まずは動くものを 1 つ、AI に作らせます。完璧を目指さず、「テキストを入れて追加するとリストに増える」だけの最小形でかまいません。
私が実際に AI へ渡したのは、次のひと言です。
ブラウザで開くだけで動く、最小のTODOアプリを作ってほしい。テキストを入力して 『追加』を押すと下のリストに増える、それだけでいい。あとで GitHub Pages にそのまま 載せたいので、インストールやビルドが要らないように、素の HTML・CSS・JavaScript だけで 作って。外部ライブラリやフレームワークは使わないで。
ポイントは 2 つだけ伝えました。「最小の TODO であること」と「素の HTML・CSS・JavaScript だけで作ること」です。あとで GitHub Pages にそのまま載せたいので、ビルドや外部ライブラリが要らない形に縛っておきます。これだけで、AI(今回は Claude) は次の index.html を 1 ファイルで返してきました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: sans-serif;
background: #f5f5f5;
display: flex;
justify-content: center;
padding: 40px 16px;
}
.container {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
padding: 32px;
width: 100%;
max-width: 480px;
}
h1 {
font-size: 1.4rem;
margin-bottom: 24px;
color: #222;
}
.input-row {
display: flex;
gap: 8px;
margin-bottom: 24px;
}
.input-row input {
flex: 1;
padding: 8px 12px;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
outline: none;
}
.input-row input:focus {
border-color: #4a90e2;
}
.input-row button {
padding: 8px 16px;
font-size: 1rem;
background: #4a90e2;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
.input-row button:hover {
background: #357abd;
}
ul {
list-style: none;
}
li {
padding: 10px 12px;
border-bottom: 1px solid #eee;
color: #333;
font-size: 1rem;
}
li:last-child {
border-bottom: none;
}
</style>
</head>
<body>
<div class="container">
<h1>TODO</h1>
<div class="input-row">
<input type="text" id="todo-input" placeholder="やることを入力..." />
<button id="add-btn">追加</button>
</div>
<ul id="todo-list"></ul>
</div>
<script>
const input = document.getElementById('todo-input');
const addBtn = document.getElementById('add-btn');
const list = document.getElementById('todo-list');
function addTodo() {
const text = input.value.trim();
if (!text) return;
const li = document.createElement('li');
li.textContent = text;
list.appendChild(li);
input.value = '';
input.focus();
}
addBtn.addEventListener('click', addTodo);
input.addEventListener('keydown', function (e) {
if (e.key === 'Enter') addTodo();
});
</script>
</body>
</html>
このファイルをブラウザで開くと、白いカードの中に入力欄と「追加」ボタンが並び、入力して押すとリストに増えます。注文どおりの最小形です。
ここで一度立ち止まって、何を指定して、何を指定しなかったか を見てみます。私が伝えたのは「最小の TODO」「素の HTML・CSS・JavaScript」の 2 点だけでした。残りは AI が勝手に埋めています。
- 空欄のまま追加を押しても増えない (
trim()で空白を落として弾く) - Enter キーでも追加できる
- タスクの保存はしない(ブラウザを再読み込みすると消える)
- 各タスクに id を振らず、画面に直接足している
- 配色は青 (
#4a90e2)、カード幅は最大 480px、入力欄の案内文は「やることを入力…」
どれも私は頼んでいません。AI が「最小の TODO ならこうだろう」と補った部分です。保存やタスクごとの id は、第10回以降で必要になったときに足していきます。最初の段階で全部を求めない。これも AI に頼むときのコツです。
コミットの前に、.gitignore を最初に置く
動くものができました。記録に残す前に、もうひと手間かけます。.gitignore を最初に置く ことです。
なぜ最初なのか。プロジェクトを作ると、自分では作った覚えのないファイルが紛れ込んできます。代表が macOS の .DS_Store(フォルダの表示設定を OS が勝手に覚える隠しファイル) や、エディタが作る .vscode/・.idea/ です。どれも他の人には関係のないゴミで、最初の記録に巻き込みたくありません。入口で「これは入れない」と宣言しておきます。
.gitignore はゼロから手書きしなくてかまいません。GitHub が github/gitignore という定番テンプレート集を公開しています。これをベースに用意する作業も、AI に任せられます。
私が AI に渡したのは、このひと言です。
このプロジェクトに合う .gitignore を、GitHub が配布している定番のテンプレートをベースに用意して。
このひと言で、AI は github/gitignore を実際に見にいき、テンプレートを取得して .gitignore を組み立てました。おもしろいのは、ここで AI が下した判断です。
素の HTML/CSS/JavaScript のプロジェクトには、ぴたりと合うテンプレートがありません。一番近いのは Node.gitignore ですが、中身は node_modules/ や dist/ など、npm を使う前提のものばかり。このアプリにそれらは存在しないので、AI は「名前を借りただけで意味がない」と見送りました。代わりに選んだのが、Global/(OS やエディタなど、言語によらず共通のもの) にある macOS・Windows・Linux・VS Code・JetBrains のテンプレートです。ビルドの生成物が出ない素の HTML では、リポジトリを汚す原因がもっぱら OS とエディタのゴミだからです。
出来上がった .gitignore は、何をベースにしたかをヘッダに書き込んだうえで、OS・エディタごとに区切られていました。全部で 140 行ほどあるので、ヘッダと代表的な行だけ抜き出します。
# ============================================================= # .gitignore for plain HTML/CSS/JavaScript project # Base: github/gitignore Global templates # - Global/macOS.gitignore # - Global/VisualStudioCode.gitignore # - Global/JetBrains.gitignore # - Global/Windows.gitignore # - Global/Linux.gitignore # ============================================================= # macOS .DS_Store ._* .Spotlight-V100 .Trashes # (中略:Windows / Linux / VS Code / JetBrains のセクションが続く) # このプロジェクト用の追加 *.log .env .env.local .sass-cache/ *.css.map *.tmp
.DS_Store を 1 行書くだけでも始められます。でも「定番テンプレートをベースに」とひと言頼むだけで、OS やエディタをまたいだ抜け漏れのない無視リストが、出典コメントつきで一度に手に入ります。.gitignore に何を入れるかという考え方(秘密情報・生成物・環境固有のゴミの 3 種類)は、第6回でひととおり扱いました。
ひとつだけ、順番にまつわる落とし穴に触れておきます。.gitignore が効くのは、まだ Git に追跡されていないファイルだけ です。もし先にコミットしてしまって、あとから .gitignore に書いても、すでに追跡中のファイルは無視されません。だから「最初に置く」のが一番ラクなのです。置き忘れて追跡されてしまった場合は、git rm --cached で追跡だけを外す必要があります。この対処は第6回で詳しく扱ったので、ここでは「最初に置けば、この手間がそもそも要らない」とだけ覚えておけば十分です。
Git で「最初の記録」を残す
index.html と .gitignore がそろいました。ここで Git の出番です。git init でこのフォルダを履歴管理の対象にし、git add で記録するものを選び、git commit で最初のスナップショットを撮ります。
コミットが「変更の差分」ではなく「その瞬間の全体を撮った 1 枚の写真 (スナップショット)」だという話は、第1回と第3回で確かめたとおりです。今から撮るのは、この小さなアプリの最初の 1 枚です。
私が AI に渡したのは、このひと言です。
このフォルダを、これから変更履歴を残せるようにして。今の状態を『最初の記録』として残して。
コマンドを 1 つも指定していません。それでも AI は、次の 3 つを順にこなしました。
git init git add index.html git commit -m "Initial commit: add todo app"
ここでも「指定した値」と「AI が勝手に決めた値」を見ておきます。私が伝えたのは「変更履歴を残せるようにして」「今の状態を最初の記録に」だけです。次の点は AI が補いました。
- 最初のブランチの名前を
mainにした - コミットメッセージを
Initial commit: add todo appと英語で命名した git add .のような全体指定ではなく、git add index.htmlとファイルを名指しした
コミットメッセージは未来の自分への手紙です。AI が付けた Initial commit: add todo app(最初のコミット。todo アプリを追加) は、最初の 1 枚として過不足ありません。気に入らなければ「最初のコミットメッセージは日本語で『TODO アプリの最初の記録』にして」と頼めば、その文言で残してくれます。
flowchart TD
W["index.html / .gitignore<br/>手元のファイル"] -->|"git add"| S["ステージング<br/>記録する物を選ぶ"]
S -->|"git commit"| R["最初のスナップショット<br/>main の先頭に残る"]作業ツリー(手元の編集場所) から、ステージング (買い物カゴ) を経て、リポジトリ (レジ) へ。第4回で見た 3 つの領域を、今まさに最初の 1 往復として通りました。
GitHub に公開する
手元に最初の記録が残りました。最後に、これを GitHub に公開します。
AI に渡したのは、このひと言だけです。
これを GitHub の公開リポジトリ todo-app-git-deep-dive に上げたい。
新規のプロジェクトなら、この一本道で公開まで終わります。
gh repo create todo-app-git-deep-dive --public --source=. --push
この 1 行が、3 つの仕事をまとめてやってくれます。GitHub 上に todo-app-git-deep-dive という公開リポジトリを作り、手元のフォルダをその送り先 (リモート) として登録し、コミットを push する。第3回や第4回では git remote add と git push を分けて打ちましたが、gh repo create --source=. --push は「作る・つなぐ・送る」を一度に済ませます。
公開でも、線引きは同じです。私が指定したのは「リポジトリ名 (todo-app-git-deep-dive)」と「公開 (--public)」の 2 点です。残りは AI が補いました。
- 送り先 (リモート) の名前を
originにした - 手元の
mainと GitHub 側のmainを結びつけ、次回からgit pushだけで送れるようにした - README を付けるかどうかを判断した (今回は付けず、
index.htmlだけを上げた)
実行後、表示された URL を開くと、リポジトリが公開されているのが確認できます。
https://github.com/ikuma-hiroyuki/todo-app-git-deep-dive
公開ページの index.html をブラウザで開いて、入力欄に 2 件入れれば 2 件並び、空欄のまま追加を押しても増えず、Enter でも追加できる。注文どおりに動いています。手元のフォルダから始まった一本道が、これで世界に開かれた 1 ページまでつながりました。
AI に頼むときの言い方
今日の作業を振り返ると、毎回「こちらが指定した値」と「指定しなかったので AI が勝手に決めた値」に分かれていました。これを整理すると、AI への頼み方のコツが見えてきます。
| 種別 | こちらが指定した値 | 指定しないと AI が勝手に決める値 |
|---|---|---|
| アプリ | 最小の TODO・素の HTML/CSS/JS・ライブラリなし | 空欄の弾き方・Enter 対応・保存の有無・配色・案内文 |
| .gitignore | 「定番テンプレートをベースに」 | どのテンプレを選ぶか (Global の 5 本)・Node を流用しない判断・独自に足すルール |
| 記録 | 「最初の記録を残して」 | ブランチ名 main・コミットメッセージ・add する対象 |
| 公開 | リポジトリ名・公開 (public) | リモート名 origin・追従設定・README の有無 |
忘れてはいけないのは、AI が勝手に決めた値が悪いわけではない、という点です。最小形を作る段階では、むしろ任せたほうが速い。問題は、どこを自分で決めるべきか を分かったうえで任せているか、です。たとえば「保存の有無」は、今は任せていい。でも第10回で保存機能を入れるときは、私が「ブラウザを閉じても残るように」と伝えます。任せる場所と、握る場所。この線引きができると、AI への指示はぐっと的確になります。
まとめ
実戦編の初回として、小さな TODO アプリをゼロから作り、GitHub に公開するまでを一本道で通しました。
- アプリは AI にひと言で作らせた。指定したのは「最小の TODO」「素の HTML/CSS/JS」の 2 点だけ。
- 記録の前に、GitHub の定番テンプレート集を元にした
.gitignoreを AI に用意させ、入口に置いた。素の HTML には専用テンプレがなく、AI は OS・エディタ用の Global テンプレを選んだ。 git init→add→commitで最初のスナップショットを撮り、gh repo create --public --source=. --pushで公開した。- AI に頼むと、結果は「指定した値」と「AI が勝手に決めた値」に分かれる。この線引きが、これから毎回の主役になる。
難しそうだった「世界に公開」が、コマンド 1 つで済む。この手応えが、実戦編を進める燃料になります。次回は、完成したこのアプリに「完了チェック」と「削除」を足します。しかも、いきなり main を触らず別のブランチで作り、わざとコンフリクトを起こして解決します。第4回で仕組みを見たブランチとマージを、今度は本物のアプリで動かします。
なお、この記事で AI に渡したひと言は、すべて Claude(Sonnet) に実際に渡し、書いてあるとおりに動くことを確かめたものです。モデルやその日の状態によって、AI が勝手に決める値 (配色やコミットメッセージなど) は変わります。指定しなかった部分は変わりうる、と思って読んでください。
パイソンエンジニア部 

