내가하고있는 모든 것이 커밋을 저지르는 것만으로 git-rebase가 병합 충돌을 일으키는 이유는 무엇입니까?


146

우리는 400 개가 넘는 커밋을 가진 Git 저장소를 가지고 있는데, 그 중 첫 12 개는 많은 시행 착오를 겪었습니다. 많은 커밋을 단일 커밋으로 스쿼시하여 커밋을 정리하고 싶습니다. 당연히 git-rebase는 갈 길입니다. 내 문제는 병합 충돌로 끝나고 이러한 충돌을 해결하기 쉽지 않다는 것입니다. 커밋을 삭제하거나 재 배열하지 않기 때문에 왜 충돌이 발생 해야하는지 이해하지 못합니다. 아마도 이것은 git-rebase가 과즙을 어떻게 수행하는지 완전히 이해하지 못하고 있음을 보여줍니다.

사용중인 스크립트의 수정 된 버전은 다음과 같습니다.


repo_squash.sh (실제로 실행되는 스크립트) :


rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
GIT_EDITOR=../repo_squash_helper.sh git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a

repo_squash_helper.sh (이 스크립트는 repo_squash.sh에서만 사용됨) :


if grep -q "pick " $1
then
#  cp $1 ../repo_squash_history.txt
#  emacs -nw $1
  sed -f ../repo_squash_list.txt < $1 > $1.tmp
  mv $1.tmp $1
else
  if grep -q "initial import" $1
  then
    cp ../repo_squash_new_message1.txt $1
  elif grep -q "fixing bad import" $1
  then
    cp ../repo_squash_new_message2.txt $1
  else
    emacs -nw $1
  fi
fi

repo_squash_list.txt : (이 파일은 repo_squash_helper.sh에서만 사용됨)


# Initial import
s/pick \(251a190\)/squash \1/g
# Leaving "Needed subdir" for now
# Fixing bad import
s/pick \(46c41d1\)/squash \1/g
s/pick \(5d7agf2\)/squash \1/g
s/pick \(3da63ed\)/squash \1/g

나는 "새 메시지"내용을 당신의 상상에 맡기겠습니다. 처음에는 "--strategy theirs"옵션없이이 작업을 수행했습니다 (즉, 기본 전략을 사용하여 문서를 올바르게 이해하는 경우 재귀 적이지만 어떤 재귀 적 전략이 사용되는지 잘 모르겠습니다). 작동하지 않습니다. 또한 repo_squash_helper.sh의 주석 처리 된 코드를 사용하여 sed 스크립트가 작동하는 원본 파일을 저장하고 sed 스크립트를 실행하여 원하는 작업을 수행했는지 확인했습니다. 그랬습니다). 다시 말하지만, 왜 충돌 이 일어날 지조차 알지 못하므로 어떤 전략이 사용되는지는 중요하지 않습니다. 조언이나 통찰력이 도움이 될 수 있지만 대부분이 스쿼시 작업을 원합니다.

Jefromi와의 토론에서 추가 정보로 업데이트되었습니다.

방대한 "실제"리포지토리에서 작업하기 전에 테스트 리포지토리에서 비슷한 스크립트를 사용했습니다. 매우 간단한 저장소였으며 테스트는 깨끗하게 진행되었습니다.

실패했을 때 나타나는 메시지는 다음과 같습니다.

Finished one cherry-pick.
# Not currently on any branch.
nothing to commit (working directory clean)
Could not apply 66c45e2... Needed subdir

이것은 첫 번째 스쿼시 커밋 후 첫 번째 선택입니다. 실행 git status하면 깨끗한 작업 디렉토리가 생성됩니다. 그런 다음을 수행 git rebase --continue하면 몇 가지 커밋 후에 매우 비슷한 메시지가 나타납니다. 그런 다음 다시 수행하면 수십 개의 커밋 후에 매우 비슷한 메시지가 나타납니다. 다시 한 번 수행하면 이번에는 약 100 개의 커밋을 거쳐 다음 메시지가 나타납니다.

Automatic cherry-pick failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
run 'git rebase --continue'
Could not apply f1de3bc... Incremental

그런 다음을 실행하면 다음을 git status얻습니다.

# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# modified:   repo/file_A.cpp
# modified:   repo/file_B.cpp
#
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified:      repo/file_X.cpp
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted:    repo/file_Z.imp

"수정 된"비트는 이상하게 들립니다. 왜냐하면 이것은 선택의 결과 일뿐입니다. "충돌"을 살펴보면 하나의 버전이 [탭] 문자로 시작하고 다른 하나는 4 개의 공백이있는 한 줄로 요약됩니다. 구성 파일을 설정하는 방법에 문제가있는 것처럼 들리지만 그 안에는 아무것도 없습니다. (core.ignorecase가 true로 설정되어 있지만 git-clone이 자동으로 수행 한 것으로 나타났습니다. 원래 소스가 Windows 시스템에 있다는 점을 고려해도 전혀 놀랍지 않습니다.)

file_X.cpp를 수동으로 수정하면 나중에 다른 충돌로 인해 실패합니다. 이번에는 한 버전이 존재한다고 생각하는 파일 (CMakeLists.txt)과 한 버전이 존재하지 않아야한다고 생각합니다. 내가이 파일을 원한다고 말 함으로써이 갈등을 고치면 (약간의 커밋) 나중에 약간의 변경 사항이있는 또 다른 갈등 (이 동일한 파일에서)이 발생합니다. 그것은 여전히 ​​갈등의 약 25 %입니다.

또한이 프로젝트가 svn 저장소에서 시작되었다는 것이 매우 중요하기 때문에 지적해야합니다. 초기 기록은 해당 svn 저장소에서 가져온 것 같습니다.

업데이트 # 2 :

나는 Jefromi의 의견에 영향을받는 종달새에서 repo_squash.sh를 다음과 같이 변경하기로 결정했습니다.

rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a

그런 다음 원래 항목을 그대로 수락했습니다. 즉, "리베이스"는 변경되지 않아야합니다. 그것은 앞에서 설명한 것과 같은 결과로 끝났습니다.

업데이트 # 3 :

또는 전략을 생략하고 마지막 명령을 다음과 같이 바꾸면

git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a

더 이상 "커밋 할 사항 없음"rebase 문제가 발생하지 않지만 여전히 다른 충돌이 남아 있습니다.

문제를 재현하는 장난감 저장소로 업데이트하십시오.

test_squash.sh (실제로 실행되는 파일) :

#========================================================
# Initialize directories
#========================================================
rm -rf test_squash/ test_squash_clone/
mkdir -p test_squash
mkdir -p test_squash_clone
#========================================================

#========================================================
# Create repository with history
#========================================================
cd test_squash/
git init
echo "README">README
git add README
git commit -m"Initial commit: can't easily access for rebasing"
echo "Line 1">test_file.txt
git add test_file.txt
git commit -m"Created single line file"
echo "Line 2">>test_file.txt 
git add test_file.txt 
git commit -m"Meant for it to be two lines"
git checkout -b dev
echo Meaningful code>new_file.txt
git add new_file.txt 
git commit -m"Meaningful commit"
git checkout master
echo Conflicting meaningful code>new_file.txt
git add new_file.txt 
git commit -m"Conflicting meaningful commit"
# This will conflict
git merge dev
# Fixes conflict
echo Merged meaningful code>new_file.txt
git add new_file.txt
git commit -m"Merged dev with master"
cd ..

#========================================================
# Save off a clone of the repository prior to squashing
#========================================================
git clone test_squash test_squash_clone
#========================================================

#========================================================
# Do the squash
#========================================================
cd test_squash
GIT_EDITOR=../test_squash_helper.sh git rebase -i HEAD@{7}
#========================================================

#========================================================
# Show the results
#========================================================
git log
git gc
git reflog
#========================================================

test_squash_helper.sh (test_sqash.sh에서 사용) :

# If the file has the phrase "pick " in it, assume it's the log file
if grep -q "pick " $1
then
  sed -e "s/pick \(.*\) \(Meant for it to be two lines\)/squash \1 \2/g" < $1 > $1.tmp
  mv $1.tmp $1
# Else, assume it's the commit message file
else
# Use our pre-canned message
  echo "Created two line file" > $1
fi

추신 : 네, 이맥스를 폴백 편집기로 사용하는 것을 보았을 때 여러분 중 일부가 울부 짖는 것을 알고 있습니다.

PPS : 리베이스 후 기존 리포지토리의 모든 클론을 날려 버려야한다는 것을 알고 있습니다. ( "저장소를 게시 한 후에 저장소를 리베이스하지 않아야한다"는 내용과 함께)

PPPS : 누구나 현상금을 추가하는 방법을 알려줄 수 있습니까? 편집 모드 또는보기 모드에 관계 없이이 화면의 어느 곳에서도 옵션이 표시되지 않습니다.


사용 된 스크립트보다 더 관련성이있는 마지막 시도 작업은 혼합 된 픽 앤 스쿼시 목록 일 것입니다. 그리고 리베이스 브랜치에 병합 커밋이 있습니까? ( rebase -p어쨌든 사용하지 않더라도 )
Cascabel

나는 당신이 "마지막 시도 행동"무슨 뜻인지 모르겠지만, 그것은 이다 마지막으로 400 또는 모든 선택되고 있으므로, 단지 상호 혼합 선택, 스쿼시의 목록입니다. rebasing 자체가 자체 병합을 수행하더라도 해당 목록에는 병합이 없습니다. 내가 읽은 것에 따르면, "rebase -p"는 대화식 모드에서는 권장되지 않습니다 (물론 대화식 모드는 아닙니다). 에서 kernel.org/pub/software/scm/git/docs/git-rebase.html "이것은 내부적으로 --interactive 기계를 사용하지만 --interactive 옵션과 결합하는 것은 명시 적으로 일반적으로 좋은 아이디어 아니다"
벤 저당 잡혀

"최종 시도 된 조치"는 다시 전달 된 픽 / 스쿼시 목록을 의미합니다. rebase --interactive이들은 git이 시도하는 조치 목록입니다. 충돌을 일으키는 단일 스쿼시로 이것을 줄이고 도우미 스크립트의 모든 추가 복잡성을 피할 수 있기를 바랍니다. 다른 누락 된 정보는 충돌이 발생할 때입니다-git이 스쿼시를 형성하기 위해 패치를 적용 할 때 또는 스쿼시를 지나서 다음 패치를 적용하려고 할 때? (그리고 당신은 GIT_EDITOR kludge에 아무런 문제가 없다고 확신하십니까? 간단한 테스트 사례에 대한 또 다른 투표입니다.)
Cascabel

유용한 의견에 감사드립니다. 답변을 반영하도록 질문을 업데이트했습니다.
Ben Hocking 2016 년

2
장난감 저장소 케이스는 스크립트보다 훨씬 이해하기 쉽고 스크립트와 스크립트와 관련이 없음을 보여줍니다. 충돌 해결과 병합이 포함 된 브랜치를 리베이스하려고한다는 사실 (약간 악의적 인 병합) ).
Cascabel

답변:


69

좋아, 나는 대답을 버릴만큼 확신합니다. 어쩌면 편집해야하지만 문제가 무엇인지 알고 있습니다.

토이 저장소 테스트 케이스에 병합이 포함되어 있습니다. 더 나쁜 것은 충돌과 병합 된 것입니다. 그리고 당신은 합병에 걸쳐 rebasing하고 있습니다. -p(완전히 작동하지 않는) 없이는 -i병합이 무시됩니다. 즉 , rebase가 다음 커밋을 체리 픽하려고 할 때 충돌 해결에서 수행 한 작업 이 없으므로 패치가 적용되지 않을 수 있습니다. ( git cherry-pick원래 커밋, 현재 커밋 및 공통 조상 사이에 3 방향 병합을 수행하여 패치를 적용 할 수 있기 때문에 이것이 병합 충돌로 표시됩니다 .)

불행하게도,로 우리가 코멘트에 언급, -i그리고 -p아주 잘 따라하지 않는다 (병합을 보존). 편집 / 리워드 기능이 작동하고 재정렬이 효과가 없다는 것을 알고 있습니다. 그러나 나는 그것이 과즙과 잘 작동 한다고 생각 합니다. 이것은 문서화되어 있지 않지만 아래에서 설명하는 테스트 사례에서 효과적이었습니다. 귀하의 사례가 길고, 더 복잡한 경우, 여전히 가능하지만 원하는 일을하는 데 많은 어려움을 겪을 수 있습니다. (이야기의 교훈 : 병합 rebase -i 하기 전에 정리하십시오 .)

A, B 및 C를 함께 스쿼시하려는 매우 간단한 경우가 있다고 가정 해 봅시다.

- o - A - B - C - X - D - E - F (master)
   \             /
    Z -----------

이제 말했듯이 X에 충돌이 없으면 git rebase -i -p예상대로 작동합니다.

충돌이 있으면 상황이 조금 까다로워집니다. 잘 스쿼시하지만 병합을 다시 만들려고 할 때 충돌이 다시 발생합니다. 다시 해결하고 인덱스에 추가 한 다음 계속해서 사용해야 git rebase --continue합니다. 물론 원래 병합 커밋에서 버전을 확인하여 다시 해결할 수 있습니다.

당신이 한 일이 있다면 rerere(당신의 repo에서 사용할 rerere.enabled자식이 할 수있을 것입니다 - True로 설정), 이것은 쉬운 방법이 될 것입니다 다시 사용 다시 유선 원래 충돌을했을 때의 솔루션을, 그리고 당신이 모두는 그것을 검사 할 제대로 작동하는지 확인하려면 파일을 색인에 추가하고 계속하십시오. (한 단계 더 나아가서을 켜면 rerere.autoupdate추가 할 수 있으므로 병합이 실패하지 않습니다). 그러나 나는 당신이 결코 rerere를 활성화하지 않았다고 추측하고 있습니다. 그래서 당신은 스스로 갈등 해결을해야 할 것입니다. *

또는 rerere-train.shgit-contrib에서 "기존 병합 커밋에서 데이터베이스를 다시 가져 오기"를 시도 하는 스크립트를 시도 할 수 있습니다. 기본적으로 모든 병합 커밋을 체크 아웃하고 병합을 시도하며 병합에 실패하면 결과를 가져 와서에 표시합니다 git-rerere. 시간이 오래 걸리고 실제로 사용한 적이 없지만 매우 도움이 될 수 있습니다.


추신 : 나의 의견을 되돌아 보면, 나는 이것을 더 빨리 붙잡아 야한다는 것을 알 수 있습니다. 병합이 포함 된 분기를 리베이스 할 것인지 물었고 대화 형 rebase 목록에 병합이 없다고 말했지만 동일한 -p옵션 이 아닙니다 . 옵션을 제공하지 않았기 때문에 병합이 없었습니다 .
Cascabel

나는 확실히 이것을 갈 것이다. 이를 올리기 때문에, 나는 또한에 나타났습니다 일부 의 경우, 단순히 입력 할 수 git commit -a -m"Some message"git rebase --continue, 그것은 계속됩니다. 이것은 -p옵션 없이도 작동 하지만 옵션으로 더 잘 작동 -p합니다 (재주문을 수행하지 않기 때문에 -p괜찮습니다). 어쨌든 계속 알려 드리겠습니다.
Ben Hocking 2016 년

테스트 예제를 설정 git config --global rerere.enabled true하고 git config --global rerere.autoupdate true실행하기 전에 주요 문제가 해결됩니다. 그러나 흥미롭게도를 지정하더라도 병합을 보존하지는 않습니다 --preserve-merges. 그러나 해당 세트가 없으면 입력 git commit -a -m"Meaningful commit"하고 git rebase --continue를 지정하는 동안 --preserve-merges병합이 유지됩니다. 어쨌든,이 문제를 극복하도록 도와 주셔서 감사합니다!
Ben Hocking 2016 년

84

새 브랜치를 생성하는 것이 마음에 들지 않으면 다음과 같이 문제를 해결했습니다.

메인에 있음 :

# create a new branch
git checkout -b new_clean_branch

# apply all changes
git merge original_messy_branch

# forget the commits but have the changes staged for commit
git reset --soft main        

git commit -m "Squashed changes from original_messy_branch"

3
가장 빠르고 효율적인 솔루션 :)
IslamTaha

2
이것은 큰 제안이었습니다! 여러 커밋을 빠르게 통합하는 데 매우 효과적이었습니다.
Philidem

3
이것은 훨씬 안전한 솔루션입니다. 또한 병합에 --squash를 추가했습니다. 존재 : git merge --squash original_messy_branch
jezpez

1
내 하루와 생명을 구해 주셔서 감사합니다. :) 누구나 얻을 수있는 최고의 솔루션

당신은 언제 git diff new_clean_branch..original_messy_branch동일해야합니까? 나를 위해 그들은 다릅니다.
LeonardChallis

5

나는 비슷한 요구 사항을 찾고 있었다. 즉, 개발 브랜치의 intermeiate 커밋을 폐기하는 것이이 절차가 나를 위해 효과적이라는 것을 알았다.
내 작업 지점에서

git reset –hard mybranch-start-commit
git checkout mybranch-end-commit . // files only of the latest commit
git add -a
git commit -m”New Message intermediate commits discarded”

비올라 우리는 지점의 시작 커밋에 최신 커밋을 연결했습니다! 병합 충돌 문제가 없습니다! 나의 학습 실습에서 나는이 단계에서이 결론에 도달했다. 목적을위한 더 나은 접근법이 있는가?


참고-나는 이것을 시도하고 있었고 mybranch-end-commit의 체크 아웃을 수행해도 중간 커밋에서 발생한 삭제를 얻지 못한다는 것을 알았습니다. 따라서 mybranch-end-commit에 존재하는 파일 만 체크 아웃합니다.
ahains

1

수동 개입을 최소화하는 위의 @ hlidka 의 위대한 대답을 바탕으로 지점에없는 마스터에 대한 새로운 커밋을 유지하는 버전을 추가하고 싶었습니다.

필자는이 git reset예제 의 단계에서 쉽게 잃을 수 있다고 생각합니다 .

# create a new branch 
# ...from the commit in master original_messy_branch was originally based on. eg 5654da06
git checkout -b new_clean_branch 5654da06

# apply all changes
git merge original_messy_branch

# forget the commits but have the changes staged for commit
# ...base the reset on the base commit from Master
git reset --soft 5654da06       

git commit -m "Squashed changes from original_messy_branch"

# Rebase onto HEAD of master
git rebase origin/master

# Resolve any new conflicts from the new commits

0

참고 -X대화 형 REBASE에 사용하면 무시 전략 옵션을 제공합니다.

커밋 db2b3b820e2b28da268cc88adff076b396392dfe (2013 년 7 월, git 1.8.4+)를 참조하십시오 .

대화식 리베이스에서 병합 옵션을 무시하지 마십시오

병합 전략 및 옵션은에서 지정할 수 git rebase있지만을 사용 -- interactive하면 완전히 무시됩니다.

서명 : Arnaud Fontaine

-X, 일반 rebase뿐만 아니라 대화식 rebase에서도 작동하고 전략이 작동하므로 초기 스크립트가 더 잘 작동 할 수 있습니다.


0

나는 1) 로컬 브랜치에서 병합 충돌을 해결하고 2) 더 많은 커밋을 추가하는 작업을 계속했다 .3) 리베이스하고 병합 충돌을 원했다.

나를 git rebase -p -i master위해 일했다. 그것은 원래의 갈등 해결 커밋을 유지하고 다른 사람들을 맨 위로 스쿼시 할 수있게했습니다.

누군가를 돕는 희망!


-p를 사용하여 리베이스를 시도 할 때 수동으로 해결해야 할 몇 가지 충돌이 여전히 있습니다. 충돌이 훨씬 적었고 이전에 충돌했던 모든 코드 파일이 아니라 프로젝트 파일로 제한되었습니다. 분명히 "병합 해결을위한 병합 충돌 해결 또는 수동 수정 사항은 유지되지 않습니다." - stackoverflow.com/a/35714301/727345
JonoB
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.