複数のgitコミットを元に戻すには?


983

次のようなgitリポジトリがあります。

A -> B -> C -> D -> HEAD

ブランチのヘッドがAを指すようにしたい、つまり、B、C、D、およびHEADを非表示にして、ヘッドをAと同義にしたい。

私はリベースを試みるか(間に変更をプッシュしたため適用されない)、または元に戻すことができるようです。しかし、どうすれば複数のコミットを元に戻すことができますか?一度に1つ元に戻しますか?順序は重要ですか?


3
リモコンをリセットしたいだけの場合は、何かでそれを壊すことができます!しかし、4つ目のコミットを使用してみましょうgit push -f HEAD~4:master(リモートブランチがマスターであると想定)。はい、そのようなコミットをプッシュできます。
u0b34a0f6ae 09/09/23

21
人々がプルした場合は、を使用して変更を元に戻すコミットを行う必要がありますgit revert
JakubNarębski、2009

1
GitのショーHEAD〜4リモートに正しいものに推進していることを確認するために使用
マット・フリーマン


5
「順序は重要ですか?」はい、コミットが同じファイルの同じ行に影響する場合。次に、最新のコミットを元に戻し、元に戻す必要があります。
avandeursen 2017

回答:


1336

コメントで書いたことを拡張する

一般的なルールは、誰かが自分の作業に基づいている可能性があるため、公開した履歴を書き換える(変更する)べきではないということです。履歴を書き換える(変更する)と、それらの変更のマージと更新が問題になります。

したがって、解決策は、削除したい変更を元に戻す新しいコミットを作成することです。これはgit revertコマンドを使用して行うことができます。

次の状況があります。

A <-B <-C <-D <-マスター<-HEAD

(ここでの矢印は、ポインタの方向を指します:コミットの場合は「親」参照、ブランチヘッド(ブランチ参照)の場合はトップコミット、HEADリファレンスの場合はブランチの名前)。

作成する必要があるのは次のとおりです。

A <-B <-C <-D <-[(BCD)^-1] <-マスター<-ヘッド

ここで、「[(BCD)^-1]」は、コミットB、C、Dの変更を元に戻すコミットを意味します。数学では、(BCD)^-1 = D ^ -1 C ^ -1 B ^ -1、次のコマンドを使用して、必要な状況を取得できます。

$ git revert --no-commit D
$ git revert --no-commit C
$ git revert --no-commit B
$ git commit -m "the commit message"

代替ソリューションは、コミットAの内容チェックアウトし 、この状態をコミットすることです。

$ git checkout -f A -- .
$ git commit -a

次に、次のような状況になります。

A <-B <-C <-D <-A '<-マスター<-ヘッド

コミットA 'の内容はコミットAと同じですが、コミット(コミットメッセージ、親、コミット日)が異なります。

チャールズ・ベイリーによって変更ジェフFerlandによって溶液は、同じ考えに基づいて構築されますが、使用してgitのリセットを

$ git reset --hard A
$ git reset --soft @{1}  # (or ORIG_HEAD), which is D
$ git commit

40
B、C、またはDでファイルを追加した場合、git checkout -f A -- .これらは削除されません。手動で行う必要があります。私は今この戦略を適用しました、ありがとうJakub
oma

18
これらのソリューションは同等ではありません。最初のものは、新しく作成されたファイルを削除しません。
m33lky 2012年

10
@Jerry:git checkout fooチェックアウトブランチ fooブランチに切り替える)またはチェックアウトファイル foo(インデックスから)を意味する場合があります--曖昧さをなくすために使用されgit checkout -- fooます。たとえば、常にファイルについてです。
JakubNarębski、2013年

87
素晴らしい答えに加えて。この略記は私にとってはgit revert --no-commit D C B
うまくいき

9
@ welldan97:コメントをありがとう。この回答を書いているときgit revert、複数のコミットを受け入れませんでした。それはかなり新しい追加です。
JakubNarębski2013

248

便利だと思った綺麗な方法

git revert --no-commit HEAD~3..

このコマンドは、最後の3つのコミットを1つのコミットだけで元に戻します。

また、履歴を書き換えません。


16
これがシンプルで最良の答えです
Julien Deniau 2017

3
@JohnLittle変更をステージングします。git commitそこから実際にコミットします。
x1a4 2017年

14
一部のコミットがマージコミットの場合、これは機能しません。
MegaManX 2018

5
最後の2つのドットは何をしますか?
カルダモン2018

5
@cardamomこれらは範囲を指定します。HEAD~3..と同じHEAD~3..HEAD
Toine H

238

そのためには、復帰したいコミットの範囲を指定して、復帰コマンドを使用する必要があります。

あなたの例を考慮に入れると、これを行う必要があります(ブランチ「マスター」にいると仮定します):

git revert master~3..master

これにより、B、C、およびDの逆コミットでローカルに新しいコミットが作成されます(つまり、これらのコミットによって導入された変更が取り消されます)。

A <- B <- C <- D <- BCD' <- HEAD

129
git revert --no-commit HEAD~2..それを行うには、もう少し慣用的な方法です。masterブランチを使用している場合は、masterを再度指定する必要はありません。この--no-commitオプションを使用すると、履歴に複数のrevert commit ...メッセージが散らばるのではなく、gitがすべてのコミットを一度に元に戻そうとします(それがあなたが望むものであると仮定します)。
kubi

6
@Victorコミット範囲を修正しました。範囲の先頭は排他的であり、含まれていません。したがって、最後の3つのコミットを元に戻す場合は、3番目のコミットの、つまりから範囲を開始する必要がありますmaster~3

2
@kubiは、単一のコミットを使用して、SHAをコミットメッセージに含める方法はありません(あなたの方法ですが、元に戻されたコミットを手動で入力する必要はありません)?
Chris S

@ChrisS私の最初の考えは、使用しない--no-commit(つまり、復帰するたびに個別のコミットを取得する)ことであり、その後、インタラクティブなリベースでそれらすべてをまとめて潰します。結合されたコミットメッセージにはすべてのSHAが含まれ、お気に入りのコミットメッセージエディタを使用して好きなようにSHAを配置できます。
Radon Rosborough、2016年

71

Jakubの回答と同様に、これにより、復帰する連続したコミットを簡単に選択できます。

# revert all commits from B to HEAD, inclusively
$ git revert --no-commit B..HEAD  
$ git commit -m 'message'

9
あなたの解決策は私にとってはうまくいきましたが、少し変更されました。この場合、Z-> A-> B-> C-> D-> HEADがあり、A状態に戻したい場合、奇妙なことにgit revert --no-commit Z ..を実行する必要があります。ヘッド
ボグダン

3
@Bogdanに同意すると、復帰範囲は次のようになります。SHA_TO_REVERT_TO..HEAD
Vadym Tyemirov

11
範囲が間違っています。それはする必要がありB^..HEAD、そうでない場合はBが除外されています。
tessus

3
@tessusに同意するので、正しいことは次のようになります:git revert --no-commit B^..HEADまたはgit revert --no-commit A..HEAD
Yoho

64
git reset --hard a
git reset --mixed d
git commit

それは、一度にそれらすべての復帰として機能します。適切なコミットメッセージを提供します。


4
彼が望んでいる場合HEADのように見えるようにA、その後、彼はおそらく、インデックスが一致させたいので、git reset --soft Dおそらくより適切です。
CBベイリー

2
--soft resettingはインデックスを移動しないので、彼がコミットすると、Dからではなくから直接コミットされたように見えます。これにより、ブランチが分割されます。--mixedは変更を残しますが、インデックスポインターを移動するため、Dは親コミットになります。
ジェフファーランド

5
はい、私はgit reset --keepが上記とまったく同じだと思います。2010年4月にリリースされたバージョン1.7.1でリリースされたため、その時点では答えはありませんでした。
ジェフファーランド2010

git checkout Aその後、git commit上記はうまくいきませんでしたが、この答えはうまくいきました。
SimplGy 2013

なぜgit reset --mixed D必要なのですか?具体的にはなぜresetですか?Dにリセットしないと、HEADがAをポイントし、B、C、Dが「ぶら下がり」ガベージコレクションされてしまうためです。しかし、なぜ--mixedですか?あなたは既に「答え--softこの方法は、A -作業ディレクトリは、Aの変更が含まれていますしながら、リセットは、インデックスを移動しません...」だから、インデックスを移動することで、この手段インデックスは、D'sの変更が含まれていますgit statusか、git diffインデックスを比較する([D]へワーキングディレクトリ[A])は物質を表示します。そのユーザーはDからAに戻りますか?
2016年

39

まず、作業コピーが変更されていないことを確認してください。次に:

git diff HEAD commit_sha_you_want_to_revert_to | git apply

その後、コミットします。元に戻す理由を文書化することを忘れないでください。


1
私のために働いた!機能ブランチが開発ブランチで行われた変更を廃止する問題(一部のバグ修正)があったため、機能ブランチは開発で行われたすべての変更(一部のファイルの削除を含む)を上書きする必要がありました。
サイレンサー

1
バイナリファイルでは機能しません:error: cannot apply binary patch to 'some/image.png' without full index line error: some/image.png: patch does not apply
GabLeRoux

2
これは、受け入れられた回答よりもはるかに柔軟なソリューションです。ありがとう!
ブライアンクン

2
re:バイナリファイルは--binaryオプションを使用します: git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply
weinerk

2
これは、マージコミットを含むコミットの範囲を元に戻したい場合でも機能します。使用git revert A..Zすると、次のようになりますerror: commit X is a merge but no -m option was given.
Juliusz Gonera 2018

35

この質問には答えられないほどイライラしています。他のすべての質問は、どのように正しく復帰し、履歴を保存するかに関するものです。この質問は、「ブランチのヘッドがAを指すようにしたい、つまり、B、C、D、およびHEADを非表示に、ヘッドをAと同義にしたい」と述べています。

git checkout <branch_name>
git reset --hard <commit Hash for A>
git push -f

私はJakubの投稿を読んで多くのことを学びましたが、(Pull-Requestなしで "testing"ブランチにプッシュするためのアクセス権を持つ)社内の一部の人は、5つの悪いコミットを修正して修正しようとし、5コミット前にミスを修正しようとしました。それだけでなく、1つまたは2つのプルリクエストが受け入れられましたが、現在は不良でした。忘れてください、最後の適切なコミット(abc1234)を見つけ、基本的なスクリプトを実行しました。

git checkout testing
git reset --hard abc1234
git push -f

私は、このリポジトリで作業している他の5人の人に、最近の数時間の変更と最新のテストからのワイプ/再ブランチをよりよく記録することを伝えました。物語の終わり。


2
私はコミットを公開していなかったので、これが私が必要とした答えでした。ありがとう、@ Suamere。
トム・バロン

これを行うためのより良い方法はです。これはgit push --force-with-lease、気化するコミットの後またはコミットの範囲内で他の誰もブランチにコミットしていない場合にのみ履歴を書き換えます。他の人がブランチを使用した場合、その履歴は決して書き換えられるべきではなく、コミットは単に目に見える形で戻されるべきです。
frandroid

1
@frandroid「履歴は決して書き換えてはならない」、絶対的な意味でのシス取引のみ。このスレッドの質問と私の答えのポイントは、特定のシナリオでは、すべての履歴を消去する必要があるということです。
Suamere

@Suamere確かに、それが問題です。しかし、あなたの答えがあなたが他の人に伝えなければならなかったことについて述べているように、問題の可能性があります。個人的な経験から、push -fは、消去しようとしているものの後に他の人がコミットした場合、コードベースを台無しにする可能性があります。--force-with-leaseを使用しても同じ結果が得られますが、リポジトリをめちゃくちゃにしようとしてもお尻が節約されます。なぜチャンスを取る?--force-with-leaseが失敗した場合、どのコミットが邪魔になるかを確認し、適切に評価して調整し、再試行できます。
frandroid 2018

1
@Suamereありがとうございます!私は質問が歴史を書き換えたいと明確に述べていることに同意します。私はあなたと同じ状況にあり、私はOPが誰かが数十の醜い復帰と奇妙なコミットと復帰の復帰を偶然に(私が休暇中に)行い、状態を復帰させる必要があると推測しています。いずれにせよ、良好な健康警告とともに、これは受け入れられる答えであるはずです。
リーリチャードソン

9

これは、Jakubの回答で提供されているソリューションの1つを拡張したものです。

ロールバックするのに必要なコミットがいくらか複雑で、いくつかのコミットはマージコミットであり、履歴の書き換えを避ける必要があるという状況に直面しました。git revert追加された復帰変更の間に最終的に競合が発生したため、一連のコマンドを使用できませんでした。私は次の手順を使用して結局しました。

まず、HEADをブランチの先端に残したまま、ターゲットコミットの内容を確認します。

$ git checkout -f <target-commit> -- .

(-は<target-commit>ファイルではなくコミットとして解釈されます。.は現在のディレクトリを指します。)

次に、ロールバックされているコミットにどのファイルが追加されたのか、したがって削除する必要があるのか​​を判断します。

$ git diff --name-status --cached <target-commit>

追加されたファイルは、行の先頭に「A」が表示され、その他の違いはないはずです。次に、ファイルを削除する必要がある場合は、これらのファイルを削除のためにステージングします。

$ git rm <filespec>[ <filespec> ...]

最後に、復帰をコミットします。

$ git commit -m 'revert to <target-commit>'

必要に応じて、目的の状態に戻っていることを確認します。

$git diff <target-commit> <current-commit>

違いはないはずです。


gitブランチの先端だけでヘッドできると確信していますか?
Suamere

2
私にはマージコミットがあったので、これは私にとってはるかに良い解決策でした。
2016年

3

共有リポジトリでコミットのグループを元に戻す簡単な方法(git revertユーザーが使用し、履歴を保持したい)は、gitと組み合わせて使用することrev-listです。後者はコミットのリストを提供し、前者はそれ自体を元に戻します。

それには2つの方法があります。単一のコミットで複数のコミットを元に戻す場合:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert -n $i; done

これにより、必要なコミットのグループが元に戻りますが、すべての変更は作業ツリーに残りますので、通常どおりすべてコミットしてください。

別のオプションは、元に戻された変更ごとに1つのコミットを行うことです。

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-edit -s $i; done

たとえば、次のようなコミットツリーがあるとします。

 o---o---o---o---o---o--->    
fff eee ddd ccc bbb aaa

変更をeeeからbbbに戻すには、次を実行します。

for i in `git rev-list eee^..bbb`; do git revert --no-edit -s $i; done

ちょうどこれを使用しました。ありがとう!
Ran Biron 2013

2

どれもうまくいかなかったので、元に戻すには3つのコミット(最後の3つのコミット)があったので、次のようにしました。

git revert HEAD
git revert HEAD~2
git revert HEAD~4
git rebase -i HEAD~3 # pick, squash, squash

魅力のように働いた:)


2
これは、変更がまだプッシュされていない場合にのみ実行可能なオプションです。
kboom 2018年

0

私の意見では、非常に簡単でクリーンな方法は次のとおりです。

Aに戻る

git checkout -f A

マスターの頭を現在の状態に向ける

git symbolic-ref HEAD refs/heads/master

セーブ

git commit

1
反対投票の理由を説明していただけますか?
nulll '13

これは正常に動作します。この有用な回答に反対票を投じる意味は何ですか?または誰がベストプラクティスであるかを説明できますか?
Levent Divilioglu

それは同じgit checkout master; git reset --hard Aですか?または、これが何をするかについてもう少し説明できませんか?
MM

ただ動作しますが、シンボリック-refのHEADは、「安全な」コマンドではないようだ
セルジオ

ERR私は枝を固定したい場合は、修正のマスターをしたくない
セルジオ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.