-
1. はじめに
- 1.1 バージョン管理について
- 1.2 Gitの簡単な歴史
- 1.3 Gitとは何か?
- 1.4 コマンドライン
- 1.5 Gitのインストール
- 1.6 Gitの初回設定
- 1.7 ヘルプの取得
- 1.8 まとめ
-
2. Gitの基本
- 2.1 Gitリポジトリの取得
- 2.2 リポジトリへの変更の記録
- 2.3 コミット履歴の表示
- 2.4 元に戻す
- 2.5 リモートとの連携
- 2.6 タグ付け
- 2.7 Gitエイリアス
- 2.8 まとめ
-
3. Gitブランチ
- 3.1 ブランチの概要
- 3.2 基本的なブランチとマージ
- 3.3 ブランチ管理
- 3.4 ブランチワークフロー
- 3.5 リモートブランチ
- 3.6 リベース
- 3.7 まとめ
-
4. サーバー上のGit
- 4.1 プロトコル
- 4.2 サーバーへのGitの導入
- 4.3 SSH公開鍵の生成
- 4.4 サーバーの設定
- 4.5 Gitデーモン
- 4.6 スマートHTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 サードパーティホスティングオプション
- 4.10 まとめ
-
5. 分散型Git
- 5.1 分散型ワークフロー
- 5.2 プロジェクトへの貢献
- 5.3 プロジェクトの維持
- 5.4 まとめ
-
6. GitHub
- 6.1 アカウント設定と構成
- 6.2 プロジェクトへの貢献
- 6.3 プロジェクトの維持
- 6.4 組織の管理
- 6.5 GitHubスクリプティング
- 6.6 まとめ
-
7. Gitツール
- 7.1 リビジョン選択
- 7.2 インタラクティブステージング
- 7.3 スタッシュとクリーン
- 7.4 作業への署名
- 7.5 検索
- 7.6 履歴の書き換え
- 7.7 リセット詳解
- 7.8 高度なマージ
- 7.9 Rerere
- 7.10 Gitを使ったデバッグ
- 7.11 サブモジュール
- 7.12 バンドル
- 7.13 置換
- 7.14 認証情報の保存
- 7.15 まとめ
-
8. Gitのカスタマイズ
- 8.1 Gitの設定
- 8.2 Git属性
- 8.3 Gitフック
- 8.4 Gitによるポリシー適用例
- 8.5 まとめ
-
9. Gitと他のシステム
- 9.1 クライアントとしてのGit
- 9.2 Gitへの移行
- 9.3 まとめ
-
10. Git内部構造
- 10.1 内部コマンドと外部コマンド
- 10.2 Gitオブジェクト
- 10.3 Git参照
- 10.4 パックファイル
- 10.5 Refspec
- 10.6 転送プロトコル
- 10.7 メンテナンスとデータ復旧
- 10.8 環境変数
- 10.9 まとめ
-
付録A. その他の環境におけるGit
- A1.1 グラフィカルインターフェース
- A1.2 Visual StudioにおけるGit
- A1.3 Visual Studio CodeにおけるGit
- A1.4 IntelliJ/PyCharm/WebStorm/PhpStorm/RubyMineにおけるGit
- A1.5 Sublime TextにおけるGit
- A1.6 BashにおけるGit
- A1.7 ZshにおけるGit
- A1.8 PowerShellにおけるGit
- A1.9 まとめ
-
付録B. アプリケーションへのGitの埋め込み
- A2.1 コマンドラインGit
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
付録C. Gitコマンド
- A3.1 設定と構成
- A3.2 プロジェクトの取得と作成
- A3.3 基本的なスナップショット
- A3.4 ブランチとマージ
- A3.5 プロジェクトの共有と更新
- A3.6 検査と比較
- A3.7 デバッグ
- A3.8 パッチ
- A3.9 メール
- A3.10 外部システム
- A3.11 管理
- A3.12 内部コマンド
7.6 Gitツール - 履歴の書き換え
履歴の書き換え
Gitを使用する際に、ローカルのコミット履歴を修正したい場面が多くあります。Gitの優れた点の一つは、可能な限り最後の瞬間まで決定を遅らせることができる点です。ステージングエリアを使って、どのファイルをどのコミットに入れるかをコミット直前に決定できますし、`git stash`を使って、まだ作業したくないことに気づいた場合、作業を一時的に退避させることができます。また、既に発生したコミットを書き換えて、異なる方法で発生したかのように見せることもできます。これには、コミットの順序の変更、メッセージの変更、コミット内のファイルの修正、コミットの結合または分割、コミットの完全な削除などが含まれ、これらすべては他の人と作業を共有する前に行うことができます。
このセクションでは、これらのタスクを実行して、他の人と共有する前にコミット履歴を望ましい状態にする方法を説明します。
注意
|
満足いくまで作業をプッシュしないでください
Gitの重要なルールの一つは、多くの作業がローカルクローン内で行われるため、ローカルで履歴を書き換える自由度が非常に高いということです。しかし、一度作業をプッシュしてしまうと、状況は全く異なり、正当な理由がない限り、プッシュされた作業は最終的なものとみなすべきです。つまり、満足して世界中の人と共有する準備ができるまで、作業をプッシュしないようにするべきです。 |
最後のコミットの変更
最新のコミットを変更することは、おそらく最も一般的な履歴の書き換えです。最新のコミットに対して、主に2つの基本的な操作を行う必要があります。コミットメッセージを変更するか、ファイルの追加、削除、変更によってコミットの実際のコンテンツを変更することです。
最後のコミットメッセージを修正するだけであれば、簡単です。
$ git commit --amend
上記の命令は、前のコミットメッセージをエディターセッションに読み込みます。そこで、メッセージを変更し、変更を保存して終了できます。エディターを保存して閉じると、エディターは、更新されたコミットメッセージを含む新しいコミットを作成し、それを最新のコミットにします。
一方、最新のコミットの実際の内容を変更したい場合は、基本的に同じ手順で進めます。最初に、忘れたと思われる変更を行い、それらの変更をステージングし、その後のgit commit --amend
によって、最新のコミットが新しく改良されたコミットに置き換えられます。
この手法には注意が必要です。修正によってコミットのSHA-1が変更されるためです。これは非常に小さなrebaseのようなものです。既にプッシュ済みの最新のコミットは修正しないでください。
ヒント
|
修正されたコミットには、修正されたコミットメッセージが必要な場合と、そうでない場合があります。
コミットを修正する際には、コミットメッセージとコミットの内容の両方を変更する機会があります。コミットの内容を大幅に変更する場合は、修正された内容を反映するようにコミットメッセージを更新する必要があります。 一方、修正が些細なもの(些細なタイプミスを修正したり、ステージングするのを忘れたファイルを追加したりすること)で、以前のコミットメッセージで問題ない場合は、変更を行い、ステージングし、次のコマンドを使用して不要なエディターセッションを完全に回避できます。
|
複数のコミットメッセージの変更
履歴のさらに後ろにあるコミットを変更するには、より複雑なツールに移動する必要があります。Gitには履歴変更ツールがありませんが、rebaseツールを使用して、一連のコミットを、別のコミットに移動するのではなく、元のベースになっていたHEADにrebaseできます。対話型rebaseツールを使用すると、変更したい各コミットの後で停止し、メッセージを変更したり、ファイルを追加したり、任意の操作を実行できます。git rebase
に-i
オプションを追加して、対話的にrebaseを実行できます。どのコミットをrebaseの対象とするかをコマンドに指示することで、履歴をどの程度遡って書き直すかを指定する必要があります。
たとえば、最後の3つのコミットメッセージ、またはそのグループ内のコミットメッセージを変更する場合は、編集したい最後のコミットの親であるHEAD~2^
またはHEAD~3
をgit rebase -i
の引数として指定します。最後の3つのコミットを編集しようとしているため、~3
の方が覚えやすいかもしれませんが、実際には4つ前のコミット、つまり編集したい最後のコミットの親を指定していることに注意してください。
$ git rebase -i HEAD~3
繰り返しますが、これはrebaseコマンドです。変更されたメッセージを含む範囲HEAD~3..HEAD
内のすべてのコミットとそのすべての子孫が書き換えられます。既に中央サーバーにプッシュ済みのコミットを含めないでください。そうすると、同じ変更の代替バージョンを提供することになり、他の開発者を混乱させることになります。
このコマンドを実行すると、テキストエディターに次のようなコミットのリストが表示されます。
pick f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file
# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
これらのコミットは、通常log
コマンドを使用して表示される順序とは逆順で表示されることに注意することが重要です。log
を実行すると、次のような結果が表示されます。
$ git log --pretty=format:"%h %s" HEAD~3..HEAD
a5f4a0d Add cat-file
310154e Update README formatting and add blame
f7f3f6d Change my name a bit
逆順であることに注目してください。対話型rebaseは、実行しようとしているスクリプトを提供します。コマンドライン(HEAD~3
)で指定したコミットから開始し、これらのコミットで導入された変更を上から下へ再生します。最も古いコミットが最初に表示されるのは、それが最初に再生されるためです。
スクリプトを編集して、編集したいコミットで停止するようにする必要があります。そのためには、編集したい各コミットの「pick」という単語を「edit」という単語に変更します。たとえば、3番目のコミットメッセージのみを変更するには、ファイルを次のように変更します。
edit f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file
エディターを保存して終了すると、Gitはリストの最後のコミットまで巻き戻し、次のメッセージとともにコマンドラインに移動します。
$ git rebase -i HEAD~3
Stopped at f7f3f6d... Change my name a bit
You can amend the commit now, with
git commit --amend
Once you're satisfied with your changes, run
git rebase --continue
これらの指示は、何をすべきかを正確に示しています。次のコマンドを入力します。
$ git commit --amend
コミットメッセージを変更し、エディターを終了します。次に、次のコマンドを実行します。
$ git rebase --continue
このコマンドにより、他の2つのコミットが自動的に適用され、作業が完了します。複数の行で「pick」を「edit」に変更すると、変更を「edit」に変更した各コミットに対してこれらの手順を繰り返すことができます。毎回、Gitは停止し、コミットを修正し、終了したら続行します。
コミットの並べ替え
対話型rebaseを使用して、コミットの順序を変更したり、コミットを完全に削除したりすることもできます。「Add cat-file」コミットを削除し、他の2つのコミットが導入される順序を変更する場合は、rebaseスクリプトを次のように変更できます。
pick f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file
次のようになります。
pick 310154e Update README formatting and add blame
pick f7f3f6d Change my name a bit
エディターを保存して終了すると、Gitはこれらのコミットの親までブランチを巻き戻し、310154e
を適用してからf7f3f6d
を適用し、停止します。これにより、これらのコミットの順序が効果的に変更され、「Add cat-file」コミットが完全に削除されます。
コミットのマージ
対話型rebaseツールを使用して、一連のコミットを単一のコミットにマージすることも可能です。スクリプトは、rebaseメッセージに役立つ指示を挿入します。
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
「pick」または「edit」ではなく「squash」を指定すると、Gitはその変更と直前の変更を適用し、コミットメッセージをマージします。したがって、これらの3つのコミットから単一のコミットを作成する場合は、スクリプトを次のようにします。
pick f7f3f6d Change my name a bit
squash 310154e Update README formatting and add blame
squash a5f4a0d Add cat-file
エディターを保存して終了すると、Gitは3つの変更をすべて適用し、3つのコミットメッセージをマージするためのエディターに戻ります。
# This is a combination of 3 commits.
# The first commit's message is:
Change my name a bit
# This is the 2nd commit message:
Update README formatting and add blame
# This is the 3rd commit message:
Add cat-file
保存すると、前の3つのコミットの変更を導入する単一のコミットが作成されます。
コミットの分割
コミットの分割は、コミットを元に戻してから、作成したいコミットの数だけ部分的にステージングしてコミットします。たとえば、3つのコミットの中央のコミットを分割したいとします。「Update README formatting and add blame」の代わりに、「Update README formatting」を最初のコミットに、「Add blame」を2番目のコミットに分割したいとします。rebase -i
スクリプトで、分割したいコミットの指示を「edit」に変更することで、これを行うことができます。
pick f7f3f6d Change my name a bit
edit 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file
次に、スクリプトがコマンドラインに移動すると、そのコミットをリセットし、リセットされた変更を取り、それらから複数のコミットを作成します。エディターを保存して終了すると、Gitはリストの最初のコミットの親まで巻き戻し、最初のコミット(f7f3f6d
)を適用し、2番目のコミット(310154e
)を適用し、コンソールに移動します。そこで、git reset HEAD^
を使用してそのコミットの混合リセットを実行できます。これにより、そのコミットが効果的に元に戻され、変更されたファイルはステージングされなくなります。これで、複数のコミットができるまでファイルのステージングとコミットを行い、作業が完了したらgit rebase --continue
を実行できます。
$ git reset HEAD^
$ git add README
$ git commit -m 'Update README formatting'
$ git add lib/simplegit.rb
$ git commit -m 'Add blame'
$ git rebase --continue
Gitはスクリプトの最後のコミット(a5f4a0d
)を適用し、履歴は次のようになります。
$ git log -4 --pretty=format:"%h %s"
1c002dd Add cat-file
9b29157 Add blame
35cfb2b Update README formatting
f7f3f6d Change my name a bit
これにより、リストの最新の3つのコミットのSHA-1が変更されるため、変更されたコミットが、既に共有リポジトリにプッシュ済みのリストに表示されないようにしてください。リストの最後のコミット(f7f3f6d
)は変更されていないことに注意してください。このコミットはスクリプトに表示されていますが、「pick」としてマークされ、rebaseの変更の前に適用されたため、Gitはコミットをそのままにします。
コミットの削除
コミットを削除する場合は、rebase -i
スクリプトを使用して削除できます。コミットのリストで、削除したいコミットの前に「drop」という単語を付けます(または、rebaseスクリプトからその行を削除します)。
pick 461cb2a This commit is OK
drop 5aecc10 This commit is broken
Gitがコミットオブジェクトを構築する方法のため、コミットを削除または変更すると、それに続くすべてのコミットが書き換えられます。リポジトリの履歴を遡るほど、再作成が必要なコミットが増えます。削除したコミットに依存するシーケンスの後期に多くのコミットがある場合、多くのマージ競合が発生する可能性があります。
このようなrebaseを途中で中止し、良い考えではないと判断した場合は、いつでも停止できます。git rebase --abort
と入力すると、リポジトリはrebaseを開始する前の状態に戻ります。
rebaseを終了して、望ましい結果ではないと判断した場合は、git reflog
を使用して、ブランチの以前のバージョンを復元できます。reflog
コマンドの詳細については、「データ復旧」を参照してください。
注意
|
Drew DeVaultは、 |
最終手段:filter-branch
大量のコミットをスクリプト可能な方法で書き直す必要がある場合(たとえば、メールアドレスをグローバルに変更したり、すべてのコミットからファイルを除去したりする場合)に使用できる、別の履歴書き換えオプションがあります。コマンドはfilter-branch
であり、履歴の大部分を書き直すことができます。そのため、プロジェクトがまだ公開されておらず、他の人が書き直そうとしているコミットに基づいて作業を行っていない限り、使用しない方が良いでしょう。ただし、非常に便利です。一般的な使用方法をいくつか学習することで、その機能の一部を理解することができます。
注意
|
|
すべてのコミットからファイルを除去する
これはかなり一般的です。無意識のうちにgit add .
を使用して巨大なバイナリファイルをコミットしてしまい、それをすべて削除したい場合があります。パスワードが含まれているファイルを誤ってコミットしてしまい、プロジェクトをオープンソースにしたい場合があります。filter-branch
はおそらく、履歴全体をクリーンアップするために使用したいツールです。履歴全体からpasswords.txt
という名前のファイルを除去するには、filter-branch
に--tree-filter
オプションを使用できます。
$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten
--tree-filter
オプションは、プロジェクトの各チェックアウト後に指定したコマンドを実行し、その結果を再コミットします。この場合、存在するかどうかを問わず、すべてのスナップショットからpasswords.txt
という名前のファイルを除去します。誤ってコミットされたすべてのエディターのバックアップファイルを削除する場合は、git filter-branch --tree-filter 'rm -f *~' HEAD
のようなコマンドを実行できます。
Gitでツリーとコミットを書き換え、最後にブランチポインタを移動できます。一般的に、テストブランチで行い、結果が本当に望むものだと確認してから`master`ブランチをハードリセットするのが良いでしょう。全てのブランチで`filter-branch`を実行するには、コマンドに`--all`を渡します。
サブディレクトリを新しいルートにする
他のソースコード管理システムからインポートを行い、意味のないサブディレクトリ(`trunk`、`tags`など)がある場合を想定します。`trunk`サブディレクトリを各コミットの新しいプロジェクトルートにしたい場合も、`filter-branch`が役立ちます。
$ git filter-branch --subdirectory-filter trunk HEAD
Rewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12)
Ref 'refs/heads/master' was rewritten
これで、新しいプロジェクトルートは毎回`trunk`サブディレクトリにあったものになります。Gitは、サブディレクトリに影響を与えなかったコミットも自動的に削除します。
メールアドレスのグローバル変更
もう1つのよくあるケースは、作業を開始する前に`git config`を実行して名前とメールアドレスを設定し忘れた場合、または職場のプロジェクトをオープンソース化して、職場のメールアドレスを個人のアドレスに変更したい場合です。いずれの場合も、`filter-branch`を使用して、複数のコミットのメールアドレスを一括で変更できます。自分のメールアドレスのみを変更する必要があるため、`--commit-filter`を使用します。
$ git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
then
GIT_AUTHOR_NAME="Scott Chacon";
GIT_AUTHOR_EMAIL="schacon@example.com";
git commit-tree "$@";
else
git commit-tree "$@";
fi' HEAD
これにより、すべてのコミットが新しいアドレスを持つように書き換えられます。コミットには親のSHA-1値が含まれているため、このコマンドは一致するメールアドレスを持つものだけでなく、履歴内のすべてのコミットのSHA-1を変更します。