我在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在这里能提供什么很好奇。


与subversion不同,git没有每个文件的历史记录。如果查看提交数据结构,它只指向以前的提交和这次提交的新树对象。没有显式的信息存储在提交对象中,哪些文件被提交更改;也不知道这些变化的性质。

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

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


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

git mv old new
git checkout HEAD old

不受控制的文件将被复制,所以你应该清理它们:

git clean -fdx new

在我的例子中,我在我的硬盘驱动器上进行了更改(从我的工作副本中的一个路径剪切/粘贴大约200个文件夹/文件到我的工作副本中的另一个路径),并使用SourceTree(2.0.20.1)来执行检测到的更改(一个添加,一个删除),只要我同时执行添加和删除,它就会自动合并为带有粉红色R图标的单个更改(我假设是重命名)。

我确实注意到,因为我一次有这么多更改,SourceTree检测所有更改的速度有点慢,所以我的一些阶段性文件看起来只是添加(绿色加)或删除(红色减),但我不断刷新文件状态,并在最终出现新的更改时持续呈现,几分钟后,整个列表完美无缺,可以提交了。

我验证了历史记录是存在的,只要当我寻找历史记录时,我检查了“跟踪重命名文件”选项。


你所要做的就是:

将文件移动到两个不同的位置, 合并执行上述操作的两个提交,和 将一份拷贝移回原来的位置。

您将能够看到两个文件的历史属性(使用git blame)和完整的更改历史(使用git log)。

假设您想要创建一个名为bar的文件foo的副本。在这种情况下,您将使用的工作流看起来像这样:

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

为什么会这样

在你执行上面的命令之后,你最终会得到一个修订历史,看起来像这样:

( revision history )            ( files )

    ORIG_HEAD                      foo
     /     \                      /   \
SAVED       ALTERNATE          bar     copy
     \     /                      \   /
      MERGED                     bar,copy
        |                           |
     RESTORED                    bar,foo

当你向Git询问foo的历史时,它会:

在merge和RESTORED之间检测副本的重命名, 检测副本来自merge的ALTERNATE父类,并且 检测foo在ORIG_HEAD和ALTERNATE之间的重命名。

从那里,它将深入到foo的历史。

当你向Git询问酒吧的历史时,它会:

注意到MERGED和RESTORED之间没有变化, 检测bar来自MERGED的SAVED父节点,并且 检测foo在ORIG_HEAD和SAVED之间的重命名。

从那里,它将深入到foo的历史。

就是这么简单。:)

您只需要强制Git进入合并状态,这样您就可以接受文件的两个可跟踪副本,我们通过并行移动原始文件(很快就会恢复)来实现这一点。


简单地复制文件,添加并提交:

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 blame和Git log。

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


我在这里稍微修改了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"

这个过程保存了历史,但没有什么变通办法:

# 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