Cách di chuyển tệp từ repit git này sang repo khác (không phải bản sao), lưu giữ lịch sử


484

Các kho Git của chúng tôi bắt đầu như là một phần của kho lưu trữ SVN quái vật duy nhất trong đó các dự án riêng lẻ đều có cây riêng của chúng như vậy:

project1/branches
        /tags
        /trunk
project2/branches
        /tags
        /trunk

Rõ ràng, thật dễ dàng để di chuyển các tập tin từ người này sang người khác svn mv. Nhưng trong Git, mỗi dự án nằm trong kho lưu trữ riêng của nó và hôm nay tôi được yêu cầu chuyển một thư mục con từ project2sang project1. Tôi đã làm một cái gì đó như thế này:

$ git clone project2 
$ cd project2
$ git filter-branch --subdirectory-filter deeply/buried/java/source/directory/A -- --all
$ git remote rm origin  # so I don't accidentally overwrite the repo ;-)
$ mkdir -p deeply/buried/different/java/source/directory/B
$ for f in *.java; do 
>  git mv $f deeply/buried/different/java/source/directory/B
>  done
$ git commit -m "moved files to new subdirectory"
$ cd ..
$
$ git clone project1
$ cd project1
$ git remote add p2 ../project2
$ git fetch p2
$ git branch p2 remotes/p2/master
$ git merge p2 # --allow-unrelated-histories for git 2.9+
$ git remote rm p2
$ git push

Nhưng điều đó có vẻ khá phức tạp. Có cách nào tốt hơn để làm điều này nói chung? Hay tôi đã áp dụng đúng phương pháp?

Lưu ý rằng điều này liên quan đến việc hợp nhất lịch sử vào một kho lưu trữ hiện có, thay vì chỉ đơn giản là tạo một kho lưu trữ độc lập mới từ một phần của kho khác ( như trong một câu hỏi trước đó ).


1
Nghe có vẻ như một cách tiếp cận hợp lý với tôi; Tôi không thể nghĩ ra bất kỳ cách rõ ràng nào để cải thiện đáng kể phương pháp của bạn. Thật tuyệt khi Git thực sự làm điều này dễ dàng ( ví dụ, tôi không muốn di chuyển một thư mục tệp giữa các kho khác nhau trong Subversion).
Greg Hewgill

1
@ebneter - Tôi đã thực hiện việc này (chuyển lịch sử từ repo svn này sang repo khác) bằng cách sử dụng các tập lệnh shell. Về cơ bản tôi đã phát lại lịch sử (diffs, cam kết thông điệp nhật ký) từ các tệp / thư mục cụ thể vào một kho lưu trữ thứ hai.
Adam Monsen

1
Tôi tự hỏi tại sao bạn không làm git fetch p2 && git merge p2thay vì git fetch p2 && git branch .. && git merge p2? Chỉnh sửa: được rồi, có vẻ như bạn muốn nhận được các thay đổi trong một nhánh mới có tên p2, không phải là nhánh hiện tại.
Lekensteyn

1
Có cách nào để ngăn chặn - bộ lọc nhánh phá hủy cấu trúc thư mục không? Bước "git mv" đó dẫn đến một cam kết lớn với đầy đủ các lần xóa tệp và tạo tệp.
Edward Falk

1
Lưu ý rằng theo lịch sử hợp nhất git 2.9 không được phép theo mặc định. Để làm cho nó hoạt động, thêm --allow-unrelated-historiesvào cuối cùng git mergeđể làm cho nó hoạt động.
Scott Berrevoets

Câu trả lời:


55

Yep, đánh vào --subdirectory-filtercủa filter-branchlà chìa khóa. Thực tế là bạn đã sử dụng nó về cơ bản chứng tỏ không có cách nào dễ dàng hơn - bạn không có lựa chọn nào khác ngoài việc viết lại lịch sử, vì bạn muốn kết thúc chỉ với một tập hợp con (được đổi tên) của tệp và điều này theo định nghĩa sẽ thay đổi giá trị băm. Vì không có lệnh nào trong số các lệnh tiêu chuẩn (ví dụ pull) viết lại lịch sử, nên không có cách nào bạn có thể sử dụng chúng để thực hiện điều này.

Tất nhiên, bạn có thể tinh chỉnh các chi tiết - một số nhân bản và phân nhánh của bạn không thực sự cần thiết - nhưng cách tiếp cận tổng thể là tốt! Thật xấu hổ vì nó phức tạp, nhưng tất nhiên, điểm git không phải là để dễ dàng viết lại lịch sử.


1
Điều gì xảy ra nếu tệp của bạn đã di chuyển qua một số thư mục và hiện nằm trong một - bộ lọc thư mục con vẫn hoạt động? (tức là tôi giả sử rằng nếu tôi chỉ muốn di chuyển một tệp, tôi có thể di chuyển nó sang thư mục con của chính nó và nó sẽ hoạt động?)
rogerdpack

1
@rogerdpack: Không, điều này sẽ không theo dõi tệp thông qua đổi tên. Tôi tin rằng nó dường như đã được tạo ra tại thời điểm nó được chuyển vào thư mục con được chọn. Nếu bạn muốn chọn chỉ một tệp, hãy xem --index-filtertrong filter-branchtrang chủ.
Cascabel

8
Có công thức nào về cách tôi có thể làm theo tên?
Night Warrier

Tôi nghĩ rằng duy trì và quản lý lịch sử là một trong những điểm chính của git.
artburkart

288

Nếu lịch sử của bạn là lành mạnh, bạn có thể lấy các cam kết dưới dạng bản vá và áp dụng chúng trong kho lưu trữ mới:

cd repository
git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > patch
cd ../another_repository
git am --committer-date-is-author-date < ../repository/patch 

Hoặc trong một dòng

git log --pretty=email --patch-with-stat --reverse -- path/to/file_or_folder | (cd /path/to/new_repository && git am --committer-date-is-author-date)

(Lấy từ tài liệu của Exherbo )


21
Đối với ba hoặc 4 tệp tôi cần để di chuyển, đây là một giải pháp đơn giản hơn nhiều so với câu trả lời được chấp nhận. Cuối cùng tôi đã cắt xén các đường dẫn trong tệp vá với find-thay thế để làm cho nó phù hợp với cấu trúc thư mục repo mới của tôi.
Rian Sanderson

8
Tôi đã thêm các tùy chọn để các tệp nhị phân (như hình ảnh) cũng được di chuyển đúng cách : git log --pretty=email --patch-with-stat --full-index --binary --reverse -- client > patch. Hoạt động mà không có vấn đề AFAICT.
Emmanuel Touzery

35
Ở bước áp dụng, tôi đã sử dụng --committer-date-is-author-datetùy chọn để lưu giữ ngày cam kết ban đầu thay vì ngày các tệp được di chuyển.
darrenmc

6
cam kết hợp nhất trong lịch sử phá vỡ lệnh "am". Bạn có thể thêm "-m --first-Parent" vào lệnh git log ở trên, sau đó nó hoạt động với tôi.
Gábor Lipták

6
@Daniel Golden Tôi đã cố gắng khắc phục sự cố với các tệp đã bị di chuyển (đó là hậu quả của lỗi git log, do đó nó không hoạt động với cả hai --follow--reversechính xác). Tôi đã sử dụng câu trả lời này và đây là một kịch bản hoàn chỉnh mà tôi sử dụng bây giờ để di chuyển các tệp
tsayen

75

Đã thử nhiều cách tiếp cận khác nhau để di chuyển tệp hoặc thư mục từ kho lưu trữ Git này sang kho khác, cách duy nhất có vẻ hoạt động đáng tin cậy được nêu dưới đây.

Nó liên quan đến việc nhân bản kho lưu trữ mà bạn muốn di chuyển tệp hoặc thư mục từ đó, di chuyển tệp hoặc thư mục đó vào thư mục gốc, viết lại lịch sử Git, nhân bản kho lưu trữ đích và kéo tệp hoặc thư mục có lịch sử trực tiếp vào kho lưu trữ đích này.

Giai đoạn một

  1. Tạo một bản sao của kho A như các bước sau đây tạo ra những thay đổi lớn cho bản sao này mà bạn không nên đẩy!

    git clone --branch <branch> --origin origin --progress \
      -v <git repository A url>
    # eg. git clone --branch master --origin origin --progress \
    #   -v https://username@giturl/scm/projects/myprojects.git
    # (assuming myprojects is the repository you want to copy from)
    
  2. cd vào nó

    cd <git repository A directory>
    #  eg. cd /c/Working/GIT/myprojects
    
  3. Xóa liên kết đến kho lưu trữ ban đầu để tránh vô tình thực hiện bất kỳ thay đổi từ xa nào (ví dụ: bằng cách đẩy)

    git remote rm origin
    
  4. Xem qua lịch sử và tệp của bạn, xóa mọi thứ không có trong thư mục 1. Kết quả là nội dung của thư mục 1 được đưa vào cơ sở của kho A.

    git filter-branch --subdirectory-filter <directory> -- --all
    # eg. git filter-branch --subdirectory-filter subfolder1/subfolder2/FOLDER_TO_KEEP -- --all
    
  5. Đối với chỉ một tệp di chuyển: đi qua những gì còn lại và xóa mọi thứ trừ tệp mong muốn. (Bạn có thể cần xóa các tệp bạn không muốn có cùng tên và cam kết.)

    git filter-branch -f --index-filter \
    'git ls-files -s | grep $'\t'FILE_TO_KEEP$ |
    GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
    git update-index --index-info && \
    mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE || echo "Nothing to do"' --prune-empty -- --all
    # eg. FILE_TO_KEEP = pom.xml to keep only the pom.xml file from FOLDER_TO_KEEP
    

Giai đoạn hai

  1. Bước dọn dẹp

    git reset --hard
    
  2. Bước dọn dẹp

    git gc --aggressive
    
  3. Bước dọn dẹp

    git prune
    

Bạn có thể muốn nhập các tệp này vào kho B trong một thư mục không phải là root:

  1. Tạo thư mục đó

    mkdir <base directory>             eg. mkdir FOLDER_TO_KEEP
    
  2. Di chuyển tập tin vào thư mục đó

    git mv * <base directory>          eg. git mv * FOLDER_TO_KEEP
    
  3. Thêm tệp vào thư mục đó

    git add .
    
  4. Cam kết thay đổi của bạn và chúng tôi đã sẵn sàng để hợp nhất các tệp này vào kho lưu trữ mới

    git commit
    

Giai đoạn ba

  1. Tạo một bản sao của kho B nếu bạn chưa có

    git clone <git repository B url>
    # eg. git clone https://username@giturl/scm/projects/FOLDER_TO_KEEP.git
    

    (giả sử FOLDER_TO_KEEP là tên của kho lưu trữ mới mà bạn đang sao chép)

  2. cd vào nó

    cd <git repository B directory>
    #  eg. cd /c/Working/GIT/FOLDER_TO_KEEP
    
  3. Tạo kết nối từ xa đến kho A như một nhánh trong kho B

    git remote add repo-A-branch <git repository A directory>
    # (repo-A-branch can be anything - it's just an arbitrary name)
    
    # eg. git remote add repo-A-branch /c/Working/GIT/myprojects
    
  4. Kéo từ nhánh này (chỉ chứa thư mục bạn muốn di chuyển) vào kho B.

    git pull repo-A-branch master --allow-unrelated-histories
    

    Việc kéo sao chép cả tập tin và lịch sử. Lưu ý: Bạn có thể sử dụng hợp nhất thay vì kéo, nhưng kéo hoạt động tốt hơn.

  5. Cuối cùng, bạn có thể muốn dọn dẹp một chút bằng cách xóa kết nối từ xa vào kho A

    git remote rm repo-A-branch
    
  6. Đẩy và bạn đã sẵn sàng.

    git push
    

Tôi đã trải qua hầu hết các bước được phác thảo ở đây tuy nhiên dường như chỉ sao chép qua lịch sử cam kết của tệp hoặc thư mục từ chủ (chứ không phải từ bất kỳ chi nhánh nào khác). Có đúng không?
Bảo-Long Nguyễn-Trong

Tôi nghĩ điều đó đúng và bạn sẽ phải trải qua các bước tương tự cho bất kỳ nhánh nào mà bạn muốn di chuyển tệp hoặc thư mục tức là. chuyển sang chi nhánh, vd. MyBranch trong kho A, nhánh lọc, v.v. Sau đó, bạn sẽ "git pull repo-A-nhánh MyBranch" trong kho B.
mcarans 27/1/2015

Cảm ơn vi đa trả lơi. Bạn có biết nếu các thẻ trên các nhánh cũng sẽ được di chuyển không?
Bảo-Long Nguyễn-Trong

Tôi sợ tôi không biết, nhưng sẽ đoán rằng họ sẽ như vậy.
mcarans

1
@mcarans Thật không may, đây không phải là cách đáng tin cậy, mặc dù có vẻ như vậy. Nó bị vấn đề tương tự như tất cả các giải pháp khác - Nó không giữ lại lịch sử đổi tên trong quá khứ. Trong trường hợp của tôi, cam kết đầu tiên là khi tôi đổi tên thư mục / tập tin. Tất cả mọi thứ ngoài đó là mất.
xZero

20

Tôi thấy điều này rất hữu ích. Đó là một cách tiếp cận rất đơn giản, nơi bạn tạo các bản vá được áp dụng cho repo mới. Xem trang được liên kết để biết thêm chi tiết.

Nó chỉ chứa ba bước (được sao chép từ blog):

# Setup a directory to hold the patches
mkdir <patch-directory>

# Create the patches
git format-patch -o <patch-directory> --root /path/to/copy

# Apply the patches in the new repo using a 3 way merge in case of conflicts
# (merges from the other repo are not turned into patches). 
# The 3way can be omitted.
git am --3way <patch-directory>/*.patch

Vấn đề duy nhất tôi gặp phải là tôi không thể áp dụng tất cả các bản vá cùng một lúc

git am --3way <patch-directory>/*.patch

Trong Windows, tôi gặp lỗi UnlimitedArgument. Vì vậy, tôi đã phải áp dụng tất cả các bản vá lần lượt.


Không làm việc cho tôi vì một số thời điểm sha-băm bị mất. Điều này đã giúp tôi: stackoverflow.com/questions/17371150/
Kẻ

Không giống như phương pháp "git log", tùy chọn này hoạt động hoàn hảo với tôi! cảm ơn!
AlejandroVD

1
Đã thử các cách tiếp cận khác nhau để chuyển các dự án sang repo mới. Đây là người duy nhất làm việc cho tôi. Không thể tin rằng một nhiệm vụ chung như vậy phải phức tạp như vậy.
Chris_D_Turk

Cảm ơn đã chia sẻ blog của Ross Hendrickson . Cách tiếp cận này làm việc cho tôi.
Kaushik Acharya

1
Đây là giải pháp rất thanh lịch, tuy nhiên, một lần nữa, nó lại gặp phải vấn đề tương tự như tất cả các giải pháp khác - Nó sẽ KHÔNG giữ lại lịch sử đổi tên trong quá khứ.
xZero

6

GIỮ TÊN GIÁM ĐỐC

Bộ lọc thư mục con (hoặc lệnh git Subree ngắn hơn) hoạt động tốt nhưng không hoạt động với tôi vì chúng xóa tên thư mục khỏi thông tin cam kết. Trong kịch bản của tôi, tôi chỉ muốn hợp nhất các phần của một kho lưu trữ vào một kho lưu trữ khác và giữ lại lịch sử VỚI tên đường dẫn đầy đủ.

Giải pháp của tôi là sử dụng bộ lọc cây và chỉ cần loại bỏ các tệp và thư mục không mong muốn khỏi bản sao tạm thời của kho lưu trữ nguồn, sau đó kéo từ bản sao đó vào kho lưu trữ đích của tôi trong 5 bước đơn giản.

# 1. clone the source
git clone ssh://<user>@<source-repo url>
cd <source-repo>
# 2. remove the stuff we want to exclude
git filter-branch --tree-filter "rm -rf <files to exclude>" --prune-empty HEAD
# 3. move to target repo and create a merge branch (for safety)
cd <path to target-repo>
git checkout -b <merge branch>
# 4. Add the source-repo as remote 
git remote add source-repo <path to source-repo>
# 5. fetch it
git pull source-repo master
# 6. check that you got it right (better safe than sorry, right?)
gitk

Đây kịch bản sẽ không thực hiện bất kỳ thay đổi để repo ban đầu của bạn. Nếu số repo được chỉ định trong tệp bản đồ không tồn tại, thì tập lệnh này sẽ cố gắng tạo nó.
Chetabahana

1
Tôi cũng nghĩ rằng việc giữ nguyên các tên thư mục là vô cùng quan trọng. Nếu không, bạn sẽ nhận được thêm cam kết đổi tên vào kho lưu trữ đích.
ipuustin

6

Một cái tôi luôn sử dụng là ở đây http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-reposeective-preserving-history/ . Đơn giản và nhanh chóng.

Để tuân thủ các tiêu chuẩn stackoverflow, đây là quy trình:

mkdir /tmp/mergepatchs
cd ~/repo/org
export reposrc=myfile.c #or mydir
git format-patch -o /tmp/mergepatchs $(git log $reposrc|grep ^commit|tail -1|awk '{print $2}')^..HEAD $reposrc
cd ~/repo/dest
git am /tmp/mergepatchs/*.patch

5

Câu trả lời này cung cấp các lệnh thú vị dựa trên git amvà được trình bày bằng các ví dụ, từng bước một.

Mục tiêu

  • Bạn muốn di chuyển một số hoặc tất cả các tệp từ kho này sang kho khác.
  • Bạn muốn giữ lịch sử của họ.
  • Nhưng bạn không quan tâm đến việc giữ thẻ và chi nhánh.
  • Bạn chấp nhận lịch sử giới hạn cho các tệp được đổi tên (và các tệp trong thư mục được đổi tên).

Thủ tục

  1. Trích xuất lịch sử ở định dạng email bằng cách sử dụng
    git log --pretty=email -p --reverse --full-index --binary
  2. Sắp xếp lại cây tập tin và cập nhật thay đổi tên tệp trong lịch sử [tùy chọn]
  3. Áp dụng lịch sử mới bằng cách sử dụng git am

1. Trích xuất lịch sử ở định dạng email

Ví dụ: Trích xuất lịch sử của file3, file4file5

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

Làm sạch đích thư mục tạm thời

export historydir=/tmp/mail/dir  # Absolute path
rm -rf "$historydir"             # Caution when cleaning

Làm sạch nguồn repo của bạn

git commit ...           # Commit your working files
rm .gitignore            # Disable gitignore
git clean -n             # Simulate removal
git clean -f             # Remove untracked file
git checkout .gitignore  # Restore gitignore

Trích xuất lịch sử của từng tệp ở định dạng email

cd my_repo/dirB
find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'

Thật không may tùy chọn --followhoặc --find-copies-harderkhông thể được kết hợp với --reverse. Đây là lý do tại sao lịch sử bị cắt khi tập tin được đổi tên (hoặc khi thư mục mẹ được đổi tên).

Sau: Lịch sử tạm thời ở định dạng email

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

2. Sắp xếp lại cây tập tin và cập nhật thay đổi tên tệp trong lịch sử [tùy chọn]

Giả sử bạn muốn di chuyển ba tệp này trong repo khác này (có thể là cùng một repo).

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB              # New tree
│   ├── dirB1         # was subdir
│   │   ├── file33    # was file3
│   │   └── file44    # was file4
│   └── dirB2         # new dir
│        └── file5    # = file5
└── dirH
    └── file77

Do đó, sắp xếp lại các tệp của bạn:

cd /tmp/mail/dir
mkdir     dirB
mv subdir dirB/dirB1
mv dirB/dirB1/file3 dirB/dirB1/file33
mv dirB/dirB1/file4 dirB/dirB1/file44
mkdir    dirB/dirB2
mv file5 dirB/dirB2

Lịch sử tạm thời của bạn là bây giờ:

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

Thay đổi tên tập tin trong lịch sử:

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

Lưu ý: Điều này viết lại lịch sử để phản ánh sự thay đổi của đường dẫn và tên tệp.
      (tức là thay đổi vị trí / tên mới trong repo mới)


3. Áp dụng lịch sử mới

Repo khác của bạn là:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

Áp dụng các cam kết từ các tệp lịch sử tạm thời:

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am 

Repo khác của bạn bây giờ là:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB            ^
│   ├── dirB1       | New files
│   │   ├── file33  | with
│   │   └── file44  | history
│   └── dirB2       | kept
│        └── file5  v
└── dirH
    └── file77

Sử dụng git status để xem số lượng cam kết sẵn sàng được đẩy :-)

Lưu ý: Vì lịch sử đã được viết lại để phản ánh đường dẫn và thay đổi tên tệp:
      (nghĩa là so với vị trí / tên trong repo trước đó)

  • Không cần phải git mvthay đổi vị trí / tên tệp.
  • Không cần phải git log --followtruy cập đầy đủ lịch sử.

Thêm mẹo: Phát hiện các tệp đã đổi tên / di chuyển trong repo của bạn

Để liệt kê các tập tin đã được đổi tên:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

Thêm tùy chỉnh: Bạn có thể hoàn thành lệnh git logbằng các tùy chọn --find-copies-harderhoặc --reverse. Bạn cũng có thể xóa hai cột đầu tiên bằng cách sử dụng cut -f3-và lấy mẫu hoàn chỉnh '{. * =>. *}'.

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'

3

Có một vết ngứa tương tự (chỉ dành cho một số tệp của một kho lưu trữ nhất định) tập lệnh này đã được chứng minh là thực sự hữu ích: git-import

Phiên bản ngắn là nó tạo các tệp vá của tệp hoặc thư mục đã cho ( $object) từ kho lưu trữ hiện có:

cd old_repo
git format-patch --thread -o "$temp" --root -- "$object"

mà sau đó được áp dụng cho một kho lưu trữ mới:

cd new_repo
git am "$temp"/*.patch 

Để biết chi tiết xin vui lòng tra cứu:


2

Thử cái này

cd repo1

Điều này sẽ xóa tất cả các thư mục ngoại trừ những thư mục được đề cập, chỉ lưu giữ lịch sử cho các thư mục này

git filter-branch --index-filter 'git rm --ignore-unmatch --cached -qr -- . && git reset -q $GIT_COMMIT -- dir1/ dir2/ dir3/ ' --prune-empty -- --all

Bây giờ bạn có thể thêm repo mới của bạn trong điều khiển git của bạn và đẩy nó đến đó

git remote remove origin <old-repo>
git remote add origin <new-repo>
git push origin <current-branch>

thêm -fvào ghi đè


CẢNH BÁO: nhánh git-filter có một loạt các vấn đề về việc tạo ra các bản ghi lại lịch sử bị xáo trộn. Nhấn Ctrl-C trước khi tiếp tục hủy bỏ, sau đó sử dụng một công cụ lọc thay thế, chẳng hạn như 'git filter-repo' ( github.com/newren/git-filter-repo ) để thay thế. Xem trang hướng dẫn chi nhánh bộ lọc để biết thêm chi tiết; để xóa cảnh báo này, đặt FILTER_BRANCH_SQUELCH_WARNING = 1.
Colin

1

Sử dụng nguồn cảm hứng từ http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-reposeective-preserving-history/ , tôi đã tạo ra hàm Powershell này để làm tương tự làm việc tuyệt vời cho tôi cho đến nay:

# Migrates the git history of a file or directory from one Git repo to another.
# Start in the root directory of the source repo.
# Also, before running this, I recommended that $destRepoDir be on a new branch that the history will be migrated to.
# Inspired by: http://blog.neutrino.es/2012/git-copy-a-file-or-directory-from-another-repository-preserving-history/
function Migrate-GitHistory
{
    # The file or directory within the current Git repo to migrate.
    param([string] $fileOrDir)
    # Path to the destination repo
    param([string] $destRepoDir)
    # A temp directory to use for storing the patch file (optional)
    param([string] $tempDir = "\temp\migrateGit")

    mkdir $tempDir

    # git log $fileOrDir -- to list commits that will be migrated
    Write-Host "Generating patch files for the history of $fileOrDir ..." -ForegroundColor Cyan
    git format-patch -o $tempDir --root -- $fileOrDir

    cd $destRepoDir
    Write-Host "Applying patch files to restore the history of $fileOrDir ..." -ForegroundColor Cyan
    ls $tempDir -Filter *.patch  `
        | foreach { git am $_.FullName }
}

Cách sử dụng cho ví dụ này:

git clone project2
git clone project1
cd project1
# Create a new branch to migrate to
git checkout -b migrate-from-project2
cd ..\project2
Migrate-GitHistory "deeply\buried\java\source\directory\A" "..\project1"

Sau khi thực hiện xong, bạn có thể sắp xếp lại các tệp trên migrate-from-project2nhánh trước khi hợp nhất nó.


1

Tôi muốn một cái gì đó mạnh mẽ và có thể tái sử dụng (chức năng một lệnh và di chuyển + hoàn tác) vì vậy tôi đã viết tập lệnh bash sau đây. Đã làm việc cho tôi nhiều lần, vì vậy tôi nghĩ tôi muốn chia sẻ nó ở đây.

Nó có thể di chuyển một thư mục tùy ý /path/to/footừ repo1thành /some/other/folder/barđếnrepo2 (đường dẫn thư mục có thể giống hoặc khác nhau, khoảng cách từ thư mục gốc có thể khác nhau).

Vì nó chỉ vượt qua các cam kết chạm vào các tệp trong thư mục đầu vào (không phải trên tất cả các cam kết của repo nguồn), nên nó khá nhanh ngay cả trên các repos nguồn lớn, nếu bạn chỉ trích xuất một thư mục con được lồng sâu mà không được chạm vào trong mỗi cam kết.

Vì những gì nó làm là tạo ra một nhánh mồ côi với tất cả lịch sử của repo cũ và sau đó hợp nhất nó vào ĐẦU, nó thậm chí sẽ hoạt động trong trường hợp xung đột tên tệp (sau đó bạn phải giải quyết hợp nhất vào cuối khóa học) .

Nếu không có xung đột tên tệp, bạn chỉ cần git commit ở cuối để hoàn tất hợp nhất.

Nhược điểm là nó có thể sẽ không theo tên tập tin (bên ngoài REWRITE_FROM thư mục) trong repo nguồn - yêu cầu kéo được chào đón trên GitHub để phù hợp với điều đó.

Liên kết GitHub: git-move-thư mục-giữa-repos-keep-history

#!/bin/bash

# Copy a folder from one git repo to another git repo,
# preserving full history of the folder.

SRC_GIT_REPO='/d/git-experimental/your-old-webapp'
DST_GIT_REPO='/d/git-experimental/your-new-webapp'
SRC_BRANCH_NAME='master'
DST_BRANCH_NAME='import-stuff-from-old-webapp'
# Most likely you want the REWRITE_FROM and REWRITE_TO to have a trailing slash!
REWRITE_FROM='app/src/main/static/'
REWRITE_TO='app/src/main/static/'

verifyPreconditions() {
    #echo 'Checking if SRC_GIT_REPO is a git repo...' &&
      { test -d "${SRC_GIT_REPO}/.git" || { echo "Fatal: SRC_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO is a git repo...' &&
      { test -d "${DST_GIT_REPO}/.git" || { echo "Fatal: DST_GIT_REPO is not a git repo"; exit; } } &&
    #echo 'Checking if REWRITE_FROM is not empty...' &&
      { test -n "${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM is empty"; exit; } } &&
    #echo 'Checking if REWRITE_TO is not empty...' &&
      { test -n "${REWRITE_TO}" || { echo "Fatal: REWRITE_TO is empty"; exit; } } &&
    #echo 'Checking if REWRITE_FROM folder exists in SRC_GIT_REPO' &&
      { test -d "${SRC_GIT_REPO}/${REWRITE_FROM}" || { echo "Fatal: REWRITE_FROM does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if SRC_GIT_REPO has a branch SRC_BRANCH_NAME' &&
      { cd "${SRC_GIT_REPO}"; git rev-parse --verify "${SRC_BRANCH_NAME}" || { echo "Fatal: SRC_BRANCH_NAME does not exist inside SRC_GIT_REPO"; exit; } } &&
    #echo 'Checking if DST_GIT_REPO has a branch DST_BRANCH_NAME' &&
      { cd "${DST_GIT_REPO}"; git rev-parse --verify "${DST_BRANCH_NAME}" || { echo "Fatal: DST_BRANCH_NAME does not exist inside DST_GIT_REPO"; exit; } } &&
    echo '[OK] All preconditions met'
}

# Import folder from one git repo to another git repo, including full history.
#
# Internally, it rewrites the history of the src repo (by creating
# a temporary orphaned branch; isolating all the files from REWRITE_FROM path
# to the root of the repo, commit by commit; and rewriting them again
# to the original path).
#
# Then it creates another temporary branch in the dest repo,
# fetches the commits from the rewritten src repo, and does a merge.
#
# Before any work is done, all the preconditions are verified: all folders
# and branches must exist (except REWRITE_TO folder in dest repo, which
# can exist, but does not have to).
#
# The code should work reasonably on repos with reasonable git history.
# I did not test pathological cases, like folder being created, deleted,
# created again etc. but probably it will work fine in that case too.
#
# In case you realize something went wrong, you should be able to reverse
# the changes by calling `undoImportFolderFromAnotherGitRepo` function.
# However, to be safe, please back up your repos just in case, before running
# the script. `git filter-branch` is a powerful but dangerous command.
importFolderFromAnotherGitRepo(){
    SED_COMMAND='s-\t\"*-\t'${REWRITE_TO}'-'

    verifyPreconditions &&
    cd "${SRC_GIT_REPO}" &&
      echo "Current working directory: ${SRC_GIT_REPO}" &&
      git checkout "${SRC_BRANCH_NAME}" &&
      echo 'Backing up current branch as FILTER_BRANCH_BACKUP' &&
      git branch -f FILTER_BRANCH_BACKUP &&
      SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
      echo "Creating temporary branch '${SRC_BRANCH_NAME_EXPORTED}'..." &&
      git checkout -b "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo 'Rewriting history, step 1/2...' &&
      git filter-branch -f --prune-empty --subdirectory-filter ${REWRITE_FROM} &&
      echo 'Rewriting history, step 2/2...' &&
      git filter-branch -f --index-filter \
       "git ls-files -s | sed \"$SED_COMMAND\" |
        GIT_INDEX_FILE=\$GIT_INDEX_FILE.new git update-index --index-info &&
        mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" HEAD &&
    cd - &&
    cd "${DST_GIT_REPO}" &&
      echo "Current working directory: ${DST_GIT_REPO}" &&
      echo "Adding git remote pointing to SRC_GIT_REPO..." &&
      git remote add old-repo ${SRC_GIT_REPO} &&
      echo "Fetching from SRC_GIT_REPO..." &&
      git fetch old-repo "${SRC_BRANCH_NAME_EXPORTED}" &&
      echo "Checking out DST_BRANCH_NAME..." &&
      git checkout "${DST_BRANCH_NAME}" &&
      echo "Merging SRC_GIT_REPO/" &&
      git merge "old-repo/${SRC_BRANCH_NAME}-exported" --no-commit &&
    cd -
}

# If something didn't work as you'd expect, you can undo, tune the params, and try again
undoImportFolderFromAnotherGitRepo(){
  cd "${SRC_GIT_REPO}" &&
    SRC_BRANCH_NAME_EXPORTED="${SRC_BRANCH_NAME}-exported" &&
    git checkout "${SRC_BRANCH_NAME}" &&
    git branch -D "${SRC_BRANCH_NAME_EXPORTED}" &&
  cd - &&
  cd "${DST_GIT_REPO}" &&
    git remote rm old-repo &&
    git merge --abort
  cd -
}

importFolderFromAnotherGitRepo
#undoImportFolderFromAnotherGitRepo

0

Trong trường hợp của tôi, tôi không cần phải giữ lại repo mà tôi đã di chuyển từ hoặc lưu giữ bất kỳ lịch sử nào trước đó. Tôi đã có một bản vá của cùng một chi nhánh, từ một điều khiển từ xa khác

#Source directory
git remote rm origin
#Target directory
git remote add branch-name-from-old-repo ../source_directory

Trong hai bước đó, tôi đã có thể khiến chi nhánh của repo khác xuất hiện trong cùng một repo.

Cuối cùng, tôi đặt chi nhánh này (mà tôi đã nhập từ repo khác) để theo tuyến chính của repo mục tiêu (để tôi có thể phân biệt chúng một cách chính xác)

git br --set-upstream-to=origin/mainline

Bây giờ nó hành xử như thể nó chỉ là một nhánh khác mà tôi đã chống lại cùng một repo đó.


0

Nếu các đường dẫn cho các tệp được đề cập là giống nhau trong hai repos và bạn muốn mang lại chỉ một tệp hoặc một tập hợp nhỏ các tệp có liên quan, thì một cách dễ dàng để làm điều này là sử dụng git cherry-pick.

Bước đầu tiên là đưa các cam kết từ repo khác vào repo cục bộ của bạn bằng cách sử dụng git fetch <remote-url>. Điều này sẽ để lại FETCH_HEADchỉ vào cam kết đầu từ repo khác; nếu bạn muốn lưu giữ một tham chiếu đến cam kết đó sau khi bạn thực hiện các lần tìm nạp khác, bạn có thể muốn gắn thẻ nó với git tag other-head FETCH_HEAD.

Sau đó, bạn sẽ cần tạo một cam kết ban đầu cho tệp đó (nếu nó không tồn tại) hoặc một cam kết đưa tệp đến trạng thái có thể được vá bằng cam kết đầu tiên từ repo khác mà bạn muốn đưa vào. Bạn có thể có thể thực hiện việc này git cherry-pick <commit-0>nếu được commit-0giới thiệu các tệp bạn muốn hoặc bạn có thể cần xây dựng cam kết 'bằng tay'. Thêm vào-n vào các tùy chọn chọn cherry nếu bạn cần sửa đổi cam kết ban đầu, ví dụ: thả các tệp từ cam kết mà bạn không muốn đưa vào.

Sau đó, bạn có thể tiếp tục git cherry-pickcác cam kết tiếp theo, sử dụng lại -nkhi cần thiết. Trong trường hợp đơn giản nhất (tất cả các cam kết là chính xác những gì bạn muốn và áp dụng sạch sẽ), bạn có thể đưa ra danh sách đầy đủ các cam kết trên dòng lệnh cherry-pick : git cherry-pick <commit-1> <commit-2> <commit-3> ....


0

Điều này trở nên đơn giản hơn bằng cách sử dụng git-filter-repo.

Để di chuyển project2/sub/dirđến project1/sub/dir:

# Create a new repo containing only the subdirectory:
git clone project2 project2_subdir
cd project2_subdir
git filter-repo --force --path sub/dir

# Merge the new repo:
cd ../project1
git remote add project2_subdir ../project2_subdir/
git merge remotes/project2_subdir/master --allow-unrelated-histories
git remote remove project2_subdir

Để cài đặt công cụ chỉ cần: pip3 install git-filter-repo ( thêm chi tiết và tùy chọn trong README )

# Before: (root)
.
|-- project1
|   `-- 3
`-- project2
    |-- 1
    `-- sub
        `-- dir
            `-- 2

# After: (project1)
.
├── 3
└── sub
    └── dir
        └── 2

-2

Phương pháp dưới đây để di chuyển GIT Stash của tôi sang GitLab bằng cách duy trì tất cả các nhánh và bảo tồn lịch sử.

Nhân bản kho lưu trữ cũ sang địa phương.

git clone --bare <STASH-URL>

Tạo một kho lưu trữ trống trong GitLab.

git push --mirror <GitLab-URL>

Ở trên tôi đã thực hiện khi chúng tôi di chuyển mã của mình từ stash sang GitLab và nó hoạt động rất tốt.

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.