GIT复制文件保留历史记录

Mar*_*nik 166 git filenames copy file

我在GIT中有一个令人困惑的问题.可以说,我有一个文件已dir1/A.txt提交,git保留了提交历史记录

现在我需要(出于某些原因)将文件复制到dir2/A.txt(不移动但复制).我知道有一个git mv命令,但我需要dir2/A.txt具有相同的提交历史dir1/A.txt,并且dir1/A.txt仍然保留在那里.

我不打算在A.txt创建副本后进行更新,所有未来的工作都将完成dir2/A.txt

我知道这听起来令人困惑,我将补充说这种情况是基于java的模块(mavenized项目),我们需要创建一个新版本的代码,以便我们的客户能够在运行时拥有2个不同的版本,第一个最终将在对齐完成时删除版本.我们当然可以使用maven版本,我只是GIT的新手,并对git可以提供的内容感到好奇.

Pet*_*ger 118

您所要做的就是将它并行移动到两个不同的位置,合并,然后将一个副本移回原始位置,您就完成了.

然后,您可以git blame在任一副本上运行,没有特殊选项,并查看两者的最佳历史归因.您还可以git log在原始版本上运行并查看完整的历史记录.

详细地说,假设您要将存储库中的foo /复制到bar /.这样做(注意我使用-n提交以避免任何重写,等等):

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
Run Code Online (Sandbox Code Playgroud)

注意:我在Linux上使用了git 2.1.4

为什么会这样

您最终将获得如下修订历史记录:

( revision history )            ( files )

    ORIG_HEAD                      foo
     /     \                      /   \
SAVED       ALTERNATE          bar     copy
     \     /                      \   /
      MERGED                     bar,copy
        |                           |
     RESTORED                    bar,foo
Run Code Online (Sandbox Code Playgroud)

当你向git询问'foo'的历史时,它会(1)在MERGED和RESTORED之间检测到'foo-magic'的重命名,(2)检测到'foo-magic'来自MERGED的ALTERNATE父级,并且(3)从ORIG_HEAD和ALTERNATE之间的'foo'检测重命名.从那里它将挖掘'foo'的历史.

当你问git关于'bar'的历史时,它会(1)注意到MERGED和RESTORED之间没有变化,(2)检测到'bar'来自已合并的父母,并且(3)检测到重命名'在ORIG_HEAD和SAVED之间的foo'.从那里它将挖掘'foo'的历史.

就这么简单.您只需要强制git进入合并情况,您可以接受文件的两个可跟踪副本,我们通过原始的并行移动(我们很快恢复)来完成此操作.

  • 这似乎不起作用,至少不是git`2.9`.我必须使用`--follow`或`-C`标志,以便让git跟踪`bar`到它的`foo`起源.`cp foo bar && git add bar && git commit`给出了相同的最终结果,没有奇怪的历史记录.我做错了吗? (7认同)
  • 这是一个巧妙的解决方案,但是有趣的是使用“简单”一词来描述这种十步解决方法,因为在任何旨在跟踪合法可复制事物历史的系统中都没有什么原子动作。 (6认同)
  • 如果您预计需要/需要在这些提交中使用git rebase,请注意此方法.当我尝试这种保存历史记录的方法时,git将此方法提交的提交视为在rebase期间与彼此冲突并且需要手动合并.冲突解决过程最终失去了我试图首先保存的提交历史记录. (5认同)
  • @ peter-dillinger,很棒的解决方法!我在/sf/answers/3253939391/中使其更具可读性. (4认同)
  • 我记得这过去对我有用。但目前还没有。来自合并分支的文件从合并提交中获取其历史记录的“起点”。在 Windows 7 上尝试了几个 GIT 版本,包括 2.24.0。也尝试使用 @LukasEder 的脚本。相同的结果。 (3认同)
  • 为了完整起见,我相信您所描述的方法也记录在本文中:[如何在保留 git 行历史记录的同时复制文件](https://devblogs.microsoft.com/oldnewthing/20190919-00/ ?p=102904) (2认同)
  • 谢谢@Bass,您提供的链接显示了比此处显示的更好的解决方案!我已将其合并到我的“更具可读性”答案中:/sf/answers/3253939391/。 (2认同)
  • /sf/answers/3253939391/ 有一个改进版本,它不会产生合并冲突,因此可以在事后重新设置基础。 (2认同)

Cli*_*nna 77

与subversion不同,git没有每个文件的历史记录.如果查看提交数据结构,它只会指向此提交的先前提交和新树对象.提交对象中没有存储明确的信息,这些信息由提交更改; 也不是这些变化的本质.

检查更改的工具可以基于启发式检测重命名.例如,"git diff"具有选项-M,可以打开重命名检测.因此,在重命名的情况下,"git diff"可能会显示已删除一个文件而另一个文件已创建,而"git diff -M"实际上会检测到移动并相应地显示更改(请参阅"man git diff"for细节).

所以在git中,这不是你如何提交你的更改,而是你如何看待提交的更改.

  • 为什么`git mv`存在? (11认同)
  • @DanielAlder No.和`git diff -M`一样,这只是对树对象的智能分析.从`git blame`手册页:"整个文件重命名中自动跟踪行的起源(目前没有选项可以关闭重命名 - ")." (6认同)
  • 而且与Mercurial不同.Mercurial有保留副本的历史. (6认同)
  • 我在http://pastebin.com/zEREyeaL上重现的例子表明`git blame`也知道重命名历史 - 不使用任何选项.这不是告诉我们历史是以某种方式存储的吗? (4认同)
  • @skirsch 方便 (2认同)

Jak*_*ron 25

只需复制文件,添加并提交即可:

cp dir1/A.txt dir2/A.txt
git add dir2/A.txt
git commit -m "Duplicated file from dir1/ to dir2/"
Run Code Online (Sandbox Code Playgroud)

然后,以下命令将显示完整的预复制历史记录:

git log --follow dir2/A.txt
Run Code Online (Sandbox Code Playgroud)

要查看原始文件中继承的逐行注释,请使用以下命令:

git blame -C -C -C dir2/A.txt
Run Code Online (Sandbox Code Playgroud)

Git不会在提交时跟踪副本,而是在使用eg 和检查历史时检测它们.git blamegit log

大部分信息来自这里的答案:使用Git记录文件复制操作

  • 这不是很有用,因为“-C -C -C”搜索*整个存储库*,除非您的存储库很小,否则速度非常慢。 (2认同)

Her*_*rvé 10

为了完整起见,我想补充一点,如果你想复制一个完整的受控和不受控制文件的目录,你可以使用以下内容:

git mv old new
git checkout HEAD old
Run Code Online (Sandbox Code Playgroud)

不受控制的文件将被复制,因此您应该清理它们:

git clean -fdx new
Run Code Online (Sandbox Code Playgroud)

  • 当我这样做时,只有原始文件与历史记录保持连接,该副本被视为具有新历史的新文件.这不回答问题:( (9认同)
  • 据我所知,第一个命令将*不复制*不受控制的文件(但移动它们),如果你之后用'clean'命令删除它们,那么移动它们的重点是什么? (3认同)

Luk*_*der 9

我在这里稍微修改了Peter的回答,以创建一个可重用的,非交互的shell脚本,该脚本称为git-split.sh

#!/bin/sh

if [[ $# -ne 2 ]] ; then
  echo "Usage: git-split.sh original copy"
  exit 0
fi

git mv "$1" "$2"
git commit -n -m "Split history $1 to $2 - rename file to target-name"
REV=`git rev-parse HEAD`
git reset --hard HEAD^
git mv "$1" temp
git commit -n -m "Split history $1 to $2 - rename source-file to temp"
git merge $REV
git commit -a -n -m "Split history $1 to $2 - resolve conflict and keep both files"
git mv temp "$1"
git commit -n -m "Split history $1 to $2 - restore name of source-file"
Run Code Online (Sandbox Code Playgroud)

  • 很好的解决方案。我在将它用于包含空格的文件时遇到问题,我修改了您的代码以解决此问题。 (3认同)