1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Git の実験的なサブコマンド git history / replay / last-modified / format-rev を試す

1
Posted at

はじめに

https://git-scm.com/docs/git.html には、説明に EXPERIMENTAL(実験的)と書かれたサブコマンドがいくつかあります。
2026/09/28 現在は、git history、git replay、git last-modified、git format-rev の4つです。
この記事では、これらの使い方を紹介します。

git history

git history は、コミット履歴を書き換えるためのコマンドです。
ドキュメントでは、git rebase より簡単に使えることを目指したコマンドだと説明されています。
git rebase とは、次の点が異なります。

  • マージを含む履歴には未対応(その場合は git rebase --rebase-merges を使う)
  • コンフリクトが起きる場合は、履歴を書き換えずにエラーで中断
  • 書き換えたコミットの子孫にあたるブランチも、書き換え後の履歴に合わせて更新

git history fixup

git history fixup は、以下の記事で紹介しています。

git history reword

git history reword <commit> は、指定したコミットのメッセージだけを書き換えます。

次の例では、2つ目のコミット add Makefile のメッセージを add Makefile for build に直します。

$ git log --oneline
3ffa098 (HEAD -> main) update README
91413d9 add Makefile
0115f50 add README

$ git history reword 91413d9

実行するとエディタが開き、対象コミットのメッセージを編集できます。
保存してエディタを閉じると、メッセージが書き換わります。

$ git log --oneline
35b1030 (HEAD -> main) update README
924c476 add Makefile for build
0115f50 add README

後続の update README もハッシュが変わっており、書き換えたコミットの上に積み直されています。
差分や著者は変わりません。

git rebase -i でも同じことはできますが、対象コミットまでの範囲を指定し、該当する行の pick を reword に書き換える必要があります。
git history reword では、直したいコミットを指定するだけで済みます。

git history split

git history split <commit> は、指定したコミットを2つに分割します。

次の例では、2つのファイルを追加したコミットを、ファイルごとの2つのコミットに分けます。

$ git log --stat --oneline
1a981d8 (HEAD -> main) add configs
 bar | 1 +
 foo | 1 +
 2 files changed, 2 insertions(+)

実行すると、変更のかたまり(hunk)ごとに、新しいコミットに切り出すかどうかを聞かれます。
選択肢は git add -p と同じ要領で、y で切り出し、n で元のコミットに残します。
ここでは bar を切り出し、foo を残します。

$ git history split HEAD
diff --git a/bar b/bar
new file mode 100644
index 0000000..5716ca5
--- /dev/null
+++ b/bar
@@ -0,0 +1 @@
+bar
(1/1) Stage addition [y,n,q,a,d,?]? y

diff --git a/foo b/foo
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/foo
@@ -0,0 +1 @@
+foo
(1/1) Stage addition [y,n,q,a,d,?]? n

選び終わるとエディタが2回開くので、切り出したコミットのメッセージ、元のコミットのメッセージの順に入力します。
ここでは、切り出したほうを add bar とし、元のほうは add configs のままにします。

$ git log --stat --oneline
acd11e1 (HEAD -> main) add configs
 foo | 1 +
 1 file changed, 1 insertion(+)
6c96cac add bar
 bar | 1 +
 1 file changed, 1 insertion(+)

切り出した add bar が元のコミットの親になり、add configs には foo の追加だけが残っています。

hunk をすべて選ぶことも、1つも選ばないこともできません。
どちらかのコミットが空になるためです。

git replay

git replay は、指定した範囲のコミットを別のコミットの上に積み直すコマンドです。
git rebase と同じ用途に使えますが、コミットしていない変更を stash したり、ブランチを切り替えたりせずに実行できます。

git rebase は、対象のブランチをチェックアウトし、手元のファイル上で1コミットずつ適用していきます。
そのため、コミットしていない変更があると実行できず、コンフリクトが起きると途中の状態で止まります。
git replay は、手元のファイルを使わずに新しいコミットを作り、最後に、ブランチの指す先だけを新しいコミットに書き換えます。
コンフリクトが起きた場合は、ブランチを更新せずに終了します。

次の例では、別のブランチで README.md を編集している途中に、mybranch を origin/main の上に積み直します。

$ git status --short
 M README.md

$ git log --oneline --graph --all
* ecb91c8 (mybranch) add b
* f868e83 add a
| * 6956df0 (origin/main) add c
|/
* 0115f50 (HEAD -> main) add README

$ git replay --onto=origin/main origin/main..mybranch

成功しても何も出力されませんが、mybranch は origin/main の上に移っています。
README.md の編集もそのまま残っています。

$ git log --oneline --graph --all
* 9bdc05e (mybranch) add b
* 5ebb856 add a
* 6956df0 (origin/main) add c
* 0115f50 (HEAD -> main) add README

$ git status --short
 M README.md

なお、チェックアウトしているブランチを対象にすると、手元のファイルは古いままになります。
チェックアウトしていないブランチに使うのが良さそうです。

--ref-action=print を付けると、ブランチは変えずに、どう更新されるかだけを確認できます。

ブランチを積み重ねて作業しているときは、--contained を付けます。
次の例では、basebranch の上に tipbranch を作っています。

$ git log --oneline --graph --all
* ecb91c8 (tipbranch) add b
* f868e83 (basebranch) add a
| * 6956df0 (origin/main) add c
|/
* 0115f50 (HEAD -> main) add README

$ git replay --contained --onto=origin/main origin/main..tipbranch

$ git log --oneline --graph --all
* 9bdc05e (tipbranch) add b
* 5ebb856 (basebranch) add a
* 6956df0 (origin/main) add c
* 0115f50 (HEAD -> main) add README

途中にある basebranch も、先端のブランチと一緒に移りました。
--contained を付けない場合は先端のブランチだけが移り、basebranch は元の位置に残ります。

--onto のほかに、--advance と --revert もあります。

--advance は、指定した範囲のコミットを、指定したブランチの上にコピーし、そのブランチを進めます。
そのブランチをチェックアウトして cherry-pick するのと同じ結果になり、コピー元のブランチはそのまま残ります。

--revert は、指定した範囲のコミットを打ち消すコミットを、指定したブランチの上に作り、そのブランチを進めます。
そのブランチをチェックアウトして revert するのと同じ結果になります。

git last-modified

git last-modified は、指定したパスごとに、最後に変更したコミットを表示するコマンドです。
git blame は行ごとに最後の変更を調べますが、git last-modified はファイルやディレクトリごとに調べます。

次の例では、README.md と Makefile を最後に変更したコミットを調べます。

$ git last-modified -- README.md Makefile
35b10300498c8ce8ae2d550d9a3177ab102d4ae9	README.md
924c476e24ebb5d037ad070cd04993dc07115043	Makefile

パスを省略すると、トップレベルのファイルとディレクトリが対象になります。
主なオプションは次のとおりです。

  • -r / --recursive: サブディレクトリの中まで再帰的にたどる(--max-depth=-1 と同じ)
  • -t / --show-trees: 再帰するときも、ディレクトリ自体の行を表示する
  • --max-depth=<depth>: たどる深さを指定する(デフォルトは 0 で、サブディレクトリの中には入らない)

出力はハッシュとパスを TAB で区切った形式で、これだけでは誰がいつ変更したのか分かりません。
著者や件名を見たいときは、次に紹介する git format-rev に渡します。

git format-rev

git format-rev は、標準入力から受け取ったコミットを、指定したフォーマットで整形するコマンドです。
単体では使わず、他のコマンドの出力をパイプで渡して使います。

--stdin-mode=revs では、1行を1つのコミットとして扱います。
次の例では、git log で出力したハッシュを、短縮ハッシュと件名の形に整形します。

$ git log --format=%H -3 | git format-rev --stdin-mode=revs --format="%h %s"
35b1030 update README
924c476 add Makefile for build
0115f50 add README

--stdin-mode=text では、テキスト中のハッシュを探して指定したフォーマットに置き換え、それ以外の部分はそのまま出力します。
短縮ハッシュは認識されないので、完全なハッシュを渡す必要があります。
git last-modified の出力を渡すと、ハッシュが著者名に置き換わります。

$ git last-modified -- README.md Makefile |
    git format-rev --stdin-mode=text --format=%an
Alice	README.md
Bob	Makefile

さいごに

EXPERIMENTAL と書かれた4つのサブコマンドを紹介しました。
今後オプションや挙動が変わる可能性があるので、使うときはその時点のドキュメントを確認するのが良さそうです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?