簡體   English   中英

是否可以在 Git 中移動/重命名文件並維護其歷史記錄?

[英]Is it possible to move/rename files in Git and maintain their history?

我想重命名/移動 Git 中的項目子樹,將其從

/project/xyz

/components/xyz

如果我使用普通的git mv project components ,那么xyz project所有提交歷史都會丟失。 有沒有辦法移動它以保持歷史?

Git 檢測重命名而不是通過提交來持久化操作,因此使用git mv還是mv並不重要。

log命令接受一個--follow參數,該參數在重命名操作之前繼續歷史,即,它使用啟發式搜索類似的內容:

http://git-scm.com/docs/git-log

要查找完整歷史記錄,請使用以下命令:

git log --follow ./path/to/file

不。

簡短的回答是否定的 在 Git 中重命名文件並記住歷史記錄是不可能的。 這是一種痛苦。

有傳言說git log --follow --find-copies-harder會起作用,但它對我不起作用,即使文件內容的更改為零,並且已使用git mv進行了移動。

(最初我使用 Eclipse 在一個操作中重命名和更新包,這可能讓 Git 感到困惑。但這是一件非常常見的事情。如果只執行mv然后執行commitmv --follow似乎確實有效不會太遠。)

Linus 說你應該全面了解軟件項目的全部內容,而不需要跟蹤單個文件。 好吧,可悲的是,我的小腦袋無法做到這一點。

真的很煩,這么多人漫不經心地重復了 Git 自動跟蹤移動的說法。 他們浪費了我的時間。 Git 不做這樣的事情。 按照設計(!)Git 根本不跟蹤移動。

我的解決方案是將文件重命名回其原始位置。 更改軟件以適應源代碼管理。 使用 Git,您似乎只需要第一次就正確地“git”。

不幸的是,這破壞了 Eclipse,它似乎使用了--follow git log --follow有時不會顯示具有復雜重命名歷史的文件的完整歷史記錄,即使git log (我不知道為什么。)

(有一些太聰明的 hack 會返回並重新提交舊工作,但它們相當可怕。請參閱 GitHub-Gist: emiller/git-mv-with-history 。)

簡而言之:如果Subversion 這樣做是錯誤的,那么 Git 這樣做也是錯誤的 - 這樣做不是某些(錯誤!)功能,而是一個錯誤。

可以重命名文件並保持歷史記錄不變,盡管這會導致文件在存儲庫的整個歷史記錄中被重命名。 這可能僅適用於痴迷 git-log-lovers,並且有一些嚴重的影響,包括:

  • 您可能正在重寫共享歷史記錄,這是使用 Git 時最重要的“不要”。 如果其他人已經克隆了存儲庫,您將通過這樣做來破壞它。 他們將不得不重新克隆以避免頭痛。 如果重命名足夠重要,這可能沒問題,但是您需要仔細考慮這一點 - 您最終可能會擾亂整個開源社區!
  • 如果您在存儲庫歷史記錄中較早使用舊名稱引用了該文件,那么您實際上是在破壞早期版本。 為了解決這個問題,你必須多做一些箍跳。 這並非不可能,只是乏味且可能不值得。

現在,既然你還和我在一起,你可能是一個重命名一個完全隔離的文件的獨立開發者。 讓我們使用filter-tree移動文件!

假設您要將old文件移動到文件夾dir並將其命名為new

這可以用git mv old dir/new && git add -u dir/new ,但這打破了歷史。

反而:

git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD

重做分支中的每個提交,在每次迭代的刻度中執行命令。 當你這樣做時,很多事情都會出錯。 我通常會測試該文件是否存在(否則它尚未移動),然后執行必要的步驟以根據我的喜好硬拔樹。 在這里,您可以通過文件 sed 來更改對文件的引用等。 把自己打昏! :)

完成后,文件被移動並且日志完好無損。 你感覺像一個忍者海盜。

還; 當然,只有將文件移動到新文件夾時才需要 mkdir 目錄。 if將避免在歷史記錄中創建此文件夾早於您的文件存在。

git log --follow [file]

將通過重命名向您展示歷史。

我願意:

git mv {old} {new}
git add -u {new}

我想重命名/移動 Git 中的項目子樹,將其從

/project/xyz

/組件/xyz

如果我使用普通的git mv project components ,那么xyz項目的所有提交歷史都會丟失。

否(8 年后,Git 2.19,2018 年第三季度),因為 Git 會檢測目錄 rename ,現在有更好的文檔記錄。

請參閱提交 b00bf1c提交 1634688提交 0661e49提交 4d34dff提交 983f464提交 c840e1a提交 9929430 (2018 年 6 月 27 日),以及提交 d4e8065 2018 年 6 月 2018 年 6 月 2018 日cdrenja 提交 d4e8062新人 18 cdrenja 2 提交 18 cdren newren
(由Junio C gitster合並-- gitster -- in commit 0ce5a69 ,2018 年 7 月 24 日)

現在在Documentation/technical/directory-rename-detection.txt

例子:

當所有x/ax/bx/c都移動到z/az/bz/c ,很可能同時添加的x/d也希望移動到z/d提示整個目錄“ x ”移動到“ z ”。

但它們還有許多其他情況,例如:

歷史的一側重命名x -> z ,另一側將一些文件重命名為x/e ,導致合並需要進行傳遞重命名。

為了簡化目錄重命名檢測,這些規則由 Git 強制執行:

目錄重命名檢測適用時的幾個基本規則限制:

  1. 如果給定目錄在合並的兩側仍然存在,我們不認為它已被重命名。
  2. 如果要重命名的文件的子集有文件或目錄妨礙(或將相互妨礙),“關閉”這些特定子路徑的目錄重命名並向用戶報告沖突.
  3. 如果歷史的另一側將目錄重命名為您的歷史側重命名的路徑,則忽略來自歷史另一側的特定重命名以進行任何隱式目錄重命名(但警告用戶)。

您可以在t/t6043-merge-rename-directories.sh看到很多測試,其中也指出:

  • a) 如果重命名將目錄拆分為兩個或多個其他目錄,重命名次數最多的目錄“獲勝”。
  • b) 避免對路徑進行目錄重命名檢測,如果該路徑是合並任一側的重命名源。
  • c) 僅當歷史的另一側是進行重命名的人時,才對目錄應用隱式目錄重命名。

是的

  1. 您可以使用git log --pretty=email將文件的提交歷史轉換為電子郵件補丁
  2. 您在新目錄中重新組織這些文件並重命名它們
  3. 您可以使用git am將這些文件(電子郵件)轉換回 Git 提交以保留歷史記錄。

局限性

  • 不保留標簽和分支
  • 歷史在路徑文件重命名(目錄重命名)時被剪切

用例子一步一步解釋

1.以電子郵件格式提取歷史記錄

示例:提取file3file4file5歷史記錄

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

設置/清理目的地

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

以電子郵件格式提取每個文件的歷史記錄

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"' {} ';'

不幸的是,選項--follow--find-copies-harder不能與--reverse結合使用。 這就是為什么在重命名文件時(或重命名父目錄時)歷史會被刪除的原因。

電子郵件格式的臨時歷史記錄:

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

Dan Bonachea建議在第一步中反轉 git log 生成命令的循環:不是每個文件運行 git log 一次,而是在命令行上使用文件列表運行一次,並生成一個統一的日志。 這種方式修改多個文件的提交在結果中保持單個提交,並且所有新提交保持其原始相對順序。 請注意,在(現在統一的)日志中重寫文件名時,這也需要在下面的第二步中進行更改。


2.重新組織文件樹並更新文件名

假設您想在另一個倉庫中移動這三個文件(可以是同一個倉庫)。

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

因此重新組織您的文件:

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

您的臨時歷史記錄現在是:

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

更改歷史記錄中的文件名:

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

3. 應用新歷史

你的另一個回購是:

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

從臨時歷史文件中應用提交:

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

--committer-date-is-author-date保留原始提交時間戳( Dan Bonachea的評論)。

你的另一個回購現在是:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB
│   ├── dirB1
│   │   ├── file33
│   │   └── file44
│   └── dirB2
│        └── file5
└── dirH
    └── file77

使用git status查看准備推送的提交量:-)


額外技巧:檢查倉庫中重命名/移動的文件

要列出已重命名的文件:

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

更多自定義:您可以使用選項--find-copies-harder --reverse--reverse完成命令git log 您還可以使用cut -f3-cut -f3- complete pattern '{.* => .*}'刪除前兩列。

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

我遇到了“在不丟失歷史記錄的情況下重命名文件夾”的問題 要修復它,請運行:

$ git mv oldfolder temp && git mv temp newfolder
$ git commit
$ git push

重命名目錄或文件(我對復雜情況了解不多,所以可能會有一些警告):

git filter-repo --path-rename OLD_NAME:NEW_NAME

在提到它的文件中重命名目錄(可以使用回調,但我不知道如何):

git filter-repo --replace-text expressions.txt

expressions.txt是一個文件,里面充滿了像literal:OLD_NAME==>NEW_NAME這樣的行(可以將Python 的RE 與regex:或glob 與glob: )。

要重命名提交消息中的目錄:

git-filter-repo --message-callback 'return message.replace(b"OLD_NAME", b"NEW_NAME")'

也支持 Python 的正則表達式,但它們必須用 Python 手動編寫。

如果存儲庫是原始的,沒有遠程,則必須添加--force以強制重寫。 (在執行此操作之前,您可能希望創建存儲庫的備份。)

如果您不想保留引用(它們將顯示在 Git GUI 的分支歷史記錄中),則必須添加--replace-refs delete-no-add

我按照這個多步驟過程將代碼移動到父目錄並保留歷史記錄。

第 0 步:從 'master' 創建一個分支 'history' 用於保管

第 1 步:使用git-filter-repo工具重寫歷史記錄。 下面的這個命令將文件夾 'FolderwithContentOfInterest' 移動到一個級別並修改了相關的提交歷史

git filter-repo --path-rename ParentFolder/FolderwithContentOfInterest/:FolderwithContentOfInterest/ --force

第 2 步:此時 GitHub 存儲庫丟失了其遠程存儲庫路徑。 添加遠程參考

git remote add origin git@github.com:MyCompany/MyRepo.git

第 3 步:在存儲庫上拉取信息

git pull

第四步:連接本地丟失分支和源分支

git branch --set-upstream-to=origin/history history

步驟 5:如果出現提示,解決文件夾結構的合並沖突

第 6 步:!!

git push

注意:修改后的歷史記錄和移動的文件夾似乎已經提交。 enter code here

完畢。 代碼移動到父目錄/所需目錄保持歷史完整!

雖然作為 Git 的核心,Git 管道不會跟蹤重命名,但如果您願意,您與 Git 日志“瓷器”一起顯示的歷史記錄可以檢測到它們。

對於給定的git log使用 -M 選項:

git log -p -M

使用當前版本的 Git。

這也適用於git diff等其他命令。

有一些選項可以使比較或多或少嚴格。 如果您重命名文件而不同時對文件進行重大更改,則 Git 日志和朋友可以更輕松地檢測到重命名。 出於這個原因,有些人在一次提交中重命名文件並在另一次提交中更改它們。

每當您要求 Git 查找文件被重命名的位置時,CPU 使用都會產生成本,因此是否使用它以及何時使用取決於您。

如果您希望始終在特定存儲庫中使用重命名檢測報告您的歷史記錄,您可以使用:

git config diff.renames 1

從一個目錄移動到另一個文件進行檢測。 下面是一個例子:

commit c3ee8dfb01e357eba1ab18003be1490a46325992
Author: John S. Gruber <JohnSGruber@gmail.com>
Date:   Wed Feb 22 22:20:19 2017 -0500

    test rename again

diff --git a/yyy/power.py b/zzz/power.py
similarity index 100%
rename from yyy/power.py
rename to zzz/power.py

commit ae181377154eca800832087500c258a20c95d1c3
Author: John S. Gruber <JohnSGruber@gmail.com>
Date:   Wed Feb 22 22:19:17 2017 -0500

    rename test

diff --git a/power.py b/yyy/power.py
similarity index 100%
rename from power.py
rename to yyy/power.py

請注意,這在您使用 diff 時都有效,而不僅僅是使用git log 例如:

$ git diff HEAD c3ee8df
diff --git a/power.py b/zzz/power.py
similarity index 100%
rename from power.py
rename to zzz/power.py

作為試用,我在功能分支中的一個文件中做了一個小改動並提交了它,然后在主分支中我重命名了文件,提交了,然后在文件的另一部分進行了小改動並提交了。 當我轉到功能分支並從 master 合並時,合並重命名了文件並合並了更改。 這是合並的輸出:

 $ git merge -v master
 Auto-merging single
 Merge made by the 'recursive' strategy.
  one => single | 4 ++++
  1 file changed, 4 insertions(+)
  rename one => single (67%)

結果是一個工作目錄,文件重命名,兩個文本都進行了更改。 因此,盡管 Git 沒有明確跟蹤重命名,但它仍有可能做正確的事情。

這是對舊問題的較晚答案,因此當時其他答案對於 Git 版本可能是正確的。

首先創建一個僅重命名的獨立提交。

然后將文件內容的任何最終更改放入單獨的提交中。

只需使用以下命令移動文件和舞台:

git add .

在提交之前,您可以檢查狀態:

git status

這將顯示:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    old-folder/file.txt -> new-folder/file.txt

我使用 Git 版本 2.26.1 進行了測試。

摘自GitHub 幫助頁面

我移動文件然后做

git add -A

將所有已刪除/新文件放入存儲區。 這里git意識到文件被移動了。

git commit -m "my message"
git push

我不知道為什么,但這對我有用。

暫無
暫無

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

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