GIT复制文件保留历史记录

时间:2013-06-05 10:20:01

标签: git copy

我在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 project),我们需要创建一个新版本的代码,以便我们的客户能够在运行时拥有2个不同的版本,最终将在对齐完成后删除第一个版本。 我们当然可以使用maven版本,我只是GIT的新手,并对git可以提供的内容感到好奇。

7 个答案:

答案 0 :(得分:96)

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

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

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

git mv foo bar
git commit -n
SAVED=`git rev-parse HEAD`
git reset --hard HEAD^
git mv foo foo-magic
git commit -n
git merge $SAVED # This will generate conflicts
git commit -a -n # Trivially resolved like this
git mv foo-magic foo
git commit -n

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

为什么会这样?

您最终会得到这样的修订历史记录:

  ORIG_HEAD
    /    \
SAVED  ALTERNATE
    \    /
    MERGED
      |
   RESTORED

当你向git询问'foo'的历史时,它会(1)在MERGED和RESTORED之间检测到'foo-magic'的重命名,(2)检测'foo-magic'来自ALTERN的父级MERGED,以及(3)在ORIG_HEAD和ALTERNATE之间检测'foo'的重命名。从那里开始,它将深入探讨'foo'的历史。

当你问git关于'bar'的历史时,它会(1)注意到MERGED和RESTORED之间没有变化,(2)检测到'bar'来自已融合的SAVE父母,以及(3)检测到从ORIG_HEAD和SAVED之间的'foo'重命名。从那里开始,它将深入探讨'foo'的历史。

就这么简单。你只需要强制git进入一个合并的情况,你可以接受文件的两个可追踪的副本,我们通过原始的平行移动(我们很快还原)来做到这一点。

答案 1 :(得分:71)

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

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

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

答案 2 :(得分:19)

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

cp dir1/A.txt dir2/A.txt
git add dir2/A.txt
git commit -m "Duplicated file from dir1/ to dir2/"

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

git log --follow dir2/A.txt

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

git blame -C -C -C dir2/A.txt

Git不会在提交时跟踪副本,而是在检查历史记录时检测到git blamegit log

大部分信息来自这里的答案:Record file copy operation with Git

答案 3 :(得分:10)

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

git mv old new
git checkout HEAD old

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

git clean -fdx new

答案 4 :(得分:7)

我对Peter's answer here进行了一些修改,以创建一个名为git-split.sh的可重用,非交互的shell脚本:

#!/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"

答案 5 :(得分:2)

在我的情况下,我在硬盘上进行了更改(从我的工作副本中的一个路径剪切/粘贴了大约200个文件夹/文件到我工作副本中的另一个路径),并使用SourceTree(2.0.20.1)进行分阶段两个检测到的更改(一个添加,一个删除),并且只要我同时添加和删除它,它会自动组合成一个带有粉红色R图标的单个更改(重命名我假设)。

我注意到因为我一次有这么大的变化,SourceTree检测到所有的变化有点慢,所以我的一些分段文件看起来只是添加(绿色加)或只是删除(红色减去) ,但我一直刷新文件状态,并在最终弹出时继续进行新的更改,几分钟后,整个列表都很完美,准备提交。

我确认历史存在,只要我查找历史记录,就会检查“关注重命名的文件”选项。

答案 6 :(得分:0)

此过程可以保留历史记录,但工作量很少:

structs