Làm thế nào để trích xuất một thư mục con git và tạo một mô-đun con từ nó?


119

Tôi đã bắt đầu một dự án vài tháng trước và lưu trữ mọi thứ trong một thư mục chính. Trong thư mục chính "Dự án" của tôi có một số thư mục con chứa những thứ khác nhau: Dự án / giấy chứa một tài liệu được viết bằng LaTeX Project / sourcecode / RailsApp chứa ứng dụng rails của tôi.

"Dự án" được GITified và đã có rất nhiều cam kết trong cả thư mục "paper" và "RailsApp". Bây giờ, khi tôi muốn sử dụng cruisecontrol.rb cho "RailsApp" của mình, tôi tự hỏi liệu có cách nào để tạo một submodule ra khỏi "RailsApp" mà không làm mất lịch sử hay không.


2
Cũng là một câu trả lời rất tốt: stackoverflow.com/questions/359424/...
Rehno Lindeque

Câu trả lời:


122

Ngày nay, có một cách dễ dàng hơn nhiều so với việc sử dụng git filter-branch: git subtree theo cách thủ công

Cài đặt

LƯU Ý git-subtree hiện là một phần của git(nếu bạn cài đặt đóng góp) kể từ ngày 1.7.11, vì vậy bạn có thể đã cài đặt nó. Bạn có thể kiểm tra bằng cách thực thi git subtree.


Để cài đặt git-subtree từ nguồn (đối với các phiên bản git cũ hơn):

git clone https://github.com/apenwarr/git-subtree.git

cd git-subtree
sudo rsync -a ./git-subtree.sh /usr/local/bin/git-subtree

Hoặc nếu bạn muốn các trang người đàn ông và tất cả

make doc
make install

Sử dụng

Chia một phần lớn hơn thành các phần nhỏ hơn:

# Go into the project root
cd ~/my-project

# Create a branch which only contains commits for the children of 'foo'
git subtree split --prefix=foo --branch=foo-only

# Remove 'foo' from the project
git rm -rf ./foo

# Create a git repo for 'foo' (assuming we already created it on github)
mkdir foo
pushd foo
git init
git remote add origin git@github.com:my-user/new-project.git
git pull ../ foo-only
git push origin -u master
popd

# Add 'foo' as a git submodule to `my-project`
git submodule add git@github.com:my-user/new-project.git foo

Để có tài liệu chi tiết (trang người), vui lòng đọc git-subtree.txt.


10
git cây con đá!
Simon Woodside

3
Nhưng điểm của git-subtree có phải là tránh sử dụng mô-đun con không? Ý tôi là, bạn thực sự là tác giả của git-subtree (trừ khi có xung đột biệt hiệu), nhưng có vẻ như git-subtree đã thay đổi, mặc dù lệnh bạn hiển thị có vẻ vẫn hợp lệ. Tôi hiểu đúng không?
Blaisorblade

17
git-cây con hiện nay là một phần của git (nếu bạn cài đặt contrib) tại ngày 1.7.11
Jeremy

8
Cũng git rm -rf ./fooxóa fookhỏi HEADnhưng không lọc my-projecttoàn bộ lịch sử của. Sau đó, git submodule add git@github.com:my-user/new-project.git foochỉ tạo foomột mô-đun con bắt đầu từ HEAD. Trong khía cạnh đó, kịch bản filter-branchlà vượt trội vì nó cho phép để đạt được "làm như thể nếu subdir là một submodule từ khi bắt đầu"
Gregory Pakosz

thx cho điều này - docs cây con git chỉ là một khó hiểu chút, và điều này là (cho tôi) điều hữu ích rõ ràng nhất mà tôi muốn làm gì với nó ...
hwjp

38

Thanh toán git bộ lọc-nhánh .

Các Examplesphần của người đàn ông show trang làm thế nào để trích xuất một thư mục con vào dự án riêng của nó trong khi vẫn giữ tất cả lịch sử của nó và loại bỏ lịch sử của các file khác / thư mục (chỉ là những gì bạn đang tìm kiếm).

Để viết lại kho lưu trữ để trông giống như thể foodir/là gốc dự án của nó và hủy tất cả lịch sử khác:

   git filter-branch --subdirectory-filter foodir -- --all

Vì vậy, bạn có thể, ví dụ, biến một thư mục con của thư viện thành một kho lưu trữ của riêng nó.
Lưu ý --rằng ngăn cách filter-branchcác tùy chọn khỏi các tùy chọn sửa đổi và --allđể viết lại tất cả các nhánh và thẻ.


1
Điều này làm việc tốt cho tôi. Nhược điểm duy nhất mà tôi nhận thấy là kết quả là một nhánh chính duy nhất với tất cả các cam kết.
aceofspades

@aceofspades: tại sao đó là nhược điểm?
naught101

2
Đối với tôi, toàn bộ điểm của việc trích xuất các cam kết từ git repo là tôi muốn giữ lại lịch sử.
aceofspades

13

Một cách làm điều này là ngược lại - xóa mọi thứ trừ tệp bạn muốn giữ lại.

Về cơ bản, hãy tạo một bản sao của kho lưu trữ, sau đó sử dụnggit filter-branch để xóa mọi thứ trừ tệp / thư mục bạn muốn giữ lại.

Ví dụ: tôi có một dự án mà từ đó tôi muốn giải nén tệp tvnamer.pyvào một kho lưu trữ mới:

git filter-branch --tree-filter 'for f in *; do if [ $f != "tvnamer.py" ]; then rm -rf $f; fi; done' HEAD

Điều đó sử dụng git filter-branch --tree-filterđể đi qua từng cam kết, chạy lệnh và gửi lại nội dung thư mục kết quả. Điều này cực kỳ nguy hiểm (vì vậy bạn chỉ nên thực hiện việc này trên một bản sao của kho lưu trữ của mình!) Và có thể mất một lúc (khoảng 1 phút trên kho lưu trữ có 300 cam kết và khoảng 20 tệp)

Lệnh trên chỉ chạy shell-script sau trên mỗi bản sửa đổi, tất nhiên bạn sẽ phải sửa đổi (để làm cho nó loại trừ thư mục con của bạn thay vì tvnamer.py):

for f in *; do
    if [ $f != "tvnamer.py" ]; then
        rm -rf $f;
    fi;
done

Vấn đề rõ ràng nhất là nó để lại tất cả các thông báo cam kết, ngay cả khi chúng không liên quan đến tệp còn lại. Tập lệnh git-remove-blank-commit , sửa lỗi này ..

git filter-branch --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'

Bạn cần sử dụng -fđối số force chạy filter-branchlại với bất kỳ thứ gì trongrefs/original/ đó (về cơ bản là bản sao lưu)

Tất nhiên điều này sẽ không bao giờ là hoàn hảo, chẳng hạn như nếu tin nhắn cam kết của bạn đề cập đến các tệp khác, nhưng nó gần như cho phép git hiện tại (theo như tôi biết).

Một lần nữa, chỉ chạy điều này trên một bản sao của kho lưu trữ của bạn! - nhưng tóm lại, để xóa tất cả các tệp trừ "thisismyfilename.txt":

git filter-branch --tree-filter 'for f in *; do if [ $f != "thisismyfilename.txt" ]; then rm -rf $f; fi; done' HEAD
git filter-branch -f --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'

4
git filter-branchcó (ngày nay?) một tùy chọn tích hợp để loại bỏ các cam kết trống, cụ thể là --prune-empty. Hướng dẫn tốt hơn để git filter-branchlà câu trả lời cho câu hỏi này: stackoverflow.com/questions/359424/...
Blaisorblade

4

Cả hai CoolAJ86apenwarr câu trả lời là rất giống nhau. Tôi quay đi quay lại giữa hai phần để cố gắng hiểu các bit còn thiếu ở một trong hai. Dưới đây là sự kết hợp của chúng.

Đầu tiên, điều hướng Git Bash đến thư mục gốc của git repo sẽ được chia nhỏ. Trong ví dụ của tôi đây là~/Documents/OriginalRepo (master)

# move the folder at prefix to a new branch
git subtree split --prefix=SubFolderName/FolderToBeNewRepo --branch=to-be-new-repo

# create a new repository out of the newly made branch
mkdir ~/Documents/NewRepo
pushd ~/Documents/NewRepo
git init
git pull ~/Documents/OriginalRepo to-be-new-repo

# upload the new repository to a place that should be referenced for submodules
git remote add origin git@github.com:myUsername/newRepo.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./SubFolderName/FolderToBeNewRepo
git submodule add git@github.com:myUsername/newRepo.git SubFolderName/FolderToBeNewRepo
git branch --delete --force to-be-new-repo

Dưới đây là bản sao ở trên với các tên có thể tùy chỉnh được thay thế và sử dụng https thay thế. Thư mục gốc bây giờ là~/Documents/_Shawn/UnityProjects/SoProject (master)

# move the folder at prefix to a new branch
git subtree split --prefix=Assets/SoArchitecture --branch=so-package

# create a new repository out of the newly made branch
mkdir ~/Documents/_Shawn/UnityProjects/SoArchitecture
pushd ~/Documents/_Shawn/UnityProjects/SoArchitecture
git init
git pull ~/Documents/_Shawn/UnityProjects/SoProject so-package

# upload the new repository to a place that should be referenced for submodules
git remote add origin https://github.com/Feddas/SoArchitecture.git
git push -u origin master
popd

# replace the folder with a submodule
git rm -rf ./Assets/SoArchitecture
git submodule add https://github.com/Feddas/SoArchitecture.git
git branch --delete --force so-package

3

Nếu bạn muốn chuyển một số tập hợp con của các tệp sang một kho lưu trữ mới nhưng vẫn giữ lịch sử, về cơ bản bạn sẽ kết thúc với một lịch sử hoàn toàn mới. Cách thức hoạt động cơ bản như sau:

  1. Tạo kho lưu trữ mới.
  2. Đối với mỗi bản sửa đổi của kho lưu trữ cũ của bạn, hãy hợp nhất các thay đổi đối với mô-đun của bạn vào kho lưu trữ mới. Điều này sẽ tạo một "bản sao" của lịch sử dự án hiện có của bạn.

Sẽ hơi đơn giản để tự động hóa điều này nếu bạn không ngại viết một script nhỏ nhưng đầy đủ. Thẳng thắn, có, nhưng cũng đau đớn. Mọi người đã viết lại lịch sử trong Git trong quá khứ, bạn có thể tìm kiếm điều đó.

Ngoài ra: sao chép kho lưu trữ và xóa giấy trong bản sao, xóa ứng dụng trong bản gốc. Quá trình này sẽ mất một phút, nó đảm bảo hoạt động và bạn có thể quay lại những việc quan trọng hơn là cố gắng xóa lịch sử git của mình. Và đừng lo lắng về dung lượng ổ cứng bị chiếm dụng bởi các bản sao lịch sử dư thừa.

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.