20210612のGitに関する記事は3件です。

Gitの内部データ構造をGraphvizで描画してみた 第4回 ワークツリーとインデックスとblob

解決すべき問題 git addコマンドやgit commitコマンドを実行したときGitレポジトリのなかで何がおきているのだろう?Gitの内部のデータ構造(ワークツリーとインデックスとcommit/tree/blobオブジェクト)が変化するらしいが、モノが動く様子を見たことはない。図で説明してほしい。 解決方法 Pythonでツール kazurayam/visualize_git_repository.py を開発した。これを使えばいま自分の手元にあるプロジェクトの .git ディレクトリのなかにあるオブジェクト群の実物を読み出し、Graphvizでグラフを生成してPNG画像ファイルを出力することができる。 説明 デモ用にディレクトリを作りファイルを3つ作ろう。git initコマンドでGitレポジトリを作ろう。git addしたらインデクスが更新されblobオブジェクトが作られる。そしてgit commitしたらblobがレポジトリに登録される。このときGitレポジトリの中で何が起きているのだろうか?Graphvizで図示してみよう。 おまけに応用問題をひとつ。ファイルを修正してgit addしたあと、git commitせずに続けてもう一度ファイルを修正してgit addしたとしよう。Gitレポジトリの中で何が起きるのだろうか? Graphvizで図示してみよう。 ステップ1 git initした後でgit addする前 デモ用のディレクトリを作ろう。このディレクトリのことを以下で $project という記号で表すことにします。 % mkdir $project % cd $project このディレクトリのなかにファイルを3つ作ろう。 % echo '*~' > .gitignore % echo '#Read me plase' > README.md % mkdir src % echo 'print("How do you do?");' > src/greeting.pl このディレクトリにGitレポジトリを作ろう。 % git init シェルコマンド ls で $projectディレクトリの内容を確認しよう。 % ls -la . total 16 drwxr-xr-x 6 kazuakiurayama staff 192 6 12 12:58 . drwxr-xr-x 4 kazuakiurayama staff 128 6 12 12:58 .. drwxr-xr-x 7 kazuakiurayama staff 224 6 12 12:58 .git -rw-r--r-- 1 kazuakiurayama staff 3 6 12 12:58 .gitignore -rw-r--r-- 1 kazuakiurayama staff 17 6 12 12:58 README.md drwxr-xr-x 3 kazuakiurayama staff 96 6 12 12:58 src $project/.gitというディレクトリができている。.gitディレクトリがいわゆる「Gitレポジトリ」の実体だ。 .gitディレクトリの中に何があるのだろうか?lsコマンドでみてみよう。 % ls -la ./.git total 16 drwxr-xr-x 7 kazuakiurayama staff 224 6 12 12:58 . drwxr-xr-x 6 kazuakiurayama staff 192 6 12 12:58 .. -rw-r--r-- 1 kazuakiurayama staff 23 6 12 12:58 HEAD -rw-r--r-- 1 kazuakiurayama staff 137 6 12 12:58 config drwxr-xr-x 5 kazuakiurayama staff 160 6 12 12:58 hooks drwxr-xr-x 4 kazuakiurayama staff 128 6 12 12:58 objects drwxr-xr-x 4 kazuakiurayama staff 128 6 12 12:58 refs $project/.git/objectsというディレクトリができている。このなかにcommitオブジェクト、treeオブジェクト、blobオブジェクトのファイルが作られて保存される。 この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 Git用語「インデックス」とは具体的には $project/.git/index という名前のバイナリファイル1個である。ところがこの段階ではgit initした直後なのでindexファイルはできていない。indexファイルはgit addコマンドで作られるのだ。 $project/.git/objectsディレクトリはできているがまだ中身が空っぽだ。git initした直後なので無理もない。 ステップ2 git addしたらインデックスとblobが更新された git addコマンドを実行しよう。何が起こるだろうか? % git add この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 3つのファイルに対応するblobオブジェクトが.git/objectsディレクトリの中にひとつづつ計3個できた。 インデックスが作られた。インデックスの中には3行あって、ファイルのパスとそれに対応するblobオブジェクトのhash値が記録されている。 ワークツリーのなかにsrcというディレクトリがある。ところがインデックスのなかにはディレクトリに対応する行が無い。ワークツリーのなかのファイルに相当する行だけがインデックスのなかにある。サブディレクトリの下にあるファイルのパス文字列としてサブディレクトリがあることが示唆されている。たとえばsrc/greeting.plのように。 ワークツリーのなかにsrcというディレクトリがある。ところが.git/objectsのなかにはそれに対応するモノがない。 git addコマンドは2つの仕事をするのだ。第一にワークツリーにあるファイルを変換してblobオブジェクトを作りだすこと。第二にインデックスを更新すること。 git addコマンドはtreeオブジェクトを作らない。treeオブジェクトを作りだすのはgit commitコマンドの役割なのだ。 ステップ3 git commitしたらblobがツリーにつながった さあ、コミットしよう。 % git commit -m "initial commit" この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 .git/objectsディレクトリのなかにcommitオブジェクトとtreeオブジェクトができた。commitからtreeへ線がつながり、treeからblobへ線がつながった。追加した3つのファイルのblobオブジェクトはひとつ残らずcommitから参照可能な形になった。 ワークツリーのsrc ディレクトリに相当する行がインデックスのなかには無かった。ところが.git/objectsディレクトリのなかにはsrcに相当するtreeオブジェクトができている。git commitコマンドが実行されたとき、インデックスに記録されていたsrc/greeting.plというファイルパス文字列に基づいてtreeオブジェクトが生成されたのだ。 インデックスの内容に変化は無い。git commitコマンドがインデックスを変更しないことがわかる ステップ4 TODO.txtファイルを追加してgit addする前 ワークツリーにsrc/TODO.txtファイルを追加しよう。 % mkdir doc % echo 'Sleep well tonight.' > doc/TODO.txt まだ git add しない。この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 ワークツリーでファイルをどんなにいじってもgit addしないうちはGitレポジトリになんら影響を及ぼさない。 ステップ5 git addしたらインデックスとblobが更新された ではgit addしよう。 % git add . この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 ワークツリーに追加されたsrc/TODO.txtファイルに対応するblobオブジェクトが作られた。 そのblobがインデックスに含まれるようになった。 addされたblobオブジェクトはtreeオブジェクトと線でつながっていない。未完だ。 上記の図を見てあなたは「commitとtreeとblobがたくさんあって線で繋がっているがごちゃごちゃして見づらい」と思ったかもしれません。別の記事ではもっときれいないツリー形の図でcommitとtreeとblobからなるグラフを紹介しています。しかし本稿ではcommitとtreeとblobをツリーの形状で示すのをあえて避けました。本稿ではaddされたblobオブジェクトがtreeと線で繋がっていない状態になるということを示すことが重要だったからです。 ステップ6 git commitしたらblobがツリーにつながった ではgit commitしよう。 % git commit -m "add src/TODO.txt" この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 doc/TODO.txtファイルに対応するblobオブジェクトがtreeオブジェクトと線でつながって、commitオブジェクトからたどれるようになった。「ファイルをコミットする」というのはこういうグラフの形状の変化を意味するのだ。 これでひと仕事済んだ。 ステップ7 READMEファイルを修正してgit addした ワークツリーにすでにあるREADME.mdファイルを修正しgit addしよう。Gitレポジトリのなかがどう変化するだろうか? まずREDME.mdファイルを修正しよう。 % echo 'Read me more carefully' > README.md git addしよう。 % git add . この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 修正した後のREADME.mdファイルに対応するblobオブジェクト(hash=5a79541)が追加された。 インデックスをみるとREADME.mdファイルのhashは新しいblobオブジェクトのhashに交換されている。 修正される前のREADME.mdファイルに対応するblobオブジェクト(hash=aadb69a)は.git/objectsディレクトリの中に残っている。いったんcommitされたblobは原則的に削除されないのだ。このblobは過去のcommitオブジェクトからリンクされていて履歴として参照可能だ。 ステップ8 READMEファイルをもう一度修正してgit addした READMEファイルを修正してgit addしたあと、git commitせずに、もう一度READMEファイルを修正してgit addしよう。インデックスやblobオブジェクトがどういう状態になるのだろうか? READMEファイルを修正しよう。 % echo 'I know you didnt read me.' > README.md addしよう。 % git add . この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 修正されたREADME.mdファイルに対応する新しいblobオブジェクト(hash=9230643)ができた。インデックスが新しいblobを参照するように更新された。 前回git addしたとき作られたblobオブジェクト(hash=5a79541)は.git/objectsディレクトリに残っている。ただしこのblobは完全に孤立している。すなわちtreeオブジェクトと線でつながっていないし、インデックスとのつながりも無くなってしまった。 ステップ9 READMEファイルをgit commitした コミットして締めくくろう。 % git commit -m "modified README.md"` この時点でvisualize_git_repositoryツールを実行したら次のグラフが生成された。 このグラフから次のことが読みとれる。 commitオブジェクトがひとつ増えた。最新のcommitオブジェクトから線が伸びて最新状態のREADME.mdファイルのblobが参照される形になった。 addされたがcommitされなかった孤立したblobオブジェクト(hash=5a79541)は放置されている。 三度目のコミットが完了した。めでたしめでたし。 ゴミ掃除 孤立したblobオブジェクトはこの先どうなる運命なのだろう? じつはGitはGarbage Collectionを備えていて、適切なタイミングで自動的にゴミを掃除してくれる。 git gc ここでは深堀りしません。 ツールについて 本稿で示したPNG画像は自作のツール visualize_git_repository で描画した。このツールはPython言語で開発した。ソースコードは下記のGitHubレポジトリにある。 https://github.com/kazurayam/visualizing-git-repository このツールは下記2つのライブラリを利用している。 pytest python graphviz PNG画像を生成するにはコマンドラインで下記の操作をする。 $ cd $visualize_git_repository $ pytest -s kazurayam/visualize_git_repository.py::test_4_index 上記の例を作るのにどういうgitコマンドを実行したのかを知りたいならプログラムのソースコードを読み解いてください。下記を入り口として解読してください。 kazurayam/gitviz/visualize_git_repository.py まとめ git addとやったとき、git commitとやったときGitレポジトリの中でデータがどのように変化していくのかをクリアに図示することができたとおもいます。 具体的で細かい情報満載のグラフを9個作った。作画ツールで矩形や曲線を手書きするやりかたでは9個も作れなかった。gitコマンドでGitレポジトリの状態を読み出してDOT言語に変換しGraphvizで図を描くというやり方だからこそできた。 author: kazurayam date: June, 2021 連作の目次 第1回 commitとtreeとblob 第2回 ブランチとマージ 第3回 タグ 第4回 ワークツリーとインデックスとblob
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Git】複数コミットをまとめてrevertする方法

この記事でわかること 特定のコミットまでの全てのコミットをまとめてrevertする方法 revertコミットをまとめる方法 特定のコミットまでの全てのコミットをまとめてrevert 数個程度のコミットでしたら普通に git revert HEAD^ とか git revert <コミットID> とかでいいのですが、100コミットとかあった場合は一つ一つやっていたら日が暮れてしまいます まとめてrevertするには以下のコマンドを使います git revert HEAD...HEAD~100 --no-edit HEAD...HEAD~100 ... 「今いるコミット」から「100個前のコミット」までを指定 --no-edit ... revertコミットのメッセージを編集しない 例えば↓のようなコミット履歴でcommit1~3全てrevertしたい場合は c59f92d (HEAD -> test) commit3 91cfe7b commit2 32349cf commit1 git revert HEAD...HEAD~3 --no-edit これで直近3つのコミットを全てrevertできます e471f93 (HEAD -> test) Revert "commit1" 20f3821 Revert "commit2" 35c18ae Revert "commit3" c59f92d commit3 91cfe7b commit2 32349cf commit1 が、このままだとrevertコミットが大量にできて履歴が見づらくなってしまうのでrevertコミットをまとめていきます revertコミットをまとめる これはrevertコミットに限った話ではないですが コミットをまとめたいときは以下のコマンドを使います git rebase -i HEAD~100 -i ... エディタを開いて編集できる HEAD~100 ... 直近何個のコミットをまとめるかを指定(今回は100個) このコマンドを打つと以下のような画面が出てきます ここでまとめるベースのコミットをpickにしてそのコミットにひっくるめたいコミットはsquashにして閉じると コミットメッセージの編集画面が出てくるので 適当に編集して 閉じると c3a9193 (HEAD -> test) commit1~3をrevert c59f92d commit3 91cfe7b commit2 32349cf commit1 revertでたくさんできたコミットを一つにまとめられます
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gitコマンドのまとめ(自分用)

最初に行う作業 リポジトリを作成する $ git init add ルートディレクトリの中身を全てインデックスに登録する $ git add . commit インデックスに登録された内容をリポジトリに記録するコミットを実行 ""の中はコミットの時にメッセージを書く $ git commit -m "初めてのコミット" ローカルリポジトリとリモートリポジトリの紐付け URLはGitHubで作成したリモートリポジトリのURLを記述 $ git remote add origin URL リモートリポジトリにプッシュ $ git push -u origin master 運用で行う作業 ブランチを表示する git branch -a マスターにチェックアウト git checkout master プル git pull orgin master コミット git commit -a ブランチ git branch [branch_name] //ブランチの作成 git checkout [branch_name] //ブランチの移動 git branch -d [branch_name] //ブランチの削除 git branch -m [branch_name] //現在のブランチ名の変更 git branch // ローカルブランチの一覧 git branch -a //リモートとローカルのブランチの一覧 git branch -r //リモートブランチの一覧 git checkout -b branch_name origin/branch_name //リモートブランチへチェックアウト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む