GIT 复制文件保存历史记录
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16937359/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
GIT copy file preserving history
提问by Mark Bramnik
I have a somewhat confusing question in GIT.
Lets say, I have a file dir1/A.txt
committed and git preserves a history of commits
我在 GIT 中有一个有点令人困惑的问题。比方说,我dir1/A.txt
提交了一个文件,git 保留了提交的历史记录
Now I need (for some reasons) to copy the file into dir2/A.txt
(not move but copy).
I know that there is a git mv
command but I need dir2/A.txt
to have the same history of commits as dir1/A.txt
, and dir1/A.txt
to still remain there.
现在我需要(出于某些原因)将文件复制到dir2/A.txt
(不是移动而是复制)。我知道有一个git mv
命令,但我需要dir2/A.txt
与 具有相同的提交历史dir1/A.txt
,并且dir1/A.txt
仍然保持在那里。
I'm not planning to update A.txt
once the copy is created and all the future work will be done on dir2/A.txt
我不打算在A.txt
创建副本后更新,所有未来的工作都将完成dir2/A.txt
I know it sounds confusing, I'll add that this situation is on java based module (mavenized project) and we need to create a new version of code so that our customers will have the ability to have 2 different versions in runtime, the first version will be removed eventually when the alignment will be done. We can use maven versioning of course, I'm just newbie in GIT and curious about what git can provide here.
我知道这听起来令人困惑,我会补充说这种情况是在基于 java 的模块(mavenized 项目)上,我们需要创建一个新版本的代码,以便我们的客户能够在运行时拥有 2 个不同的版本,第一个当对齐完成时,版本最终将被删除。我们当然可以使用 maven 版本控制,我只是 GIT 的新手,对 git 可以在这里提供什么感到好奇。
采纳答案by CliffordVienna
Unlike subversion, git does not have a per-file history. If you look at the commit data structure, it only points to the previous commits and the new tree object for this commit. No explicit information is stored in the commit object which files are changed by the commit; nor the nature of these changes.
与 subversion 不同,git 没有每个文件的历史记录。如果您查看提交数据结构,它仅指向先前的提交和此提交的新树对象。提交对象中没有存储明确的信息,哪些文件被提交更改;也不是这些变化的性质。
The tools to inspect changes can detect renames based on heuristics. E.g. "git diff" has the option -M that turns on rename detection. So in case of a rename, "git diff" might show you that one file has been deleted and another one created, while "git diff -M" will actually detect the move and display the change accordingly (see "man git diff" for details).
检查更改的工具可以基于启发式检测重命名。例如,“git diff”有选项 -M 可以打开重命名检测。因此,在重命名的情况下,“git diff”可能会显示一个文件已被删除并创建了另一个文件,而“git diff -M”实际上会检测到移动并相应地显示更改(参见“man git diff”细节)。
So in git this is not a matter of how you commit your changes but how you look at the committed changes later.
因此,在 git 中,这不是您如何提交更改的问题,而是您以后如何查看已提交的更改的问题。
回答by Peter Dillinger
All you have to do is:
您所要做的就是:
- move the file to two different locations,
- merge the two commits that do the above, and
- move one copy back to the original location.
- 将文件移动到两个不同的位置,
- 合并执行上述操作的两个提交,以及
- 将一份副本移回原始位置。
You will be able to see historical attributions (using git blame
) and full history of changes (using git log
) for both files.
您将能够查看两个文件的历史属性(使用git blame
)和完整的更改历史(使用git log
)。
Suppose you want to create a copy of file foo
called bar
. In that case the workflow you'd use would look like this:
假设您要创建一个foo
名为bar
. 在这种情况下,您将使用的工作流程如下所示:
git mv foo bar
git commit
SAVED=`git rev-parse HEAD`
git reset --hard HEAD^
git mv foo copy
git commit
git merge $SAVED # This will generate conflicts
git commit -a # Trivially resolved like this
git mv copy foo
git commit
Why this works
为什么这有效
After you execute the above commands, you end up with a revision history that looks like this:
执行上述命令后,您最终会得到如下所示的修订历史记录:
( revision history ) ( files )
ORIG_HEAD foo
/ \ / \
SAVED ALTERNATE bar copy
\ / \ /
MERGED bar,copy
| |
RESTORED bar,foo
When you ask Git about the history of foo
, it will:
当你向 Git 询问 的历史时foo
,它会:
- detect the rename from
copy
between MERGED and RESTORED, - detect that
copy
came from the ALTERNATE parent of MERGED, and - detect the rename from
foo
between ORIG_HEAD and ALTERNATE.
- 检测
copy
MERGED和RESTORED之间的重命名, - 检测
copy
来自 Merged 的 ALTERNATE 父级,以及 - 检测
foo
ORIG_HEAD 和 ALTERNATE 之间的重命名。
From there it will dig into the history of foo
.
从那里,它将深入了解foo
.
When you ask Git about the history of bar
, it will:
当你向 Git 询问 的历史时bar
,它会:
- notice no change between MERGED and RESTORED,
- detect that
bar
came from the SAVED parent of MERGED, and - detect the rename from
foo
between ORIG_HEAD and SAVED.
- 注意 MERGED 和 RESTORED 之间没有变化,
- 检测
bar
来自MERGED的SAVED父级,以及 - 检测
foo
ORIG_HEAD 和 SAVED 之间的重命名。
From there it will dig into the history of foo
.
从那里,它将深入了解foo
.
It's that simple. :)
就这么简单。:)
You just need to force Git into a merge situation where you can accept two traceable copies of the file(s), and we do this with a parallel move of the original (which we soon revert).
您只需要强制 Git 进入合并情况,您可以接受文件的两个可追踪副本,我们通过并行移动原始文件(我们很快将其还原)来完成此操作。
回答by Jakob Buron
Simply copy the file, add and commit it:
只需复制文件,添加并提交它:
cp dir1/A.txt dir2/A.txt
git add dir2/A.txt
git commit -m "Duplicated file from dir1/ to dir2/"
Then the following commands will show the full pre-copy history:
然后以下命令将显示完整的预复制历史记录:
git log --follow dir2/A.txt
To see inherited line-by-line annotations from the original file use this:
要查看从原始文件继承的逐行注释,请使用以下命令:
git blame -C -C -C dir2/A.txt
Git does not track copies at commit-time, instead it detectsthem when inspecting history with e.g. git blame
and git log
.
Git 不会在提交时跟踪副本,而是在使用 eg和 来检查历史记录时检测它们。git blame
git log
Most of this information comes from the answers here: Record file copy operation with Git
这些信息大部分来自这里的答案:Record file copy operation with Git
回答by Lukas Eder
I've slightly modified Peter's answer hereto create a reusable, non-interactive shell script called git-split.sh
:
我在这里稍微修改了Peter 的回答,以创建一个可重用的非交互式 shell 脚本,名为git-split.sh
:
#!/bin/sh
if [[ $# -ne 2 ]] ; then
echo "Usage: git-split.sh original copy"
exit 0
fi
git mv "" ""
git commit -n -m "Split history to - rename file to target-name"
REV=`git rev-parse HEAD`
git reset --hard HEAD^
git mv "" temp
git commit -n -m "Split history to - rename source-file to temp"
git merge $REV
git commit -a -n -m "Split history to - resolve conflict and keep both files"
git mv temp ""
git commit -n -m "Split history to - restore name of source-file"
回答by Hervé
For completeness, I would add that, if you wanted to copy an entire directory full of controlled AND uncontrolled files, you could use the following:
为了完整起见,我想补充一点,如果您想复制一个充满受控和非受控文件的整个目录,您可以使用以下命令:
git mv old new
git checkout HEAD old
The uncontrolled files will be copied over, so you should clean them up:
不受控制的文件将被复制,因此您应该清理它们:
git clean -fdx new
回答by darbvin
In my case, I made the change on my hard drive (cut/pasted about 200 folders/files from one path in my working copy to another path in my working copy), and used SourceTree (2.0.20.1) to stage both the detected changes (one add, one remove), and as long as I staged both the add and remove together, it automatically combined into a single change with a pink R icon (rename I assume).
就我而言,我在我的硬盘驱动器上进行了更改(将大约 200 个文件夹/文件从我的工作副本中的一个路径剪切/粘贴到我的工作副本中的另一个路径),并使用 SourceTree (2.0.20.1) 暂存检测到的更改(一个添加,一个删除),只要我同时进行添加和删除,它就会自动合并为一个带有粉红色 R 图标的更改(我假设重命名)。
I did notice that because I had such a large number of changes at once, SourceTree was a little slow detecting all the changes, so some of my staged files look like just adds (green plus) or just deletes (red minus), but I kept refreshing the file status and kept staging new changes as they eventually popped up, and after a few minutes, the whole list was perfect and ready for commit.
我确实注意到,因为我一次有如此多的更改,SourceTree 检测所有更改的速度有点慢,所以我的一些暂存文件看起来只是添加(绿色加号)或只是删除(红色减号),但我不断刷新文件状态并在它们最终弹出时不断进行新的更改,几分钟后,整个列表是完美的,可以提交了。
I verified that the history is present, as long as when I look for history, I check the "Follow renamed files" option.
我验证了历史记录存在,只要在查找历史记录时,我选中“关注重命名的文件”选项。
回答by Leonardo Ramos Duarte
This process preserve history, but is little workarround:
此过程保留历史记录,但几乎没有解决方法:
# make branchs to new files
$: git mv arquivos && git commit
# in original branch, remove original files
$: git rm arquivos && git commit
# do merge and fix conflicts
$: git merge branch-copia-arquivos
# back to original branch and revert commit removing files
$: git revert commit