餡子付゛録゛

ソフトウェア開発ツールの便利な使い方を紹介。

覚えておくと安心が得られるGitの操作の取り消しや履歴改編

主流の分散バージョン管理システムのGitはチュートリアル本が公開されていて、それを読みながら使い始める事は難しくありませんが、人は過ちを犯すものなので、操作の取り消しや履歴改編についてメモを用意して置いたほうが、利用において心理的な障壁は低くなります。GitHubなどにある共有リポジトリにpushする前に、恥ずかしい間違いは無かったことにできるようにしておきましょう。

リビジョン指定方法

本題に入る前に、Gitコマンドで使うリビジョン*1指定方法を復習します。

まず、コミットハッシュ値(commit hash value)の先頭4文字以上で絶対指定でき、これが基本です*2。コミットハッシュ値がc49ccf7812135fddc8b10e6fcd7657269301b520のリビジョンであれば、c49cで指定することができます。万が一、複数のリビジョンが該当する場合は古い方を指定することになりますが、そのときは長く記述しましょう。

次に、HEADでブランチヘッド((そのブランチの最新のコミット))を指定できます。さらに、{commit hash valueの先頭4文字以上}かHEADの後に~をつけると1つ前、~~をつけると2つ前…と相対指定ができます。~3と書くとブランチヘッドの3前のリビジョンを指定できます。また、c49c~~と言う風にも指定することができます。

^(か^1)と^2も指定できます。これは~と似ていますが、マージにより親リビジョンが二つある場合、^1が同じブランチの親、^2がマージ先の親リビジョンになります。~と^は混じえて使えます。HEAD~~^2~3と書くと、ブランチヘッドの2つ前のさらにマージ先親リビジョンの3つ前のリビジョンとなります。日常的にはHEAD~やHEAD~$[n}と{commit hash valueの先頭4文字以上}しか使わない気もしますが、覚えておきましょう。

コミット・ツリーの例

上図のリポジトリーのコミットハッシュ値e956…のリビジョンを指定する場合は、e956,HEAD~,HEAD~1,HEAD^,HEAD^1,c494c~,c494c~1,c494c^,c494c^1のどれかで指定できます。

日々のワークフローにおける誤りの訂正

Gitのコマンドは複数つかって一つの作業になることが多く、作業しながら覚えた方が早いです。変更履歴の記録をしていきましょう。最初は誤って記録してしまわないように気を使うかも知れませんが、(個人で管理している範囲に置いては)訂正は容易です。訂正が楽すぎて油断し、雑なコミットが積み上がりますが。

Git利用のワークフロー

上は典型的なGit利用のワークフローを図示したものです。履歴の更新操作と更新操作は実線で、それを取り消す方法を破線で結んでいます。

  • git-initやgit-cloneでつくったGitリポジトリーは、rm -fr .gitでワーキングツリー*3のファイルを残してGitリポジトリーで無くすことができます。
  • ワーキングツリーの変更は、git-checkoutで取り消せます。
  • ステージングは、git-resetで取り消せます。
  • 直近のコミットは、新たなgit-addとgit-commit --amendで上書き(もしくは追加)できます。コミットメッセージだけの上書きもできます。
  • 直近のコミットは、git reset --soft HEAD~で取り消せます。ワーキングツリーのファイルはそのままなので、修正後に再コミットもできます。
  • git reset --soft HEAD~の取り消しは、git reflogで消したコミットの番号(e.g. HEAD@{1})を確認して、git-resetすることで、コミット取り消しの取り消しができます。
  • git rebase -iで、直近以外の昔のコミットの整理統合や分割、変更ができます。
  • リモートリポジトリの設定や再設定はgit-remote set urlでできます*4

以上の修正方法を大雑把に把握しておきましょう。また、よくある応用例を以下に紹介します。

誤字脱字の発見と歴史改ざん

気づいたら入り込んでいたTYPOを消したい場合は、以下のようにします。

git blame ${filename} # 修正したい行がコミットされたリビジョンを特定。${filename}は . でもよい。
git rebase -i ${commit hash value}~
# blameで突き止めたリビジョンにeditを指定
vi ${filename}
git add ${filename}
git commit --amend
git rebase --continue

コミット先のブランチを修正

複数のブランチ*5を切り替えて編集しているときに、間違ったブランチにコミットしてしまった場合も、簡単に無かったことにできます。

git reset HEAD~
git stash #  git stash push ${filename}で特定のファイルの指定もできる
git checkout ${branchname}
git stash pop
git add -p . 
git commit

git-stashは編集中のファイルを一時退避し、リポジトリーのファイルをブランチヘッドの状態に戻すコマンドです。git-stash popで退避した編集データで復旧できます。復旧しないで退避した編集データを破棄する場合は、git stash listで復旧している編集データを確認して、git-stash dropをします。

コミットを複数に分割

複数の機能の変更を同時にコミットしてしまっても、後で分割することができます。

git reset HEAD~
# もしくは git rebase -i ${commit hash value}~ で分割したいリビジョンにeditを指定し、git reset ${commit hash value}~
git add -p . # 一部分をステージングする
git commit -m "ある部分"
git add -p . # 一部分をステージングする
git commit -m "違う部分"
git add .
git commit -m "残りの部分"
git rebase --continue

ユーザー情報の設定

gitでコミット*6をするにはユーザー名とメールアドレスの情報を設定する必要があり、OSのユーザーアカウントに紐付けるグローバル設定を最初に行うのが通例ですが、Gitリポジトリ*7ごとに設定もできます。

git config user.name ${name}
git config user.email ${address}

これで.git/configに設定が書き込まれます。

複数コミットしてからユーザー情報を訂正した場合は、GitのCommitユーザを修正する方法 #GitHub - Qiitaを参照してください。

*1:リポジトリーのあるバージョン

*2:commit hash valueはリビジョンのオブジェクトのアルゴリズムSHA1によるハッシュ値を16進数表記したものですが、ほとんど一意なリビジョンを指すラベルと覚えておけば、だいたい済みます

*3:編集するファイルとディレクトリーで、.git以下に無いもの

*4:リモートリポジトリーがGitHubなどではなく別のディレクトリーである場合、receive.denyCurrentBranch云々と言うエラーが出て拒絶されますが、その場合はリモートリポジトリーでgit config --bool core.bare trueとする必要があります。

*5:派生バージョンの管理リポジトリ

*6:履歴に記録する行為、もしくは特定のバージョン。

*7:Gitで管理するファイルがあるディレクトリーで、トップに管理情報の入った.gitディレクトリが存在する。