lệnh git để tạo một nhánh giống như một nhánh khác


81

Tôi đang cố gắng lấy một nhánh với những thay đổi và đưa nó trở lại giống hệt với phần thượng nguồn mà nó đã tách ra. Các thay đổi đều là cục bộ và đã được đẩy lên github, vì vậy không git resethoặc git rebasethực sự khả thi, vì chúng thay đổi lịch sử, đó là một điều tồi tệ với một nhánh đã được đẩy.

Tôi cũng đã thử git mergevới các chiến lược khác nhau nhưng không có chiến lược nào trong số chúng hoàn tác các thay đổi cục bộ, tức là nếu tôi đã thêm một tệp, việc hợp nhất có thể đưa các tệp khác trở lại dòng, nhưng tôi sẽ vẫn có tệp đó mà ngược dòng không có.

Tôi chỉ có thể tạo một nhánh mới ở phía ngược dòng, nhưng tôi thực sự muốn hợp nhất về mặt lịch sử sửa đổi áp dụng tất cả các thay đổi để lấy nhánh của tôi và làm cho nó giống hệt với nhánh ngược lại, để tôi có thể đẩy thay đổi đó một cách an toàn mà không làm tắc nghẽn lịch sử. Có một lệnh hoặc một loạt lệnh như vậy không?


9
Nếu bạn không quan tâm đến việc bảo toàn các thay đổi, tại sao không xóa và tạo lại nhánh? "Lịch sử của dự án" không cần phải thiêng liêng. Git là một công cụ giúp các nhà phát triển giao tiếp. Nếu những thay đổi này không giúp được điều đó, hãy vứt bỏ chúng.
wnoise

+100 @wnoise - đặc biệt là nếu các thay đổi đã được hợp nhất trong.
wuputah

7
Tôi quan tâm đến việc bảo tồn cả lịch sử vì nó được xuất bản để cộng tác và tôi có thể muốn quay lại nó. Tại sao phải sử dụng kiểm soát sửa đổi nếu bạn chỉ giữ bản mới nhất?
Arne Claassen

1
Đây là một lập luận chủ quan, nhưng đối với tôi, mục đích của một VCS không phải là để ghi lại mọi chi tiết vụn vặt của lịch sử của dự án, mà chỉ để ghi lại những thay đổi đối với nội dung (cam kết), cho phép bạn thao tác dựa trên cây / lịch sử. trên các cam kết đó (phân nhánh, hợp nhất, khôi phục, đặt lại, v.v.) và cho phép bạn xem các báo cáo dựa trên lịch sử (khác biệt, nhật ký, đổ lỗi, v.v.). git là "trình theo dõi nội dung ngu ngốc" - tôi xem nó như một công cụ để quản lý mã nguồn chứ không phải một cỗ máy thời gian.
wuputah

5
Như bạn đã nói, đó là chủ quan. Tôi quan tâm đến việc có thể xem xét các phương pháp tiếp cận đã bị bỏ qua và có thể xem những quyết định nào đã được đưa ra vào một thời điểm nào đó trong quá khứ. Và tôi quan tâm rằng quyết định từ bỏ thứ gì đó của tôi không phá hủy các điểm hợp nhất mà những người khác có thể đang chỉ vào.
Arne Claassen

Câu trả lời:


107

Bạn có thể hợp nhất chi nhánh ngược dòng với chi nhánh của mình dev, với trình điều khiển hợp nhất tùy chỉnh "keepTheirs" :
Xem " " git merge -s theirs"cần thiết - nhưng tôi biết nó không tồn tại ".
Trong trường hợp của bạn, chỉ .gitattributescần một và một keepTheirstập lệnh như:

mv -f $3 $2
exit 0

git merge --strategy=theirs Mô phỏng # 1

Hiển thị dưới dạng hợp nhất, với ngược dòng là nguồn gốc đầu tiên.

Jefromi đề cập (trong các nhận xét) merge -s oursbằng cách hợp nhất công việc của bạn ở thượng nguồn (hoặc trên một nhánh tạm thời bắt đầu từ ngược dòng), rồi chuyển tiếp nhanh nhánh của bạn đến kết quả của việc hợp nhất đó:

git checkout -b tmp origin/upstream
git merge -s ours downstream         # ignoring all changes from downstream
git checkout downstream
git merge tmp                        # fast-forward to tmp HEAD
git branch -D tmp                    # deleting tmp

Điều này có lợi khi ghi lại tổ tiên ngược dòng là gốc đầu tiên, do đó hợp nhất có nghĩa là "tiếp thu nhánh chủ đề lỗi thời này" thay vì "hủy nhánh chủ đề này và thay thế bằng nhánh chủ đề ngược dòng" .

(Chỉnh sửa 2011):

Quy trình làm việc này đã được OP báo cáo trong bài đăng blog này :

Tại sao tôi lại muốn điều này?

Miễn là repo của tôi không liên quan gì đến phiên bản công khai, mọi thứ đều ổn, nhưng vì bây giờ tôi muốn khả năng hợp tác trên WIP với các thành viên khác trong nhóm và những người đóng góp bên ngoài, tôi muốn đảm bảo rằng các nhánh công khai của tôi đáng tin cậy để những người khác phân nhánh và lấy từ, tức là không phải rebase và đặt lại trên những thứ tôi đã đẩy sang bản sao lưu từ xa, vì nó hiện đã có trên GitHub và công khai.

Vì vậy, điều đó để lại cho tôi cách tôi nên tiếp tục.
99% thời gian bản sao của tôi sẽ đi vào bản chủ ngược dòng, vì vậy tôi muốn làm việc với bản sao của mình và đẩy vào phần lớn thời gian.
Nhưng thỉnh thoảng, những gì tôi có trong tay wipsẽ bị vô hiệu bởi những gì đi ngược dòng và tôi sẽ từ bỏ một phần của mình wip.
Tại thời điểm đó, tôi muốn đưa chủ của mình trở lại đồng bộ với ngược dòng, nhưng không hủy bất kỳ điểm cam kết nào trên trang chủ được đẩy công khai của tôi. Tức là tôi muốn hợp nhất với ngược dòng kết thúc với tập thay đổi làm cho bản sao của tôi giống hệt với ngược dòng .
Và đó là điều git merge --strategy=theirsnên làm.


git merge --strategy=theirs Mô phỏng # 2

Hiển thị như một hợp nhất, với của chúng tôi là cha mẹ đầu tiên.

(do jcwenger đề xuất )

git checkout -b tmp upstream
git merge -s ours thebranch         # ignoring all changes from downstream
git checkout downstream
git merge --squash tmp               # apply changes from tmp but not as merge.
git rev-parse upstream > .git/MERGE_HEAD #record upstream 2nd merge head
git commit -m "rebaselined thebranch from upstream" # make the commit.
git branch -D tmp                    # deleting tmp

git merge --strategy=theirs Mô phỏng # 3

Bài đăng trên blog này đề cập đến :

git merge -s ours ref-to-be-merged
git diff --binary ref-to-be-merged | git apply -R --index
git commit -F .git/COMMIT_EDITMSG --amend

đôi khi bạn muốn làm điều này, và không phải vì bạn có "tào lao" trong lịch sử của mình, mà có lẽ vì bạn muốn thay đổi đường cơ sở để phát triển trong một kho lưu trữ công cộng, nơi nên tránh khôi phục .


git merge --strategy=theirs Mô phỏng # 4

(cùng một bài blog)

Ngoài ra, nếu bạn muốn giữ cho các nhánh ngược dòng cục bộ có thể chuyển tiếp nhanh, một thỏa hiệp tiềm năng là làm việc với sự hiểu biết rằng đối với sid / không ổn định, nhánh ngược dòng có thể được đặt lại / khôi phục theo thời gian (dựa trên các sự kiện cuối cùng đã xảy ra kiểm soát của bạn ở phía dự án thượng nguồn).
Đây không phải là một vấn đề lớn và làm việc với giả định đó có nghĩa là rất dễ dàng để giữ cho nhánh thượng nguồn cục bộ ở trạng thái chỉ cần cập nhật nhanh.

git branch -m upstream-unstable upstream-unstable-save
git branch upstream-unstable upstream-remote/master
git merge -s ours upstream-unstable
git diff --binary ref-to-be-merged | git apply -R --index --exclude="debian/*"
git commit -F .git/COMMIT_EDITMSG --amend

git merge --strategy=theirs Mô phỏng # 5

(do Barak A. Pearlmutter đề xuất ):

git checkout MINE
git merge --no-commit -s ours HERS
git rm -rf .
git checkout HERS -- .
git checkout MINE -- debian # or whatever, as appropriate
git gui # edit commit message & click commit button

git merge --strategy=theirs Mô phỏng # 6

(được đề xuất bởi cùng một Michael Gebetsroither ):

Michael Gebetsroither tán tỉnh, cho rằng tôi đã "gian lận";) và đưa ra một giải pháp khác với các lệnh sửa ống nước cấp thấp hơn:

(Sẽ không là git nếu không thể thực hiện được với các lệnh chỉ git, mọi thứ trong git với diff / patch / apply không phải là một giải pháp thực sự;).

# get the contents of another branch
git read-tree -u --reset <ID>
# selectivly merge subdirectories
# e.g superseed upstream source with that from another branch
git merge -s ours --no-commit other_upstream
git read-tree --reset -u other_upstream     # or use --prefix=foo/
git checkout HEAD -- debian/
git checkout HEAD -- .gitignore
git commit -m 'superseed upstream source' -a

git merge --strategy=theirs Mô phỏng # 7

Các bước cần thiết có thể được mô tả như sau:

  1. Thay thế worktree của bạn bằng ngược dòng
  2. Áp dụng các thay đổi cho chỉ mục
  3. Thêm ngược dòng làm nguồn gốc thứ hai
  4. Cam kết

Lệnh git read-treeghi đè chỉ mục bằng một cây khác, hoàn thành bước thứ hai và có các cờ để cập nhật cây công việc, hoàn thành bước đầu tiên . Khi cam kết, git sử dụng SHA1 trong .git / MERGE_HEAD làm cha mẹ thứ hai, vì vậy chúng tôi có thể điền điều này để tạo một cam kết hợp nhất. Do đó, điều này có thể được thực hiện với:

git read-tree -u --reset upstream                 # update files and stage changes
git rev-parse upstream > .git/MERGE_HEAD          # setup merge commit
git commit -m "Merge branch 'upstream' into mine" # commit

7
Bạn luôn có thể sử dụng chi nhánh của chúng tôi thay vì của họ: kiểm tra chi nhánh khác, hợp nhất chi nhánh của bạn vào nó, sau đó chuyển tiếp nhanh của chi nhánh của bạn đến hợp nhất. git checkout upstream; git merge -s ours downstream; git checkout downstream; git merge upstream. (Sử dụng một nhánh tạm thời ở thượng nguồn nếu cần.) Điều này có lợi khi ghi lại tổ tiên ngược dòng là nguồn gốc đầu tiên, do đó hợp nhất có nghĩa là "tiếp thu nhánh chủ đề lỗi thời này" thay vì "hủy nhánh chủ đề này và thay thế nó với ngược dòng ”.
Cascabel

@Jefromi: điểm xuất sắc, như thường lệ. Tôi đã bao gồm nó trong câu trả lời của tôi.
VonC

Một tùy chọn khác - như git merge --strategy = theirs Simulation # 1 - ngoại trừ tùy chọn này bảo toàn brange làm phụ huynh hợp nhất đầu tiên: git checkout -b tmp origin / ngược dòng git merge -s của chúng tôi xuống phía dưới # bỏ qua tất cả các thay đổi từ git xuống dòng thanh toán xuống dòng hợp nhất git --squash tmp # áp dụng các thay đổi từ tmp nhưng không áp dụng dưới dạng hợp nhất. git rev-parse ngược dòng> .git / MERGE_HEAD #record ngược dòng làm đầu hợp nhất thứ hai git commit -m "rebaselined our from up" # make the commit. git branch -D tmp # delete tmp
jcwenger

2
Wow, ai có thể nghĩ rằng --strategy = của họ có thể được thực hiện theo nhiều cách. Bây giờ nếu nó chỉ có thể là trong phiên bản tiếp theo của git
Arne Claassen

VonC và kiến ​​thức của anh ấy thật đáng kinh ngạc. Anh ấy giống như JonSkeet của git. :)
sjas

13

Tôi nghe có vẻ như bạn chỉ cần làm:

$ git reset --hard origin/master

Nếu không có thay đổi nào để đẩy ngược dòng và bạn chỉ muốn nhánh ngược dòng là nhánh hiện tại của bạn, thì điều này sẽ thực hiện điều đó. Không có hại khi thực hiện điều này cục bộ nhưng bạn sẽ mất mọi thay đổi cục bộ ** chưa được chuyển sang chế độ chính.

** Trên thực tế, các thay đổi vẫn còn nếu bạn đã cam kết cục bộ, vì các cam kết sẽ vẫn ở trong của bạn git reflog, thường là trong ít nhất 30 ngày.


điều này hoạt động nếu bạn cần thay đổi đó với bất kỳ giá nào, vì nó có thể thay đổi lịch sử của nhánh (bạn phải đẩy bằng -f). có thể được định cấu hình để bị chặn, vì vậy về cơ bản nó sẽ chỉ hoạt động đối với các kho lưu trữ riêng của bạn, trên thực tế.
ribamar,

12

Bạn có thể làm điều này khá dễ dàng ngay bây giờ:

$ git fetch origin
$ git merge origin/master -s recursive -Xtheirs

Điều này giúp repo cục bộ của bạn đồng bộ với nguồn gốc và lưu giữ lịch sử.


5
git merge -s recursive -Xtheirskhông tự động hợp nhất các tệp nhị phân, vì vậy bạn sẽ gặp phải tình huống xung đột mà bạn phải giải quyết theo cách thủ công. Quy trình làm việc dựa trên git merge -s ourskhông bị điều này.
Stefaan

Điều này xuất hiện để tạo một cam kết trống.
Robin Green

Lời xin lỗi của tôi - nó thực sự hoạt động, nhưng git showtrên một cam kết hợp nhất chỉ hiển thị các giải pháp xung đột và -Xtheirsrõ ràng là không có giải pháp xung đột nào nếu được sử dụng.
Robin Green

5

Một mô phỏng khác cho git merge -s theirs ref-to-be-merged:

git merge --no-ff -s ours ref-to-be-merged         # enforce a merge commit; content is still wrong
git reset --hard HEAD^2; git reset --soft HEAD@{1} # fix the content
git commit --amend

Một giải pháp thay thế cho thiết lập lại kép sẽ được áp dụng bản vá ngược:

git diff --binary ref-to-be-merged | git apply -R --index

Công dụng thú vị của bản vá ngược. +1
VonC

Việc đặt lại không hoạt động đối với tôi, tôi nhận được "đối số không rõ ràng:" HEAD2 ": bản sửa đổi không xác định hoặc đường dẫn không có trong cây làm việc." . (Có, tôi đã gõ HEAD^2) Phương pháp vá lỗi đã hoạt động.
user247702

@Stijn: Nhiều khả năng bạn đã thực sự nhập không ^chính xác. Đôi khi các tổ hợp phím khác như "ctrl-c" được hiển thị dưới dạng "^ C". - Nếu bạn thực sự gõ đúng "^", bạn đã tìm thấy một lỗi nghiêm trọng trong phiên bản git của mình.
michas

@michas Trên Windows, ký tự ^ được sử dụng để thoát, vì vậy để sử dụng nó như một ký tự, bạn phải tự thoát ra khỏi nó. git reset --hard HEAD^^2; git reset --soft HEAD@{1}
Joshua Walsh

4

Cũng có một cách mà không cần đến sự trợ giúp của lệnh sửa ống nước - IMHO là cách đơn giản nhất. Giả sử bạn muốn mô phỏng "của họ" cho trường hợp 2 nhánh:

head1=$(git show --pretty=format:"%H" -s foo)
head2=$(git show --pretty=format:"%H" -s bar)
tree=$(git show --pretty=format:"%T" -s bar)
newhead=$(git commit-tree $tree -p $head1 -p $head2 <<<"merge commit message")
git reset --hard $newhead

Điều này hợp nhất số lượng đầu tùy ý (2 trong ví dụ trên) bằng cách sử dụng cây của một trong số chúng (thanh trong ví dụ trên, cung cấp cây 'của họ'), bỏ qua mọi vấn đề khác biệt / tệp (cây cam kết là lệnh cấp thấp, vì vậy nó không quan tâm đến những). Lưu ý rằng head có thể chỉ là 1 (tương đương với cherry-pick với "của họ").

Lưu ý rằng đầu mẹ nào được chỉ định trước, có thể ảnh hưởng đến một số nội dung (xem ví dụ: - đầu tiên-cha của lệnh git-log) - vì vậy hãy ghi nhớ điều đó.

Thay vì git-show, có thể sử dụng bất kỳ thứ gì khác có khả năng xuất ra các băm cây và cam kết - bất kỳ thứ gì được sử dụng để phân tích cú pháp (cat-file, rev-list, ...). Bạn có thể làm theo mọi thứ với git commit --amend để làm đẹp thông điệp cam kết một cách tương tác.


Đây là điều dễ hiểu nhất trong mắt tôi. Bạn đang sử dụng các lệnh của hệ thống ống nước để nói "Tạo một đối tượng cam kết mới với cây này, cây cha đầu tiên này, cây cha mẹ thứ hai này. Sau đó, trỏ phần đầu đến bản cam kết mới này.", Đó chính xác là những gì chúng ta muốn "git merge -s của họ" làm. Hạn chế là bạn cần lưu 4 băm khác nhau để thao tác này hoạt động.
Michael R

2

Tay nặng, nhưng quái, điều gì có thể xảy ra?

  • Kiểm tra nhánh X bạn muốn trông giống nhánh Y
  • cp -r .git /tmp
  • Kiểm tra chi nhánh Y git checkout y
  • rm -rf .git && cp -r /tmp/.git .
  • Cam kết & thúc đẩy mọi sự khác biệt
  • LÀM XONG.

Đây là cách đơn giản và thô bạo nhất để làm cho hai nhánh giống hệt nhau, giả sử bạn không quan tâm đến việc duy trì lịch sử hợp nhất.
Damon D

1

thay đổi sang nhánh ngược dòng từ xa và thực hiện git mergevới chiến lược hợp nhất được đặt thành ours.

git checkout origin/master
git merge dev --strategy=ours
git commit ...
git push

Tất cả lịch sử sẽ vẫn hiện diện, nhưng bạn sẽ có thêm một cam kết hợp nhất. Điều quan trọng ở đây là bắt đầu từ phiên bản bạn muốn và hợp nhất oursvới github chi nhánh thực sự đang ở.


1
Tôi cần điều ngược lại. Điều đó sẽ đưa nhánh của tôi và tích hợp nó vào thượng nguồn, nhưng không thay đổi đầu ngược dòng. Nhưng tôi cần phải đi ngược dòng và tích hợp nó vào nhánh của tôi, để lại cái đầu của tôi giống như người ngược dòng. Về cơ bản một cái gì đó giống như --strategy=theirs, ngoại trừ gần nhất --strategy=recursive -X=theirskhông hoàn toàn làm điều đó.
Arne Claassen

--strategy=theirslà đối lập với --strategy=ours. Bạn bắt đầu từ đầu đối diện (vì vậy hãy bắt đầu từ github và hợp nhất theo cách khác).
kelloti

không có --strategy=theirs, đó là vấn đề. Điều gần nhất là --strategy=recursive -X theirskhông hoàn toàn ngược lại, vì nó sẽ không loại bỏ các thay đổi cục bộ không liên quan, nếu chúng không xung đột.
Arne Claassen

Hai điều này đối lập nhau: git checkout dev; git merge origin/master --strategy=oursgit checkout origin/master; git merge dev --strategy=ours
wuputah

2
@Arne: Xem bình luận của tôi về câu trả lời của VonC. Sự hiện diện của ourschiến lược giúp bạn hoàn toàn có thể thực hiện một theirschiến lược.
Cascabel

1

Sử dụng git reset BACKWARDS!

Bạn có thể làm cho một nhánh trông giống như bất kỳ cam kết nào khác git reset, nhưng bạn phải thực hiện nó một cách vòng vo.

Để làm cho một nhánh trên cam kết <old>trông giống như một cam kết <new>, bạn có thể làm

git reset --hard <new>

để tạo <new>nội dung của cây làm việc.

Sau đó làm

git reset --mixed <old> 

để thay đổi nhánh trở lại cam kết ban đầu nhưng vẫn để cây làm việc ở <new> trạng thái .

Sau đó, bạn có thể thêm và cam kết các thay đổi, để làm cho chi nhánh của bạn khớp chính xác với nội dung của <new>cam kết.

Nó phản trực quan rằng để chuyển từ <old>trạng thái sang trạng thái <new>bạn cần thực hiện git reset từ <new> đến <old> . Tuy nhiên, với tùy chọn --mixedcây làm việc được giữ nguyên <new>và con trỏ nhánh được đặt thành <old>, do đó khi các thay đổi được thực hiện, nhánh sẽ trông như thế nào chúng ta muốn.

Cảnh báo

Đừng quên các cam kết của bạn, ví dụ như quên những gì <old>đang làm git reset --hard <new>.


0

Tôi đã theo dõi những vai trò đó:

Tìm nạp nguồn gốc, thiết lập lại cứng từ nhánh sau đó đệ quy từ của họ và sau đó buộc đẩy sang nhánh

VỀ RỦI RO CỦA CHÍNH BẠN

git fetch origin
git reset --hard origin/<branch>
git merge origin/<branch> -s recursive -Xtheirs
git push -f <remote> <branch>
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.