잘못된 병합을 수정하고 올바른 병합을 고정 병합으로 재생하는 방법은 무엇입니까?


407

나는 실수로 원하지 않는 파일 ( filename.orig병합을 해결하는 동안)을 커밋하기 전에 저장소에 저장했지만 지금까지는 알지 못했습니다. 리포지토리 기록에서 파일을 완전히 삭제하고 싶습니다.

filename.orig처음에는 저장소에 추가되지 않은 변경 히스토리를 다시 작성할 수 있습니까?



답변:


297

귀하의 상황이 질문에 설명 된 것이 아닌 경우이 레시피를 사용하지 마십시오. 이 레시피는 잘못된 병합을 수정하고 올바른 커밋을 고정 병합에서 재생하기위한 것입니다.

filter-branch당신이 원하는 것을 할 것이지만 , 그것은 매우 복잡한 명령이며 아마도 이것을 사용하여 선택할 것입니다 git rebase. 아마 개인적인 취향 일 것입니다. 솔루션은 한 번에 한 단계 씩 동등한 논리 연산을 수행하는 filter-branch반면 약간 더 복잡한 단일 명령 rebase으로 수행 할 수 있습니다.

다음 레시피를 시도하십시오.

# 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

(실제로 임시 분기가 필요하지는 않지만 '분리 된 HEAD'로이를 수행 할 수 있지만 임시 분기를 사용하는 대신 git commit --amend단계에 의해 생성 된 커밋 ID 를 기록하여 git rebase명령 에 제공해야합니다. 이름.)


6
하지 않을까요 git rebase -i빠르고 여전히 쉽게 등 수? $ git rebase -i <sh1-of-merge> 올바른 것을 "edit"로 표시 $ git rm somefile.orig $ git commit --a $ $ git rebase --continue 그러나 어떤 이유로 나는 여전히 마지막 파일을 가지고 있습니다 내가 한 시간. 아마도 뭔가 빠졌을 것입니다.
Wernight

12
git rebase -i특히 여러 개의 rebase-y 작업을 수행 할 때 매우 유용하지만 실제로 누군가의 어깨를 가리 키지 않을 때 정확하게 설명하기가 어려우며 편집자가 어떤 일을하고 있는지 알 수 있습니다. vim을 사용하지만 "ggjcesquash <Esc> jddjp : wq"및 "현재 두 번째 줄 다음으로 맨 위 줄을 이동하고 네 번째 줄의 첫 번째 단어를 'edit'로 변경하여 저장하고 종료 "는 실제 단계보다 더 복잡해 보입니다. 일반적으로 일부 작업 --amend--continue작업 으로 끝납니다 .
CB Bailey

3
나는 이것을했지만 새 메시지가 수정 된 메시지 위에 다시 적용되었으며 동일한 메시지가 나타납니다. 분명히 git은 원치 않는 파일을 포함하는 이전의 수정되지 않은 커밋과 다른 브랜치의 고정 커밋 사이에 3 가지 방법으로 병합을 수행하여 이전 파일 위에 새로운 커밋을 만들어 파일을 다시 적용했습니다.

6
@UncleCJ : 파일이 병합 커밋에 추가 되었습니까? 이것은 중요합니다. 이 레시피는 잘못된 병합 커밋에 대처하도록 설계되었습니다. 원하지 않는 파일이 기록의 정상적인 커밋에 추가 된 경우 작동하지 않습니다.
CB Bailey

1
smartgit을 사용하고 터미널을 사용하지 않고이 모든 것을 할 수있는 방법에 놀랐습니다! 레시피에 감사드립니다!
cregox

209

소개 : 5 가지 솔루션 제공

원래 포스터는 다음과 같이 말합니다.

실수로 원하지 않는 파일을 여러 커밋 전에 내 저장소에 커밋했습니다 ... 리포지토리 기록에서 파일을 완전히 삭제하고 싶습니다.

filename.orig처음에는 저장소에 추가되지 않은 변경 히스토리를 다시 작성할 수 있습니까?

git에서 파일 히스토리를 완전히 제거하는 방법에는 여러 가지가 있습니다.

  1. 커밋을 수정합니다.
  2. 하드 리셋 (아마도 리베이스).
  3. 비 대화식 리베이스.
  4. 대화식 리베이스.
  5. 가지 필터링.

원래 포스터의 경우 커밋을 수정하는 것은 실제로 몇 가지 추가 커밋을했기 때문에 실제로는 선택 사항이 아니지만 완전성을 위해 그것을 원하는 방법을 설명합니다. 이전 커밋을 수정합니다.

이러한 모든 솔루션에는 히스토리 / 커밋을 변경 / 다시 쓰기 가 포함되어 있으므로 커밋 사본이 오래된 사람은 히스토리를 새 히스토리와 다시 동기화하기 위해 추가 작업을 수행해야합니다.


해결 방법 1 : 커밋 수정

이전 커밋에서 실수로 파일을 추가하는 등의 변경을 한 경우 해당 변경 내역이 더 이상 존재하지 않게하려면 이전 커밋을 수정하여 파일에서 파일을 제거하면됩니다.

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

해결 방법 2 : 하드 리셋 (아마도 리베이스 추가)

솔루션 # 1과 마찬가지로 이전 커밋을 제거하려면 부모로 간단하게 재설정하는 옵션도 있습니다.

git reset --hard HEAD^

이 명령은 분기를 이전의 첫 번째 부모 커밋으로 강제 재설정합니다 .

그러나 원본 포스터와 마찬가지로 변경을 취소하려는 커밋 후에 여러 커밋을 한 경우에도 하드 리셋을 사용하여 수정할 수 있지만 리베이스를 사용하는 것도 포함됩니다. 히스토리에서 커밋을 다시 수정하는 데 사용할 수있는 단계는 다음과 같습니다.

# 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 master@{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 master@{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)

수정 또는 제거하려는 커밋이이 목록의 맨 위에 있습니다. 제거하려면 목록에서 해당 행을 삭제하십시오. 그렇지 않으면 다음 과 같이 첫 번째 줄에서 "pick"을 "edit"로 바꾸십시오 .

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

그런 다음을 입력하십시오 git rebase --continue. 커밋을 완전히 제거하기로 선택한 경우 확인 이외의 모든 작업을 수행해야합니다 (확인 이외의 경우이 솔루션의 마지막 단계 참조). 반면에 커밋을 수정하려면 git이 커밋을 다시 적용한 다음 rebase를 일시 중지합니다.

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

그게 다야. 마지막 단계로 커밋을 수정했는지 아니면 완전히 제거했는지에 관계없이 브랜치가 rebase 이전의 상태와 다른 것으로 인해 분기에 다른 예기치 않은 변경 사항이 없는지 확인하는 것이 좋습니다.

git diff master@{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 master@{1}

필터 브랜치 대안 : BFG Repo Cleaner

BFG Repo Cleaner 도구가보다 빠르게 실행 된다는 말을 들었 git filter-branch으므로 옵션으로 확인하고 싶을 수도 있습니다. 필터 브랜치 문서 에서 공식적 으로 실행 가능한 대안으로 언급되었습니다 .

git-filter-branch를 사용하면 Git 히스토리를 복잡한 쉘 스크립트로 다시 작성할 수 있지만 큰 파일이나 암호와 같은 원치 않는 데이터를 단순히 제거하는 경우 이러한 유연성이 필요하지 않습니다 . 이러한 작업의 경우 git-filter-branch에 대한 JVM 기반 대안 인 BFG Repo-Cleaner 를 고려할 수 있습니다 . 일반적으로 이러한 사용 사례의 경우 10-50 배 이상 빠르며 특성이 매우 다릅니다.

  • 파일의 특정 버전은 정확히 한 번만 정리됩니다 . BFG는 git-filter-branch와 달리 파일이 기록 내에서 언제 어디서 커밋되었는지에 따라 다르게 처리 할 수있는 기회를 제공하지 않습니다. 이 제약 조건은 BFG의 핵심 성능 이점을 제공하며 불량 데이터를 정리하는 작업에 적합합니다. 불량 데이터의 위치를 신경 쓰지 않고 원하는 데이터 만 보관할 수 있습니다.

  • 기본적으로 BFG는 멀티 코어 시스템을 최대한 활용하여 커밋 파일 트리를 병렬로 정리합니다. 자식 필터 - 지점의 세척 과정의 커밋은 순차적으로 (즉, 단일 스레드 방식으로), 그것은 비록 입니다 각각에 대해 실행 스크립트가 커밋 가능한, 자신의 parallellism을 포함하는 필터를 작성합니다.

  • 명령 옵션은 단지 예를 들어, 데이터 - 불필요한을 제거하는 작업에 더 많은 자식 필터 지점보다 더 제한하고, 최선을 다하고 있습니다 --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 Repo Cleaner ( 제작자 자신의 답변 참조 ).

filter-branch해시를 다시 계산 합니까 ? 팀이 큰 파일을 필터링해야하는 리포지토리와 함께 작업하는 경우 모든 사람이 동일한 리포지토리 상태를 갖도록하려면 어떻게해야합니까?
YakovL

@YakovL. 모든 것이 해시를 다시 계산합니다. 실제로 커밋은 변경할 수 없습니다. 완전히 새로운 기록을 만들고 분기 포인터를 이동합니다. 모든 사람이 동일한 이력을 갖도록하는 유일한 방법은 하드 리셋입니다.
Mad Physicist

118

이후에 커밋하지 않은 경우 git rm파일과 git commit --amend.

당신이 가지고 있다면

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

에서 merge-point로 변경 될 때마다 HEADfilename.orig를 삭제하고 변경 사항을 다시 작성하십시오. 사용하면 --ignore-unmatch어떤 이유로 인해 filename.orig가 변경 사항에서 누락 된 경우 명령이 실패하지 않습니다. git-filter-branch 매뉴얼 페이지 의 예제 섹션에서 권장되는 방법입니다 .

Windows 사용자를위한 참고 사항 : 파일 경로 슬래시를 사용해야합니다.


3
감사! git filter-branch는 대답으로 주어진 rebase 예제가 효과가없는 곳에서 나를 위해 일했습니다 : 단계가 작동하는 것처럼 보이지만 밀어 넣기가 실패했습니다. 당기고 성공적으로 밀었지만 파일은 여전히 ​​주변했습니다. 리베이스 단계를 다시 시도했지만 병합 충돌로 인해 혼란스러워졌습니다. github.com/guides/completely-remove-a-file-from-all-revisions git filter-branch -f --index- 그러나 약간 다른 filter-branch 명령을 사용했습니다. 필터 'git update-index --remove filename'<introduction-revision-sha1> .. HEAD
atomicules

1
어느 것이 개선 된 방법 인지 확실하지 않습니다 . Git 공식 문서 git-filter-branch는 첫 번째 문서 를 제공하는 것 같습니다.
Wernight

5
zyxware.com/articles/4027/을 확인하십시오. 이것이 포함 된 가장 완벽하고 직접적인 솔루션입니다filter-branch
leontalbot

2
@atomicules, 로컬 리포지토리를 원격 리포지토리로 푸시하려고 시도하면 git은 로컬에서 가지고 있지 않은 변경 사항이 있기 때문에 원격에서 먼저 당기기를 주장합니다. --force 플래그를 사용하여 원격으로 푸시하면 파일이 완전히 제거됩니다. 그러나 파일 이외의 것을 덮어 쓰지 않도록주의하십시오.
sol0mka

1
Windows 를 사용할 때는 사용 "하지 말아야합니다. 그렇지 않으면 '도움이되지 않는 "잘못된 개정"오류가 표시됩니다.
cz

49

이것이 가장 좋은 방법입니다 :
http://github.com/guides/completely-remove-a-file-from-all-revisions

파일 사본을 먼저 백업하십시오.

편집하다

검토 중에 Neon 의 수정 사항이 거부되었습니다.
아래의 네온 게시물을 참조하십시오. 유용한 정보가 포함되어있을 수 있습니다!


예를 들어 *.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

마스터 지점이 하나 밖에 없기 때문에 왜 그런지 확실하지 않습니다. 어쨌든, 마침내 새로운 빈 저장소와 빈 git 저장소로 밀어 넣어서 git repo를 실제로 정리했습니다.

$ 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_gitand ../tmpdir디렉토리를 삭제할 수 있습니다 (몇 주 또는 몇 달 후에는 경우에 따라)


1
"아직 저에게 도움이되지 않습니까?" 댓글
shadi

큰 대답이지만 --prune-emptyfilter-branch 명령에 추가 하는 것이 좋습니다 .
ideasman42

27

Git 히스토리를 다시 작성하려면 영향을받는 커밋 ID를 모두 변경해야하므로 프로젝트를 수행하는 모든 사람은 이전 리포지토리 사본을 삭제하고 히스토리를 정리 한 후 새로운 복제본을 작성해야합니다. 당신의 불필요한 파일이 실제로 문제를 일으키는 것이 아니라, 경우에만 - 당신이 좋은 이유를 필요로하는 더는 불편을 더 많은 사람들이 그것을 수행하는 당신 프로젝트에서 작업하는 당신이 원한다면, 당신은뿐만 아니라 망할 놈의 기록을 정리 할 수 에!

가능한 한 쉽게 만들려면 Git 히스토리에서 파일을 제거하기 위해 특별히 설계된 더 빠르고 간단한 대안 인 BFG Repo-Cleaner를 사용하는 것이 좋습니다 git-filter-branch. 여기에서 인생을 더 편하게 만드는 한 가지 방법은 실제로 모든 심판, 기본적으로 모든 태그, 지점 등을 처리하지만 10-50 배입니다. 빠릅니다.

: 당신은 신중하게 여기 단계를 수행해야 http://rtyley.github.com/bfg-repo-cleaner/#usage -하지만, 코어 비트는 바로 이것이다 : 다운로드 BFG 항아리 (자바 6 이상 필요)이 명령을 실행 :

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

전체 리포지토리 기록이 스캔되고 filename.orig( 최근 커밋에없는 ) 파일이 제거됩니다. git-filter-branch동일한 작업을 수행하는 것보다 훨씬 쉽습니다 !

전체 공개 : 저는 BFG Repo-Cleaner의 저자입니다.


4
이 도구는 훌륭한 도구입니다. 단일 명령으로 매우 명확한 출력을 생성 하고 새 명령에 대한 모든 이전 커밋과 일치 하는 로그 파일을 제공합니다 . Java 설치를 좋아하지 않지만 그만한 가치가 있습니다.
mikemaccana

이것은 나를 위해 일한 유일한 방법이지만 git filter-branch를 올바르게 작동하지 않았기 때문입니다. :-)
Kevin LaBranche

14
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

1
모든 답변이 필터 지점 트랙에있는 것처럼 보이지만이 기록은 기록의 모든 분기를 청소하는 방법을 강조합니다.
Cameron Lowell Palmer

4

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

4

내가 찾은 가장 간단한 방법 leontalbotAnoopjohn게시게시물 인 ( 의견으로) 제안되었습니다 . 나는 그 자체의 공간 가치가 있다고 생각합니다.

(나는 그것을 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그것을 지적.

노트

스크립트에는 유효성 검사가 포함되어 있지 않으므로 실수하지 말고 문제가 발생할 경우 백업을해야합니다. 그것은 나를 위해 일했지만 당신의 상황에서 작동하지 않을 수 있습니다. 주의해서 사용하십시오 (무슨 일인지 알고 싶다면 링크를 따라 가십시오).


3

확실히 git filter-branch갈 길입니다.

안타깝게도 filename.orig태그, 참조 로그 항목, 리모컨 등으로 여전히 참조 할 수 있기 때문에 리포지토리에서 완전히 제거해도 충분하지 않습니다 .

이러한 참조도 모두 제거한 다음 가비지 수집기를 호출하는 것이 좋습니다. 웹 사이트 의 git forget-blob스크립트를 사용 하여이 모든 작업을 한 번에 수행 할 수 있습니다.

git forget-blob filename.orig


1

정리하려는 최신 커밋 인 경우 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

git reflog expire --expire=now --all; git gc --prune=now매우 나쁜 일입니다. 디스크 공간이 부족하지 않은 경우 git 쓰레기가 몇 주 후에 이러한 커밋을 수집하도록하십시오
avmohan

지적 해 주셔서 감사합니다. 내 저장소는 많은 큰 이진 파일과 함께 제출되었으며 저장소는 매일 밤마다 완전히 백업됩니다. 그래서 난 그냥 모든 비트를 원했다;)
clarkttfu


-1

다음을 사용할 수도 있습니다.

git reset HEAD file/path


3
파일이 커밋에 추가되면 인덱스에서 파일을 제거하지 않아도 인덱스가 파일의 HEAD 버전으로 재설정됩니다.
CB Bailey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.