【问题标题】:Git copy file preserving history [duplicate]Git复制文件保存历史[重复]
【发布时间】:2013-06-01 00:02:19
【问题描述】:

我在 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 可以在这里提供什么。

【问题讨论】:

    标签: git file copy filenames


    【解决方案1】:

    你所要做的就是:

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

    您将能够查看这两个文件的历史归属(使用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 的历史时,它会:

    1. 在 MERGED 和 RESTORED 之间检测 copy 的重命名,
    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 进入合并状态,您可以接受文件的两个可追踪副本,我们通过并行移动原始文件(我们很快会恢复)来做到这一点。

    【讨论】:

    • 这似乎不起作用,至少不适用于 git 2.9。我必须使用--follow-C 标志,以便git 将bar 跟踪到它的foo 起源。 cp foo bar && git add bar && git commit 给出了相同的最终结果,但没有奇怪的历史。我是不是做错了什么?
    • @peter-dillinger,很好的解决方法!我在stackoverflow.com/a/46484848/1389680 中使它更具可读性。
    • 这是一个巧妙的解决方案,但它是一个有趣的使用“简单”这个词来描述这个十步解决方法,因为在任何系统中都没有原子操作,旨在跟踪合法可复制事物的历史。
    • 如果您预计曾经想要/需要在这些提交上使用 git rebase,请注意这种方法。当我尝试这种保留历史的方法时,git 认为这种方法所做的提交在 rebase 期间相互冲突,需要手动合并。冲突解决过程最终丢失了我最初尝试保存的提交历史记录。
    • 我记得过去这对我有用。但目前还没有。来自合并分支的文件从合并提交中获得其历史“起点”。在 Windows 7 上尝试了几个 GIT 版本,包括 2.24.0。也尝试使用来自@LukasEder 的脚本。结果相同。
    【解决方案2】:

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

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

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

    【讨论】:

    • 我在pastebin.com/zEREyeaL 上的可重现示例表明git blame 也知道重命名历史 - 无需使用任何选项。这不是告诉我们历史是以某种方式存储的吗?
    • @DanielAlder No. 像git diff -M 这只是对树对象的智能分析。来自 git blame 手册页:“在整个文件重命名时会自动跟踪行的起源(目前没有关闭重命名跟踪的选项)。”
    • 那为什么git mv会存在呢?
    • @skirsch 方便
    • 与 Mercurial 不同。 Mercurial 有历史保存副本。
    【解决方案3】:

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

    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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 很好的解决方案。我在使用包含空格的文件时遇到了问题,我修改了你的代码来解决这个问题。
      • hm,使用 git v2.17.1 这给我留下了一个新提交的文件 $2 - 这种方法仍然适合你吗?
      • hm,即使v2.1.4复制文件的日志文件是空的..
      • @frans:当时有效。如果您看到任何改进,请随时进行编辑...
      • 尚未找到解决方案。恐怕这种方法不再奏效了。
      【解决方案5】:

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

      git mv old new
      git checkout HEAD old
      

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

      git clean -fdx new
      

      【讨论】:

      • 据我所见,第一个命令将不会复制不受控制的文件(但会移动它们),如果使用 ' 删除它们,移动它们有什么意义之后清理'命令?
      • @hans_meine 你说得对,还不如先打扫干净再走。
      • 当我这样做时,只有原始文件与历史保持连接,副本被认为是具有新历史的新文件。这没有回答问题:(
      【解决方案6】:

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

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

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

      【讨论】:

        【解决方案7】:

        此过程保留历史记录,但几乎没有解决方法:

        # 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
        

        【讨论】:

          猜你喜欢
          • 2013-08-09
          • 2019-05-05
          • 1970-01-01
          • 1970-01-01
          • 2018-10-28
          • 1970-01-01
          • 2014-09-25
          • 2017-11-30
          • 1970-01-01
          相关资源
          最近更新 更多