首页 文章

是否可以在Git中移动/重命名文件并保留其历史记录?

提问于
浏览
567

我想在Git中重命名/移动项目子树

/project/xyz

/components/xyz

如果我使用普通 git mv project components ,那么 xyz project 的所有提交历史都会丢失 . 有没有办法移动这个以保持历史?

9 回答

  • 13

    我移动文件,然后做

    git add -A
    

    将所有删除/新文件放入sataging区域 . 这里git意识到文件被移动了 .

    git commit -m "my message"
    git push
    

    我不知道为什么但这对我有用 .

  • 2

    Git检测重命名而不是使用提交持久化操作,因此无论使用 git mv 还是 mv 无关紧要 .

    log 命令采用 --follow 参数,该参数在重命名操作之前继续历史记录,即,它使用启发式方法搜索类似的内容:

    http://git-scm.com/docs/git-log

    要查找完整历史记录,请使用以下命令:

    git log --follow ./path/to/file
    
  • -1

    虽然可以在整个存储库历史记录中重命名文件,但可以重命名文件并保持历史记录不变 . 这可能只适用于痴迷的git-log爱好者,并且有一些严重的影响,包括:

    • 你可能正在重写一个共享历史记录,这是最重要的事情,这样做会打破它 . 他们将不得不重新克隆以避免头痛 . 如果重命名非常重要,这可能没问题,但是您需要仔细考虑这一点 - 最终可能会破坏整个开源社区!

    • 如果您在存储库历史记录中早先使用了've referenced the file using it'的旧名称,那么're effectively breaking earlier versions. To remedy this, you'将不得不做更多的跳跃 . 这不是不可能的,只是单调乏味,可能不值得 .

    现在,因为你're still with me, you'可能是个人开发者重命名完全隔离的文件 . 让我们使用 filter-tree 移动文件!

    假设您要将文件 old 移动到文件夹 dir 并将其命名为 new

    这可以通过 git mv old dir/new && git add -u dir/new 完成,但这打破了历史 .

    代替:

    git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD
    

    将重做分支中的每个提交,在每次迭代的ticks中执行命令 . 当你这样做时,很多东西都可能出错 . 我通常会测试文件是否存在(否则它还没有移动)然后执行必要的步骤来按照自己的喜好对树进行修剪 . 在这里,您可以通过文件来修改对文件的引用,等等 . 把自己打昏! :)

    完成后,文件将被移动并且日志完好无损 . 你觉得自己像个忍者海盗 .

    也;只有将文件移动到新文件夹时才需要mkdir目录 . if将避免在历史记录中早于文件存在而创建此文件夹 .

  • 79

    简短的回答是 NO ,无法在Git中重命名文件并记住历史记录 . 这是一种痛苦 .

    有传言说 git log --follow --find-copies-harder会起作用但它对我不起作用,即使文件内容没有变化,并且已经用git mv进行了移动 .

    (最初我使用Eclipse在一个操作中重命名和更新包,这可能让git感到困惑 . 但这是非常常见的事情 . 如果只执行 mv 然后 commitmv 不是,那么 --follow 似乎确实有效太远 . )

    Linus说,您应该全面了解软件项目的全部内容,而不需要跟踪单个文件 . 好吧,可悲的是,我的小脑子不能这样做 .

    很多人都盲目地重复了git自动跟踪动作的说法,这是 really annoying . 他们浪费了我的时间 . Git没有这样的事情 . By design(!) Git does not track moves at all.

    我的解决方案是将文件重命名回原始位置 . 更改软件以适合源控件 . 使用git,你似乎需要第一次正确使用git .

    不幸的是,这破坏了Eclipse,它似乎使用 --follow .
    git log --follow有时不会显示具有复杂重命名历史的文件的完整历史记录,即使 git log 也是如此 . (我不知道为什么 . )

    (有一些太聪明的黑客可以追溯并重新开始做旧工作,但它们相当令人恐惧 . 请参阅GitHub-Gist:emiller/git-mv-with-history . )

  • 580
    git log --follow [file]
    

    将通过重命名向您显示历史记录 .

  • 17

    我做:

    git mv {old} {new}
    git add -u {new}
    
  • 70

    目标

    • 使用git am(灵感来自Smar,借鉴Exherbo)

    • 添加复制/移动文件的提交历史记录

    • 从一个目录到另一个目录

    • 或从一个存储库到另一个存储库

    限制

    • 不保留标签和分支

    • 历史记录被剪切在路径文件重命名(目录重命名)

    摘要

    • 使用以电子邮件格式提取历史记录
      git log --pretty=email -p --reverse --full-index --binary

    • 重新组织文件树并更新文件名

    • 使用添加新历史记录
      cat extracted-history | git am --committer-date-is-author-date


    1.以电子邮件格式提取历史记录

    示例:提取 file3file4file5 的历史记录

    my_repo
    ├── dirA
    │   ├── file1
    │   └── file2
    ├── dirB            ^
    │   ├── subdir      | To be moved
    │   │   ├── file3   | with history
    │   │   └── file4   | 
    │   └── file5       v
    └── dirC
        ├── file6
        └── file7
    

    设置/清除目的地

    export historydir=/tmp/mail/dir       # Absolute path
    rm -rf "$historydir"    # Caution when cleaning the folder
    

    以电子邮件格式提取每个文件的历史记录

    cd my_repo/dirB
    find -name .git -prune -o -type d -o -exec bash -c 'mkdir -p "$historydir/${0%/*}" && git log --pretty=email -p --stat --reverse --full-index --binary -- "$0" > "$historydir/$0"' {} ';'
    

    不幸的是,选项 --follow--find-copies-harder 不能与 --reverse 结合使用 . 这就是重命名文件时(或重命名父目录时)切断历史记录的原因 .

    电子邮件格式的临时历史记录:

    /tmp/mail/dir
        ├── subdir
        │   ├── file3
        │   └── file4
        └── file5
    

    Dan Bonachea建议在第一步中反转git log generation命令的循环:不是每个文件运行一次git log,而是在命令行上使用一个文件列表运行一次,并生成一个统一的日志 . 这种方式提交修改多个文件仍然是结果中的单个提交,并且所有新提交都保持其原始相对顺序 . 请注意,在(现在统一)日志中重写文件名时,还需要在下面的第二步中进行更改 .


    2.重新组织文件树并更新文件名

    假设您要在这个其他仓库中移动这三个文件(可以是相同的仓库) .

    my_other_repo
    ├── dirF
    │   ├── file55
    │   └── file56
    ├── dirB              # New tree
    │   ├── dirB1         # from subdir
    │   │   ├── file33    # from file3
    │   │   └── file44    # from file4
    │   └── dirB2         # new dir
    │        └── file5    # from file5
    └── dirH
        └── file77
    

    因此,重新组织您的文件:

    cd /tmp/mail/dir
    mkdir -p dirB/dirB1
    mv subdir/file3 dirB/dirB1/file33
    mv subdir/file4 dirB/dirB1/file44
    mkdir -p dirB/dirB2
    mv file5 dirB/dirB2
    

    您的临时历史现在是:

    /tmp/mail/dir
        └── dirB
            ├── dirB1
            │   ├── file33
            │   └── file44
            └── dirB2
                 └── file5
    

    更改历史记录中的文件名:

    cd "$historydir"
    find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'
    

    3.应用新历史记录

    你的另一个回购是:

    my_other_repo
    ├── dirF
    │   ├── file55
    │   └── file56
    └── dirH
        └── file77
    

    从临时历史文件中应用提交:

    cd my_other_repo
    find "$historydir" -type f -exec cat {} + | git am --committer-date-is-author-date
    

    --committer-date-is-author-date 保留原始提交时间戳(Dan Bonachea的评论) .

    你的其他回购现在是:

    my_other_repo
    ├── dirF
    │   ├── file55
    │   └── file56
    ├── dirB
    │   ├── dirB1
    │   │   ├── file33
    │   │   └── file44
    │   └── dirB2
    │        └── file5
    └── dirH
        └── file77
    

    使用 git status 查看准备推送的提交量:-)


    额外技巧:检查您的仓库中重命名/移动的文件

    列出已重命名的文件:

    find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'
    

    更多自定义:您可以使用选项 --find-copies-harder--reverse 完成命令 git log . 您还可以使用 cut -f3- 和grepping完整模式'{.* => .*}'删除前两列 .

    find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'
    
  • 4

    我想在Git中重命名/移动项目子树,从/ project / xyz移动它
    to / components / xyz如果我使用普通的git mv项目组件,那么xyz项目的所有提交历史都会丢失 .

    不(8年后,Git 2.19,Q8 2018),因为Git将会 detect the directory rename ,现在这个记录更好了 .

    请参见commit b00bf1ccommit 1634688commit 0661e49commit 4d34dffcommit 983f464commit c840e1acommit 9929430(2018年6月27日)和commit d4e8062commit 5dacd4a(2018年6月25日)Elijah Newren (newren) .
    (由Junio C Hamano合并 - gitster - 提交0ce5a69,2018年7月24日)

    现在在_821652中解释了这个问题:

    例:

    当x / a,x / b和x / c全部移动到z / a,z / b和z / c时,同时添加的x / d也可能想要移动到z / d通过提示整个目录'x'移动到'z' .

    但它们还有很多其他情况,例如:

    历史记录的一侧重命名x - > z,另一侧重命名某个文件为x / e,导致合并需要进行传递重命名 .

    为简化目录重命名检测,这些规则由Git强制执行:

    当目录重命名检测适用时,一些基本规则限制:

    如果给定目录仍然存在于合并的两侧,我们不认为它已被重命名 . 如果要重命名的文件的子集在路上有一个文件或目录(或者会相互阻塞),则“关闭”这些特定子路径的目录重命名并向用户报告冲突 . 如果历史记录的另一端将目录重命名为您的历史记录方重命名的路径,则忽略历史记录另一侧的特定重命名以进行任何隐式目录重命名(但警告用户) .

    您可以在t/t6043-merge-rename-directories.sh中看到很多测试,这也指出:

    a)如果重命名将目录拆分为两个或更多其他目录,则重命名最多的目录为“wins” . b)如果路径是合并任一侧的重命名源,则避免对路径进行目录重命名检测 . c)如果历史记录的另一侧是重命名的目录,则仅将隐式目录重命名应用于目录 .

  • 39

    虽然git的核心是git管道没有跟踪重命名,但是如果你愿意的话,使用git log“porcelain”显示的历史可以检测到它们 .

    对于给定的 git log ,请使用-M选项:

    git log -p -M

    使用当前版本的git .

    这适用于其他命令,如 git diff .

    有一些选项可以使比较或多或少严格 . 如果重命名文件而不同时对文件进行重大更改,则git日志和朋友可以更轻松地检测重命名 . 出于这个原因,有些人在一次提交中重命名文件,在另一次提交中更改它们 .

    每当你要求git找到重命名文件的位置时,使用cpu就会有成本,所以无论你是否使用它,以及什么时候取决于你 .

    如果您希望始终在特定存储库中使用重命名检测报告您的历史记录,则可以使用:

    git config diff.renames 1

    检测到从一个目录移动到另一个目录的文件 . 这是一个例子:

    commit c3ee8dfb01e357eba1ab18003be1490a46325992
    Author: John S. Gruber <JohnSGruber@gmail.com>
    Date:   Wed Feb 22 22:20:19 2017 -0500
    
        test rename again
    
    diff --git a/yyy/power.py b/zzz/power.py
    similarity index 100%
    rename from yyy/power.py
    rename to zzz/power.py
    
    commit ae181377154eca800832087500c258a20c95d1c3
    Author: John S. Gruber <JohnSGruber@gmail.com>
    Date:   Wed Feb 22 22:19:17 2017 -0500
    
        rename test
    
    diff --git a/power.py b/yyy/power.py
    similarity index 100%
    rename from power.py
    rename to yyy/power.py
    

    请注意,无论何时使用diff,这都有效,而不仅仅是 git log . 例如:

    $ git diff HEAD c3ee8df
    diff --git a/power.py b/zzz/power.py
    similarity index 100%
    rename from power.py
    rename to zzz/power.py
    

    作为试验,我在功能分支中的一个文件中做了一个小的更改并提交了它,然后在主分支中我重命名了文件,提交了,然后在文件的另一部分做了一个小的更改并提交了 . 当我进入功能分支并从主服务器合并时,合并重命名了该文件并合并了更改 . 这是合并的输出:

    $ git merge -v master
     Auto-merging single
     Merge made by the 'recursive' strategy.
      one => single | 4 ++++
      1 file changed, 4 insertions(+)
      rename one => single (67%)
    

    结果是一个工作目录,文件重命名和两个文本更改 . 因此git可以做正确的事情,尽管它没有明确跟踪重命名 .

    这是对旧问题的迟到答案,因此其他答案可能对于当时的git版本是正确的 .

相关问题