Git の元に戻すオプション

Gitでは何も削除されないので、Gitで作業するときは作業を取り消すことができます。

すべてのバージョン管理システムには、作業を取り消すためのオプションがあります。しかし、Gitの非中央集権的な性質のために、これらのオプションは倍増します。あなたが取るアクションは、開発者のステージに基づいています。

GitとGitLabでの作業について、詳しくはこちらをご覧ください:

変更を取り消せるとき

Git の標準的なワークフローの場合:

  1. ファイルを作成あるいは編集します。ファイルはステージされていない状態から始まります。新しいファイルであれば、Gitによってまだ追跡されていません。
  2. ローカルリポジトリ (git add) にファイルを追加すると、ファイルはステージされた状態になります。
  3. ファイルをローカルリポジトリ (git commit) にコミットします。
  4. その後、リモートリポジトリ (git push) にコミットすることで、他の開発者とファイルを共有することができます。

このワークフローでは、いつでも変更を取り消すことができます:

ローカル変更の取り消し

変更をリモートリポジトリにプッシュするまでは、Git で行った変更はローカルの開発環境にしか存在しません。

ステージされていないローカルの変更を元に戻す

変更を行ったがまだステージされていない場合、作業を取り消すことができます。

  1. git statusを実行して、ファイルがステージされていない(git add <file> を使用していない)ことを確認します:

    $ git status
    On branch main
    Your branch is up-to-date with 'origin/main'.
    Changes not staged for commit:
      (use "git add <file>..." to update what will be committed)
      (use "git checkout -- <file>..." to discard changes in working directory)
       
        modified:   <file>
    no changes added to commit (use "git add" and/or "git commit -a")
    
  2. オプションを選択して、変更を取り消します:

    • ローカルの変更を上書きします:

       git checkout -- <file>
      
    • ローカルの変更を保存して、後で再利用できるようにします:

       git stash
      
    • すべてのファイルのローカル変更を永久に破棄するには:

       git reset --hard
      

ステージされたローカルの変更を元に戻す

ステージングにファイルを追加した場合、元に戻すことができます。

  1. git statusを実行して、ファイルがステージングされている(git add <file> を使用した)ことを確認してください:

    $ git status
    On branch main
    Your branch is up-to-date with 'origin/main'.
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
       
      new file:   <file>
    
  2. オプションを選択して、変更を取り消します:

    • ファイルのステージを解除しますが、変更は保持します:

       git restore --staged <file>
      
    • 変更を保持したまま、すべてのステージを解除するには

       git reset
      
    • ファイルを現在のコミット(HEAD)にアンステージするには :

       git reset HEAD <file>
      
    • ローカルでの変更をすべて破棄し、後のために保存します:

       git stash
      
    • すべてを永久に破棄するには

       git reset --hard
      

ローカルの変更を素早く保存

別のブランチに変更したい場合は、git stash.

  1. 作業を保存したいブランチから、git stashと入力してください。
  2. 別のブランチ (git checkout <branchname>) にスワップします。
  3. コミット、プッシュ、テスト。
  4. 変更を再開したいブランチに戻ります。
  5. git stash list を使って、以前に隠したコミットをすべてリストアップします。

    stash@{0}: WIP on submit: 6ebd0e2... Update git-stash documentation
    stash@{1}: On master: 9cc0589... Add git-stash
    
  6. git stash を実行してください:

    • git stash pop を使って、以前に隠した変更をやり直し、隠したリストから削除してください。
    • git stash apply を使って、以前に隠した変更をやり直しますが、隠しリストに残しておきます。

コミットしたローカルの変更を元に戻す

ローカルリポジトリ (git commit) にコミットすると、Git はその変更を記録します。まだリモートリポジトリにプッシュしていないので、あなたの変更は公開されません (あるいは、他の開発者と共有されません)。この時点で、変更を取り消すことができます。

ステージしたローカルの変更を履歴を変更せずに元に戻す

コミット履歴を保持したままコミットを取り消すことができます。

この例では、A,B,C,D,Eの5つのコミットを使用しており、これらはA-B-C-D-Eの順にコミットされています。取り消したいコミットはBです。

  1. 元に戻したいコミットのコミット SHA を探します。コミットのログを見るには、git log と入力してください。
  2. オプションを選択して、変更を取り消します:

    • コミットによる追加と削除の変更を入れ替えるにはB

       git revert <commit-B-SHA>
      
    • Bのコミットで変更されたファイルやディレクトリを元に戻しますが、ステージ状態には保持します:

       git checkout <commit-B-SHA> <file>
      
    • コミットB からのファイルやディレクトリの変更を取り消します:

       git reset <commit-B-SHA> <file>
      

複数のコミットされた変更の取り消し

複数のコミットを取り消すことができます。たとえば、feature ブランチでA-B-C-D をコミットした後でCD が間違っていることに気づいたとしましょう。

複数の間違ったコミットからリカバリするには

  1. 最後に正しいコミットをチェックしましょう。この例では、B.

    git checkout <commit-B-SHA>
    
  2. 新しいブランチを作成します。

    git checkout -b new-path-of-feature
    
  3. 変更を追加、プッシュ、コミットします。

コミットはA-B-C-D-E になりました。

GitLab を使えば、そのコミットをcherry-pickして新しいマージリクエストにすることもできます。

note
もうひとつの解決策は、B にリセットしてE をコミットすることです。しかし、この方法ではA-B-Eになってしまい、他の開発者が内部で持っているものと衝突してしまいます。

ステージされたローカルの変更を履歴変更で元に戻す

以下のタスクは Git の履歴を書き換えます。

特定のコミットを削除

特定のコミットを削除することができます。たとえば、A-B-C-D というコミットがあり、B を削除したい場合。

  1. 現在のコミットD からB までの範囲をリベースします:

    git rebase -i A
    

    コミットの一覧がエディターに表示されます。

  2. コミットBの内部で、pickdrop に置き換えてください。
  3. その他のコミットはデフォルトのpick のままにしてください。
  4. 保存してエディターを終了します。

特定のコミットの変更

特定のコミットを修正することができます。たとえば、A-B-C-D というコミットがあり、B というコミットで導入した内容を変更したい場合。

  1. 現在のコミットD からB までの範囲をリベースします:

    git rebase -i A
    

    コミットの一覧がエディターに表示されます。

  2. コミットBの内部で、pickedit に置き換えてください。
  3. その他のコミットはデフォルトのpick のままにしてください。
  4. 保存してエディターを終了します。
  5. エディタでファイルを開き、編集を行い、変更をコミットします:

    git commit -a
    

アンドゥのやり直し

ローカルでの過去のコミットを呼び出すことができます。Git はブランチやタグで到達できないコミットを定期的に削除しているからです。

リポジトリの履歴を表示して過去のコミットを追跡するには、git reflog show を実行します。例えば

$ git reflog show

# Example output:
b673187 HEAD@{4}: merge 6e43d5987921bde189640cc1e37661f7f75c9c0b: Merge made by the 'recursive' strategy.
eb37e74 HEAD@{5}: rebase -i (finish): returning to refs/heads/master
eb37e74 HEAD@{6}: rebase -i (pick): Commit C
97436c6 HEAD@{7}: rebase -i (start): checkout 97436c6eec6396c63856c19b6a96372705b08b1b
...
88f1867 HEAD@{12}: commit: Commit D
97436c6 HEAD@{13}: checkout: moving from 97436c6eec6396c63856c19b6a96372705b08b1b to test
97436c6 HEAD@{14}: checkout: moving from master to 97436c6
05cc326 HEAD@{15}: commit: Commit C
6e43d59 HEAD@{16}: commit: Commit B

この出力は、リポジトリの履歴を示しています:

  • コミット SHA。
  • コミットが行われたのはHEAD-changing actions のいくつ前か (HEAD@{12} は 12HEAD-changing actions 前)。
  • コミット、リベース、マージなどのアクション。
  • HEADを変更したアクションの説明。

履歴を変更せずにリモートの変更を元に戻す

リモートリポジトリの変更を取り消すには、取り消したい変更内容で新しいコミットを作成します。履歴を保持し、タイムラインと開発構造を明確にするこのプロセスに従うべきです。しかし、この手順が必要なのは、自分の作業が他の開発者が作業ベースとしているブランチにマージされた場合だけです。

Use revert to keep branch flowing

特定のコミットで行われた変更を差し戻すにはB

git revert B

履歴の変更中にリモートの変更を元に戻す

リモートの変更を取り消し、履歴を変更することができます。

履歴が更新されていても、古いコミットにはコミット SHA でアクセスできます。少なくとも、切り離されたコミットの自動クリーンアップがすべて実行されるか、手動でクリーンアップが実行されるまではそうです。クリーンアップを実行しても、古いコミットを指す参照が残っている場合は削除されないかもしれません。

Modifying history causes problems on remote branch

履歴の変更が許容される場合

公開ブランチや他の開発者が使う可能性のあるブランチで作業しているときには、履歴を変更すべきではありません。

GitLab のような大規模なオープンソースリポジトリに貢献する場合は、コミットをひとつにまとめることができます。

機能ブランチ上のコミットをマージ先のブランチ上のひとつのコミットにまとめるには、git merge --squash を使います。

note
デフォルトブランチや共有ブランチのコミット履歴は、決して変更しないでください。

履歴の変更方法

マージリクエストの feature ブランチは公開ブランチであり、他の開発者が使用する可能性があります。しかし、プロジェクトのルールによっては、レビューが終わった後にgit rebase を使って対象ブランチの表示コミット数を減らす必要があるかもしれません。

git rebase -i を使うと、履歴を修正できます。このコマンドを使うと、コミットを修正したりつぶしたり削除したりできます。

#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Empty commits are commented out
note
リベースを中止する場合は、エディターを閉じないでください。代わりに、コメントされていない行をすべて削除して保存してください。

共有ブランチやリモートブランチでは、git rebase を慎重に使用してください。リモートリポジトリにプッシュする前に、ローカルで実験してください。

# Modify history from commit-id to HEAD (current commit)
git rebase -i commit-id

コミットから機密情報を削除

Git を使って、過去のコミットから機密情報を削除することができます。しかし、その過程で履歴が変更されてしまいます。

特定のフィルターを使って履歴を書き換えるには、git filter-branchを実行します。

履歴からファイルを完全に削除するには、次のようにします:

git filter-branch --tree-filter 'rm filename' HEAD

git filter-branch コマンドは、大きなリポジトリでは遅いかもしれません。Git コマンドをより高速に実行するツールがあります。これらのツールがより高速なのは、git filter-branch のような機能セットではなく、特定のユースケースに特化しているからです。

リポジトリ履歴や GitLab ストレージからファイルをパージする方法については、リポジトリサイズの削減をご覧ください。