是否可以在 Git 中移动/重命名文件并维护其历史记录?
我想在 Git 中重命名/移动项目子树,将其从 移动
/project/xyz
到
/components/xyz
如果我使用普通的 git mv 项目组件,那么 xyz 项目的所有提交历史记录都会获取丢失的。有没有办法移动这个以便保留历史?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(15)
Git 检测重命名而不是通过提交持久化操作,因此无论您使用 git mv 还是 mv 并不重要,只要移动操作与任何操作分开提交即可。对文件的更改。
log
命令采用--follow
参数,在重命名操作之前继续历史记录,即,它使用启发式搜索相似内容。要查找完整历史记录,请使用以下命令:
Git detects renames rather than persisting the operation with the commit, so whether you use
git mv
ormv
doesn't matter, as long as the move operation is committed separately from any changes to the file.The
log
command takes a--follow
argument that continues history before a rename operation, i.e., it searches for similar content using heuristics.To lookup the full history, use the following command:
不。
简短的回答是不。在 Git 中重命名文件并记住历史记录是不可能的。这是一种痛苦。
有传言称 git log --follow
--find-copies-harder
会起作用,但它对我不起作用,即使文件内容的更改为零,并且这些动作是通过git mv
进行的。(最初我使用 Eclipse 在一次操作中重命名和更新包,这可能会让 Git 感到困惑。但这是一件很常见的事情。如果只有一个
,
被执行,然后是--follow
似乎确实可以工作mvcommit
并且mv
并不太远。)Linus 说你应该全面理解一个软件项目的全部内容,不需要跟踪单个文件。好吧,遗憾的是,我的小脑袋做不到这一点。
这么多人漫不经心地重复 Git 自动跟踪移动的说法,这真的很烦人。他们浪费了我的时间。 Git 没有做这样的事情。 按照设计(!)Git 根本不跟踪移动。
我的解决方案是将文件重命名回原来的位置。更改软件以适应源代码管理。使用 Git,您似乎只需要第一次就“git”正确。
不幸的是,这破坏了 Eclipse,它似乎使用
--follow
。git log --follow
有时不会显示具有复杂重命名历史的文件的完整历史记录,即使git log
会显示。 (我不知道为什么。)(有一些过于聪明的 hack 会返回并重新提交旧的工作,但它们相当可怕。请参阅 GitHub-Gist:emiller/git-mv-with-history。)
简而言之:如果 Subversion 这样做 是错误的,那么 Git 这样做也是错误的 - 这样做不是某种(错误!)功能,这是一个错误。
No.
The short answer is NO. It is not possible to rename a file in Git and remember the history. And it is a pain.
Rumor has it that
git log --follow
--find-copies-harder
will work, but it does not work for me, even if there are zero changes to the file contents, and the moves have been made withgit mv
.(Initially I used Eclipse to rename and update packages in one operation, which may have confused Git. But that is a very common thing to do.
--follow
does seem to work if only amv
is performed and then acommit
and themv
is not too far.)Linus says that you are supposed to understand the entire contents of a software project holistically, not needing to track individual files. Well, sadly, my small brain cannot do that.
It is really annoying that so many people have mindlessly repeated the statement that Git automatically tracks moves. They have wasted my time. Git does no such thing. By design(!) Git does not track moves at all.
My solution is to rename the files back to their original locations. Change the software to fit the source control. With Git you just seem to need to "git" it right the first time.
Unfortunately, that breaks Eclipse, which seems to use
--follow
.git log --follow
sometimes does not show the full history of files with complicated rename histories even thoughgit log
does. (I do not know why.)(There are some too clever hacks that go back and recommit old work, but they are rather frightening. See GitHub-Gist: emiller/git-mv-with-history.)
In short: if Subversion doing this is wrong, then Git doing this is also wrong - doing this isn't some (mis!)feature, it's a mistake.
可以重命名文件并保持历史记录完整,尽管这会导致文件在存储库的整个历史记录中被重命名。这可能仅适用于痴迷于 git-log 的爱好者,并且会产生一些严重的影响,包括:
现在,既然您仍然和我在一起,您可能是一个单独的开发人员,正在重命名一个完全独立的文件。让我们使用
filter-tree
移动文件!假设您要将文件
old
移动到文件夹dir
中,并将其命名为new
这可以使用
git 来完成mv 旧目录/新 && git add -u dir/new
,但这打破了历史。相反:
将重做分支中的每个提交,在每次迭代的刻度中执行命令。当你这样做时,很多事情都可能出错。我通常会测试该文件是否存在(否则它还没有移动),然后执行必要的步骤以按照我的喜好将树拔掉。在这里,您可以通过文件来改变对文件的引用等等。把自己打垮! :)
完成后,文件将被移动并且日志完好无损。你感觉就像一个忍者海盗。
还;当然,仅当您将文件移动到新文件夹时, mkdir dir 才是必需的。 if 将避免在历史上早于您的文件存在的时间创建此文件夹。
It is possible to rename a file and keep the history intact, although it causes the file to be renamed throughout the entire history of the repository. This is probably only for the obsessive git-log-lovers, and has some serious implications, including these:
Now, since you're still with me, you're a probably solo developer renaming a completely isolated file. Let's move a file using
filter-tree
!Assume you're going to move a file
old
into a folderdir
and give it the namenew
This could be done with
git mv old dir/new && git add -u dir/new
, but that breaks history.Instead:
will redo every commit in the branch, executing the command in the ticks for each iteration. Plenty of stuff can go wrong when you do this. I normally test to see if the file is present (otherwise it's not there yet to move) and then perform the necessary steps to shoehorn the tree to my liking. Here you might sed through files to alter references to the file and so on. Knock yourself out! :)
When completed, the file is moved and the log is intact. You feel like a ninja pirate.
Also; The mkdir dir is only necessary if you move the file to a new folder, of course. The if will avoid the creation of this folder earlier in history than your file exists.
将通过重命名向您展示历史记录。
will show you the history through renames.
我愿意:
I do:
否(8 年后,Git 2.19,2018 年第 3 季度),因为 Git 会检测目录重命名,并且现在对此进行了更好的记录。
请参阅 提交 b00bf1c、提交 1634688,提交 0661e49、提交 4d34dff、提交 983f464, 提交 c840e1a 、提交 9929430(2018 年 6 月 27 日)和 提交 d4e8062,提交 5dacd4a(2018 年 6 月 25 日),作者:Elijah Newren (
newren
)。(由 Junio C Hamano --
gitster
-- 合并于 提交 0ce5a69,2018 年 7 月 24 日)现在在
文档/technical/directory-rename-detection.txt
:示例:
但还有许多其他情况,例如:
为了简化目录重命名检测,这些规则由 Git 强制执行:
一些基本规则限制了何时
目录重命名检测适用:
您可以在 很多测试“noreferrer”>
t/t6043-merge-rename-directories.sh
,其中还指出:No (8 years later, Git 2.19, Q3 2018), because Git will detect the directory rename, and this is now better documented.
See commit b00bf1c, commit 1634688, commit 0661e49, commit 4d34dff, commit 983f464, commit c840e1a, commit 9929430 (27 Jun 2018), and commit d4e8062, commit 5dacd4a (25 Jun 2018) by Elijah Newren (
newren
).(Merged by Junio C Hamano --
gitster
-- in commit 0ce5a69, 24 Jul 2018)That is now explained in
Documentation/technical/directory-rename-detection.txt
:Example:
But they are many other cases, like:
To simplify directory rename detection, those rules are enforced by Git:
a couple basic rules limit when
directory rename detection applies:
You can see a lot of tests in
t/t6043-merge-rename-directories.sh
, which also point out that:是
git am
的历史记录。限制
通过示例逐步说明
1. 以电子邮件格式提取历史记录
示例:提取
file3
、file4< 的历史记录/code> 和
file5
设置/清理目标
以电子邮件格式提取每个文件的历史记录
不幸的是,选项
--follow
或--find-copies-harder 不能与
--reverse
结合使用。这就是为什么当文件重命名时(或当父目录重命名时)历史记录会被剪切。电子邮件格式的临时历史记录:
Dan Bonachea 建议在第一步中反转 git 日志生成命令的循环:而不是运行 git每个文件记录一次,在命令行上使用文件列表运行一次并生成单个统一日志。这样,修改多个文件的提交在结果中仍保留为单个提交,并且所有新提交都保持其原始相对顺序。请注意,在(现已统一)日志中重写文件名时,这还需要在下面的第二步中进行更改。
2. 重新组织文件树并更新文件名
假设您想将这三个文件移动到另一个存储库(可以是同一个存储库)中。
因此,请重新组织您的文件:
您的临时历史记录现在是:
同时更改历史记录中的文件名:
3.应用新历史记录
您的其他存储库是:
应用临时历史记录文件中的提交:
--committer-date-is-author-date< /code> 保留原始提交时间戳(Dan Bonachea 的评论)。
您的其他存储库现在是:
使用 git status 查看准备推送的提交数量:-)
额外技巧:检查存储库中重命名/移动的文件
列出已重命名的文件:
更多自定义:您可以使用选项
--find-copies-harder
或--reverse
来完成命令git log
。您还可以使用cut -f3-
和 grep 完整模式'{.* =>; 删除前两列。 .*}'
。Yes
git log --pretty=email
git am
.Limitation
Step by step explanation with examples
1. Extract history in email format
Example: Extract history of
file3
,file4
andfile5
Set/clean the destination
Extract history of each file in email format
Unfortunately option
--follow
or--find-copies-harder
cannot be combined with--reverse
. This is why history is cut when file is renamed (or when a parent directory is renamed).Temporary history in email format:
Dan Bonachea suggests to invert the loops of the git log generation command in this first step: rather than running git log once per file, run it exactly once with a list of files on the command line and generate a single unified log. This way commits that modify multiple files remain a single commit in the result, and all the new commits maintain their original relative order. Note this also requires changes in second step below when rewriting filenames in the (now unified) log.
2. Reorganize file tree and update filenames
Suppose you want to move these three files in this other repo (can be the same repo).
Therefore reorganize your files:
Your temporary history is now:
Change also filenames within the history:
3. Apply new history
Your other repo is:
Apply commits from temporary history files:
--committer-date-is-author-date
preserves the original commit time-stamps (Dan Bonachea's comment).Your other repo is now:
Use
git status
to see amount of commits ready to be pushed :-)Extra trick: Check renamed/moved files within your repo
To list the files having been renamed:
More customizations: You can complete the command
git log
using options--find-copies-harder
or--reverse
. You can also remove the first two columns usingcut -f3-
and grepping complete pattern'{.* => .*}'
.我按照这个多步骤过程将代码移动到父目录并保留历史记录。
第 0 步:从“master”创建一个分支“历史记录”以进行保管
第 1 步:使用 git-filter- repo 重写历史的工具。下面的命令将文件夹“FolderwithContentOfInterest”移动到上一级并修改相关提交历史记录
第 2 步:此时 GitHub 存储库丢失了其远程存储库路径。添加远程引用
步骤 3:在存储库上拉取信息
步骤 4:将本地丢失的分支与原始分支连接
步骤 5:如果出现提示,请解决文件夹结构的合并冲突
步骤 6:推送!
注意:修改的历史记录和移动的文件夹似乎已经提交。
在此处输入代码
完成。代码移动到父/所需目录,保持历史记录完整!
I followed this multi-step process to move code to the parent directory and retained history.
Step 0: Created a branch 'history' from 'master' for safekeeping
Step 1: Used git-filter-repo tool to rewrite history. This command below moved folder 'FolderwithContentOfInterest' to one level up and modified the relevant commit history
Step 2: By this time the GitHub repository lost its remote repository path. Added remote reference
Step 3: Pull information on repository
Step 4: Connect the local lost branch with the origin branch
Step 5: Address merge conflict for the folder structure if prompted
Step 6: Push!!
Note: The modified history and moved folder appear to already be committed.
enter code here
Done. Code moves to the parent / desired directory keeping history intact!
我遇到了“重命名文件夹而不丢失历史记录”的问题。要修复它,请运行:
I have faced the issue "Renaming the folder without loosing history". To fix it, run:
重命名目录或文件(我对复杂情况不太了解,因此可能有一些警告):
要重命名提及它的文件中的目录(可以使用回调,但我不知道如何):
expressions.txt
是一个充满了诸如literal:OLD_NAME==>NEW_NAME
之类的文件(可以将 Python 的 RE 与regex:
一起使用,或者glob 与glob:
)。要重命名提交消息中的目录:
还支持 Python 的正则表达式,但必须手动用 Python 编写它们。
如果存储库是原始存储库,没有远程存储库,则必须添加
--force
来强制重写。 (在执行此操作之前,您可能需要创建存储库的备份。)如果您不想保留引用(它们将显示在 Git GUI 的分支历史记录中),则必须添加
--replace-参考删除不添加
。To rename a directory or file (I don't know much about complex cases, so there might be some caveats):
To rename a directory in files that mention it (it's possible to use callbacks, but I don't know how):
expressions.txt
is a file filled with lines likeliteral:OLD_NAME==>NEW_NAME
(it's possible to use Python's RE withregex:
or glob withglob:
).To rename a directory in messages of commits:
Python's regular expressions are also supported, but they must be written in Python, manually.
If the repository is original, without remote, you will have to add
--force
to force a rewrite. (You may want to create a backup of your repository before doing this.)If you do not want to preserve refs (they will be displayed in the branch history of Git GUI), you will have to add
--replace-refs delete-no-add
.只需移动文件并使用以下命令进行暂存:
在提交之前,您可以检查状态:
这将显示:
我使用 Git 版本 2.26.1 进行了测试。
摘自 GitHub 帮助页面。
Simply move the file and stage with:
Before commit you can check the status:
That will show:
I tested with Git version 2.26.1.
Extracted from GitHub Help Page.
虽然 Git 的核心(Git 管道)不跟踪重命名,但如果您愿意,您使用 Git 日志“瓷器”显示的历史记录可以检测到它们。
对于给定的 git log 使用 -M 选项:
使用当前版本的 Git。
这也适用于其他命令,例如
git diff
。有一些选项可以使比较更加严格或不那么严格。如果您重命名一个文件而不同时对该文件进行重大更改,那么 Git 日志和朋友就可以更轻松地检测到重命名。因此,有些人在一次提交中重命名文件,并在另一次提交中更改它们。
每当您要求 Git 查找文件已重命名的位置时,都会产生 CPU 使用成本,因此是否使用以及何时使用它取决于您。
如果您希望始终在特定存储库中通过重命名检测来报告您的历史记录,您可以使用:
检测到从一个目录移动到另一个目录的文件。这是一个示例:
请注意,只要您使用 diff,这都有效,而不仅仅是使用
git log
。例如:作为一项试验,我在功能分支中的一个文件中做了一个小的更改并提交了它,然后在主分支中我重命名了该文件并提交了,然后在文件的另一部分中做了一个小的更改并提交了它。当我转到功能分支并从主分支合并时,合并重命名了文件并合并了更改。以下是合并的输出:
结果是一个工作目录,其中文件已重命名,并且文本均已更改。因此,尽管 Git 没有明确跟踪重命名,但它还是有可能做正确的事情。
这是对一个老问题的迟到答案,因此其他答案对于当时的 Git 版本可能是正确的。
While the core of Git, the Git plumbing doesn't keep track of renames, the history you display with the Git log "porcelain" can detect them if you like.
For a given
git log
use the -M option:With a current version of Git.
This works for other commands like
git diff
as well.There are options to make the comparisons more or less rigorous. If you rename a file without making significant changes to the file at the same time it makes it easier for Git log and friends to detect the rename. For this reason some people rename files in one commit and change them in another.
There's a cost in CPU use whenever you ask Git to find where files have been renamed, so whether you use it or not, and when, is up to you.
If you would like to always have your history reported with rename detection in a particular repository you can use:
Files moving from one directory to another is detected. Here's an example:
Please note that this works whenever you are using diff, not just with
git log
. For example:As a trial I made a small change in one file in a feature branch and committed it and then in the master branch I renamed the file, committed, and then made a small change in another part of the file and committed that. When I went to feature branch and merged from master the merge renamed the file and merged the changes. Here's the output from the merge:
The result was a working directory with the file renamed and both text changes made. So it's possible for Git to do the right thing despite the fact that it doesn't explicitly track renames.
This is an late answer to an old question so the other answers may have been correct for the Git version at the time.
首先创建一个仅重命名的独立提交。
然后对文件内容的任何最终更改都会放入单独的提交中。
First create a standalone commit with just a rename.
Then any eventual changes to the file content put in the separate commit.
就我而言,我将两个文件从“resources”目录移动到“src/main/resources”。如以下代码所示,它们显示为“已删除”。
但是,当我将重定位的文件添加到暂存区域,然后添加已删除的文件后,系统将它们识别为“重命名”。
当我检查这两个文件的历史记录时,它完好无损,并且它们的永久链接仍然有效。所以,一切都如我们所希望的那样进行。
In my case, I moved two files from a 'resources' directory into 'src/main/resources'. As following code shows they appeared as 'deleted'.
However, after I added the relocated files into staging area and then added the deleted ones, the system recognized them as "renamed".
When I check the history of both files, it was intact and their permanent links were still working. So, everything works as we might have hoped.
是的
你可以:
(不要在同一提交中移动和重命名)
现在该文件将保留其 git 历史记录
Yes
you can:
(dont move and rename in the same commit)
now the file will keep their git history