Is it possible to move/rename files in Git and maintain their history?
我想在Git中重命名/移动项目子树,将其从
1 | /project/xyz |
到
1 | /components/xyz |
号
如果我使用一个普通的
Git检测重命名,而不是使用commit来持久化操作,因此使用
网址:http://git-scm.com/docs/git-log
要查找完整历史记录,请使用以下命令:
1 | git log --follow ./path/to/file |
号
可以重命名一个文件并保持历史完整,尽管它会导致在存储库的整个历史中重命名该文件。这可能只适用于痴迷于Git日志的爱好者,并有一些严重的影响,包括:
- 您可以重写共享的历史记录,这是使用Git时最重要的不要。如果有人克隆了存储库,那么您将破坏它。为了避免头痛,他们必须重新克隆。如果重命名足够重要,这可能没问题,但您需要仔细考虑这一点——最终可能会让整个开源社区感到不安!
- 如果您在存储库历史记录的早期版本中引用了使用旧名称的文件,那么实际上您正在破坏早期版本。要解决这个问题,你得多跳一点。这不是不可能的,只是乏味,可能不值得。
现在,既然你还和我在一起,你可能是一个单独的开发者,正在重命名一个完全独立的文件。让我们用
假设您要将一个文件
这可以用
相反:
1 | git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD |
。
将重做分支中的每个提交,在每次迭代的标记中执行命令。当你这样做的时候,很多事情都会出错。我通常会测试文件是否存在(否则它还没有移动),然后执行必要的步骤按我的喜好把树推到合适的位置。在这里,您可以通过文件来更改对文件的引用,等等。振作起来!:)
完成后,文件将被移动,日志将保持完整。你觉得自己像个忍者海盗。
当然,只有将文件移到新文件夹中时,mkdir目录才是必需的。if将避免在历史记录中早于文件存在的时间创建此文件夹。
不。
简短的回答是"否",不可能在git中重命名文件并记住历史。这是一种痛苦。
有传言说,
(最初我使用Eclipse在一个操作中重命名和更新包,这可能会混淆Git。但这是一件很常见的事情。如果只执行
Linus说,应该从整体上理解软件项目的全部内容,而不需要跟踪单个文件。可悲的是,我的小脑袋不能做到这一点。
这么多人无心重复了Git自动跟踪移动的声明,真的很烦人。他们浪费了我的时间。吉特不做这种事。按设计!!)Git根本不跟踪移动。
我的解决方案是将文件重命名回其原始位置。更改软件以适合源代码管理。有了Git,你似乎第一次就需要正确的Git。
不幸的是,这打破了Eclipse,后者似乎使用了
(有一些太聪明的黑客回去重新投入旧的工作,但他们相当可怕。参见Github要旨:Emiller/Git MV和历史。)
1 | git log --follow [file] |
将通过重命名向您显示历史。
我愿意:
1 2 | git mv {old} {new} git add -u {new} |
目标
- use
git am (inspired from smar,borrowed from exherbo) - 添加复制/移动文件的提交历史记录
- 从一个目录到另一个目录
- 或者从一个存储库到另一个存储库
限制
- 不保留标签和分支
- 在路径文件重命名(目录重命名)上剪切历史记录
总结
1。以电子邮件格式提取历史记录
示例:提取
1 2 3 4 5 6 7 8 9 10 11 12 | my_repo ├── dirA │ ├── file1 │ └── file2 ├── dirB ^ │ ├── subdir | To be moved │ │ ├── file3 | with history │ │ └── file4 | │ └── file5 v └── dirC ├── file6 └── file7 |
设置/清理目的地
1 2 | export historydir=/tmp/mail/dir # Absolute path rm -rf"$historydir" # Caution when cleaning the folder |
号
以电子邮件格式提取每个文件的历史记录
1 2 | 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"' {} ';' |
不幸的是,选项
电子邮件格式的临时历史记录:
1 2 3 4 5 | /tmp/mail/dir ├── subdir │ ├── file3 │ └── file4 └── file5 |
。
dan bonachea建议在第一步中反转git log generation命令的循环:不要为每个文件运行一次git log,而是使用命令行上的文件列表运行它一次,并生成一个统一的日志。通过这种方式提交,修改多个文件在结果中保持单个提交,并且所有新提交都保持其原始相对顺序。注意:在(现在是统一的)日志中重写文件名时,还需要在下面的第二步中进行更改。
2。重新组织文件树并更新文件名假设您想在另一个repo中移动这三个文件(可以是同一个repo)。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
因此,重新组织文件:
1 2 3 4 5 6 | 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 |
。
您的临时历史记录现在是:
1 2 3 4 5 6 7 | /tmp/mail/dir └── dirB ├── dirB1 │ ├── file33 │ └── file44 └── dirB2 └── file5 |
。
同时更改历史记录中的文件名:
1 2 | cd"$historydir" find * -type f -exec bash -c 'sed"/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i"$0"' {} ';' |
三。应用新历史记录
你的其他回购协议是:
1 2 3 4 5 6 | my_other_repo ├── dirF │ ├── file55 │ └── file56 └── dirH └── file77 |
。
从临时历史文件应用提交:
1 2 | cd my_other_repo find"$historydir" -type f -exec cat {} + | git am --committer-date-is-author-date |
您的其他回购协议现在是:
1 2 3 4 5 6 7 8 9 10 11 12 | my_other_repo ├── dirF │ ├── file55 │ └── file56 ├── dirB │ ├── dirB1 │ │ ├── file33 │ │ └── file44 │ └── dirB2 │ └── file5 └── dirH └── file77 |
。
使用
要列出已重命名的文件:
1 | find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>' |
号
更多定制:您可以使用选项
1 | find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}' |
号
I would like to rename/move a project subtree in Git moving it from
1 /project/xyzto
/components/xyz
If I use a plain
git mv project components , then all the commit history for thexyz project gets lost.
号
没有(8年后,Git2.19,2018年第3季度),因为Git将检测到目录重命名,现在这是更好的记录。
见Elijah Newren(
这在
例子:
When all of
x/a ,x/b andx/c have moved toz/a ,z/b andz/c , it is likely thatx/d added in the meantime would also want to move toz/d by
taking the hint that the entire directory 'x ' moved to 'z '.
号
但也有很多其他情况,比如:
one side of history renames
x -> z , and the other renames some file to
x/e , causing the need for the merge to do a transitive rename.
号
为了简化目录重命名检测,这些规则由git强制执行:
一些基本规则限制了应用目录重命名检测:
If a given directory still exists on both sides of a merge, we do not consider it to have been renamed. If a subset of to-be-renamed files have a file or directory in the way (or would be in the way of each other),"turn off" the directory rename for those specific sub-paths and report the conflict to the user. If the other side of history did a directory rename to a path that your side of history renamed away, then ignore that particular rename from the other side of history for any implicit directory renames (but warn the user).
号
您可以在
- a) If renames split a directory into two or more others, the directory with the most renames,"wins".
- b) Avoid directory-rename-detection for a path, if that path is the source of a rename on either side of a merge.
- c) Only apply implicit directory renames to directories if the other side
of history is the one doing the renaming.
号
虽然Git的核心是Git管道,但它不跟踪重命名,但如果您愿意,可以通过Git日志"瓷质"来检测它们。
对于给定的
git log -p -M
号
使用当前版本的Git。
这也适用于其他命令,如
有一些选项可以使比较更严格或更不严格。如果重命名文件时不同时对文件进行重大更改,则Git日志和朋友更容易检测到重命名。因此,有些人在一次提交中重命名文件,在另一次提交中更改文件。
每当你要求Git查找文件的重命名位置时,CPU的使用都会有一定的代价,所以无论你是否使用它,以及何时使用,都取决于你自己。
如果希望在特定存储库中始终报告具有重命名检测功能的历史记录,则可以使用:
git config diff.renames 1
号
检测到从一个目录移动到另一个目录的文件。下面是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | commit c3ee8dfb01e357eba1ab18003be1490a46325992 Author: John S. Gruber <[email protected]> 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 <[email protected]> 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,这都是有效的,而不仅仅是与
1 2 3 4 5 | $ 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合并时,合并重命名了文件并合并了更改。下面是合并的输出:
1 2 3 4 5 6 | $ 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版本是正确的。
我移动文件,然后做
1 | git add -A |
号
将所有已删除/新文件放入Sataging区域。在这里,Git意识到文件被移动了。
1 2 | git commit -m"my message" git push |
号
我不知道为什么,但这对我有用。