commitと履歴|コマンドライン・Git・変更管理
このページはプロモーション(広告)を含みます。
コマンドライン・Git・変更管理(初級・読む30分+演習35分)
この授業のゴール
- 混ざった変更を目的ごとに分け、1つのcommitに1つの目的が入るようにステージしてcommitできる
- 何を・なぜ変えたかがわかるcommitメッセージを書き、悪い例との違いを説明できる
- git log・git show に相当する操作で履歴をたどり、ある値が変わったcommitを特定できる
- commitとバックアップの違い、取り消せる操作と取り消せない操作(git restore など)を区別できる
仕事でどう使うか
「先週の会議資料と今週の資料で、同じ月の売上が違う。何を変えた?」――このとき履歴が update、修正、aaa だけだと、誰も答えられません。commit(コミット)は、ステージした内容を履歴の1点として、説明つきで記録する操作です。良いcommitが並んだ履歴は、そのままプロジェクトの作業記録になります。
| 路線 | この授業の使いどころ |
|---|---|
| DS | 数字が変わった理由を、履歴から「このcommitで欠損の扱いを変えたため」と説明する |
| ML | 実験ログにcommit IDを書き、どのコードで学習したモデルかを後から再現できるようにする |
| DX | 業務ルール(閾値・マスタ)の変更を、承認理由つきで記録し、監査や引き継ぎに使う |
| RS | 論文の図を作ったコードの版をcommit IDで特定し、査読対応や再現依頼に応える |
前提
前提:
この授業でも、模擬Git(ブラウザ内の練習用モデル)と、Macのターミナルの実Gitを区別して使います。模擬のcommit IDは c1, c2 …、実Gitでは 3f9a2c1 のような16進数です。
身近な状況:「update」だらけの履歴
2つのチームの履歴(新しい順)を比べます。どちらも同じ作業をしました。
| チームX | チームY |
|---|---|
e41b2 update |
9c0d1 READMEに実行場所を追記 |
77a90 修正 |
5b2e7 金額0円の行を欠損扱いしない(返品0円の注文が消えていた) |
1f3c8 いろいろ |
a3f10 集計スクリプトの初版を追加 |
4月の売上が先週と違う理由を聞かれたとき、チームYは2行目を見れば答えられます。「金額0円の行が消えていたのを直したので、件数が増えた」。チームXは、全部の差分を開いて読み比べるしかありません。
違いは2つです。①1つのcommitに1つの目的だけが入っている。②メッセージに何を・なぜが書いてある。この授業ではこの2つを身につけます。
直感的な説明:commitは「説明つきの節目」
commitは、作業の意味のある節目で撮る写真のようなものです。写真の裏に「何の写真か、なぜ撮ったか」を書いておくと、何年後でも探せます。
- 撮る範囲を選ぶ(ステージ)→ 撮る(commit)→ 裏に説明を書く(メッセージ)
- 1枚に1つの場面。旅行と会議を同じ写真に収めない。
たとえと実際の違い:写真は撮った瞬間しか残りませんが、Gitのcommitは親commitへの矢印を持ち、鎖のようにつながります。だから「この節目の直前はどうだったか」を機械的にたどれます。また、写真の束(commitの履歴)はMacの中の
.gitにあるだけなので、Macが壊れれば一緒に失われます。commitはバックアップではありません。
正確な定義:commitの中身・単位・メッセージ
1つのcommitは次の情報を持ちます。
| 要素 | 内容 |
|---|---|
| スナップショット | commit時点のステージにあった全ファイルの内容(差分ではなく一式) |
| 親(parent) | 直前のcommit。最初のcommitには親がない。マージcommitは親が2つ(branchとmerge) |
| 作者と日時 | user.name・user.email と記録した時刻 |
| メッセージ | 変更の要約と理由 |
| commit ID | 上の全情報から計算されるハッシュ値(内容から決まる指紋のような値)。1文字でも違えば別のIDになる |
良いcommitの単位:①目的が1つ(「バグ修正」と「文言の調整」を混ぜない)、②その時点でコードが壊れていない、③差分を読めば理由が追える大きさ。
メッセージの型:1行目に50字程度の要約(何をしたか)。必要なら空行のあとに本文で「なぜ」「影響」を書きます。
金額0円の行を欠損扱いしない
返品処理で0円の注文が正しく存在するのに、amount == 0 を
欠損として除外していた。空欄だけを欠損とするよう変更。
4月の件数が 1,203 → 1,215 に増える。
履歴を見るコマンド(実Git)
| コマンド | 働き | 安全性 |
|---|---|---|
git log --oneline |
1行ずつ履歴を表示(新しい順) | 読むだけ |
git log -p -- ファイル |
そのファイルの変更履歴を差分つきで表示 | 読むだけ |
git show ID |
そのcommitのメッセージと差分を表示 | 読むだけ |
git diff ID1 ID2 |
2つのcommitの差 | 読むだけ |
git restore --staged ファイル |
ステージから外す(作業ツリーの変更は残る) | 安全 |
git restore ファイル |
作業ツリーの変更を捨ててステージの内容に戻す | 未commitの変更は戻せない |
git log の表示が1画面を超えると、less(ページ送りの表示)に切り替わります。スペースで次へ、q で終了です。
段階付き例題:混ざった変更を2つのcommitに分ける
状況:作業ツリーに次の3つの変更が混ざっています。
| ファイル | 変更 | 記録すべきか |
|---|---|---|
| src/clean.py | 0円の行を除外していたバグを修正 | する(目的1) |
| src/clean.py | 確認用に入れた print('DEBUG', len(rows)) |
しない |
| README.md | 実行場所の説明を追記 | する(目的2) |
ステップ1:status と diff で全体を見る。3つの変更を確認し、目的ごとに分類します(上の表)。
ステップ2:目的1だけをステージ。clean.py の修正部分だけを git add -p src/clean.py で選びます(DEBUG行の塊では n)。git diff --staged で修正だけが入っていることを確かめます。
ステップ3:commit。git commit -m "金額0円の行を欠損扱いしない"。
ステップ4:目的2をステージしてcommit。git add README.md → git commit -m "READMEに実行場所を追記"。
ステップ5:確認。git log --oneline に2つのcommitが増え、git status には clean.py(DEBUG行だけ)が unstaged で残ります。DEBUG行は確認が終わったらエディタで消します。
| 入力 | 処理 | 出力 | 意味 |
|---|---|---|---|
| 3つの混ざった変更 | 目的ごとにステージ→commit を2回 | commit 2つ+未記録のDEBUG行 | 片方だけ取り消す・説明することが可能になる |
Macのターミナルで試す(実Git):前の授業の ~/se01-practice/git-demo を使います。
cd ~/se01-practice/git-demo
git status
git add -p report.md
git commit -m "対象月を2026-04に更新"
git add report.md
git commit -m "担当を鈴木に変更(上司確認済み)"
git log --oneline
git show HEAD
git log -p -- report.md
git status で前回の状態を確かめてから進めます。前回 A をすでにステージしていれば、git add -p は「担当の塊だけ」を聞いてくるので n と答えて、1つ目のcommitに進めます(前回 A までcommit済みなら、1つ目のcommitは飛ばします)。最後の3行で、2つのcommitの差分と、report.md の全履歴が見られます(q で終了)。
注意:
git commitを-mなしで実行すると、メッセージ入力用のエディタ(初期設定では vim のことが多い)が開きます。慣れないうちは、Escキーを押してから:q!と入力してEnterで抜けられます(メッセージが空なのでcommitは中止されます)。その後-mつきでやり直してください。
操作して理解する:模擬Gitで履歴をたどる
ブラウザ内の模擬で、3つのcommitを作り、履歴から「閾値を変えたcommit」を探します。各commitは (ID, メッセージ, その時点のファイル一式, 親のID) のタプルとしてリスト commits に古い順に入れます。
演習で使う MiniGit クラスでは、同じ情報を g.log()((ID, メッセージ) のリストを新しい順に返す)、g.tree(ID)(そのcommit時点のファイル一式の辞書)、g.commits[ID]['parents'](親のIDのリスト)で取り出せます。
# 模擬:commit を (ID, メッセージ, ファイル一式, 親ID) として古い順に記録する
commits = []
def commit(message, files):
parent = commits[-1][0] if commits else None
commits.append(('c%d' % (len(commits) + 1), message, dict(files), parent))
files = {'config.txt': 'threshold=0.5\nmin_rows=100\n'}
commit('集計設定の初版を追加', files)
files['config.txt'] = 'threshold=0.5\nmin_rows=50\n'
commit('最低行数を50に下げる(4月は店舗数が少ないため)', files)
files['config.txt'] = 'threshold=0.7\nmin_rows=50\n'
commit('閾値を0.7に上げる(誤検知が多いと現場から指摘)', files)
for cid, msg, snap, parent in reversed(commits): # git log --oneline(新しい順)
print(cid, msg)
print('c1 の設定:', commits[0][2]['config.txt'].split()) # git show c1:config.txt に相当
snap_of = {cid: snap for cid, msg, snap, parent in commits}
for cid, msg, snap, parent in commits: # 古い順に、親と比べて探す
before = snap_of[parent]['config.txt'] if parent else ''
if 'threshold=0.7' in snap['config.txt'] and 'threshold=0.7' not in before:
print('閾値を変えたのは', cid, '→', msg)
出力:
c3 閾値を0.7に上げる(誤検知が多いと現場から指摘)
c2 最低行数を50に下げる(4月は店舗数が少ないため)
c1 集計設定の初版を追加
c1 の設定: ['threshold=0.5', 'min_rows=100']
閾値を変えたのは c3 → 閾値を0.7に上げる(誤検知が多いと現場から指摘)
試してみよう:①min_rows=50 を初めて含むcommitを探すように書き換える(答えは c2)。②メッセージを update に変えたら、出力を見ただけで理由が答えられるか考える。③実Gitでは、ステージに何も変更がない状態で git commit すると nothing to commit で止まります。Macの練習用リポジトリで確かめてみましょう。
発展:履歴を書き換える操作と、再現性のためのcommit ID
履歴の書き換え:git commit --amend(直前のcommitを作り直す)や git rebase(commitを並べ替え・まとめる)は、新しいIDの別commitを作って置き換える操作です。まだ誰とも共有していない自分のcommitなら便利ですが、共有済みのcommitを書き換えると、他の人の履歴と食い違って混乱します。初学者のうちは「共有したcommitは書き換えない」と決めておきましょう。取り消したいときは、打ち消す変更を新しいcommitとして足す git revert ID が安全です。
メッセージの慣習:チームによっては fix:(バグ修正)、feat:(機能追加)、docs:(文書)のような接頭辞を付けます(Conventional Commits と呼ばれる書き方)。チームの規約に合わせます。
再現性:MLの実験記録や論文の付録に「commit 5b2e7 のコードで実行」と書くと、同じコードを正確に取り出せます。ただし、データ・乱数のシード・ライブラリの版(「依存と実行環境」(有料版))も記録しないと、結果は再現しません。commit ID はコードの版を特定するだけです。
不具合の入ったcommitを探す:git bisect は、良い版と悪い版の間を二分探索して、不具合を入れたcommitを絞り込むコマンドです。100個のcommitでも約7回の確認で見つかります($2^7 = 128 \ge 100$)。
よくある失敗
失敗1:目的の違う変更を1つのcommitに混ぜる
誤り:バグ修正・文言調整・データの差し替えを git add . でまとめて いろいろ修正 とcommitした。
| 起きること | 理由 |
|---|---|
| バグ修正だけ取り消したいのに、文言調整も一緒に戻る | 1つのcommitは1つの単位でしか扱えない |
| レビューで差分が数百行になり、本当に見るべき3行が埋もれる | 目的が混ざっている |
| 数字が変わった原因が特定できない | どの変更が効いたか区別できない |
直し方:commitの前に git status で変更を一覧し、目的ごとに git add ファイル や git add -p でステージを分ける。1つ記録するたびに git diff --staged で中身を確かめる。
失敗2:メッセージが「何を」も「なぜ」も伝えない
| 悪い例 | 問題 | 良い例 |
|---|---|---|
update |
何をしたか不明 | READMEに実行場所を追記 |
clean.pyを修正 |
ファイル名は差分を見ればわかる。なぜが無い | 金額0円の行を欠損扱いしない(返品0円の注文が消えていた) |
閾値変更 |
値と理由が無い | 異常判定の閾値を0.5→0.7(誤検知が多いと現場から指摘) |
なぜ誤りか:差分は「何が変わったか」を示しますが、「なぜ」はメッセージにしか残りません。半年後の自分も他人と同じです。
直し方:「このcommitを適用すると〜になる」と読める1行目を書き、理由・影響(件数や数値の変化)を本文に足す。
失敗3:git restore で未commitの作業を消す
誤り:ステージから外すつもりで git restore report.md(--staged なし)を実行した。
work = {'report.md': '売上: 1,250千円\n'} # 1時間かけて直した(未commit・未ステージ)
index = {'report.md': '売上: 1,200千円\n'} # ステージ(= 最新commitと同じ内容)
work['report.md'] = index['report.md'] # git restore report.md に相当:作業ツリーをステージの内容で上書き
print('作業ツリー:', work['report.md'].strip(), '/ 変更あり:', work != index)
出力:
作業ツリー: 売上: 1,200千円 / 変更あり: False
1時間分の編集(1,250千円)が消え、「変更なし」の状態になりました。commitもステージもしていない変更は、Gitのどこにも保存されていないので、Gitでは取り戻せません。
直し方:ステージから外すだけなら git restore --staged ファイル(作業ツリーは残る)。作業ツリーの変更を捨てる git restore ファイル や git checkout -- ファイル は、git diff で捨てる内容を確かめてから使う。迷ったら、先にcommitしてしまうほうが安全です(commitは後から打ち消せます)。
まとめ
- commitはステージの内容を、親・作者・日時・メッセージとともに記録した履歴の1点。IDは内容から決まる。
- 1つのcommitには1つの目的。混ざった変更は git add -p やファイル指定で分けてステージする。
- メッセージは「何を」を1行目に、「なぜ・影響」を本文に書く。
- git log・git show・git log -p で履歴をたどり、値が変わったcommitを特定できる。
- commitはバックアップではない。git restore(--staged なし)は未commitの変更を消すので注意する。
用語
- commit(commit)
ステージの内容を履歴に記録したもの、または記録する操作。スナップショット・親・作者・日時・メッセージを持つ。
- 親commit(parent commit)
あるcommitの直前のcommit。履歴は親をたどる鎖としてつながる。
- ハッシュ値(hash)
データの内容から計算される固定長の値。内容が1文字でも変われば別の値になる。commit IDに使われる。
- git revert(git revert)
指定したcommitの変更を打ち消す新しいcommitを作る操作。履歴を書き換えない。
よくある質問
なぜcommitを細かく分ける必要があるのですか?
取り消し・レビュー・原因特定の単位がcommitだからです。目的ごとに分かれていれば、問題のある変更だけを git revert で打ち消したり、数字が変わった原因を1つのcommitに絞ったりできます。
commitとpushの違いは何ですか?
commitは手元のリポジトリに履歴を記録するだけです。pushは、その履歴をGitHubなどのリモートリポジトリに送る操作です(秘密情報と共同作業)。commitしただけでは、同僚には見えませんし、Macが壊れれば失われます。
どのくらいの頻度でcommitすればよいですか?
『1つの目的が終わって、壊れていない状態になったら』が目安です。半日以上commitしないと変更が混ざりやすくなります。作業途中でも区切りたいときは、自分だけのブランチ(branchとmerge)でこまめに記録する方法があります。
間違えてcommitしてしまいました。どうすればよいですか?
まだ誰とも共有していなければ、メッセージや内容の小さな誤りは git commit --amend で直せます。共有済みなら、打ち消すcommitを足す git revert ID が安全です。パスワードなどの秘密をcommitした場合は別の対応が必要です(秘密情報と共同作業)。
履歴にある古い版のファイルを見たいときは?
git show ID:ファイルのパス で、そのcommit時点の内容が表示されます。作業ツリーは変わらないので安全です。git log -p -- ファイル でそのファイルの変更の流れを読むこともできます。
仕事で、メッセージに何を書けば十分ですか?
①何を変えたか(1行目)、②なぜ変えたか(依頼・不具合・判断の根拠)、③影響(件数や数値がどう変わるか、互換性)の3点です。チケット番号や依頼メールの日付を添えると、後から根拠をたどれます。
確認問題
問1 1つのcommitに入れる変更の単位として最も適切なのはどれか。
- ① 1日の作業すべて
- ② 1つの目的(例:0円の行の扱いを直す)に必要な変更
- ③ 1行ずつ必ず別のcommit
- ④ ファイルを保存するたびに1回
解答と解説
正解:②
目的が1つなら、取り消し・レビュー・原因特定がその単位でできます。
問2 commitメッセージとして最も良いものはどれか。
- ① update
- ② clean.pyを修正
- ③ 金額0円の行を欠損扱いしない(返品0円の注文が消えていた)
- ④ aaa
解答と解説
正解:③
何を変えたかと、なぜ変えたかの両方が1行でわかります。
問3 未commitの編集がある report.md について、ステージから外すだけのつもりで git restore report.md を実行した。何が起きるか。
- ① 作業ツリーの編集が捨てられ、Gitでは取り戻せない
- ② ステージから外れ、編集は残る
- ③ 最新commitが削除される
- ④ 何も起きない
解答と解説
正解:①
--staged なしの restore は作業ツリーをステージの内容で上書きします。どこにも保存されていない編集は戻せません。
問4 commitとバックアップの関係について正しいものはどれか。
- ① commitすればクラウドにも自動で保存される
- ② commitはバックアップより必ず安全である
- ③ commitしたファイルは削除できなくなる
- ④ commitは手元の .git に履歴を残すだけで、別の場所への写しにはならない
解答と解説
正解:④
履歴はMac内の .git にあります。ディスク故障やフォルダの削除で一緒に失われます。リモートに送って初めて別の場所に写しができます。
問5 git log の出力について正しい説明はどれか。
- ① 古い順に表示される
- ② HEADから親をたどって新しい順に表示される
- ③ 未commitの変更も表示される
- ④ ステージの内容だけを表示する
解答と解説
正解:②
git log は最新のcommitから親へ向かってたどるので、新しい順です。
問6 MLの実験記録に commit ID を書いた。これだけで保証されることはどれか。
- ① 実験に使ったコードの版を正確に特定できる
- ② 同じ精度が必ず再現される
- ③ 使ったデータも特定できる
- ④ ライブラリの版も特定できる
解答と解説
正解:①
commit ID はそのcommitのスナップショット(コード)を特定します。データ・シード・環境は別に記録が必要です。
次に学ぶこと
次は branchとmerge で、本線(main)を壊さずに作業を進める branch(ブランチ)と、作業をまとめる merge(マージ)を学びます。今日 ~/se01-practice/git-demo に作った2つのcommitの上に、ブランチを作ります。
ステージや差分の読み方に不安があれば Gitと変更差分 に戻りましょう。
キャリア学習:データサイエンティスト・機械学習エンジニア・DX推進・研究者を目指す人の学習アプリ。164モジュール・984授業を、ブラウザで動くSQLとPythonの演習で。基礎と各分野の最初のモジュールは無料。全部は2,980円の買い切り。
アプリを無料で開く有料版と無料版の違い