簡體   English   中英

GIT 拆分存儲庫目錄保留*移動/重命名*歷史

[英]GIT Split Repository directory preserving *move / renames* history

假設您有存儲庫:

myCode/megaProject/moduleA
myCode/megaProject/moduleB

隨着時間的推移(幾個月),您重新組織項目。 重構代碼使模塊獨立。 megaProject 目錄中的文件被移動到它們自己的目錄中。 強調移動- 這些文件的歷史被保留。

myCode/megaProject
myCode/moduleA
myCode/moduleB

現在您希望將這些模塊移動到它們自己的 GIT 存儲庫中。 只留下 megaProject 的原件。

myCode/megaProject
newRepoA/moduleA
newRepoB/moduleB

filter-branch命令被記錄為執行此操作,但當文件移出目標目錄時它不會遵循歷史記錄。 所以當文件被移動到他們的新目錄時,歷史就開始了,而不是文件在舊的 megaProject 目錄中的歷史。

如何根據目標目錄拆分 GIT 歷史記錄,並遵循此路徑之外的歷史記錄 - 僅留下與這些文件相關的提交歷史記錄而不留下其他任何內容?

關於 SO 的許多其他答案都集中在通常將回購分開 - 但沒有提到分開並遵循移動歷史。

這是一個基於@rksawyer 腳本的版本,但它使用git-filter-repo代替。 我發現它比 git-filter-branch(現在被 git 推薦作為替代品)更容易使用並且更快。

# This script should run in the same folder as the project folder is.
# This script uses git-filter-repo (https://github.com/newren/git-filter-repo).
# The list of files and folders that you want to keep should be named <your_repo_folder_name>_KEEP.txt. I should contain a line end in the last line, otherwise the last file/folder will be skipped.
# The result will be the folder called <your_repo_folder_name>_REWRITE_CLONE. Your original repo won't be changed.
# Tags are not preserved, see line below to preserve tags.
# Running subsequent times will backup the last run in <your_repo_folder_name>_REWRITE_CLONE_BKP.

# Define here the name of the folder containing the repo: 
GIT_REPO="git-test-orig"

clone="$GIT_REPO"_REWRITE_CLONE
temp=/tmp/git_rewrite_temp
rm -Rf "$clone"_BKP
mv "$clone" "$clone"_BKP
rm -Rf "$temp"
mkdir "$temp"
git clone "$GIT_REPO" "$clone"
cd "$clone"
git remote remove origin
open .
open "$temp"

# Comment line below to preserve tags
git tag | xargs git tag -d

echo 'Start logging file history...'
echo "# git log results:\n" > "$temp"/log.txt

while read p
do
    shopt -s dotglob
    find "$p" -type f > "$temp"/temp
    while read f
    do
        echo "## " "$f" >> "$temp"/log.txt
        # print every file and follow to get any previous renames
        # Then remove blank lines.  Then remove every other line to end up with the list of filenames       
        git log --pretty=format:'%H' --name-only --follow -- "$f" | awk 'NF > 0' | awk 'NR%2==0' | tee -a "$temp"/log.txt
        
        echo "\n\n" >> "$temp"/log.txt
    done < "$temp"/temp
done < ../"$GIT_REPO"_KEEP.txt > "$temp"/PRESERVE

mv "$temp"/PRESERVE "$temp"/PRESERVE_full
awk '!a[$0]++' "$temp"/PRESERVE_full > "$temp"/PRESERVE

sort -o "$temp"/PRESERVE "$temp"/PRESERVE

echo 'Starting filter-branch --------------------------'
git filter-repo --paths-from-file "$temp"/PRESERVE --force --replace-refs delete-no-add
echo 'Finished filter-branch --------------------------'

它將git log的結果記錄到/tmp/git_rewrite_temp/log.txt中的一個文件中,因此如果您不需要 log.txt 並希望它運行得更快,您可以去掉這些行。

在克隆的存儲庫中運行git filter-branch --subdirectory-filter將刪除所有不影響該子目錄中內容的提交,其中包括那些影響文件移動之前的提交。

相反,您需要使用帶有腳本的--index-filter標志來刪除您不感興趣的所有文件,並使用--prune-empty標志來忽略任何影響其他內容的提交。

Kevin Deldycke 的博客文章中有一個很好的例子:

git filter-branch --prune-empty --tree-filter 'find ./ -maxdepth 1 -not -path "./e107*" -and -not -path "./wordpress-e107*" -and -not -path "./.git" -and -not -path "./" -print -exec rm -rf "{}" \;' -- --all

此命令有效地依次檢查每個提交,從工作目錄中刪除所有無用的文件,如果自上次提交以來有任何更改,則將其簽入(重寫歷史記錄)。 您需要調整該命令以刪除所有文件,例如/moduleA/megaProject/moduleA中的文件以及您希望從/megaProject中保留的特定文件。

我知道沒有簡單的方法可以做到這一點,但是可以做到。

filter-branch的問題在於它的工作原理

在每個修訂版上應用自定義過濾器

如果您可以創建一個不會刪除您的文件的過濾器,它們將在目錄之間進行跟蹤。 當然,對於任何不平凡的存儲庫來說,這可能都是不平凡的。

首先:讓我們假設它是一個普通的存儲庫。 您從未重命名過文件,也從未在兩個模塊中擁有同名文件。 您需要做的就是獲取模塊中的文件列表find megaProject/moduleA -type f -printf "%f\n" > preserve然后使用這些文件名和您的目錄運行過濾器:

保存.sh

cmd="find . -type f ! -name d1"
while read f; do
  cmd="$cmd ! -name $f"
done < /path/to/myCode/preserve
for i in $($cmd)
do
  rm $i
done

git filter-branch --prune-empty --tree-filter '/path/to/myCode/preserve.sh' HEAD

當然,重命名使這變得困難。 git filter-branch做的一件好事是給你$GIT_COMMIT環境變量。 然后你可以花哨並使用類似的東西:

for f in megaProject/moduleA
do
 git log --pretty=format:'%H' --name-only --follow -- $f |  awk '{ if($0 != ""){ printf $0 ":"; next; } print; }'
done > preserve

通過提交構建一個文件名歷史記錄,可以用來代替簡單示例中的簡單preserve文件,但是您有責任跟蹤每次提交時應該出現哪些文件。 這實際上不應該太難編碼,但我還沒有看到有人這樣做過。

繼續上面的答案。 首先遍歷使用 git log --follow 保留的目錄中的所有文件,以 git 之前移動/重命名的舊路徑/名稱。 然后使用 filter-branch 遍歷每個修訂,刪除不在步驟 1 中創建的列表中的任何文件。

#!/bin/bash
DIRNAME=dirD

# Catch all files including hidden files
shopt -s dotglob
for f in $DIRNAME/*
do
# print every file and follow to get any previous renames
# Then remove blank lines.  Then remove every other line to end up with the list of filenames
 git log --pretty=format:'%H' --name-only --follow -- $f | awk 'NF > 0' | awk 'NR%2==0'
done > /tmp/PRESERVE

sort -o /tmp/PRESERVE /tmp/PRESERVE
cat /tmp/PRESERVE

然后創建一個腳本 (preserve.sh),filter-branch 將調用每個修訂版。

#!/bin/bash
DIRNAME=dirD

# Delete everything that's not in the PRESERVE list
echo 'delete this files:'
cmd=`find . -type f -not -path './.git/*' -not -path './$DIRNAME/*'`
echo $cmd > /tmp/ALL


# Convert to one filename per line and remove the lead ./
cat /tmp/ALL | awk '{NF++;while(NF-->1)print $NF}' | cut -c3- > /tmp/ALL2
sort -o /tmp/ALL2 /tmp/ALL2

#echo 'before:'
#cat /tmp/ALL2

comm -23 /tmp/ALL2 /tmp/PRESERVE > /tmp/DELETE_THESE
echo 'delete these:'
cat /tmp/DELETE_THESE
#exit 0

while read f; do
  rm $f
done < /tmp/DELETE_THESE

現在使用 filter-branch,如果在修訂版中刪除了所有文件,則修剪該提交及其消息。

 git filter-branch --prune-empty --tree-filter '/FULL_PATH/preserve.sh' master

這是我為 linux/wsl 編寫的@Roberto 發布的腳本版本。 如果您不指定“myrepo_KEEP.txt”,它將根據當前文件結構創建一個。 傳遞 repo 以處理:

prune.sh MyRepo

# This script should run one level up from the git repo folder (i.e. the  containing folder)
# This script uses git-filter-repo (github.com/newren/git-filter-repo).
# The result will be the folder called <your_repo_folder_name>_REWRITE_CLONE. Your original repo won't be changed.
# Tags are not preserved, see line below to preserve tags.
# Running subsequent times will backup the last run in <your_repo_folder_name>_REWRITE_CLONE_BKP.
# Optionally, list the files and folders that you want to keep the KEEP_FILE (<your_repo_folder_name>_KEEP.txt) 
## It should contain a line end in the last line, otherwise the last file/folder will be skipped.
## If this file is missing it will be created by this script with all current folders listed. 

echo "Prune git repo"

# User needs to pass in the repo name
GIT_REPO=$1

if [ -z $GIT_REPO ]; then
    echo "Pass in the directory to prune"
else
    KEEP_FILE="${GIT_REPO}"_KEEP.txt

    # Build up a list of current directories in the repo, if one hasn't been supplied
    if [ ! -f "${KEEP_FILE}" ]; then
        echo "Keeping all current files in repo (generating keep file)"
        cd $GIT_REPO
        find . -type d -not -path '*/\.*' > "../${KEEP_FILE}"
        cd ..
    fi

    echo "Pruning $GIT_REPO"

    clone="${GIT_REPO}_REWRITE_CLONE"
    
    # Shift backup
    bkp="${clone}_BKP"
    temp=/tmp/git_rewrite_temp
    echo $clone
    rm -Rf "$bkp"
    mv "$clone" "$bkp"
    
    # Setup temp
    rm -Rf "$temp"
    mkdir "$temp"   
    
    # Clone
    echo "Cloning repo...from $GIT_REPO to $clone"
    if git clone "$GIT_REPO" "$clone"; then
        cd "$clone"
        git remote remove origin

        # Comment line below to preserve tags
        git tag | xargs git tag -d

        echo 'Start logging file history...'
        echo "# git log results:\n" > "$temp"/log.txt

        # Follow the renames
        while read p
        do
            shopt -s dotglob
            find "$p" -type f > "$temp"/temp
            while read f
            do
                echo "## " "$f" >> "$temp"/log.txt
                # print every file and follow to get any previous renames
                # Then remove blank lines.  Then remove every other line to end up with the list of filenames       
                git log --pretty=format:'%H' --name-only --follow -- "$f" | awk 'NF > 0' | awk 'NR%2==0' | tee -a "$temp"/log.txt

                echo "\n\n" >> "$temp"/log.txt
            done < "$temp"/temp
        done < ../"${KEEP_FILE}" > "$temp"/PRESERVE

        mv "$temp"/PRESERVE "$temp"/PRESERVE_full
        awk '!a[$0]++' "$temp"/PRESERVE_full > "$temp"/PRESERVE

        sort -o "$temp"/PRESERVE "$temp"/PRESERVE

        echo 'Starting filter-branch --------------------------'
        git filter-repo --paths-from-file "$temp"/PRESERVE --force --replace-refs delete-no-add
        echo 'Finished filter-branch --------------------------'
        cd ..
    fi
fi

感謝@rksawyer 和@Roberto。

我們將自己描繪成一個更糟糕的角落,數十個分支中有數十個項目,每個項目都依賴 1-4 個其他項目,總共有 56k 次提交。 filter-branch 最多需要 24 小時才能拆分單個目錄。

我最終使用 libgit2sharp 和原始文件系統訪問在 .NET 中編寫了一個工具,以便為每個項目拆分任意數量的目錄,並且只保留新存儲庫中每個項目的相關提交/分支/標簽。 它沒有修改源回購,而是寫出 N 個其他回購,僅包含配置的路徑/參考。

歡迎您查看這是否適合您的需求,修改它等。 https://github.com/CurseStaff/GitSplit

暫無
暫無

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

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