Tại sao git-rebase cho tôi hợp nhất các xung đột khi tất cả những gì tôi đang làm là xóa sổ cam kết?


146

Chúng tôi có một kho lưu trữ Git với hơn 400 cam kết, vài chục đầu tiên trong số đó có rất nhiều lỗi và thử. Chúng tôi muốn dọn sạch những cam kết này bằng cách ép nhiều người vào một cam kết duy nhất. Đương nhiên, git-rebase dường như là con đường để đi. Vấn đề của tôi là nó kết thúc bằng xung đột hợp nhất, và những xung đột này không dễ giải quyết. Tôi không hiểu tại sao nên có bất kỳ xung đột nào cả, vì tôi chỉ thực hiện các cam kết (không xóa hoặc sắp xếp lại). Rất có thể, điều này chứng tỏ rằng tôi không hoàn toàn hiểu cách git-rebase thực hiện các cú đánh của nó.

Đây là phiên bản sửa đổi của tập lệnh tôi đang sử dụng:


repo_squash.sh (đây là tập lệnh thực sự đang chạy):


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 (tập lệnh này chỉ được sử dụng bởi 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: (tệp này chỉ được sử dụng bởi 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

Tôi sẽ để lại nội dung "tin nhắn mới" cho trí tưởng tượng của bạn. Ban đầu, tôi đã làm điều này mà không có tùy chọn "--strargety thems" (nghĩa là sử dụng chiến lược mặc định, nếu tôi hiểu tài liệu chính xác là đệ quy, nhưng tôi không chắc chiến lược đệ quy nào được sử dụng), và nó cũng không t làm việc Ngoài ra, tôi nên chỉ ra rằng, bằng cách sử dụng mã nhận xét trong repo_squash_helper.sh, tôi đã lưu tệp gốc mà tập lệnh sed hoạt động và chạy tập lệnh sed để chống lại nó để đảm bảo rằng nó đang làm những gì tôi muốn ( nó là) Một lần nữa, tôi thậm chí không biết tại sao lại có sẽ có một cuộc xung đột, vì vậy nó dường như không quan trọng lắm đến việc sử dụng chiến lược nào. Bất kỳ lời khuyên hoặc cái nhìn sâu sắc sẽ hữu ích, nhưng chủ yếu tôi chỉ muốn làm cho việc nghiền nát này hoạt động.

Cập nhật với thông tin bổ sung từ cuộc thảo luận với Jefromi:

Trước khi làm việc trên kho lưu trữ "thực" khổng lồ của chúng tôi, tôi đã sử dụng các tập lệnh tương tự trên kho lưu trữ thử nghiệm. Đó là một kho lưu trữ rất đơn giản và thử nghiệm đã hoạt động sạch sẽ.

Thông điệp tôi nhận được khi thất bại là:

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

Đây là lựa chọn đầu tiên sau lần cam kết đầu tiên. Chạy git statusmang lại một thư mục làm việc sạch sẽ. Nếu sau đó tôi làm một git rebase --continue, tôi nhận được một tin nhắn rất giống sau một vài lần cam kết. Nếu sau đó tôi làm lại, tôi nhận được một tin nhắn rất giống nhau sau vài chục lần cam kết. Nếu tôi làm điều đó một lần nữa, lần này nó sẽ trải qua khoảng một trăm cam kết và mang lại thông điệp này:

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

Nếu sau đó tôi chạy git status, tôi nhận được:

# 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

Bit "cả hai sửa đổi" nghe có vẻ kỳ lạ đối với tôi, vì đây chỉ là kết quả của một lựa chọn. Cũng đáng lưu ý rằng nếu tôi nhìn vào "xung đột", nó sẽ chuyển thành một dòng duy nhất với một phiên bản bắt đầu bằng ký tự [tab] và một phiên bản khác có bốn khoảng trắng. Điều này nghe có vẻ như là một vấn đề với cách tôi thiết lập tệp cấu hình của mình, nhưng không có gì thuộc loại này. (Tôi đã lưu ý rằng core.ignorecase được đặt thành đúng, nhưng rõ ràng git-clone đã tự động làm điều đó. Tôi không hoàn toàn ngạc nhiên khi xem xét rằng nguồn ban đầu là trên máy Windows.)

Nếu tôi sửa thủ công file_X.cpp, thì nó sẽ thất bại ngay sau đó với một xung đột khác, lần này là giữa một tệp (CMakeLists.txt) mà một phiên bản cho rằng nên tồn tại và một phiên bản cho rằng không nên. Nếu tôi khắc phục xung đột này bằng cách nói rằng tôi muốn tệp này (mà tôi làm), một vài lần cam kết sau tôi sẽ nhận được một xung đột khác (trong cùng tệp này) khi hiện tại có một số thay đổi không tầm thường. Nó vẫn chỉ còn khoảng 25% trong các cuộc xung đột.

Tôi cũng nên chỉ ra, vì điều này có thể rất quan trọng, rằng dự án này đã bắt đầu trong một kho lưu trữ svn. Lịch sử ban đầu rất có thể đã được nhập từ kho svn đó.

Cập nhật số 2:

Trên một vỏ cây (bị ảnh hưởng bởi ý kiến ​​của Jefromi), tôi đã quyết định thay đổi repo_squash.sh của mình thành:

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

Và sau đó, tôi chỉ chấp nhận các mục gốc, như là. Tức là, "rebase" không nên thay đổi một điều. Nó đã kết thúc với cùng một kết quả mô tả trước đó.

Cập nhật số 3:

Ngoài ra, nếu tôi bỏ qua chiến lược và thay thế lệnh cuối cùng bằng:

git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a

Tôi không còn gặp phải vấn đề nổi loạn "không có gì để cam kết", nhưng tôi vẫn còn những mâu thuẫn khác.

Cập nhật với kho đồ chơi tái tạo vấn đề:

test_squash.sh (đây là tệp bạn thực sự chạy):

#========================================================
# 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 (được sử dụng bởi 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

PS: Vâng, tôi biết một số bạn co rúm người lại khi thấy tôi sử dụng emacs làm biên tập viên dự phòng.

PPS: Chúng tôi biết rằng chúng tôi sẽ phải thổi bay tất cả các bản sao của kho lưu trữ hiện có sau cuộc nổi loạn. (Dọc theo dòng "bạn sẽ không khởi động lại một kho lưu trữ sau khi nó được xuất bản".)

PPPS: Bất cứ ai cũng có thể cho tôi biết làm thế nào để thêm một tiền thưởng cho điều này? Tôi không thấy tùy chọn ở bất cứ đâu trên màn hình này cho dù tôi đang ở chế độ chỉnh sửa hay chế độ xem.


Liên quan nhiều hơn các tập lệnh được sử dụng là hành động cố gắng cuối cùng - có vẻ khá chắc chắn là một danh sách các lựa chọn và bí đao xen kẽ, phải không? Và có bất kỳ cam kết hợp nhất trong chi nhánh nổi loạn? (Mặc dù bạn vẫn không sử dụng rebase -p)
Cascabel

Tôi không chắc chắn những gì bạn có nghĩa là bằng cách "hành động cố gắng cuối cùng", nhưng nó chỉ là một danh sách các lựa chọn trộn lẫn và bí, với người cuối cùng 400 hoặc lâu hơn là tất cả các lựa chọn. Không có sự hợp nhất nào trong danh sách đó, mặc dù chính cuộc nổi loạn đang thực hiện việc hợp nhất của chính nó. Theo những gì tôi đã đọc, "rebase -p" không được khuyến nghị với chế độ tương tác (tất nhiên trong trường hợp của tôi không phải là tất cả tương tác đó). Từ kernel.org/pub/software/scm/git/docs/git-rebase.html : "Điều này sử dụng máy móc - tương tác bên trong, nhưng kết hợp nó với tùy chọn --interactive nói chung không phải là một ý tưởng hay"
Ben Hocking

Theo "hành động cố gắng cuối cùng" tôi có nghĩa là danh sách nhặt / bí được chuyển trở lại rebase --interactive- đó là một danh sách các hành động để git thử. Tôi đã hy vọng bạn có thể giảm điều này xuống một quả bí đao gây ra xung đột và tránh tất cả sự phức tạp thêm của các tập lệnh trợ giúp của bạn. Thông tin còn thiếu khác là khi xảy ra xung đột - khi git áp dụng các bản vá để tạo thành quả bí, hoặc khi nó cố gắng di chuyển qua quả bí và áp dụng bản vá tiếp theo? (Và bạn có chắc chắn không có gì xấu xảy ra với loại bùn GIT_EDITOR của mình không? Một phiếu bầu khác cho trường hợp thử nghiệm đơn giản.)
Cascabel

Cảm ơn các ý kiến ​​hữu ích. Tôi đã cập nhật câu hỏi để phản ánh câu trả lời của tôi.
Ben Hocking

2
Trường hợp kho lưu trữ đồ chơi dễ hiểu hơn nhiều so với các tập lệnh của bạn - và cho thấy các tập lệnh không liên quan gì đến nó, thực tế là bạn đang cố gắng chống lại một nhánh có chứa một sự hợp nhất với giải quyết xung đột (một sự hợp nhất xấu xa nhẹ ).
Cascabel

Câu trả lời:


69

Được rồi, tôi đủ tự tin để đưa ra câu trả lời. Có thể sẽ phải chỉnh sửa nó, nhưng tôi tin rằng tôi biết vấn đề của bạn là gì.

Trường hợp kiểm tra repo đồ chơi của bạn có một sự hợp nhất trong đó - tệ hơn, nó có một sự hợp nhất với các xung đột. Và bạn đang nổi loạn trên sự hợp nhất. Không có -p(mà không hoàn toàn làm việc với -i), các hợp nhất được bỏ qua. Điều này có nghĩa là bất cứ điều gì bạn đã làm trong giải quyết xung đột của bạn đều không có khi rebase cố gắng chọn cam kết tiếp theo, vì vậy bản vá của nó có thể không được áp dụng. (Tôi tin rằng điều này được thể hiện dưới dạng xung đột hợp nhất vìgit cherry-pick có thể áp dụng bản vá bằng cách thực hiện hợp nhất ba chiều giữa cam kết ban đầu, cam kết hiện tại và tổ tiên chung.)

Thật không may, như chúng tôi đã lưu ý trong các bình luận, -i-p(bảo toàn sự hợp nhất) không hòa hợp với nhau. Tôi biết rằng chỉnh sửa / viết lại công việc, và sắp xếp lại không. Tuy nhiên, tôi tin rằng nó hoạt động tốt với bí. Điều này không được ghi lại, nhưng nó hoạt động cho các trường hợp thử nghiệm mà tôi mô tả dưới đây. Nếu trường hợp của bạn là cách, cách phức tạp hơn, bạn có thể gặp nhiều rắc rối khi làm những gì bạn muốn, mặc dù điều đó vẫn có thể xảy ra. (Đạo đức của câu chuyện: làm sạch với rebase -i trước khi hợp nhất.)

Vì vậy, giả sử chúng ta có một trường hợp rất đơn giản, trong đó chúng ta muốn kết hợp A, B và C:

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

Bây giờ, như tôi đã nói, nếu không có xung đột trong X, hãy git rebase -i -phoạt động như bạn mong đợi.

Nếu có xung đột, mọi thứ trở nên phức tạp hơn một chút. Nó sẽ thực hiện tốt việc nghiền nát, nhưng sau đó khi nó cố gắng tạo lại sự hợp nhất, các xung đột sẽ lại xảy ra. Bạn sẽ phải giải quyết chúng một lần nữa, thêm chúng vào chỉ mục, sau đó sử dụnggit rebase --continue để tiếp tục. (Tất nhiên, bạn có thể giải quyết chúng một lần nữa bằng cách kiểm tra phiên bản từ cam kết hợp nhất ban đầu.)

Nếu bạn tình cờ đã rereređược kích hoạt trong repo của bạn ( rerere.enabledset là true), đây sẽ là cách dễ dàng hơn - git sẽ có thể tái sử dụng lại ghi chép lại giải pháp từ khi bạn ban đầu đã có những mâu thuẫn, và tất cả những gì bạn phải làm là kiểm tra nó để đảm bảo nó hoạt động đúng, thêm các tệp vào chỉ mục và tiếp tục. (Bạn thậm chí có thể đi xa hơn một bước, bật rerere.autoupdatevà nó sẽ thêm chúng cho bạn, vì vậy việc hợp nhất thậm chí sẽ không thành công). Tuy nhiên, tôi đoán rằng bạn chưa bao giờ kích hoạt lại, vì vậy bạn sẽ phải tự mình giải quyết xung đột. *

* Hoặc, bạn có thể thử rerere-train.shtập lệnh từ git-contrib, cố gắng "Prime [the] cơ sở dữ liệu từ các cam kết hợp nhất hiện có" - về cơ bản, nó kiểm tra tất cả các cam kết hợp nhất, cố gắng hợp nhất chúng và nếu hợp nhất thất bại, nó lấy kết quả và hiển thị chúng git-rerere. Điều này có thể tốn thời gian và tôi chưa bao giờ thực sự sử dụng nó, nhưng nó có thể rất hữu ích.


PS Nhìn lại những bình luận của tôi, tôi thấy tôi nên nắm bắt điều này sớm hơn. Tôi đã hỏi nếu bạn đang từ chối một chi nhánh có chứa các sự hợp nhất và bạn nói rằng không có sự hợp nhất nào trong danh sách rebase tương tác, điều này không giống nhau - không có sự hợp nhất nào ở đó bởi vì bạn không cung cấp -ptùy chọn này.
Cascabel

Tôi chắc chắn sẽ cho điều này đi. Kể từ khi gửi bài này, tôi cũng đã nhận thấy trong một số trường hợp, tôi chỉ đơn giản có thể gõ git commit -a -m"Some message"git rebase --continue, và nó sẽ tiếp tục. Điều này hoạt động ngay cả khi không có -ptùy chọn, nhưng nó hoạt động thậm chí còn tốt hơn với -ptùy chọn (vì tôi không thực hiện bất kỳ đặt hàng lại nào, có vẻ như điều đó -plà tốt). Dù sao, tôi sẽ giữ cho bạn được đăng.
Ben Hocking

Cài đặt git config --global rerere.enabled truegit config --global rerere.autoupdate truetrước khi chạy ví dụ kiểm tra sẽ giải quyết vấn đề chính. Thật thú vị, tuy nhiên, nó không bảo tồn các sự hợp nhất, ngay cả khi chỉ định --preserve-merges. Tuy nhiên, nếu tôi không có các bộ đó, và tôi gõ git commit -a -m"Meaningful commit"git rebase --continuetrong khi chỉ định --preserve-merges, nó sẽ giữ nguyên các kết hợp. Dù sao, cảm ơn vì đã giúp tôi vượt qua vấn đề này!
Ben Hocking

84

Nếu bạn không phiền khi tạo một chi nhánh mới, đây là cách tôi xử lý vấn đề:

Đang trên chính:

# 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
Giải pháp nhanh nhất và hiệu quả :)
IslamTaha

2
Đây là một gợi ý tuyệt vời! Làm việc rất tốt để nhanh chóng củng cố một số cam kết.
philidem

3
Đây là một giải pháp an toàn hơn nhiều. Tôi cũng đã thêm --sash vào hợp nhất. Là: git merge --squash gốc_messy_branch
jezpez

1
Cảm ơn bạn đã cứu ngày và cuộc sống của tôi :) Giải pháp tốt nhất mà mọi người có thể nhận được

Khi bạn làm git diff new_clean_branch..original_messy_branchhọ nên giống nhau? Đối với tôi họ khác nhau.
LeonardChallis

5

Tôi đang tìm kiếm một yêu cầu tương tự, tức là loại bỏ các cam kết liên ngành của ngành phát triển của tôi, tôi đã thấy quy trình này hiệu quả với tôi.
trên chi nhánh làm việc của tôi

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 chúng tôi đã kết nối cam kết mới nhất với cam kết bắt đầu của chi nhánh! Không có vấn đề xung đột hợp nhất! Trong thực tiễn học tập của tôi, tôi đã đi đến kết luận này ở giai đoạn này, Có cách tiếp cận nào tốt hơn cho mục đích này không.


FYI - Tôi chỉ đang thử điều này và thấy rằng việc kiểm tra mybranch-end-commit không giúp tôi xóa được các lỗi xảy ra trong các cam kết trung gian. Vì vậy, nó chỉ kiểm tra các tệp tồn tại trong mybranch-end-commit.
ahains 5/11/2015

1

Xây dựng trên @ hlidka câu trả lời tuyệt vời của ở trên nhằm giảm thiểu sự can thiệp thủ công, tôi muốn thêm một phiên bản bảo tồn bất kỳ cam kết mới nào đối với chủ nhân không có trong nhánh để ép.

Vì tôi tin rằng những thứ này có thể dễ dàng bị mất trong git resetbước trong ví dụ đó.

# 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

Lưu ý rằng -Xvà các tùy chọn chiến lược đã bị bỏ qua khi được sử dụng trong một cuộc nổi loạn tương tác.

Xem cam kết db2b3b820e2b28da268cc88adff076b394392dfe (tháng 7 năm 2013, git 1.8.4+),

Đừng bỏ qua các tùy chọn hợp nhất trong rebase tương tác

Chiến lược hợp nhất và các tùy chọn của nó có thể được chỉ định trong git rebase, nhưng với -- interactive, chúng hoàn toàn bị bỏ qua.

Đã ký tắt: Arnaud Fontaine

Điều đó có nghĩa là -Xvà chiến lược hiện hoạt động với rebase tương tác, cũng như rebase đơn giản và tập lệnh ban đầu của bạn giờ có thể hoạt động tốt hơn.


0

Tôi đang gặp phải một vấn đề đơn giản nhưng tương tự, trong đó tôi có 1) giải quyết xung đột hợp nhất trên một chi nhánh địa phương, 2) tiếp tục làm việc thêm nhiều cam kết nhỏ hơn, 3) muốn nổi loạn và đánh xung đột hợp nhất.

Đối với tôi, git rebase -p -i masterđã làm việc. Nó giữ cam kết giải quyết xung đột ban đầu và cho phép tôi đè bẹp những người khác lên hàng đầu.

Mong rằng sẽ giúp được ai đó!


Tôi vẫn có một vài xung đột để giải quyết thủ công khi xem xét cuộc nổi loạn với -p Phải thừa nhận rằng có rất ít xung đột và chúng chỉ giới hạn ở tệp dự án thay vì tất cả các tệp mã mà tôi đã xung đột trước đó. Rõ ràng "Hợp nhất các giải pháp xung đột hoặc sửa đổi thủ công để hợp nhất các cam kết không được bảo tồn." - stackoverflow.com/a/35714602/727345
JonoB
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.