Câu trả lời:
Sử dụng git rebase
. Đó là lệnh "Take commit (s) và đặt nó trên một lệnh cha (cơ sở)" khác trong Git.
Tuy nhiên, một số điều cần biết:
Vì các SHA cam kết liên quan đến cha mẹ của họ, nên khi bạn thay đổi cha mẹ của một cam kết nhất định, SHA của nó sẽ thay đổi - cũng như các SHA của tất cả các cam kết đi sau nó (gần đây hơn) trong quá trình phát triển.
Nếu bạn đang làm việc với những người khác và bạn đã đẩy cam kết trong câu hỏi công khai đến nơi họ đã kéo nó, sửa đổi cam kết có lẽ là một ý tưởng tồi ™. Điều này là do # 1, và do đó, sự nhầm lẫn dẫn đến các kho lưu trữ của người dùng khác sẽ gặp phải khi cố gắng tìm hiểu điều gì đã xảy ra do SHA của bạn không còn phù hợp với cam kết "tương tự" của họ. (Xem phần "THU HỒI TỪ UPSTREAM REBASE" của trang man được liên kết để biết chi tiết.)
Điều đó nói rằng, nếu bạn hiện đang ở trong một chi nhánh với một số cam kết rằng bạn muốn chuyển đến một phụ huynh mới, thì nó sẽ trông giống như thế này:
git rebase --onto <new-parent> <old-parent>
Điều đó sẽ di chuyển mọi thứ sau khi <old-parent>
trong chi nhánh hiện tại để ngồi trên đầu <new-parent>
thay thế.
--onto
được sử dụng cho! Các tài liệu luôn luôn hoàn toàn tối nghĩa với tôi, ngay cả sau khi đọc câu trả lời này!
git rebase --onto <new-parent> <old-parent>
tôi biết mọi thứ về cách sử dụng rebase --onto
mà mọi câu hỏi và tài liệu khác mà tôi đã đọc cho đến nay đều thất bại.
rebase this commit on that one
, hoặc git rebase --on <new-parent> <commit>
. Sử dụng cha mẹ cũ ở đây không có ý nghĩa với tôi.
git rebase <new-parent>
.
Nếu nó chỉ ra rằng bạn cần tránh việc đánh bại các cam kết tiếp theo (ví dụ vì việc viết lại lịch sử sẽ không thể thực hiện được), thì bạn có thể sử dụng thay thế git (có sẵn trong Git 1.6.5 trở lên).
# …---o---A---o---o---…
#
# …---o---B---b---b---…
#
# We want to transplant B to be "on top of" A.
# The tree of descendants from B (and A) can be arbitrarily complex.
replace_first_parent() {
old_parent=$(git rev-parse --verify "${1}^1") || return 1
new_parent=$(git rev-parse --verify "${2}^0") || return 2
new_commit=$(
git cat-file commit "$1" |
sed -e '1,/^$/s/^parent '"$old_parent"'$/parent '"$new_parent"'/' |
git hash-object -t commit -w --stdin
) || return 3
git replace "$1" "$new_commit"
}
replace_first_parent B A
# …---o---A---o---o---…
# \
# C---b---b---…
#
# C is the replacement for B.
Với sự thay thế ở trên được thiết lập, mọi yêu cầu cho đối tượng B sẽ thực sự trả về đối tượng C. Nội dung của C hoàn toàn giống với nội dung của B ngoại trừ cha mẹ đầu tiên (cùng cha mẹ (ngoại trừ đầu tiên), cùng một cây, cùng một thông điệp cam kết).
Thay thế được kích hoạt theo mặc định, nhưng có thể được bật bằng cách sử dụng --no-replace-objects
tùy chọn để git (trước tên lệnh) hoặc bằng cách đặt GIT_NO_REPLACE_OBJECTS
biến môi trường. Thay thế có thể được chia sẻ bằng cách đẩy refs/replace/*
(ngoài bình thườngrefs/heads/*
).
Nếu bạn không thích cam kết (được thực hiện với sed ở trên), thì bạn có thể tạo cam kết thay thế bằng các lệnh cấp cao hơn:
git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -
Sự khác biệt lớn là trình tự này không truyền bá cha mẹ bổ sung nếu B là một cam kết hợp nhất.
refs/replace/
hệ thống phân cấp ref. Nếu bạn chỉ cần làm một lần, thì bạn có thể thực hiện git push yourremote 'refs/replace/*'
trong kho lưu trữ nguồn và git fetch yourremote 'refs/replace/*:refs/replace/*'
trong kho đích. Nếu bạn cần thực hiện nhiều lần, thì thay vào đó bạn có thể thêm các refspec đó vào một remote.yourremote.push
biến cấu hình (trong kho lưu trữ nguồn) và một remote.yourremote.fetch
biến cấu hình (trong kho đích).
git replace --grafts
. Không chắc chắn khi điều này được thêm vào, nhưng toàn bộ mục đích của nó là tạo ra một sự thay thế giống như cam kết B nhưng với cha mẹ được chỉ định.
Lưu ý rằng việc thay đổi một cam kết trong Git yêu cầu tất cả các cam kết tuân theo nó phải được thay đổi. Điều này không được khuyến khích nếu bạn đã xuất bản phần này của lịch sử và ai đó có thể đã xây dựng công trình của họ trên lịch sử rằng đó là trước khi thay đổi.
Giải pháp thay thế để git rebase
đề cập trong câu trả lời của Amber là sử dụng mô ghép cơ chế (xem định nghĩa của Git ghép trong Git Thuật ngữ và tài liệu hướng dẫn của .git/info/grafts
tập tin trong Git Repository Layout tài liệu) để mẹ thay đổi của một cam kết, kiểm tra rằng nó đã làm điều đúng với một số người xem lịch sử ( gitk
, git log --graph
, v.v.) và sau đó sử dụng git filter-branch
(như được mô tả trong phần "Ví dụ" trên trang chủ của nó) để làm cho nó vĩnh viễn (và sau đó loại bỏ mảnh ghép và tùy ý xóa các giới thiệu ban đầu được sao lưu bởigit filter-branch
, hoặc kho lưu trữ lại):
echo "$ commit-id $ graft-id" >> .git / thông tin / ghép chi nhánh bộ lọc git $ graft-id..IAD
GHI CHÚ !!! Giải pháp này khác với giải pháp rebase ở chỗ git rebase
sẽ thay đổi / ghép thay đổi , trong khi giải pháp dựa trên ghép sẽ chỉ đơn giản là cam kết như vậy , không tính đến sự khác biệt giữa cha mẹ cũ và cha mẹ mới!
git replace
đã ghép các git ghép (giả sử bạn có git 1.6.5 trở lên).
git-replace
+ git-filter-branch
? Trong thử nghiệm của tôi, filter-branch
dường như tôn trọng sự thay thế, đánh bại toàn bộ cây.
git filter-branch
viết lại lịch sử, tôn trọng các mảnh ghép và thay thế (nhưng trong các thay thế lịch sử viết lại sẽ được đưa ra); đẩy sẽ dẫn đến thay đổi không fasf-chuyển tiếp. git replace
tạo ra sự thay thế có thể chuyển nhượng tại chỗ; đẩy sẽ được chuyển tiếp nhanh, nhưng bạn sẽ cần phải đẩy refs/replace
để chuyển thay thế để có lịch sử sửa chữa. HTH
Để làm rõ các câu trả lời trên và không biết xấu hổ cắm kịch bản của riêng tôi:
Nó phụ thuộc vào việc bạn muốn "rebase" hay "reparent". Một cuộc nổi loạn , theo đề xuất của Amber , di chuyển xung quanh khác . Một người ăn năn , theo gợi ý của Jakub và Chris , di chuyển xung quanh những bức ảnh chụp toàn bộ cây. Nếu bạn muốn hối lỗi, tôi khuyên bạn nên sử dụng git reparent
thay vì thực hiện công việc một cách thủ công.
Giả sử bạn có hình ảnh bên trái và bạn muốn nó trông giống như hình ảnh bên phải:
C'
/
A---B---C A---B---C
Cả việc nổi loạn và hối lỗi sẽ tạo ra cùng một bức tranh, nhưng định nghĩa về sự C'
khác biệt. Với git rebase --onto A B
, C'
sẽ không chứa bất kỳ thay đổi được giới thiệu bởi B
. Với git reparent -p A
, C'
sẽ giống hệt với C
(ngoại trừ điều đó B
sẽ không có trong lịch sử).
reparent
kịch bản; nó hoạt động rất đẹp; rất khuyến khích . Tôi cũng khuyên bạn nên tạo branch
nhãn mới và đến checkout
chi nhánh đó trước khi gọi (vì nhãn chi nhánh sẽ di chuyển).
Chắc chắn câu trả lời của @ Jakub đã giúp tôi vài giờ trước khi tôi đang thử chính xác điều tương tự như OP.
Tuy nhiên, git replace --graft
bây giờ là giải pháp dễ dàng hơn về ghép. Ngoài ra, một vấn đề lớn với giải pháp đó là nhánh lọc khiến tôi mất mọi nhánh không được sáp nhập vào nhánh của HEAD. Sau đó, git filter-repo
đã làm công việc hoàn hảo và hoàn hảo.
$ git replace --graft <commit> <new parent commit>
$ git filter-repo --force
Để biết thêm thông tin: kiểm tra phần "Lịch sử ghép lại" trong tài liệu