簡體   English   中英

如何在保留子目錄的同時拆分 git 存儲庫?

[英]How to split a git repository while preserving subdirectories?

我想要的是類似於這個問題 但是,我希望拆分為單獨存儲庫的目錄保留為該存儲庫中的子目錄:

我有這個:

foo/
  .git/
  bar/
  baz/
  qux/

我想把它分成兩個完全獨立的存儲庫:

foo/
  .git/
  bar/
  baz/

quux/
  .git/
  qux/  # Note: still a subdirectory

如何在 git 中做到這一點?

如果有某種方法可以在整個歷史記錄中將所有新存儲庫的內容移動到子目錄中,我可以使用此答案中的方法。

您確實可以使用子目錄過濾器后跟索引過濾器將內容放回子目錄中,但是為什么要麻煩,當您可以單獨使用索引過濾器時呢?

這是手冊頁中的示例:

git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD

這只是刪除一個文件名; 您想要做的是刪除除給定子目錄之外的所有內容。 如果您想保持謹慎,可以明確列出要刪除的每條路徑,但如果您只想全力以赴,則可以執行以下操作:

git filter-branch --index-filter 'git ls-tree -z --name-only --full-tree $GIT_COMMIT | grep -zv "^directory-to-keep$" | xargs -0 git rm --cached -r' -- --all

我希望可能有一種更優雅的方式; 如果有人有什么,請提出建議!

關於該命令的一些說明:

  • filter-branch 在內部將 GIT_COMMIT 設置為當前提交的 SHA1
  • 我不希望--full-tree是必要的,但顯然 filter-branch 從.git-rewrite/t目錄而不是 repo 的頂層運行索引過濾器。
  • grep 可能有點矯枉過正,但我​​認為這不是速度問題。
  • --all將此應用於所有引用; 我想你真的想要那個。 --將它與過濾器分支選項分開)
  • -z-0告訴 ls-tree、grep 和 xargs 使用 NUL 終止來處理文件名中的空格。

編輯,很久以后:Thomas 很有幫助地提出了一種刪除現在為空的提交的方法,但它現在已經過時了。 如果您有舊版本的 git,請查看編輯歷史記錄,但是對於現代 git,您需要做的就是添加此選項:

--prune-empty

這將刪除在應用索引過濾器后所有為空的提交。

我想做一個類似的事情,但由於我想保留的文件列表很長,使用無數的 grep 來做這件事沒有意義。 我寫了一個從文件中讀取文件列表的腳本:

#!/bin/bash

# usage:
# git filter-branch --prune-empty --index-filter \
# 'this-script file-with-list-of-files-to-be-kept' -- --all

if [ -z $1 ]; then
    echo "Too few arguments."
    echo "Please specify an absolute path to the file"
    echo "which contains the list of files that should"
    echo "remain in the repository after filtering."
    exit 1
fi

# save a list of files present in the commit
# which is currently being modified.
git ls-tree -r --name-only --full-tree $GIT_COMMIT > files.txt

# delete all files that shouldn't be removed
while read string; do
    grep -v "$string" files.txt > files.txt.temp
    mv -f files.txt.temp files.txt
done < $1

# remove unwanted files (i.e. everything that remained in the list).
# warning: 'git rm' will exit with non-zero status if it gets
# an invalid (non-existent) filename OR if it gets no arguments.
# If something exits with non-zero status, filter-branch will abort.
# That's why we have to check carefully what is passed to git rm.
if [ "$(cat files.txt)" != "" ]; then
    cat files.txt | \
    # enclose filenames in "" in case they contain spaces
    sed -e 's/^/"/g' -e 's/$/"/g' | \
    xargs git rm --cached --quiet
fi

令人驚訝的是,結果證明這比我最初預期的要多得多,所以我決定把它貼在這里。

當我自己遇到這個問題時,這就是我最終為解決這個問題所做的:

git filter-branch --index-filter \
'git ls-tree --name-only --full-tree $GIT_COMMIT | \
 grep -v "^directory-to-keep$" | \
 sed -e "s/^/\"/g" -e "s/$/\"/g" | \
 xargs git rm --cached -r -f --ignore-unmatch \
' \
--prune-empty -- --all

該解決方案基於 Jefromi 的回答和Detach (move) 子目錄到單獨的 Git 存儲庫中,以及此處關於 SO 的許多評論。

Jefromi 的解決方案對我不起作用的原因是,我的存儲庫中有文件和文件夾,其名稱包含特殊字符(主要是空格)。 此外git rm抱怨不匹配的文件(用--ignore-unmatch解決)。

您可以保持過濾不可知的目錄不在 repo 的根目錄中或被移動:

grep --invert-match "^.*directory-to-keep$"

最后,您可以使用它來過濾掉固定的文件或目錄子集:

egrep --invert-match "^(.*file-or-directory-to-keep-1$|.*file-or-directory-to-keep-2$|…)"

要在之后進行清理,您可以使用以下命令:

$ git reset --hard
$ git show-ref refs/original/* --hash | xargs -n 1 git update-ref -d
$ git reflog expire --expire=now --all
$ git gc --aggressive --prune=now

使用git-filter-repo從 2.25 版開始,這不是 git 的一部分。 這需要 Python3 (>=3.5) 和 git 2.22.0

mkdir new_repoA
mkdir new_repoB
git clone originalRepo newRepoA
git clone originalRepo newRepoB

pushd
cd new_repoA
git filter-repo --path foo/bar --path foo/baz

popd
cd new_repoB 
git filter-repo --path foo/qux

對於包含 ~12000 次提交的存儲庫, git-filter-branch花費了 24 多個小時,而git-filter-repo花費了不到一分鍾。

更清潔的方法:

git filter-branch --index-filter '
                git read-tree --empty
                git reset $GIT_COMMIT path/to/dir
        ' \
        -- --all -- path/to/dir

或者堅持只使用核心命令,在git read-tree --prefix=path/to/dir/ $GIT_COMMIT:path/to/dir進行重置。

在 rev-list args 上指定path/to/dir會盡早進行修剪,使用如此便宜的過濾器並不重要,但無論如何避免浪費精力是件好事。

如果您希望將單個目錄拆分為單獨的 git 存儲庫

git-filter-branch--subdirectory-filter選項,它比前面提到的解決方案簡單得多,只是:

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

此外,它更改路徑並將目錄內容放置在新存儲庫的頂部,而不僅僅是過濾和刪除其他內容。

我將git-filter-repofilename-callback

stephen@B450-AORUS-M:~/source/linux$ git filter-repo --force --filename-callback '
  if b"it87.c" in filename:
    return filename
  else:
    # Keep the filename and do not rename it
    return None
  '
warning: Tag points to object of unexpected type tree, skipping.
warning: Tag points to object of unexpected type tree, skipping.
Parsed 935794 commitswarning: Omitting tag 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c,
since tags of trees (or tags of tags of trees, etc.) are not supported.
warning: Omitting tag 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c,
since tags of trees (or tags of tags of trees, etc.) are not supported.
Parsed 937142 commits
New history written in 177.03 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
HEAD is now at a57e6edb85a3 treewide: Replace GPLv2 boilerplate/reference with SPDX - rule 157
Enumerating objects: 20210, done.
Counting objects: 100% (20210/20210), done.
Delta compression using up to 12 threads
Compressing objects: 100% (17718/17718), done.
Writing objects: 100% (20210/20210), done.
Total 20210 (delta 1841), reused 20038 (delta 1669), pack-reused 0
Completely finished after 179.76 seconds.

它沒有刪除空的合並提交,可能是由於一堆與樹的一側相關聯的標簽。

我嘗試使用投票最多的答案,它似乎沒有刪除任何內容,並且花了很長時間。

Rewrite 3e80e1395bd4f410b79dc0f17113f5b6b409c7d8 (329/937142) (8 seconds passed, remaining 22779 predicted)

22779 秒 = 6.3275 小時

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM