簡體   English   中英

如何在保留歷史記錄的同時將 git repo 重新植根到父文件夾?

[英]How do I Re-root a git repo to a parent folder while preserving history?

我在/foo/bar/baz有一個 git repo,有一個很大的提交歷史和多個分支。

我現在希望/foo/qux/foo/bar/baz位於同一個/foo/qux中,這意味着我需要它們都位於以/foo根的/foo 但是,我想保留對/foo/bar/baz所做更改的歷史記錄。

我首先想到了 git format-patch,然后是 apply,但不保留提交消息。

所以,

我需要重新root repo

(1) 到任意更高的祖先目錄 (2) 同時通過使它看起來像我一直在提交到/foo/bar/baz來保留我的提交歷史

您想要的是git filter-branch ,它可以將整個存儲庫移動到子樹中,通過使其看起來好像一直都是這樣來保留歷史記錄。 在使用之前備份您的存儲庫!

這就是魔法。 /foo/bar ,運行:

git filter-branch --commit-filter '
    TREE="$1";
    shift;
    SUBTREE=`echo -e 040000 tree $TREE"\tbar" | git mktree`
    git commit-tree $SUBTREE "$@"' -- --all

這將使/foo/bar存儲庫具有另一個“bar”子目錄,其中包含其整個歷史記錄中的所有內容。 然后你可以將整個 repo 上移到foo級別並向其中添加baz代碼。

更新

好的,這是怎么回事。 提交是指向“樹”的鏈接(將其視為代表整個文件系統子目錄內容的 SHA)加上一些“父”SHA 和一些元數據鏈接作者/消息/等。 git commit-tree命令是將所有這些組合在一起的低級位。 --commit-filter的參數被視為 shell 函數並在過濾過程中代替git commit-tree運行,並且必須像它一樣運行。

我正在做的是獲取第一個參數,要提交的原始樹,並構建一個新的“樹對象”,通過另一個低級 git 命令git mktree表示它位於子文件夾中。 要做到這一點,我必須向它輸入一些看起來像 git 樹的東西,即一組(模式 SP 類型 SP SHA TAB 文件名)行; 因此是 echo 命令。 當我鏈接到真正的commit-tree時, mktree的輸出然后被替換為第一個參數; "$@"是一種完整傳遞所有其他參數的方法,已經用shift去除了第一個參數。 有關信息,請參閱git help mktreegit help commit-tree

因此,如果您需要多個級別,則必須嵌套一些額外級別的樹對象(這未經過測試,但這是一般想法):

git filter-branch --commit-filter '
    TREE="$1"
    shift
    SUBTREE1=`echo -e 040000 tree $TREE"\tbar" | git mktree`
    SUBTREE2=`echo -e 040000 tree $SUBTREE1"\tb" | git mktree`
    SUBTREE3=`echo -e 040000 tree $SUBTREE2"\ta" | git mktree`
    git commit-tree $SUBTREE3 "$@"' -- --all

這應該將實際內容向下移動到a/b/bar (注意相反的順序)。

更新:綜合改進來自下面 Matthew Alpert 的回答。 沒有-- --all這僅適用於當前簽出的分支,但由於問題是詢問整個存儲庫,因此這樣做比逐個分支更有意義。

與其創建新的存儲庫,不如將當前存儲庫中的內容移動到正確的位置:在當前目錄中創建一個新的目錄bar並將當前內容移動到其中(因此您的代碼在/foo/bar/bar )。 然后在新的bar目錄 ( /foo/bar/baz ) 旁邊創建一個baz目錄。 mv /foo /foo2; mv /foo2/bar /foo; rmdir /foo2 mv /foo /foo2; mv /foo2/bar /foo; rmdir /foo2就完成了:)。

Git 的重命名跟蹤意味着您的歷史記錄仍然有效,而 Git 的內容散列意味着即使您移動了內容,您仍然引用存儲庫中的相同對象。

我有一個似乎沒有人說過的解決方案:

我特別需要的是將父目錄中的文件包含在我的存儲庫中(有效地將 repo 向上移動一個目錄)。

我通過以下方式實現了這一目標:

  • 將所有文件(.git 除外)移動到具有相同名稱的新子目錄中。 告訴 git (使用git mv
  • 將所有文件從父目錄移動到現在為空的(除了 .git/)當前目錄並告訴 git (使用git add
  • 將整個內容提交到尚未移動的 repo ( git commit ) 中。
  • 將當前目錄在目錄層次結構中上移一級。 (使用命令行 jiggery-pokery)

我希望這可以幫助下一個人 - 我可能只是度過了一個無腦的一天,但我發現上面的答案過於詳盡和可怕(對於需要的東西。)我知道這類似於上面 Andrew Aylett 的答案,但我的情況似乎有點不同,我想要一個更一般的看法。

最常見的解決方案

在大多數正常情況下,git 會根據其位置(即 .git 目錄)查看所有文件,而不是使用絕對文件路徑。

因此,如果您不介意歷史記錄中的提交表明您已將所有內容向上移動,那么有一個非常簡單的解決方案,即移動 git 目錄。 唯一有點棘手的是確保 git 理解文件是相同的,並且它們只是相對於他移動:

# Create sub-directory with the same name in /foo/bar
mkdir bar

# Move everything down, notifying git :
git mv file1 file2 file3 bar/

# Then move everything up one level :
mv .git ../.git
mv bar/* .
mv .gitignore ../

# Here, take care to move untracked files

# Then delete unused directory
rmdir bar

# and commit
cd ../
git commit

唯一需要注意的是,在移動到新目錄時正確更新 .gitignore,以避免暫存不需要的文件或忘記一些文件。

獎金解決方案

在某些設置中,當 git 看到與已刪除文件完全相同的新文件時,它會設法自行確定文件已被移動。 在這種情況下,解決方案更簡單:

mv .git ../.git
mv .gitignore ../.gitignore

cd ../
git commit

再次,小心你的 .gitignore

這增加了 Walter Mundt 接受的答案。 我寧願評論他的回答,但我沒有聲譽。

所以 Walter Mundt 的方法效果很好,但它一次只對一個分支有效。 在第一個分支之后,可能會出現需要 -f 強制執行操作的警告。 因此,要一次對所有分支執行此操作,只需在末尾添加“---all”:

git filter-branch --commit-filter '
    tree="$1";
    shift;
    subtree=`echo -e 040000 tree $tree"\tsrc" | git mktree`
    git commit-tree $subtree "$@"' -- --all

並且要對特定分支執行此操作,請將它們的名稱添加到末尾,盡管我無法想象您為什么只更改某些分支的目錄結構。

在 git filter-branch 的手冊頁中閱讀有關此內容的更多信息。 但是,請注意使用此類命令后可能會出現推送困難的警告。 只要確保你知道你在做什么。

對於這種方法的任何潛在問題,我很感激。

這特別回答了“我如何將我的 git repo 向上移動一個或多個目錄並使它看起來總是那樣?

隨着git >= 2.22.0 git filter-repo的出現,可以利用git filter-repo來重寫歷史記錄,使其看起來好像該父目錄一直是其中的一部分。

這與@Walter-Mundt 的答案使用git filter-branch完成的事情相同,但更簡單,執行起來也不那么脆弱。

請注意,現在git filter-repogit filter-branch宣傳為更安全的替代方案


因此,鑒於您的存儲庫位於/foo/bar/baz並且您想將其向上移動到/foo

首先,為了防止在重寫歷史記錄時對工作區中的文件進行任何更改,暫時將存儲庫變成所謂的“裸”存儲庫,如下所示:

cd /foo/bar/baz
git config --local --bool core.bare true

實際的歷史重寫現在可以直接在.git目錄中完成:

cd ./.git
git filter-repo --path-rename :bar/baz/

這將重寫 repo 的完整歷史記錄,就好像每條路徑總是預先添加bar/baz/一樣(如果 repo 的根目錄向上兩級,他們就會這樣做)。 此操作不會影響實際文件,因為它現在是一個裸存儲庫。

最后,再次將其打開,將.git目錄移動到指定位置,然后重置:

git config --local --bool core.bare false
cd ..
mv ./.git ../..
cd ../..
git reset

我認為, git reset取消了存儲庫被關閉並再次返回的后遺症。 在執行git reset之前嘗試git status以了解我的意思。

最終的git status現在應該證明一切正常,對/foo/qux一些新的未跟蹤文件進行模數處理。

警告- 如果您在未克隆的存儲庫上嘗試上述操作,則git filter-repo將拒絕發揮其魔力,除非--force它... 准備好備份並考慮自己警告。

1 除了已接受的答案,這幫助我讓它工作:當我將列出的文本放入 shell 腳本時,由於某種原因 -e 被保留了。 (很可能是因為我太厚,無法使用 shell 腳本)

當我刪除 -e 並移動引號以包含所有內容時,它起作用了。 SUBTREE2= echo "040000 tree $SUBTREE1 modules" | git mktree echo "040000 tree $SUBTREE1 modules" | git mktree

請注意, $SUBTREE1 和 modules 之間有一個制表符,它與 -e 應該解釋的 \\t 相同。

您可以在foo創建一個 git repo 並通過git submodules引用bazbar

然后barbaz共存,保留了完整的歷史。


如果你真的只想要一個 repo (foo),其中包含 bar 和 baz 歷史,那么一些移植技術或子樹合並策略是有序的。

暫無
暫無

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

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