Hợp nhất kho lưu trữ git trong thư mục con


83

Tôi muốn hợp nhất một kho lưu trữ git từ xa trong kho lưu trữ git đang làm việc của mình dưới dạng một thư mục con của nó. Tôi muốn kho lưu trữ kết quả chứa lịch sử đã hợp nhất của hai kho lưu trữ và mỗi tệp của kho lưu trữ được hợp nhất sẽ giữ lại lịch sử của nó như trong kho lưu trữ từ xa. Tôi đã thử sử dụng chiến lược cây con như đã đề cập trong Cách sử dụng chiến lược hợp nhất cây con , nhưng sau khi làm theo quy trình đó, mặc dù kho lưu trữ kết quả thực sự chứa lịch sử hợp nhất của hai kho lưu trữ, các tệp riêng lẻ đến từ kho lưu trữ từ xa đã không giữ lại lịch sử của chúng (`git log 'trên bất kỳ mục nào trong số họ chỉ hiển thị thông báo" Chi nhánh đã hợp nhất ... ").

Ngoài ra, tôi không muốn sử dụng mô-đun con vì tôi không muốn hai kho lưu trữ git kết hợp riêng biệt nữa.

Có thể hợp nhất một kho lưu trữ git từ xa vào một kho lưu trữ khác dưới dạng một thư mục con với các tệp riêng lẻ đến từ kho lưu trữ từ xa có lưu giữ lịch sử của chúng không?

Cảm ơn rất nhiều sự giúp đỡ nào.

CHỈNH SỬA: Tôi hiện đang thử một giải pháp sử dụng git filter-branch để viết lại lịch sử kho lưu trữ đã hợp nhất. Nó dường như hoạt động, nhưng tôi cần phải kiểm tra nó một số nữa. Tôi sẽ trở lại để báo cáo về những phát hiện của tôi.

CHỈNH SỬA 2: Với hy vọng làm cho bản thân rõ ràng hơn, tôi đưa ra các lệnh chính xác mà tôi đã sử dụng với chiến lược cây con của git, dẫn đến mất lịch sử rõ ràng của các tệp của kho lưu trữ từ xa. Đặt A là git repo mà tôi hiện đang làm việc và B là git repo mà tôi muốn kết hợp vào A như một thư mục con của nó. Nó đã làm như sau:

git remote add -f B <url-of-B>
git merge -s ours --no-commit B/master
git read-tree --prefix=subdir/Iwant/to/put/B/in/ -u B/master
git commit -m "Merge B as subdirectory in subdir/Iwant/to/put/B/in."

Sau các lệnh này và đi vào thư mục subir / Iwant / to / put / B / in, tôi thấy tất cả các tệp của B, nhưng git logtrên bất kỳ tệp nào trong số đó chỉ hiển thị thông báo cam kết "Hợp nhất B làm thư mục con trong subir / Iwant / to / put /Thùng rác." Lịch sử tệp của họ như ở B đã bị mất.

Những gì có vẻ hoạt động (vì tôi là người mới bắt đầu sử dụng git, tôi có thể sai) là những điều sau:

git remote add -f B <url-of-B>
git checkout -b B_branch B/master  # make a local branch following B's master
git filter-branch --index-filter \ 
   'git ls-files -s | sed "s-\t\"*-&subdir/Iwant/to/put/B/in/-" |
        GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD 
git checkout master
git merge B_branch

Lệnh trên cho nhánh bộ lọc được lấy từ git help filter-branch, trong đó tôi chỉ thay đổi đường dẫn phụ.


Nói gì gitkvề lịch sử? Tôi đã sử dụng hợp nhất cây con git thành công trong quá khứ. Có lẽ bạn có thể tiết lộ các lệnh chính xác của bạn? Tôi không chắc git-filter-branch là cách tiếp cận phù hợp. Tôi có thể khuyên bạn nên thử git-fast-export và git-fast-import để tổng hợp lịch sử mới.
Seth Robertson

Sau khi thực hiện thủ tục cây con gitkcho thấy hai đại diện được hợp nhất trên các mẹo của chúng và không liên quan đến các cam kết ban đầu của chúng. (Sẽ hữu ích nếu tôi đăng ảnh chụp màn hình của chế độ xem lịch sử của gitk? Tôi có thể không?) Rất tiếc, các tệp riêng lẻ của kho lưu trữ từ xa đã không giữ lại lịch sử của chúng nếu tôi làm trong thiết bị đầu cuối git log <file-from-remote-repo>. Tôi nhìn vào git-fast-exportgit-fast-import; Tôi rất mới với git. Tôi sẽ chỉnh sửa câu hỏi của mình để hiển thị chính xác những lệnh nào tôi đã sử dụng với cây con git. Cảm ơn rất nhiều vì trả lời của bạn.
christosc

@christosc: phương pháp thứ hai của bạn hoạt động tốt và rất đơn giản, Cảm ơn rất nhiều! Tôi chỉ phải thay đổi subir / Iwant / thành / put / B / in / và biến nó thành một dòng (vì msysgit trên Windows dường như không hỗ trợ trả về dòng trong các lệnh với): git filter-branch --index-filter 'git ls-tệp -s | sed "s- \ t \" * - & subir / Iwant / to / put / B / in / - "| GIT_INDEX_FILE = $ GIT_INDEX_FILE.new git update-index --index-info && mv" $ GIT_INDEX_FILE.new "" $ GIT_INDEX_FILE "'HEAD
hào nhoáng

@ user1121352 Rất vui khi được giúp đỡ bạn.
christosc

Tôi thường làm theo câu trả lời này: stackoverflow.com/a/1684694/207791
Victor Sergienko

Câu trả lời:


37

Sau khi nhận được lời giải thích đầy đủ hơn về những gì đang xảy ra, tôi nghĩ rằng tôi hiểu nó và trong mọi trường hợp, tôi có một giải pháp khác. Cụ thể, tôi tin rằng những gì đang xảy ra là phát hiện đổi tên đang bị đánh lừa bởi hợp nhất cây con với --prefix. Đây là trường hợp thử nghiệm của tôi:

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA
cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB
cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
git read-tree --prefix=bdir -u B/master
git commit -m "subtree merge B into bdir"
cd bdir
echo BBB>>B
git commit -a -m BBB

Chúng tôi tạo thư mục git a và b với một số cam kết mỗi thư mục. Chúng tôi thực hiện hợp nhất cây con và sau đó chúng tôi thực hiện cam kết cuối cùng trong cây con mới.

Chạy gitk(theo z / a) cho thấy lịch sử có xuất hiện, chúng ta có thể xem. Đang chạy git logcho thấy rằng lịch sử xuất hiện. Tuy nhiên, việc xem xét một tệp cụ thể có một vấn đề: git log bdir/B

Chà, có một mẹo mà chúng ta có thể chơi. Chúng ta có thể xem lịch sử đổi tên trước của một tệp cụ thể bằng cách sử dụng --follow. git log --follow -- B. Điều này là tốt nhưng không tuyệt vời vì nó không liên kết được lịch sử của việc hợp nhất trước với sau hợp nhất.

Tôi đã thử chơi với -M và -C, nhưng tôi không thể làm cho nó theo một tệp cụ thể.

Vì vậy, giải pháp, tôi cảm thấy, là nói với git về việc đổi tên sẽ diễn ra như một phần của hợp nhất cây con. Thật không may, git-read-tree khá kén chọn hợp nhất cây con nên chúng tôi phải làm việc thông qua một thư mục tạm thời, nhưng điều đó có thể biến mất trước khi chúng tôi cam kết. Sau đó, chúng ta có thể xem toàn bộ lịch sử.

Trước tiên, hãy tạo một kho lưu trữ "A" và thực hiện một số cam kết:

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA

Thứ hai, tạo một kho lưu trữ "B" và thực hiện một số cam kết:

cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB

Và mẹo để thực hiện điều này : buộc Git nhận ra tên đổi bằng cách tạo một thư mục con và chuyển nội dung vào đó.

mkdir bdir
git mv B bdir
git commit -a -m bdir-rename

Quay lại kho lưu trữ "A" và tìm nạp và hợp nhất nội dung của "B":

cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
# According to Alex Brown and pjvandehaar, newer versions of git need --allow-unrelated-histories
# git merge -s ours --allow-unrelated-histories --no-commit B/master
git read-tree --prefix= -u B/master
git commit -m "subtree merge B into bdir"

Để cho thấy rằng chúng hiện đã được hợp nhất:

cd bdir
echo BBB>>B
git commit -a -m BBB

Để chứng minh toàn bộ lịch sử được lưu giữ trong một chuỗi kết nối:

git log --follow B

Chúng tôi nhận được lịch sử sau khi thực hiện việc này, nhưng vấn đề là nếu bạn thực sự giữ repo "b" cũ xung quanh và thỉnh thoảng hợp nhất từ ​​đó (giả sử nó thực sự là repo được duy trì riêng biệt của bên thứ ba) thì bạn đang gặp rắc rối vì bên thứ ba đó sẽ không thực hiện đổi tên. Bạn phải cố gắng kết hợp các thay đổi mới vào phiên bản b của bạn với tên đổi lại và tôi e rằng điều đó sẽ không suôn sẻ. Nhưng nếu b bỏ đi, bạn thắng.


Quả thực điều đó hoạt động @Seth! Và tôi không phải dùng đến việc viết lại lịch sử như với nhánh bộ lọc, điều này tạo ra một lịch sử hơi lừa dối (ví dụ như trong khi xem git log --stat). Ngoài ra, tôi đã không nhận thấy sự --followchuyển đổi trong tài liệu của git log; có vẻ rất tiện dụng với việc đổi tên. Cảm ơn bạn rất nhiều vì câu trả lời rất chi tiết và thông tin của bạn!
christosc

2
Phản hồi này sẽ hữu ích hơn nhiều nếu mã mẫu được chia thành các dòng có thể đọc được thay vì một dòng lót được phân tách bằng dấu chấm phẩy. ;)
jwadsack

Tôi muốn hợp nhất "b" thành "a" và giữ nguyên lịch sử của nó. Làm thế nào tôi có thể làm điều đó?
emeraldhieu

3
Xem stackoverflow.com/questions/37937984/… để biết bản sửa lỗi
Alex Brown,

1
Như @AlexBrown đã đề cập, trên các phiên bản mới của gitđiều này tạo ra fatal: refusing to merge unrelated historiesvà vì vậy bạn phải chạy git merge -s ours --allow-unrelated-histories --no-commit B/masterthay thế.
pjvandehaar

61

git-subtreelà một tập lệnh được thiết kế cho chính xác trường hợp sử dụng này nhằm hợp nhất nhiều kho lưu trữ thành một trong khi vẫn bảo toàn lịch sử (và / hoặc tách lịch sử của các cây con, mặc dù điều đó dường như không liên quan đến câu hỏi này). Nó được phân phối như một phần của cây git kể từ bản phát hành 1.7.11 .

Để hợp nhất một kho lưu trữ <repo>tại bản sửa đổi <rev>dưới dạng thư mục con <prefix>, hãy sử dụng git subtree addnhư sau:

git subtree add -P <prefix> <repo> <rev>

git-subtree triển khai chiến lược hợp nhất cây con theo cách thân thiện hơn với người dùng.

Các nhược điểm là trong lịch sử sáp nhập các tập tin là không tiền tố (không phải trong một thư mục con). Giả sử bạn hợp nhất kho lưu trữ avào b. Kết quả là git log a/f1sẽ hiển thị cho bạn tất cả các thay đổi (nếu có) ngoại trừ những thay đổi trong lịch sử đã hợp nhất. Bạn có thể làm:

git log --follow -- f1

nhưng điều đó sẽ không hiển thị các thay đổi khác sau đó trong lịch sử hợp nhất.

Nói cách khác, nếu bạn không thay đổi acác tệp trong kho lưu trữ b, thì bạn cần chỉ định --followvà một đường dẫn chưa được định sẵn. Nếu bạn thay đổi chúng trong cả hai kho, thì bạn có 2 lệnh, không lệnh nào hiển thị tất cả các thay đổi.

Thêm về nó ở đây .


Đẹp! Đây chính xác là những gì tôi cần trong một dòng. Cảm ơn, tương lai!
iameli

Đây là giải pháp hoàn hảo để hợp nhất một kho lưu trữ khác vào kho lưu trữ của tôi theo hướng phụ.
eitch

1
Lưu ý rằng điều này sẽ không hoạt động với các thư mục con hiện có tại <prefix>. Ví dụ: để hợp nhất một thư mục con đã được di chuyển theo cách thủ công vào kho lưu trữ của chính nó khi nào đó và bạn muốn hợp nhất nó trở lại.
Richard Kiefer

6

Tôi muốn

  1. giữ lịch sử tuyến tính mà không có hợp nhất rõ ràng và
  2. làm cho nó trông giống như các tệp của kho lưu trữ đã hợp nhất luôn tồn tại trong thư mục con và do một tác dụng phụ làm cho nó git log -- filehoạt động mà không cần --follow.

Bước 1 : Viết lại lịch sử trong kho lưu trữ nguồn để làm cho nó trông giống như tất cả các tệp luôn tồn tại bên dưới thư mục con.

Tạo một nhánh tạm thời cho lịch sử được viết lại.

git checkout -b tmp_subdir

Sau đó, sử dụng git filter-branchnhư được mô tả trong Làm cách nào để viết lại lịch sử để tất cả các tệp, ngoại trừ những tệp tôi đã di chuyển, đều nằm trong một thư mục con? :

git filter-branch --prune-empty --tree-filter '
if [ ! -e foo/bar ]; then
    mkdir -p foo/bar
    git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files foo/bar
fi'

Bước 2 : Chuyển sang kho mục tiêu. Thêm kho lưu trữ nguồn làm từ xa trong kho lưu trữ đích và tìm nạp nội dung của nó.

git remote add sourcerepo .../path/to/sourcerepo
git fetch sourcerepo

Bước 3 : Sử dụng merge --ontođể thêm các cam kết của kho lưu trữ nguồn được viết lại trên đầu của kho lưu trữ mục tiêu.

git rebase --preserve-merges --onto master --root sourcerepo/tmp_subdir

Bạn có thể kiểm tra nhật ký để thấy rằng điều này thực sự mang lại cho bạn những gì bạn muốn.

git log --stat

Bước 4 : Sau khi rebase, bạn đang ở trạng thái "HEAD tách rời". Bạn có thể tua nhanh cái chủ sang cái đầu mới.

git checkout -b tmp_merged
git checkout master
git merge tmp_merged
git branch -d tmp_merged

Bước 5 : Cuối cùng một số dọn dẹp: Xóa điều khiển từ xa tạm thời.

git remote rm sourcerepo

git rebasedường như không cho phép các tùy chọn được chỉ định cùng nhau: "error: không thể kết hợp các tùy chọn tương tác (- tương tác, --exec, --rebase-merges, --preserve-merge, --keep-blank, --root + - -onto) với các tùy chọn sáng (--committer-date-is-author-date) "
Sam

Hấp dẫn! Cố gắng thả --committer-date-is-author-date. Kiểm tra các tùy chọn không tương thích đã được thêm gần đây trong git v2.19.0 ( github.com/git/git/commit/… ). Từ mô tả, nó có vẻ như --committer-date-is-author-dateđã bị bỏ qua một cách im lặng.
hfs 19/09/18

Thay vì sử dụng filter-branchlệnh cũ , hãy sử dụng git filter-repo --to-subdirectory-filter <dir>, cách này nhanh hơn và dễ dàng hơn.
Willem

5

Nếu bạn thực sự muốn ghép mọi thứ lại với nhau, hãy tìm cách ghép. Bạn cũng nên sử dụng git rebase --preserve-merges --onto. Ngoài ra còn có một tùy chọn để giữ ngày tác giả cho thông tin người cam kết.


@adymitruk Cảm ơn bạn đã trả lời. Tôi thực sự mới sử dụng git, vì vậy tôi sẽ xem xét giải pháp bạn đề xuất. Tôi đã thử git filter-branchvà nó có vẻ hiệu quả, nhưng có lẽ của bạn tốt hơn. Tôi se thử no.
christosc

@adymitruk Tôi có thể sử dụng rebase với hai kho lưu trữ không liên quan đến nhau như các chi nhánh không? Ý tôi là hai kho Tôi muốn hợp nhất có cam kết ban đầu không phổ biến ...
christosc

Cảm ơn @adymitruk. Tôi không chắc liệu việc phục hồi có thể được thực hiện với hai kho lưu trữ không liên quan hay không. Nó chắc chắn sẽ hữu ích ...
christosc

Nhưng đừng sợ bộ lọc-nhánh. Nó đã cứu chúng ta nhiều lần. Chỉ cần tạo một chi nhánh khác trước và bạn luôn có thể quay lại. Điều đó, hoặc sử dụng bản ghi lại.
Adam Dymitruk

Tôi hiểu rồi… Trong mọi trường hợp, tốt hơn hết tôi nên đọc tài liệu về các khái niệm và lệnh git này. Chỉ có nhưng ít kinh nghiệm về VCS, cụ thể là svn, tôi thực sự bị choáng ngợp bởi git. Sức mạnh của nó mặc dù có vẻ là đáng giá.
christosc

4

Tôi tìm thấy giải pháp sau đây khả thi đối với tôi. Đầu tiên, tôi đi vào dự án B, tạo một nhánh mới, trong đó tất cả các tệp sẽ được chuyển đến thư mục con mới. Sau đó, tôi đẩy nhánh mới này về nguồn gốc. Tiếp theo, tôi đi đến dự án A, thêm và tìm nạp điều khiển từ xa của B, sau đó tôi kiểm tra nhánh đã chuyển, tôi quay lại chế độ chính và hợp nhất:

# in local copy of project B
git checkout -b prepare_move
mkdir subdir
git mv <files_to_move> subdir/
git commit -m 'move files to subdir'
git push origin prepare_move

# in local copy of project A
git remote add -f B_origin <remote-url>
git checkout -b from_B B_origin/prepare_move
git checkout master
git merge from_B

Nếu tôi vào thư mục phụ subdir, tôi có thể sử dụng git log --followvà vẫn có lịch sử.

Tôi không phải là một chuyên gia về git, vì vậy tôi không thể bình luận liệu đây có phải là một giải pháp đặc biệt tốt hay nó có những lưu ý hay không, nhưng cho đến nay có vẻ như tất cả đều ổn.


Mọi người dường như tán thành cách tiếp cận này ở đây: stackoverflow.com/questions/1683531/…
nacross

3

Bạn đã thử thêm kho lưu trữ bổ sung dưới dạng mô-đun con git chưa? Nó sẽ không hợp nhất lịch sử với kho chứa, trên thực tế, nó sẽ là một kho lưu trữ độc lập.

Tôi đề cập đến nó, bởi vì bạn chưa.


1
Cảm ơn câu trả lời của Abizern. Trên thực tế, tôi muốn hai lịch sử kho lưu trữ được hợp nhất thành một; Tôi không muốn chúng tách biệt nữa, đó là lý do tại sao tôi không đề cập đến các mô-đun con.
christosc

0

Giả sử bạn muốn hợp nhất kho lưu trữ avào b(tôi cho rằng chúng nằm cùng với nhau):

cd a
git filter-repo --to-subdirectory-filter a
cd ..
cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

Đối với điều này bạn không cần git-filter-repocài đặt ( filter-branchđược khuyến khích ).

Ví dụ về việc hợp nhất 2 kho lưu trữ lớn, đặt một trong số chúng vào thư mục con: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

Thêm về nó ở đây .

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.