내가하고있는 모든 일이 커밋을 저지르는 것인데 왜 git-rebase가 병합 충돌을 제공합니까?
우리는 400 개가 넘는 커밋을 가진 Git 저장소를 가지고 있는데, 그 중 처음 몇 개는 많은 시행 착오를 겪었습니다. 많은 커밋을 단일 커밋으로 스쿼시하여 커밋을 정리하고 싶습니다. 당연히 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
The "both modified" bit sounds weird to me, since this was just the result of a pick. It's also worth noting that if I look at the "conflict", it boils down to a single line with one version beginning it with a [tab] character, and the other one with four spaces. This sounded like it might be an issue with how I've set up my config file, but there's nothing of the sort in it. (I did note that core.ignorecase is set to true, but evidently git-clone did that automatically. I'm not completely surprised by that considering that the original source was on a Windows machine.)
If I manually fix file_X.cpp, it then fails shortly afterward with another conflict, this time between a file (CMakeLists.txt) that one version thinks should exist and one version thinks shouldn't. If I fix this conflict by saying I do want this file (which I do), a few commits later I get another conflict (in this same file) where now there's some rather non-trivial changes. It's still only about 25% of the way through the conflicts.
I should also point out, since this might be very important, that this project started out in an svn repository. That initial history very likely was imported from that svn repository.
Update #2:
On a lark (influenced by Jefromi's comments), I decided to do the change my repo_squash.sh to be:
rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a
And then, I just accepted the original entries, as is. I.e., the "rebase" shouldn't have changed a thing. It ended up with the same results describe previously.
Update #3:
Alternatively, if I omit the strategy and replace the last command with:
git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a
I no longer get the "nothing to commit" rebase problems, but I'm still left with the other conflicts.
Update with toy repository that recreates problem:
test_squash.sh (this is the file you actually run):
#========================================================
# 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 (used by 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
P.S.: Yes, I know some of you cringe when you see me using emacs as a fall-back editor.
P.P.S.: We do know we'll have to blow away all of our clones of the existing repository after the rebase. (Along the lines of "thou shalt not rebase a repository after it's been published".)
P.P.P.S: Can anyone tell me how to add a bounty to this? I'm not seeing the option anywhere on this screen whether I'm in edit mode or view mode.
All right, I'm confident enough to throw out an answer. Maybe will have to edit it, but I believe I know what your problem is.
Your toy repo test case has a merge in it - worse, it has a merge with conflicts. And you're rebasing across the merge. Without -p
(which doesn't totally work with -i
), the merges are ignored. This means that whatever you did in your conflict resolution isn't there when the rebase tries to cherry-pick the next commit, so its patch may not apply. (I believe this is shown as a merge conflict because git cherry-pick
can apply the patch by doing a three-way merge between the original commit, the current commit, and the common ancestor.)
Unfortunately, as we noted in the comments, -i
and -p
(preserve merges) don't get along very well. I know that editing/rewording work, and that reordering doesn't. However, I believe that it works fine with squashes. This is not documented, but it worked for the test cases I describe below. If your case is way, way more complex, you may have a lot of trouble doing what you want, though it'll still be possible. (Moral of the story: clean up with rebase -i
before merging.)
So, let's suppose we have a very simple case, where we want to squash together A, B, and C:
- o - A - B - C - X - D - E - F (master)
\ /
Z -----------
Now, like I said, if there were no conflicts in X, git rebase -i -p
works as you'd expect.
If there are conflicts, things get a little trickier. It'll do fine squashing, but then when it tries to recreate the merge, the conflicts will happen again. You'll have to resolve them again, add them to the index, then use git rebase --continue
to move on. (Of course, you can resolve them again by checking out the version from the original merge commit.)
If you happen to have rerere
enabled in your repo (rerere.enabled
set to true), this will be way easier - git will be able to reuse the recorded resolution from when you originally had the conflicts, and all you have to do is inspect it to make sure it worked right, add the files to the index, and continue. (You can even go one step farther, turning on rerere.autoupdate
, and it'll add them for you, so the merge won't even fail). I'm guessing, however, that you didn't ever enable rerere, so you're going to have to do the conflict resolution yourself.*
* Or, you could try the rerere-train.sh
script from git-contrib, which attempts to "Prime [the] rerere database from existing merge commits" - basically, it checks out all the merge commits, tries to merge them, and if the merge fails, it grabs the results and shows them to git-rerere
. This could be time-consuming, and I've never actually used it, but it might be very helpful.
If you don't mind creating a new branch, this is how I dealt with the problem:
Being on master:
# 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 master
git commit -m "Squashed changes from original_messy_branch"
I was looking for a similar requirement , i.e. discarding intermeiate commits of my development branch , I've found this procedure worked for me.
on my working branch
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”
viola we have connected the latest commit to the start commit of the branch! No merge conflict issues! In my learning practice I have come to this conclusion at this stage , Is there a better approach for the purpose .
Note that -X
and strategy options were ignored when used in an interactive rebase.
See commit db2b3b820e2b28da268cc88adff076b396392dfe (July 2013, git 1.8.4+),
Do not ignore merge options in interactive rebase
Merge strategy and its options can be specified in
git rebase
, but with-- interactive
, they were completely ignored.Signed-off-by: Arnaud Fontaine
That means -X
and strategy now work with interactive rebase, as well as plain rebase, and your initial script could now work better.
I was running into a simpler but similar issue, where I had 1) resolved a merge conflict on a local branch, 2) kept working adding lots more little commits, 3) wanted to rebase and hit merge conflicts.
For me, git rebase -p -i master
worked. It kept the original conflict resolution commit and allowed me to squash the others on top.
Hope that helps someone!
'Programming' 카테고리의 다른 글
ASP.NET에서 더 많은 제어권을 얻으려면 어떻게해야합니까? (0) | 2020.07.10 |
---|---|
'+'오류 전에 JSHint의 잘못된 줄 바꿈에 대한 설명 (0) | 2020.07.10 |
보호 된 메모리를 읽거나 쓰려고했습니다. (0) | 2020.07.10 |
** kwargs를 사용하여 ** kwargs 인수를 다른 함수에 전달 (0) | 2020.07.10 |
통합 테스트와 기능 테스트의 차이점은 무엇입니까? (0) | 2020.07.10 |