-
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 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 サードパーティによる Git ホスティング
- 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 Git オブジェクトの置き換え
- 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 配管(Plumbing)と磁器(Porcelain)
- 10.2 Gitオブジェクト
- 10.3 Gitの参照
- 10.4 Packfile
- 10.5 Refspec
- 10.6 転送プロトコル
- 10.7 メンテナンスとデータリカバリ
- 10.8 環境変数
- 10.9 まとめ
-
A1. 付録 A: その他の環境でのGit
- A1.1 グラフィカルインタフェース
- A1.2 Visual StudioでGitを使う
- A1.3 EclipseでGitを使う
- A1.4 BashでGitを使う
- A1.5 ZshでGitを使う
- A1.6 PowershellでGitを使う
- A1.7 まとめ
-
A2. 付録 B: Gitをあなたのアプリケーションに組み込む
- A2.1 Gitのコマンドラインツールを使う方法
- A2.2 Libgit2を使う方法
- A2.3 JGit
-
A3. 付録 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.13 Git のさまざまなツール - Git オブジェクトの置き換え
Git オブジェクトの置き換え
Git オブジェクトは変更できません。その代わりに用意されているのが、Git データベース上のオブジェクトを他のオブジェクトと置き換えたかのように見せる方法です。
replace
コマンドを使うと、「このオブジェクトを参照するときは、あたかもあちらを参照してるかのように振る舞え」と Git に指示できます。プロジェクトの歴史のなかで、コミットを別のコミットで置き換えたいときに便利です。
具体的な例として、長い歴史を経たコードベースがあって、それを2つに分割するケースを考えてみましょう。1つは短い歴史で新入りの開発者向け、もう1つは長い歴史でデータマイニングを行いたい人向けです。とある歴史を別の歴史と結びつけるには、新しいほうの歴史の最古のコミットを、古いほうの歴史の最新のコミットと置き換えてやればいいのです。これの利点は、そうしておけば新しいほうの歴史のコミットをすべて書き換える必要がなくなることです。通常であれば、歴史をつなぐにはそうせざるを得ません(コミットの親子関係が算出される SHA-1 に影響するため)。
では、既存のリポジトリを使って実際に試してみましょう。まずは、そのリポジトリを最近のものと過去の経緯を把握するためのものの2つに分割してみます。そのうえで、その2つを結合しつつ前者のリポジトリの SHA-1 を変更せずに済ますために replace
を使ってみます。
ここでは、コミットが5つだけある以下のようなリポジトリを使って説明します。
$ git log --oneline
ef989d8 fifth commit
c6e1e95 fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
このリポジトリを2つの歴史に分割してみましょう。1つめの歴史はコミット1からコミット4までで、過去の経緯を把握するためのリポジトリです。2つめの歴史はコミット4とコミット5だけで、これは最近の歴史だけのリポジトリになります。
過去の経緯を把握するための歴史は簡単に取り出せます。過去のコミットを指定してブランチを切り、新たに作成しておいたリモートリポジトリの master としてそのブランチをプッシュすればよいのです。
$ git branch history c6e1e95
$ git log --oneline --decorate
ef989d8 (HEAD, master) fifth commit
c6e1e95 (history) fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
作成した history
ブランチを、新規リポジトリの master
ブランチにプッシュします。
$ git remote add project-history https://github.com/schacon/project-history
$ git push project-history history:master
Counting objects: 12, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (12/12), 907 bytes, done.
Total 12 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (12/12), done.
To git@github.com:schacon/project-history.git
* [new branch] history -> master
これで新たに作った歴史が公開されました。続いて難しいほう、最近の歴史を小さくするための絞り込みです。双方の歴史に重なる部分がないとコミットの置き換え(一方の歴史のコミットをもう一方の歴史の同等のコミットで置き換え)が出来なくなるので、ここでは最近の歴史をコミット4と5だけに絞り込みます(そうすればコミット4が重なることになります)。
$ git log --oneline --decorate
ef989d8 (HEAD, master) fifth commit
c6e1e95 (history) fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
こういったケースでは、ベースとなるコミットを作って、歴史を展開するための手順を説明しておくとよいでしょう。絞りこまれた歴史のベースコミットに行き当たって「この先が知りたいのに」となった開発者達が、次に取るべき手順を把握できるからです。実際にどうするかというと、まずは上述した手順を含めたコミットオブジェクト(これが最近の歴史の方の基点となります)を作り、残りのコミット(コミット4と5)をそれにリベースします。
そのためには、どこで分割するかを決める必要があります。この例ではコミット3、SHA でいうと 9c68fdc
です。そのコミットの後ろに、ベースとなるコミットを作成します。このベースコミットは commit-tree
コマンドで作成できます。ツリーを指定して実行すると、親子関係のない新規のコミットオブジェクト SHA-1 が生成されます。
$ echo 'get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf
注記
|
|
これでベースとなるコミットができたので、git rebase --onto
を使って残りの歴史をリベースしましょう。--onto
オプションの引数は先ほど実行した commit-tree
コマンドの返り値、リベースの始点はコミット3(保持しておきたい1つめのコミットの親にあたるコミット。9c68fdc
)です。。
$ git rebase --onto 622e88 9c68fdc
First, rewinding head to replay your work on top of it...
Applying: fourth commit
Applying: fifth commit
以上で、仮で作ったベースコミットのうえに最近の歴史をリベースできました。ベースコミットには、必要であれば全歴史を組み直すための手順が含まれた状態です。この歴史を新しいプロジェクトとしてプッシュしておきましょう。もしそのリポジトリがクローンされると、直近のコミット2つとベースコミット(手順含む)だけが取得されます。
では次に、プロジェクトをクローンする側の動きを見ていきましょう。初回のクローンで、全歴史を必要としているとします。 絞りこまれたリポジトリをクローンした状態で全歴史を取得するには、過去の経緯を把握するためのリポジトリをリモートとして追加してフェッチします。
$ git clone https://github.com/schacon/project
$ cd project
$ git log --oneline master
e146b5f fifth commit
81a708d fourth commit
622e88e get history from blah blah blah
$ git remote add project-history https://github.com/schacon/project-history
$ git fetch project-history
From https://github.com/schacon/project-history
* [new branch] master -> project-history/master
こうすると、master
ブランチを見れば最近のコミットがわかり、project-history/master
ブランチを見れば過去のコミットがわかるようになります。
$ git log --oneline master
e146b5f fifth commit
81a708d fourth commit
622e88e get history from blah blah blah
$ git log --oneline project-history/master
c6e1e95 fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
ここで git replace
を実行すると、これら2つをつなぐことができます。置き換えられるコミット、置き換えるコミットの順に指定して実行しましょう。この例では、master
ブランチのコミット4を、project-history/master
ブランチのコミット4で置き換えることになります。
$ git replace 81a708d c6e1e95
では、 master
ブランチの歴史を確認してみましょう。以下のようになっているはずです。
$ git log --oneline master
e146b5f fifth commit
81a708d fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
ね、これいいでしょ?上流の SHA-1 をすべて書き換えることなく、歴史上のコミット1つをまったく別のコミットと置き換えることができました。他の Git ツール(bisect
や blame
など)も、期待通りに動作してくれます。
1つ気になるのが、表示されている SHA-1 が 81a708d
のまま、という点です。実際に使われているデータは、置き換えるのに使ったコミット c6e1e95
のものなのですが……仮に cat-file
のようなコマンドを実行しても、置き換え後のデータが返ってきます。
$ git cat-file -p 81a708d
tree 7bc544cf438903b65ca9104a1e30345eee6c083d
parent 9c68fdceee073230f19ebb8b5e7fc71b479c0252
author Scott Chacon <schacon@gmail.com> 1268712581 -0700
committer Scott Chacon <schacon@gmail.com> 1268712581 -0700
fourth commit
振り返ってみればわかるように、81a708d
の本当の親は仮のコミット(622e88e
)であって、このコマンド出力にある 9c68fdce
ではありません。
もう1つ注目したいのが、参照のなかに保持されているデータです。
$ git for-each-ref
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/heads/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/remotes/history/master
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/HEAD
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/replace/81a708dd0e167a3f691541c7a6463343bc457040
これはつまり、置き換えの内容を簡単に共有できるということです。サーバーにプッシュできるデータですし、ダウンロードするのも簡単です。この節で説明したように歴史を結びつける場合には、この方法は役に立ちません(というのも、全員が両方の歴史をダウンロードしてしまうからです。そうであれば、わざわざ分割する必要はないですよね)。とはいえ、これが役に立つケースもあるでしょう。