web-dev-qa-db-ja.com

どのように悪いマージを修正し、あなたの良いコミットを修正マージに再生しますか?

私はこれまで気付かずに、コミット前に誤って不要なファイル(マージの解決中にfilename.orig)をリポジトリにコミットしました。リポジトリの履歴からファイルを完全に削除したいです。

そもそもfilename.origがリポジトリに追加されなかったように変更履歴を書き換えることは可能ですか?

391
Grant Limberg

あなたの状況が質問に記載されているものではない場合は、このレシピを使わないでください。このレシピは悪いマージを修正し、あなたの良いコミットを修正されたマージに再生するためのものです。

filter-branchはあなたが望むことをするでしょうが、それはかなり複雑なコマンドです、そして、私はおそらくgit rebaseでこれをすることを選びます。それはおそらく個人的な好みです。 filter-branchは単一の、少し複雑なコマンドでそれを行うことができますが、rebaseソリューションは一度に1ステップずつ同等の論理演算を実行します。

以下のレシピを試してください。

# create and check out a temporary branch at the location of the bad merge
git checkout -b tmpfix <sha1-of-merge>

# remove the incorrectly added file
git rm somefile.orig

# commit the amended merge
git commit --amend

# go back to the master branch
git checkout master

# replant the master branch onto the corrected merge
git rebase tmpfix

# delete the temporary branch
git branch -d tmpfix

(実際には一時的なブランチは必要ありません。 'detached HEAD'でこれを行うことができますが、git commit --amendステップで生成されるコミットIDをメモしてgit rebaseコマンドに渡す必要があります。一時的なブランチ名

292
CB Bailey

はじめに:5つのソリューションを利用できます

元のポスターの状態:

私は誤って不要なファイルをコミットしました...数回前にリポジトリに...リポジトリの履歴からファイルを完全に削除したいです。

filename.origが最初にリポジトリに追加されないように変更履歴を書き換えることは可能ですか?

Gitからファイルの履歴を完全に削除するには、さまざまな方法があります。

  1. コミットの修正。
  2. ハードリセット(おそらくプラスリベース)。
  3. 非対話型のリベース。
  4. インタラクティブなリベース。
  5. ブランチのフィルタリング。

オリジナルのポスターの場合、コミットを修正すること自体は実際には選択肢ではありません。彼は後でいくつかの追加のコミットを行ったためですが、完全を期すために、私はそれを行う方法も説明します以前のコミットを修正します。

これらのソリューションはすべて、変更/書き換え履歴/コミットを別の方法で含むことに注意してください。したがって、コミットの古いコピーを持つ人は誰でも履歴を新しい履歴と再同期するには追加の作業が必要です。


解決策1:コミットの修正

前のコミットで誤って変更(ファイルの追加など)を行った場合、その変更の履歴をこれ以上残したくない場合は、前のコミットを修正してファイルを削除できます。

git rm <file>
git commit --amend --no-edit

解決策2:ハードリセット(おそらくプラスリベース)

解決策#1と同様に、以前のコミットを単に削除したい場合は、単に親にハードリセットを行うオプションもあります。

git reset --hard HEAD^

このコマンドは、ブランチを前のバージョンにハードリセットします1st 親コミット。

ただし、、元のポスターのように、必要なコミットの後にいくつかのコミットを行った場合変更を取り消すには、ハードリセットを使用して変更を元に戻すことができますが、リベースを使用する必要があります。以下に、履歴をさかのぼってコミットを修正するために使用できる手順を示します。

# Create a new branch at the commit you want to amend
git checkout -b temp <commit>

# Amend the commit
git rm <file>
git commit --amend --no-edit

# Rebase your previous branch onto this new commit, starting from the old-commit
git rebase --preserve-merges --onto temp <old-commit> master

# Verify your changes
git diff [email protected]{1}

解決策3:非インタラクティブなリベース

これは、履歴からコミットを完全に削除する場合にのみ機能します。

# Create a new branch at the parent-commit of the commit that you want to remove
git branch temp <parent-commit>

# Rebase onto the parent-commit, starting from the commit-to-remove
git rebase --preserve-merges --onto temp <commit-to-remove> master

# Or use `-p` insteda of the longer `--preserve-merges`
git rebase -p --onto temp <commit-to-remove> master

# Verify your changes
git diff [email protected]{1}

解決策4:インタラクティブなリベース

このソリューションでは、ソリューション#2および#3と同じことを実行できます。つまり、直前のコミットよりも過去のコミットを変更または削除できるため、どのソリューションを使用するかはユーザー次第です。インタラクティブなリベースは、パフォーマンス上の理由から、数百のコミットのリベースに適していないため、このような状況では、非インタラクティブなリベースまたはフィルターブランチソリューション(以下を参照)を使用します。

インタラクティブなリベースを開始するには、次を使用します。

git rebase --interactive <commit-to-amend-or-remove>~

# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~

これにより、gitはコミット履歴を変更または削除したいコミットの親に巻き戻します。次に、使用するように設定されているエディターgitで、巻き戻されたコミットのリストを逆順に表示します(これはデフォルトでVimです)。

pick 00ddaac Add symlinks for executables
pick 03fa071 Set `Push.default` to `simple`
pick 7668f34 Modify Bash config to use Homebrew recommended PATH
pick 475593a Add global .gitignore file for OS X
pick 1b7f496 Add alias for Dr Java to Bash config (OS X)

変更または削除するコミットは、このリストの一番上にあります。削除するには、リスト内のその行を削除するだけです。それ以外の場合、1で「選択」を「編集」に置き換えますst 次のような行:

edit 00ddaac Add symlinks for executables
pick 03fa071 Set `Push.default` to `simple`

次に、git rebase --continueと入力します。コミットを完全に削除することを選択した場合、必要なことはそれだけです(検証以外、このソリューションの最終ステップを参照)。一方、コミットを変更する場合、gitはコミットを再適用し、リベースを一時停止します。

Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

この時点で、ファイルを削除してコミットを修正し、リベースを続行できます。

git rm <file>
git commit --amend --no-edit
git rebase --continue

それでおしまい。最後の手順として、コミットを変更したか完全に削除したかにかかわらず、リベース前の状態と比較することで、ブランチに他の予期しない変更が加えられていないことを確認することをお勧めします。

git diff [email protected]{1}

解決策5:ブランチのフィルタリング

最後に、このソリューションは、ファイルの存在のすべての痕跡を履歴から完全に消去する場合に最適であり、他のソリューションはどれもタスクに完全には対応していません。

git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'

これにより、ルートコミットから開始して、すべてのコミットから<file>が削除されます。代わりに、コミット範囲HEAD~5..HEADを書き直したい場合は、 この回答 で指摘されているように、filter-branchに追加の引数として渡すことができます。

git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD

繰り返しますが、filter-branchが完了したら、通常、ブランチをフィルタリング操作前の以前の状態と比較することにより、他の予期しない変更がないことを確認することをお勧めします。

git diff [email protected]{1}

フィルターブランチの代替:BFG Repo Cleaner

BFG Repo Cleaner ツールの実行速度はgit filter-branchより速いと聞いたので、オプションとしてチェックアウトすることもできます。 実行可能な代替手段として filter-branch documentation でも公式に言及されています:

git-filter-branchを使用すると、Git履歴のシェルスクリプトによる複雑な書き直しを行うことができますが、単純に不要なデータを削除する場合には、この柔軟性はおそらく必要ありませんファイルまたはパスワード。これらの操作については、 BFG Repo-Cleaner 、git-filter-branchのJVMベースの代替手段であり、通常、これらのユースケースでは少なくとも10〜50倍速く、まったく異なる特徴:

  • ファイルの特定のバージョンは、onceだけが完全に消去されます。 git-filter-branchとは異なり、BFGは、履歴内のどこで、またはいつコミットされたかに基づいて、ファイルを異なる方法で処理する機会を与えません。この制約は、BFGのコアパフォーマンスの利点を提供し、不良データをクレンジングするタスクに適しています-気にしませんwhere itgone

  • デフォルトでは、BFGはマルチコアマシンを最大限に活用し、コミットファイルツリーを並行してクレンジングします。 git-filter-branchは、コミットを順番に(つまり、シングルスレッド方式で)クリーンアップしますが、各コミットに対して実行されるスクリプトで、独自の並列性を含むフィルターを記述することは可能です 。

  • コマンドオプション は、git-filterブランチよりもはるかに制限的で、不要なデータを削除するタスク専用です-例:--strip-blobs-bigger-than 1M

追加のリソース

  1. Pro Git§6.4 Gitツール-履歴の書き換え
  2. git-filter-branch(1)マニュアルページ
  3. git-commit(1)マニュアルページ
  4. git-reset(1)マニュアルページ
  5. git-rebase(1)マニュアルページ
  6. BFGリポジトリクリーナー作成者自身からのこの回答 も参照)。
203
user456814

それ以降何もコミットしていない場合は、ファイルをgit rmgit commit --amendを変更してください。

あなたが持っている場合

git filter-branch \
--index-filter 'git rm --cached --ignore-unmatch path/to/file/filename.orig' merge-point..HEAD

merge-pointからHEADへの各変更を経て、filename.origを削除して、変更を書き換えます。 --ignore-unmatchを使用することは、何らかの理由でfilename.origが変更に含まれていなくてもコマンドが失敗しないことを意味します。これは git-filter-branchのmanページ の例のセクションからの推奨方法です。

Windowsユーザーへの注意:ファイルパスはスラッシュを使用する必要があります

118
Schwern

これが最善の方法です。
http://github.com/guides/completely-remove-a-file-from-all-revisions

最初にファイルのコピーを必ずバックアップしてください。

編集

Neon による編集は、残念ながらレビュー中に拒否されました。
下記のNeonsの投稿を参照してください。役に立つ情報が含まれているかもしれません!


例えば。誤ってgitリポジトリにコミットされたすべての*.gzファイルを削除する方法

$ du -sh .git ==> e.g. 100M
$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch *.gz' HEAD
$ git Push Origin master --force
$ rm -rf .git/refs/original/
$ git reflog expire --expire=now --all
$ git gc --Prune=now
$ git gc --aggressive --Prune=now

それでもまだうまくいきませんでしたか。 (私は現在gitバージョン1.7.6.1にいます)

$ du -sh .git ==> e.g. 100M

私は1つのマスターブランチしか持っていなかったので、理由はわかりません。とにかく、私はようやく私のgitレポジトリを新しい空の裸のgitリポジトリにプッシュすることで本当にきれいにしました。

$ git init --bare /path/to/newcleanrepo.git
$ git Push /path/to/newcleanrepo.git master
$ du -sh /path/to/newcleanrepo.git ==> e.g. 5M 

(はい!)

それから私はそれを新しいディレクトリに複製し、その.gitフォルダの上に移動します。例えば.

$ mv .git ../large_dot_git
$ git clone /path/to/newcleanrepo.git ../tmpdir
$ mv ../tmpdir/.git .
$ du -sh .git ==> e.g. 5M 

(そう、ついにクリーンアップしました!)

問題がないことを確認したら、../large_dot_gitディレクトリおよび../tmpdirディレクトリを削除できます(念のため、今から2、3週間以内に)

47
Darren

Gitの履歴を書き換えるには、影響を受けるすべてのコミットIDを変更する必要があるため、プロジェクトに取り組んでいるすべての人がリポジトリの古いコピーを削除し、履歴を消去した後に新しいクローンを作成する必要があります。不便な人が多ければ多いほど、それを行うには正当な理由が必要です。余計なファイルは実際には問題を引き起こしていませんが、youだけが作業している場合あなたがしたい場合は、プロジェクト、あなたは同様にGitの歴史をクリーンアップするかもしれません!

できるだけ簡単にするために、 BFG Repo-Cleaner を使用することをお勧めします。これは、Git履歴からファイルを削除するために特別に設計されたgit-filter-branchのより簡単で速い代替手段です。ここであなたの生活を楽にする1つの方法は、デフォルトでallrefsを実際に処理することです(すべてのタグ、ブランチなど)が、 10 - 50x もっと早く。

あなたは慎重にここの手順に従うべきです: http://rtyley.github.com/bfg-repo-cleaner/#usage - しかしコアビットはまさにこれです: BFG jarをダウンロードする (Javaが必要です) 6以上を実行して次のコマンドを実行します。

$ Java -jar bfg.jar --delete-files filename.orig my-repo.git

リポジトリの履歴全体がスキャンされ、filename.origという名前のファイル( latestcommit にはありません)が削除されます。これはgit-filter-branchを使って同じことをするよりもかなり簡単です。

完全開示:私はBFG Repo-Cleanerの作者です。

26
Roberto Tyley
You should probably clone your repository first.

Remove your file from all branches history:
git filter-branch --tree-filter 'rm -f filename.orig' -- --all

Remove your file just from the current branch:
git filter-branch --tree-filter 'rm -f filename.orig' -- --HEAD    

Lastly you should run to remove empty commits:
git filter-branch -f --Prune-empty -- --all
13
paulalexandru

それをCharles Baileyのソリューションに追加するために、私は以前のコミットから不要なファイルを削除するためにgit rebase -iを使用しました。ステップ:

# Pick your commit with 'e'
$ git rebase -i

# Perform as many removes as necessary
$ git rm project/code/file.txt

# amend the commit
$ git commit --amend

# continue with rebase
$ git rebase --continue

私が見つけた最も簡単な方法は(コメントとして)leontalbotによって提案されたもので、これは Anoopjohnによって投稿された投稿 です。答えとしてそれ自身のスペースの価値があると思います。

(私はそれをbashスクリプトに変換しました)

#!/bin/bash
if [[ $1 == "" ]]; then
    echo "Usage: $0 FILE_OR_DIR [remote]";
    echo "FILE_OR_DIR: the file or directory you want to remove from history"
    echo "if 'remote' argument is set, it will also Push to remote repository."
    exit;
fi
FOLDERNAME_OR_FILENAME=$1;

#The important part starts here: ------------------------

git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch $FOLDERNAME_OR_FILENAME" -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --Prune=now
git gc --aggressive --Prune=now

if [[ $2 == "remote" ]]; then
    git Push --all --force
fi
echo "Done."

すべてのクレジットはそれを指摘するためにAnnopjohnに、そしてleontalbotに行きます。

NOTE

スクリプトには検証が含まれていないので、間違いをしないようにし、何か問題が発生した場合に備えてバックアップを作成してください。それは私のために働いたが、それはあなたの状況ではうまくいかないかもしれません。注意して使用してください(何が起こっているのか知りたい場合はリンクをたどってください)。

4
lepe

確かに、git filter-branchがその道です。

残念ながら、これはリポジトリからfilename.origを完全に削除するのに十分ではありません。タグ、reflogエントリ、リモート参照などでまだ参照されている可能性があるためです。

これらの参照もすべて削除してから、ガベージコレクタを呼び出すことをお勧めします。あなたは this のウェブサイトからgit forget-blobスクリプトを使用して、これらすべてをワンステップで行うことができます。

git forget-blob filename.orig

3
nachoparker

それがあなたが片付けたい最新のコミットであるならば、私はgitバージョン2.14.3(Apple Git-98)で試みました:

touch empty
git init
git add empty
git commit -m init

# 92K   .git
du -hs .git

dd if=/dev/random of=./random bs=1m count=5
git add random
git commit -m mistake

# 5.1M  .git
du -hs .git

git reset --hard HEAD^
git reflog expire --expire=now --all
git gc --Prune=now

# 92K   .git
du -hs .git
1
clarkttfu

これが git filter-branch のために設計されたものです。

0
CesarB