特定のコミットを削除する


287

私はプロジェクトで友人と働いていました、そして彼は編集されるべきではないたくさんのファイルを編集しました。どういうわけか、私はそれを引っ張ったとき、または私が欲しがっている特定のファイルだけを選ぼうとしたときに、彼の仕事を私のものにマージしました。私は長い間探して遊んでいて、それらのファイルへの編集を含むコミットを削除する方法を見つけようとしていましたが、それは元に戻すとリベースの間でトスアップのようであり、簡単な例はなく、ドキュメントは私が私よりも多くを知っていると仮定しています。

だからここに質問の簡略版があります:

次のシナリオで、コミット2を削除するにはどうすればよいですか?

$ mkdir git_revert_test && cd git_revert_test

$ git init
Initialized empty Git repository in /Users/josh/deleteme/git_revert_test/.git/

$ echo "line 1" > myfile

$ git add -A

$ git commit -m "commit 1"
[master (root-commit) 8230fa3] commit 1
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 myfile

$ echo "line 2" >> myfile

$ git commit -am "commit 2"
[master 342f9bb] commit 2
 1 files changed, 1 insertions(+), 0 deletions(-)

$ echo "line 3" >> myfile

$ git commit -am "commit 3"
[master 1bcb872] commit 3
 1 files changed, 1 insertions(+), 0 deletions(-)

期待される結果は

$ cat myfile
line 1
line 3

これが私が元に戻そうとしている方法の例です

$ git revert 342f9bb
Automatic revert failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>' or 'git rm <paths>'
and commit the result.

4
同じ問題を検索しているときに誰かがこれを見つけた場合、私がやったことは次のとおりです:コピーして貼り付けます。真剣に。提案されたソリューションを機能させるために6時間以上費やしましたが、役に立ちませんでした。結局、私は時間外になり、オリジナルを引き上げて、約20個のファイルをコピーして貼り付けました。5分もかからず、それ以来、問題はありませんでした(これらのファイルが、この大失敗の前に発生した他のブランチの変更とマージされている場合でも)。このアプローチもお勧めします。それが最も単純であるだけでなく、それが機能する唯一のものであることも疑います。
ジョシュアチーク

私は同様の問題に直面しましたが、おそらくもっと複雑です:スカッシュしたい数百のコミットを持つブランチがありました。残念ながら、別のブランチからのコミットは中間点でブランチにマージされたため、スカッシュする前に「アンマージ」が必要でした。以下のtkで提案されているのと同じようなパスをたどりましたが(チェリーピッキング+範囲表記を使用)、他のいくつかのファイルで競合が発生しました。最後に、コピー&ペースト+いくつかの手動編集が、最も簡単で予測可能な方法です。これにあまりにも多くの時間を費やしていることに気づいたら、検討する価値があります。
フェデリコ

回答:


73

元に戻す差分を計算するときにGitが使用するアルゴリズムでは、

  1. 元に戻される行は、その後のコミットによって変更されません。
  2. 履歴の後半には、他の「隣接する」コミットはありません。

「隣接」の定義は、コンテキストdiffからのデフォルトの行数(3)に基づいています。したがって、 'myfile'が次のように作成された場合:

$ cat >myfile <<EOF
line 1
junk
junk
junk
junk
line 2
junk
junk
junk
junk
line 3
EOF
$ git add myfile
$ git commit -m "initial check-in"
 1 files changed, 11 insertions(+), 0 deletions(-)
 create mode 100644 myfile

$ perl -p -i -e 's/line 2/this is the second line/;' myfile
$ git commit -am "changed line 2 to second line"
[master d6cbb19] changed line 2
 1 files changed, 1 insertions(+), 1 deletions(-)

$ perl -p -i -e 's/line 3/this is the third line/;' myfile
$ git commit -am "changed line 3 to third line"
[master dd054fe] changed line 3
 1 files changed, 1 insertions(+), 1 deletions(-)

$ git revert d6cbb19
Finished one revert.
[master 2db5c47] Revert "changed line 2"
 1 files changed, 1 insertions(+), 1 deletions(-)

その後、すべて期待どおりに動作します。

2番目の答えは非常に興味深いものでした。Revert Strategyと呼ばれる機能はまだ公式にリリースされていません(Git v1.7.2-rc2で利用可能です)。次のようにgitを呼び出すことができます。

git revert --strategy resolve <コミット>

そして、それはあなたが何を意味するのかを理解するより良い仕事をするはずです。利用可能な戦略のリストが何なのか、また戦略の定義もわかりません。


1
perl -psedのように、ループして入力を出力に渡す非常に短い(1行の)プログラムを書くのに役立ちます。 perl -iその場でファイルを編集するために使用されます。 評価perl -eするコードを送信する方法です。
Hal Eisen

281

これには4つの方法があります。

  • クリーンな方法で、元に戻しますが、元に戻します。

    git revert --strategy resolve <commit>
    
  • 厳しい方法ですが、最後のコミットのみを完全に削除します。

    git reset --soft "HEAD^"
    

注:git reset --hard最後のコミット以降のファイル内のすべての変更も破棄されるため、避けてください。--soft動作しない場合は、--mixedまたはを試してください--keep

  • リベース(最後の5つのコミットのログを表示し、不要な行を削除するか、並べ替える、または複数のコミットを1つにまとめる、または他のことを実行する、これは非常に用途の広いツールです):

    git rebase -i HEAD~5
    

そして、間違いがあった場合:

git rebase --abort
  • クイックリベース:IDを使用して特定のコミットのみを削除します。

    git rebase --onto commit-id^ commit-id
    
  • 代替案:あなたも試すことができます:

    git cherry-pick commit-id
    
  • さらに別の選択肢:

    git revert --no-commit
    
  • 最後の手段として、履歴編集の完全な自由が必要な場合(たとえば、gitでは目的の内容を編集できないため)、この非常に高速なオープンソースアプリケーションであるreposurgeonを使用できます。

注:もちろん、これらの変更はすべてローカルで行われます。git push後でリモートに変更を適用する必要があります。そして、リポジトリがコミットを削除したくない場合(すでにプッシュしたコミットを削除したい場合に発生する「早送り不可」)はgit push -f、変更を強制的にプッシュするために使用できます。

注2:ブランチで作業していてプッシュを強制する必要がある場合は、git push --force他のブランチを上書きする可能性があるため、絶対に回避する必要があります(現在のチェックアウトが別のブランチにある場合でも、ブランチに変更を加えた場合)。することを好むあなたはプッシュを強制するときは、常にリモートブランチを指定git push --force origin your_branch


@gaborousの提案に基づいて、「git rebase -i HEAD〜2」を実行します。今、いくつかのオプションがあります。vimでコメント化された行をいくつか見ることができます:それらの1つは単に行を削除できることを示しています(削除したいコミットでなければなりません)、このコミットは履歴にあるログとともに削除されます。
Ilker Cat

git revert --strategy resolve <commit>。このコマンドは私にとってはうまくいきました。おかげで:)
Swathin

2
git rebase -i HEAD~5私のために働いた。次に、不要なコミットを削除したところ、30秒未満で問題を解決できました。ありがとうございました。
lv10 2017年

117

ここに簡単な解決策があります:

git rebase -i HEAD~x

(注意: xはコミット数です)

実行するとメモ帳ファイルが開きます。dropコミット以外に入力します。
Vimがわからない場合は、編集する各単語をクリックして、「I」キー(挿入モードの場合)を押してください。入力が完了したら、「esc」キーを押して挿入モードを終了します。



ここに画像の説明を入力してください

これで完了です... gitダッシュボードを同期すると、変更がリモートにプッシュされます。

ドロップするコミットがすでにリモートにある場合は、強制的にプッシュする必要があります。--forceは有害と見なされるため 、使用してくださいgit push --force-with-lease


問題のコミットのハッシュのみを知っている場合、「x」がどうあるべきかを見つける良い方法はありますか?
jlewkovich

1
cpp-mentalityの場合:x(コミット数)は包括的です。たとえば、HEAD〜4には、最後の4つのコミットが含まれます。
ヘルペスフリーエンジニア、

完璧な提案。追加したいだけですが、これはドロップされたコミットからの変更も破棄します。
celerno

3つのコミットを元に戻そうとしました:git rebase -i HEAD-3でエラーが発生しました致命的:単一のリビジョンが必要ですアップストリーム 'HEAD-3'が無効です
Ustin

1
@Ustin -3ではなく〜3です
JD-V

37

あなたの選択は

  1. エラーを維持して修正を導入し、
  2. エラーを取り除き、履歴を変更します。

(1)誤った変更が他の誰かによって拾われた場合、および(2)エラーがプッシュされていないプライベートブランチに限定されている場合を選択する必要があります。

Git revertは自動化されたツールであり(1)、以前のコミットを取り消す新しいコミットを作成します。プロジェクト履歴にエラーと削除が表示されますが、リポジトリからプルしたユーザーは更新時に問題に遭遇しません。この例では自動化された方法で機能していないため、「myfile」を編集して(2行目を削除する)、実行して、競合に対処する必要がgit add myfileありgit commitます。その後、履歴には4つのコミットが含まれ、コミット4はコミット2に戻ります。

履歴が変更されても誰も気にしない場合は、書き直してコミット2を削除できます(選択肢2)。これを行う簡単な方法は、を使用することgit rebase -i 8230fa3です。これによりエディターに移動します。コミットを削除することで、誤ったコミットを含めないように選択できます(他のコミットメッセージの横に「ピック」を保持します。これを実行した結果を確認してください。


マージがあったように聞こえるので、リベースはトリッキーかもしれません。
Cascabel

3
git rebase -i 8230fa3、およびコミットの行を削除すると、ローカルのみの変更でうまく機能しました。ありがとう!
Samuel

26

アプローチ1

最初に、元に戻す必要があるコミットハッシュ(ex:1406cd61)を取得します。簡単な修正はコマンドの下にあります、

$ git revert 1406cd61

1406cd61コミット後に1406cd61ファイルに関連する変更をさらにコミットすると、上記の単純なコマンドは機能しません。次に、チェリーピッキングである以下の手順を実行する必要があります。

アプローチ2

以下の一連のアクションに従ってください。--forceを使用しているため、これを行うにはgitリポジトリの管理者権限が必要です。

ステップ1:削除するコミットの前にコミットを見つけるgit log

ステップ2:そのコミットをチェックアウトするgit checkout <commit hash>

ステップ3:現在のチェックアウトコミットを使用して新しいブランチを作成するgit checkout -b <new branch>

ステップ4:削除されたコミットの後にコミットを追加する必要がありますgit cherry-pick <commit hash>

ステップ5:保持したい他のすべてのコミットについて、ステップ4を繰り返します。

ステップ6:すべてのコミットが新しいブランチに追加され、コミットされたら。すべてが正しい状態にあり、意図したとおりに機能していることを確認します。すべてがコミットされたことを再確認します。git status

ステップ7:壊れたブランチに切り替えるgit checkout <broken branch>

ステップ8:壊れたブランチでハードリセットを実行して、削除するコミットの前にコミットしますgit reset --hard <commit hash>

ステップ9:固定ブランチをこのブランチにマージしますgit merge <branch name>

ステップ10:マージされた変更を元に戻します。警告:これにより、リモートリポジトリが上書きされます。git push --force origin <branch name>

ステップ2と3をステップ8に置き換え、ステップ7と9を実行しないことで、新しいブランチを作成せずにプロセスを実行できます。


1
最初のアプローチは魅力のように機能しました。これには、目的のコミットが元に戻されたことを指定するコミットが含まれています。これは、追跡の目的で非常に便利です。
suarsenegger

18

不要なコミットはで削除できgit rebaseます。同僚のトピックブランチからのコミットをトピックブランチに含めたが、後でそれらのコミットを望まない場合を考えます。

git checkout -b tmp-branch my-topic-branch  # Use a temporary branch to be safe.
git rebase -i master  # Interactively rebase against master branch.

この時点で、テキストエディターはインタラクティブなリベースビューを開きます。例えば

git-rebase-todo

  1. 行を削除して、不要なコミットを削除します
  2. 保存して終了

リベースが成功しなかった場合は、一時的なブランチを削除して、別の戦略を試してください。それ以外の場合は、次の手順に進みます。

git checkout my-topic-branch
git reset --hard tmp-branch  # Overwrite your topic branch with the temp branch.
git branch -d tmp-branch  # Delete the temporary branch.

トピックブランチをリモートにプッシュする場合、コミット履歴が変更されているため、プッシュを強制する必要がある場合があります。他の人が同じブランチで作業している場合は、ヘッドアップしてください。


「別の戦略を試す」の例を挙げていただけませんか?
pfabri

@pfabriを使用すると、たとえば、不良コミットを除外する2つの範囲のコミットを簡単に選択できます。不正なコミットを元に戻すことができます。手動で変更を取り消すか、悪いコミットなしで新しいブランチから開始して手動で適切な変更をやり直すことで、gitソリューションの使用を回避できます。不良コミットに機密データが含まれている場合は、より慎重な戦略が必要になります。help.github.com
Dennis

8

ここの他の回答から、私は git rebase -iコミットを削除するためにように使用できるので、ここでテストケースを書き留めても大丈夫だと思います(OPによく似ています)

以下はbash/tmpフォルダーにテストリポジトリを作成するために貼り付けることができるスクリプトです。

set -x

rm -rf /tmp/myrepo*
cd /tmp

mkdir myrepo_git
cd myrepo_git
git init
git config user.name me
git config user.email me@myself.com

mkdir folder
echo aaaa >> folder/file.txt
git add folder/file.txt
git commit -m "1st git commit"

echo bbbb >> folder/file.txt
git add folder/file.txt
git commit -m "2nd git commit"

echo cccc >> folder/file.txt
git add folder/file.txt
git commit -m "3rd git commit"

echo dddd >> folder/file.txt
git add folder/file.txt
git commit -m "4th git commit"

echo eeee >> folder/file.txt
git add folder/file.txt
git commit -m "5th git commit"

この時点で、file.txt次の内容のa があります。

aaaa
bbbb
cccc
dddd
eeee

この時点で、HEADは5番目のコミットにあり、HEAD〜1は4番目になり、HEAD〜4は1番目のコミットになります(したがって、HEAD〜5は存在しません)。3番目のコミットを削除したいとしましょう。myrepo_gitディレクトリで次のコマンドを発行できます。

git rebase -i HEAD~4

「致命的:単一リビジョンが必要です。上流のHEAD〜5無効です」というgit rebase -i HEAD~5結果になることに注意してください。)テキストエディター(@Dennisの回答のスクリーンショットを参照)が開き、次の内容が表示されます。

pick 5978582 2nd git commit
pick 448c212 3rd git commit
pick b50213c 4th git commit
pick a9c8fa1 5th git commit

# Rebase b916e7f..a9c8fa1 onto b916e7f
# ...

したがって、要求されたHEAD〜4 以降の(ただしを含まない)すべてのコミットを取得します。行pick 448c212 3rd git commitを削除してファイルを保存します。あなたはこの応答を受け取りますgit rebase

error: could not apply b50213c... 4th git commit

When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To check out the original branch and stop rebasing run "git rebase --abort".
Could not apply b50213c... 4th git commit

この時点folder/file.txtで、テキストエディターでmyrepo_git / を開きます。変更されていることがわかります。

aaaa
bbbb
<<<<<<< HEAD
=======
cccc
dddd
>>>>>>> b50213c... 4th git commit

基本的に、gitHEADが2番目のコミットに到達したときに、aaaa+のコンテンツがあったことがわかりbbbbます。cccc+ 追加されたパッチがありdddd、既存のコンテンツに追加する方法がわかりません。

したがって、ここでgitは決定できません- 決定を下さなければならないのはあなたです。3番目のコミットを削除することにより、それによって導入された変更(ここでは、行cccc)を維持するか、そうしません。含む-あなたがいない場合は、単に余分な行を削除するccccには-をfolder/file.txt:それはこのようになりますので、テキストエディタを使用して

aaaa
bbbb
dddd

...そして保存しfolder/file.txtます。これで、myrepo_gitディレクトリで次のコマンドを発行できます。

$ nano folder/file.txt  # text editor - edit, save
$ git rebase --continue
folder/file.txt: needs merge
You must edit all merge conflicts and then
mark them as resolved using git add

ああ-私たちは紛争を解決してきたことをマークするために、我々はしなければならない 、前にやって:git addfolder/file.txtgit rebase --continue

$ git add folder/file.txt
$ git rebase --continue

ここで、テキストエディターが再び開き、行が表示されます。4th git commitここでは、コミットメッセージを変更する機会があります(この場合は、意味が変わっている4th (and removed 3rd) commitか、または類似している可能性があります)。したくないとしましょう-保存せずにテキストエディターを終了してください。それを行うと、次のようになります。

$ git rebase --continue
[detached HEAD b8275fc] 4th git commit
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.

この時点で、(たとえば、元のコミットのタイムスタンプは明らかに変更されていないgitk .)内容の履歴(say または他のツールで調べることもできます)がありますfolder/file.txt

1st git commit  |  +aaaa
----------------------------------------------
2nd git commit  |   aaaa
                |  +bbbb
----------------------------------------------
4th git commit  |   aaaa
                |   bbbb
                |  +dddd
----------------------------------------------
5th git commit  |   aaaa
                |   bbbb
                |   dddd
                |  +eeee

そして、以前は、その行cccc(削除した3番目のgit commitの内容)を保持することにした場合、次のようになっていました。

1st git commit  |  +aaaa
----------------------------------------------
2nd git commit  |   aaaa
                |  +bbbb
----------------------------------------------
4th git commit  |   aaaa
                |   bbbb
                |  +cccc
                |  +dddd
----------------------------------------------
5th git commit  |   aaaa
                |   bbbb
                |   cccc
                |   dddd
                |  +eeee

まあ、これは私が見つけたいと思っていた種類の読書git rebaseでした。コミットやリビジョンの削除という点でどのように機能するかを理解するためです。他の人にも役立つことを願っています...


非常に貴重なウォークスルー-私が苦労していた正確なユースケース、これは私に非常に役立ちました。
pfabri

2

そのため、ある時点で、悪いコミットがマージコミットに組み込まれたようです。マージコミットはまだプルされていますか?はいの場合は、使用する必要がありますgit revert。あなたはあなたの歯をグリットし、対立を乗り越えなければならないでしょう。いいえの場合、おそらくリベースまたはリバートのいずれかが可能ですが、前に行うことができます、マージコミットの。その後、マージをやり直します。

最初のケースで私たちがあなたに与えることができる助けはほとんどありません。元に戻してみて、自動復元に失敗したことがわかったら、競合を調べて適切に修正する必要があります。これは、マージの競合を修正するのとまったく同じプロセスです。を使用git statusして、競合の場所を確認し、マージされていないファイルを編集し、競合するハンクを見つけ、それらを解決する方法を見つけ、競合するファイルを追加し、最後にコミットします。git commit単独で使用する場合(no -m <message>)、エディターにポップアップ表示されるメッセージは、によって作成されたテンプレートメッセージである必要がありますgit revert。競合を修正した方法についてのメモを追加し、保存して終了してコミットできます。

2番目のケースでは、マージに問題を修正します。マージ以降にさらに作業を行ったかどうかによって、2つのサブケースがあります。そうでない場合はgit reset --hard HEAD^、マージをノックオフして元に戻し、マージをやり直すことができます。しかし、私はあなたが持っていると思います。したがって、最終的には次のようになります。

  • マージの直前に一時的なブランチを作成し、チェックアウトします
  • 元に戻す(または使用) git rebase -i <something before the bad commit> <temporary branch>、不良コミットを削除するために)
  • マージをやり直す
  • 次の作業をリベースします: git rebase --onto <temporary branch> <old merge commit> <real branch>
  • 一時的なブランチを削除する

1

それで、あなたはいくつかの仕事をしてそれをプッシュしました、それらをコミットAとBと呼びましょう。同僚もいくつかの仕事をし、CとDをコミットしました。また(Fをコミット)、あなたの同僚が彼が持ってはならないいくつかのことを変えたことを発見しました。

したがって、コミット履歴は次のようになります。

A -- B -- C -- D -- D' -- E -- F

あなたは本当にC、D、D 'を取り除く必要があります。あなたは同僚の仕事をあなたの仕事にマージしたと言っているので、これらのコミットはすでに「そこに」あるので、例えばgit rebaseを使用してコミットを削除することはノーノーです。私を信じて、私は試しました。

今、私は2つの方法を考えています:

  • EとFを同僚または他の誰か(通常は「元の」サーバー)にまだプッシュしていない場合でも、当面は履歴からそれらを削除できます。これはあなたが保存したいあなたの仕事です。これは、

    git reset D'
    

    (D 'を、から取得できる実際のコミットハッシュに置き換えます。 git log

    この時点で、コミットEおよびFはなくなり、変更はローカルワークスペースでのコミットされていない変更です。この時点で、それらをブランチに移動するか、パッチにして後で使用するために保存します。今、あなたの同僚の仕事を、自動git revertまたは手動で元に戻します。それが終わったら、その上で作業を再生します。マージの競合があるかもしれませんが、少なくともそれらは同僚のコードではなく、あなた書いたコードに含まれます

  • 同僚のコミット後に行った作業をすでにプッシュしている場合でも、手動またはを使用して「リバースパッチ」を取得できますがgit revert、作業が「途中」であるため、いわばおそらくより多くのマージ競合とより混乱するもの。それはあなたが最終的に終わったもののように見えます...


0

gitの復帰--strategy決意 コミットの場合は、マージです: 使用gitの元に戻す--strategy解決-m 1

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