commitと履歴|コマンドライン・Git・変更管理

このページはプロモーション(広告)を含みます。

コマンドライン・Git・変更管理(初級・読む30分+演習35分)

この授業のゴール

仕事でどう使うか

「先週の会議資料と今週の資料で、同じ月の売上が違う。何を変えた?」――このとき履歴が 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は、作業の意味のある節目で撮る写真のようなものです。写真の裏に「何の写真か、なぜ撮ったか」を書いておくと、何年後でも探せます。

たとえと実際の違い:写真は撮った瞬間しか残りませんが、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(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日の作業すべて
  2. ② 1つの目的(例:0円の行の扱いを直す)に必要な変更
  3. ③ 1行ずつ必ず別のcommit
  4. ④ ファイルを保存するたびに1回
解答と解説

正解:②

目的が1つなら、取り消し・レビュー・原因特定がその単位でできます。

問2 commitメッセージとして最も良いものはどれか。

  1. ① update
  2. ② clean.pyを修正
  3. ③ 金額0円の行を欠損扱いしない(返品0円の注文が消えていた)
  4. ④ aaa
解答と解説

正解:③

何を変えたかと、なぜ変えたかの両方が1行でわかります。

問3 未commitの編集がある report.md について、ステージから外すだけのつもりで git restore report.md を実行した。何が起きるか。

  1. ① 作業ツリーの編集が捨てられ、Gitでは取り戻せない
  2. ② ステージから外れ、編集は残る
  3. ③ 最新commitが削除される
  4. ④ 何も起きない
解答と解説

正解:①

--staged なしの restore は作業ツリーをステージの内容で上書きします。どこにも保存されていない編集は戻せません。

問4 commitとバックアップの関係について正しいものはどれか。

  1. ① commitすればクラウドにも自動で保存される
  2. ② commitはバックアップより必ず安全である
  3. ③ commitしたファイルは削除できなくなる
  4. ④ commitは手元の .git に履歴を残すだけで、別の場所への写しにはならない
解答と解説

正解:④

履歴はMac内の .git にあります。ディスク故障やフォルダの削除で一緒に失われます。リモートに送って初めて別の場所に写しができます。

問5 git log の出力について正しい説明はどれか。

  1. ① 古い順に表示される
  2. ② HEADから親をたどって新しい順に表示される
  3. ③ 未commitの変更も表示される
  4. ④ ステージの内容だけを表示する
解答と解説

正解:②

git log は最新のcommitから親へ向かってたどるので、新しい順です。

問6 MLの実験記録に commit ID を書いた。これだけで保証されることはどれか。

  1. ① 実験に使ったコードの版を正確に特定できる
  2. ② 同じ精度が必ず再現される
  3. ③ 使ったデータも特定できる
  4. ④ ライブラリの版も特定できる
解答と解説

正解:①

commit ID はそのcommitのスナップショット(コード)を特定します。データ・シード・環境は別に記録が必要です。

次に学ぶこと

次は branchとmerge で、本線(main)を壊さずに作業を進める branch(ブランチ)と、作業をまとめる merge(マージ)を学びます。今日 ~/se01-practice/git-demo に作った2つのcommitの上に、ブランチを作ります。

ステージや差分の読み方に不安があれば Gitと変更差分 に戻りましょう。

キャリア学習:データサイエンティスト・機械学習エンジニア・DX推進・研究者を目指す人の学習アプリ。164モジュール・984授業を、ブラウザで動くSQLとPythonの演習で。基礎と各分野の最初のモジュールは無料。全部は2,980円の買い切り。

アプリを無料で開く有料版と無料版の違い