211

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

Now I need 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.

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

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 to Git and curious about what Git can provide here.

4

7 回答 7

202

你所要做的就是:

  1. 将文件移动到两个不同的位置,
  2. 合并执行上述操作的两个提交,并且
  3. 将一份副本移回原始位置。

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

假设您要创建一个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

为什么这有效

执行上述命令后,您最终会得到如下所示的修订历史记录:

( revision history )            ( files )

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

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

  1. copy从MERGED 和 RESTORED 之间检测重命名,
  2. 检测到copy来自 MERGED 的 ALTERNATE 父级,并且
  3. foo从ORIG_HEAD 和 ALTERNATE 之间检测重命名。

从那里它将深入挖掘历史foo

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

  1. 注意 MERGED 和 RESTORED 之间没有变化,
  2. 检测到bar来自 MERGED 的 SAVED 父级,并且
  3. foo从ORIG_HEAD 和 SAVED 之间检测重命名。

从那里它将深入挖掘历史foo

就是这么简单。:)

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

于 2017-05-18T00:31:32.010 回答
85

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

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

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

于 2013-06-05T10:43:11.433 回答
37

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

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

于 2017-06-15T11:39:10.733 回答
21

我在这里稍微修改了彼得的答案,以创建一个可重用的非交互式 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"
于 2018-12-19T10:49:04.350 回答
10

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

git mv old new
git checkout HEAD old

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

git clean -fdx new
于 2014-01-29T11:29:14.920 回答
2

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

我确实注意到,因为我一次进行了大量更改,SourceTree 检测所有更改的速度有点慢,所以我的一些暂存文件看起来只是添加(绿色加号)或只是删除(红色减号),但我不断刷新文件状态并在最终弹出时不断进行新的更改,几分钟后,整个列表就完美了,可以提交了。

我验证了历史记录是否存在,只要在查找历史记录时,我就会选中“关注重命名的文件”选项。

于 2017-05-16T18:47:39.183 回答
0

这个过程保留了历史,但几乎没有解决方法:

# 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
于 2019-09-18T14:45:20.467 回答