YakstYakstは、海外の役立つブログ記事などを人力で翻訳して公開するプロジェクトです。修正ありGitで操作を取り消す方法色々コミットなどのGitの色々な操作を取り消すための方法。その操作が必要になる場面と、方法、その仕組みと意味合いまで丁寧な説明。GitHub社のブログから。未報告Tweetあらゆるバージョン管理システムの最も便利な機能の1つに、間違いを「取り消す(undoする)」ことができるというのがあります。Gitにおいては、「取り消す」という言葉には少しずつ異なる様々な意味合いがあります。新しいコミットをすると、ある時点におけるあなたのリポジトリのスナップショットをGitが保存します。それにより、後からあなたのプロジェクトを以前の状態に戻すのにGitを使うことができるわけです。この記事では、あなたの変更を「取り消し」たくなるだろうよくあるシナリオを提示して、そのためにうまい具合にGitを使えるような方法を取り上げようと思います。pushを実行してGitHubに変更を送信してから、コミットの1つに問題があることに気付きました。あなたはそのコミットを取り消したくなりました。revertは、与えられたSHAと反対の(元に戻す)新しいコミットを作ります。古いコミットで「やったこと」は、新しいコミットでは「やったことの反対」になります。古いコミットで削除されたものは新しいコミットで追加され、古いコミットで追加されたものは新しいコミットで削除されます。ヒストリを書き換えないという点で、これがGitの安全かつ最も基本的な「取り消し」のシナリオです。pushすることができます。#42」だったと気付きました。--amendは、前のコミットの内容のステージされた全ての変更をまとめる新しいコミットで、最新のコミットを置き換えて更新します。現時点で何もステージされていない場合は、前のコミットメッセージを置き換えるだけです。キーボードの上を猫が歩き回ってしまい、何かが保存された挙句、エディタがクラッシュしてしまいました。変更をコミットしてはいません。ファイル内の全ての変更を取り消し、前のコミットの状態まで戻してしまいたくなりました。checkoutは、ワーキングディレクトリ内をGitが管理している直前の状態に戻します。戻したいブランチ名や特定のSHAを指定することもできますが、デフォルトではGitはHEAD、つまり現在チェックアウトされているブランチの最新の状態をチェックアウトするものとして扱います。この方法で「取り消し」した変更は、完全に消えてしまいます。それまで行ったものはコミットされませんので、後でどうしても戻したくなってもGitは助けてくれません。あなたはローカル上で何かのコミットを行いました(まだプッシュはしていません)。あまりにもできがひどかったので、最後の3つのコミットを取り消して、何もなかったことにしたくなりました。resetは、指定されたSHAまであなたのリポジトリの歴史を巻き戻し、その間のコミットがなかったかのようにしてしまいます。resetはワーキングディレクトリはそのまま保持します。コミットは消えてしまいますが、コンテンツはディスク上に残ります。この方が安全ですが、コミットだけでなく変更も一発で「取り消し」してしまいたくなることもあるでしょう。その際には、--hardオプションを使います。--hardでそれらの変更を「取り消し」した(上の手順を参照)後、やっぱり取り消したものを戻したい!と思いました。reflogを使うと、プロジェクトのヒストリを戻すのに素晴らしい情報が得られます。reflogを使えば、コミットしたものなら何でも、ほとんどのものを元に戻せます。logコマンドはよく使っているでしょう。reflogはこれに似ていますが、こちらはHEADが変更された時を一覧表示してくれます。表示されるのはHEADの変更のみです。<問題のあったファイル名>`では変更されません(前のシナリオで見たように、これらの変更はコミットされないので、reflogはリカバリの際には使い物になりません)。reflogでは、全ての歴史を見られるわけではありません。Gitは「参照できない」オブジェクトを定期的に掃除します。月単位の日が経ったコミットが見られることは期待しない方がよいでしょう。あなたが実行したreflogは、あなたのみのものです。reflogを元にリストアすることはできません。なると、中途半端な過去のコミットを「取り消す」のにreflogを使うにはどうしたらよいのでしょうか?これは、どこまでやりたいかによって変わってきます。コミットを行った後、masterからチェックアウトした状態でやってしまったことに気づきました。今のコミットをfeatureブランチでやり直したいと思っています。<ブランチ名>して新しいブランチを作るのには慣れているでしょう。これは、新しいブランチを作り、すぐにチェックアウトする簡単な方法として一般的ですが、まだブランチのスイッチはしたくないとしましょう。featureで、最新のコミットを指したfeatureという新しいブランチを作りつつも、masterをチェックアウトした状態のままにします。--hardでmasterを手元のコミットを実行する前のorigin/masterに巻き戻します。大丈夫、そのコミットはfeatureブランチに残っています。checkoutで新しいfeatureブランチにスイッチすれば、先ほどまでやっていた作業の内容がそのままになっているはずです。masterを元にした新しいfeatureブランチで開発を始めましたが、masterはorigin/masterからはかなり遅れていました。masterブランチはorigin/masterと同期しましたが、featureブランチも遅れた状態ではなく最新からコミットし始めたいと思いました。<新しいブランチ名>し、さらに変更をコミットしても可能です。この方法だとコミット履歴が消えてしまいます。いい方法が上で紹介した方法です。masterはいくつかの処理を実行します。最初に現在チェックアウトしているブランチとmasterの共通の過去のヒストリを特定します。現在チェックアウトしているブランチをその過去のヒストリにリセットします。その時、その間のコミットを一時領域に退避させておきます。現在チェックアウトしているブランチをmasterの最後まで進め、masterの最後のコミットより後に、一時領域に置いていたコミットをリプレイします。ある方法で機能を作り始めたが、途中で他の方法がいいのではと思い始めました。たくさんのコミットをしていますが、その中のいくつかだけがその方法に必要です。それ以外のコミットは消してしまいたいと思っています。-iはrebaseを「インタラクティブモード」で実行します。上述のような動きでrebaseを行いますが、各コミットをリプレイする前に都度一時停止し、各コミットを変更するか確認してからリプレイします。-iはデフォルトのテキストエディタを開いて、適用されるコミットの一覧を表示します。始めの2つの列がカギになります。1列目は、2列目にあるSHAで特定されるコミットを選択するかどうかのコマンドです。-iのデフォルトでは、各コミットを適用するという意味のpickコマンドになっています。コミットを適用しないなら、エディタでその行を削除してしまいましょう。ダメなコミットが必要ないので、1行目と3、4行目を削除してしまうことにしましょう。コミットの内容はそのまま保持したいけれど、コミットメッセージを変更したければ、rewordコマンドを使えます。1列目のpickをreward(あるいは単にr)で置き換えましょう。-iはSHAの列以降を無視してしまうので、それだとうまく動きません。SHAの後の文字列は、あくまで0835fe2が何のコミットなのかを思い出すために存在しているのです。-iが実行された後に、書き換える必要のあるものについて新しいコミットメッセージの入力を求められます。2つのコミットをまとめてしまいたい時は、下のようにsquashあるいはfixupコメントを使用できます。squashとfixupは、「上」のコミットにまとめます。これらの「まとめる」コマンドが指定されたコミットは、直前のコミットにマージされるのです。上の例で言うと、0835fe2と6943e85が1つのコミットとしてまとめられ、それから38f5e4eとaf67f82がまた別のコミットとして一緒になります。squashを選ぶと、Gitは新しいまとめられたコミットに新しいコミットメッセージを与えるようにプロンプトを出します。fixmeを選ぶと、リストの最初のコミットのコミットメッセージが新しいコミットのメッセージになります。この例の場合、af67f82が「やっちまった(ooops)」コミットなので38f5e4eのコミットメッセージをそのまま使えばいいのですが、0835fe2と6943e85をまとめたものには新しいコミットメッセージを付けたいということになります。保存してエディタを終了すると、Gitは上から順にコミットを適用していきます。保存前にコミットの順番を変更すれば、その順番に変更して適用できます。必要なら、af67f82と0835fe2を以下のように変更することもできます。以前のコミットにファイルを含め忘れてしまいました。忘れたファイルが以前のコミットに含まれた状態になったらいいのにと思っています。--amendはもう使えません。--squashを使うとタイピングの手間が省けます)。--fixupを使うこともできます。--fixupを使えば良いでしょう。-iは対話的なリベースのエディタを起動しますが、下図のようにコミットの一覧内では既にsquash!やfixup!がコミット対象と対になった状態になります。--squashや--fixupを使う際には、修正したいコミットのSHAを覚えておく必要はなく、1つ前のコミットか5つ前のコミットかを覚えておけば十分です。Gitの^や~が便利でしょう。HEAD^はHEADの1つ前のコミットという意味ですし、HEAD~4はHEADの4つ前のコミットという意味、つまり5つ戻るという意味です。application.logファイルを間違ってリポジトリに入れてしまったので、アプリケーションを起動するたびに、application.logにステージされていない変更があると言われるようになってしまいました。*.logを.gitignoreに入れましたが、まだ同じようなメッセージが出てしまいます。.gitignoreは、Gitがファイルの変更をトラッキングしないようにしたり、トラッキングされたことのないファイルの変更を通知したりしないようにはできますが、一度でもaddしたりcommitしてしまうと、ファイルの変更をずっと追跡してしまうようになります。-fで.gitignoreを無視するよう強制するようにした場合も同じことが起こります。-fは使わない方がいいでしょう。--cachedを使い、トラッキング対象から除外しつつディスク上にはファイルを残します。statusにも表示されなくなり、また間違ってファイルをコミットしてしまうこともなくなります。まとめGitで色々な操作を取り消す方法を説明しました。ここで使ってきた各種Gitコマンドをもっと詳しく知りたいなら、関連するドキュメントを参照しましょう。@yakstcom_req_jaYakstYakstお問い合わせ。