2016年9月29日
Git Undo エイリアスを定義する

(2016-08-25)by Enrico Campidoglio
本記事は、原著者の許諾のもとに翻訳・掲載しております。
(注:2017/06/22、いただいたフィードバックを元に翻訳を修正いたしました。)
このような経験はありませんか?「ローカルのコミットをし過ぎてしまったことに急に気づいて ローカルコミットを書き直している最中 、rebaseしすぎてしまい、自分が思い描くような履歴になっていなかった」。どうですか? 私はあります。そのような時、「ただ CTRL + Z で開始時に戻れればいいのに……」と思います。もちろん、決してそんなに単純ではありません。 GUI でさえもです。
そんな絶望的な瞬間を経験することがあったので、 git undo コマンドを独自に作成する決心をしました。以下に私のアイデアと、そこに行き着くまでの過程を紹介したいと思います。
Reflog
Gitのundo操作を行うために私が最初に目を付けたのは、reflogです。「 reflogとは何だろう? 」と思うかもしれませんね。Gitでは、 ブランチの参照 が移動する度に ^(1) 、いわばローカルジャーナルと言われる場所に、移動する前の値が記録されます。このジャーナルのことを リファレンスログ 、または reflog と呼びます。
リポジトリには、 各ブランチ に対するreflogが1つずつと、それらとは別に HEAD 参照に対するreflogが1つあります。
ブランチのreflog内のエントリのリストを入手する方法は、 git reflog (ブランチ名) と入力するだけの簡単なものです。
git reflog masterこれにより、 master ブランチのreflog内のエントリが出力されます。

HEAD 自体のreflogを参照したい場合は、引数を省けばいいだけです。
git reflogこれで HEAD 参照のみに対する同出力が得られます。

ぱっと見ただけでは分かりませんが、reflog内のエントリは 新しいものが順に 上から記録されています。
逆にすぐに分かることは、各エントリにはそれぞれ インデックス が付いていることです。このインデックスを使うことで、特定のreflogのエントリに関連するコミットを直接参照することができるため、実はこれは 非常に 有用です。ここでは、reflogのエントリを参照するには以下の構文を用いる、ということだけ言っておきましょう。
reference@{index} @ マークの両側には以下を入力します。
referenceには、ブランチ名もしくはHEAD名。indexには、reflog内のエントリの位置 ^(2) 。
例えば、 HEAD が 2つ前 に参照していたコミットを確認したいとしましょう。その場合は、 git show コマンドの後に HEAD@{2} と入力します。
git show HEAD@{2}また、 master が 直前 に参照していたコミットを確認したい場合は、以下のようになります。
git show master@{1}Undo エイリアス
重要なことは、 reflog は、ブランチによって参照されたコミットの履歴を追跡するということです。これはWebブラウザがアクセスしたURLの履歴を追跡するのと同じことです。
つまり、 @{1} によって参照されたコミットは、 常に 現在の値の1つ前に参照したコミットであるということです。
refloを git reset コマンドと合わせて使う場合の構文は、以下のようになります。
git reset –-hard master@{1}これによって、 インデックス と 作業ディレクトリ である HEAD を、ブランチによって参照された直前のコミットに移動することができます。これは、Webブラウザで 「戻る」ボタンをクリック するのと同じことなのです!
この時点で、独自の git undo コマンドを実装するのに必要な全てが揃いました。実装する際の エイリアス は以下の通りです。
git config --global alias.undo '!f() { \
git reset --hard $(git rev-parse --abbrev-ref HEAD)@{${1-1}}; \
}; f'これではあまりにも長ったらしくて分からりづらいので、1つずつ区切って見ていきましょう。
-
!f() { ... } f
ここでは、fという名の シェル関数 をエイリアスとして定義し、それを即座に呼び出します。 -
$(git rev-parse --abbrev-ref HEAD)@{...}
現在のブランチ名を取得するために、git rev-parseコマンドのあとに、--abbrev-refオプションを入力します。その後に、reflog内1つ前の位置への参照(例えばmaster@{1}など)を作るため、@{...}を連結させます。 -
${1-1}
最初のパラメータ$1としてreflogの中で位置を指定し、デフォルト値に1を指定します。エイリアスをシェル関数として定義する理由はここにあります。つまり、このパラメータのデフォルト値を標準的な Bash構文 を使って指定できるようにするためです。
このようなオプションのパラメータを使うことの利点は、任意の回数の操作を取り消すことができるということです。何も指定しない場合は、直前の操作を取り消すことができます。
試してみましょう
以下のような履歴があったとします。 ^(3)

この履歴にはコミット C で分岐する master と feature の2つのブランチがあります。この例では、 master ブランチにある最新のコミット、つまりコミット F を取り除き feature ブランチとマージしたいものとします。
git reset --hard HEAD^
git merge featureこの時点で、以下のような履歴が出来上がります。

ご覧のように、うまくいきました。ですが、まだ満足いく結果ではありません。ある理由で前の履歴に戻りたいとします。つまり実際には、直前の2つの操作、 merge と reset を取り消すということです。ここで undo エイリアスの出番です。
git undo 2これによって master@{2} で参照されたコミットが HEAD に移動します。このコミットは、 master ブランチが2つ前のreflogエントリで差していたコミットです。それではさらに履歴をチェックしてみましょう。

全てが元通りになりました。\o/
では undoを をundoしたい場合は? 簡単ですよ。 git undo そのものは、undo操作を1回行うということなので、次のように入力すれば十分です。
git undoつまり、引数なしで、 git undo 1 と入力したのと同じ意味になるのです。
どうです、この記事は役に立ちましたか? もしこの記事で紹介したのと同じような、別のテクニックも学びたいようであれば、他にも Pluralsight の Advanced Git Tips and Tricks(さらに進んだGitのコツとテクニック) コースでいくつか記事を書き溜めているので読んでみてください。
-
つまり、前にコミットしたのとは異なるコミットを指すように 変更 されます。 ↩
-
ここでは 日付 を使用することもできます。例えば
master@{yesterday}またはHEAD@{2.days.ago}を試してみてください。素晴らしいと思いませんか? ↩ -
履歴は簡潔で色鮮やかなものが好きです。このため、私はただの
git logを使いません。その代わりにlgというエイリアスを定義しており、 出力をカスタマイズする のため--prettyオプションを使っています。さらに詳しく知りたい場合は、このことについて以前 見た目のいい履歴についての重要性 について書いた記事があるので読んでみてください。 ↩
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
- X: @yosuke_furukawa
- Github: yosuke-furukawa










