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

是否可以在Git中移动/重命名文件并维护其历史记录?,git,rename,mv,Git,Rename,Mv,我想重命名/移动Git中的项目子树,将其从 /project/xyz /project/xyz 到 如果我使用一个普通的gitmv项目组件,那么xyz项目的所有提交历史都会丢失。有没有一种方法可以让历史得以保存 git log --follow [file] 将通过重命名向您显示历史记录。Git检测重命名,而不是通过提交来持久化操作,因此您是否使用Git mv或mv并不重要 log命令接受一个--follow参数,该参数在重命名操作之前继续历史记录,即,它使用启发式搜索类似内容: 要查找

我想重命名/移动Git中的项目子树,将其从

/project/xyz
/project/xyz

如果我使用一个普通的
gitmv项目组件
,那么
xyz项目的所有提交历史都会丢失。有没有一种方法可以让历史得以保存

git log --follow [file]

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

Git检测重命名,而不是通过提交来持久化操作,因此您是否使用
Git mv
mv
并不重要

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

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

git log --follow ./path/to/file
我有:

git mv {old} {new}
git add -u {new}
可以重命名文件并保持历史记录完整,尽管这会导致在存储库的整个历史记录中重命名文件。这可能只适用于痴迷于git日志的人,并且有一些严重的影响,包括:

  • 您可能正在重写共享历史,这是使用Git时最重要的一点。如果其他人克隆了存储库,您将通过此操作破坏它。他们将不得不重新克隆以避免头痛。如果重命名是很重要的,这可能是好的,但是你需要仔细考虑这一点——你可能会扰乱整个开源社区!李>
  • 如果您在存储库历史记录的早期使用文件的旧名称引用了该文件,则实际上是在破坏早期版本。要解决这个问题,你必须多跳一点篮球。这不是不可能的,只是单调乏味,可能不值得
现在,既然你还和我在一起,你可能是一个单独的开发人员,正在重命名一个完全隔离的文件。让我们使用
过滤器树移动文件

假设您要将一个文件
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
将重做分支中的每个提交,在每个迭代的记号中执行命令。当你这样做的时候,很多事情都会出错。我通常会测试文件是否存在(否则它还没有移动),然后执行必要的步骤将树固定到我喜欢的位置。在这里,您可以浏览文件来更改对文件的引用,等等。把你自己击倒!:)

完成后,文件将被移动,日志将保持完整。你觉得自己像个忍者海盗

还有,;当然,只有将文件移动到新文件夹时,才需要mkdir。if将避免在您的文件存在之前创建此文件夹。

否。 简而言之,答案是否定的。在Git中重命名文件并记住历史记录是不可能的。这是一种痛苦

有传言说,
git log--follow
会起作用,但它对我不起作用,即使文件内容没有任何更改,而且移动是用

(最初我使用Eclipse在一个操作中重命名和更新包,这可能会混淆Git。但这是一个非常常见的操作。
--follow
似乎可以工作,如果只执行
mv
,然后执行
commit
,并且
mv
并不太远。)

Linus说,您应该全面地理解软件项目的全部内容,而不需要跟踪单个文件。唉,可悲的是,我的小脑袋做不到这一点

有那么多人无意识地重复Git自动跟踪移动的说法,这真的很烦人。他们浪费了我的时间。Git不会做这样的事情

我的解决方案是将文件重命名回其原始位置。更改软件以适合源代码管理。有了Git,你似乎只需要第一次“Git”就可以了

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

(有一些太聪明的黑客回到过去重新提交旧的工作,但它们相当可怕。参见GitHub要点:)

简而言之:如果是错误的,那么Git这样做也是错误的-这样做不是什么(错误!)特性,这是一个错误。

是的
  • 您可以使用git log--pretty=email将文件的提交历史记录转换为电子邮件修补程序
  • 您可以在新目录中重新组织这些文件并重命名它们
  • 您可以将这些文件(电子邮件)转换回Git提交,以使用保存历史记录
  • 限度
    • 不保留标签和分支
    • 在路径文件重命名(目录重命名)时剪切历史记录

    用例子一步一步地解释 1.以电子邮件格式提取历史记录 示例:提取
    file3
    file4
    file5

    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"' {} ';'
    
    不幸的是,选项<代码>--跟随<代码>或<代码>--查找更难的副本<代码>不能与<代码>--反向<代码>组合使用。这就是重命名文件(或重命名父目录)时剪切历史记录的原因

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

    /tmp/mail/dir
        ├── subdir
        │   ├── file3
        │   └── file4
        └── file5
    
    建议在第一步中反转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"' {} ';'
    
    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
    
    my_other_repo
    ├── dirF
    │   ├── file55
    │   └── file56
    ├── dirB
    │   ├── dirB1
    │   │   ├── file33
    │   │   └── file44
    │   └── dirB2
    │        └── file5
    └── dirH
        └── file77
    
    find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'
    
    find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'
    
    git add -A
    
    git commit -m "my message"
    git push
    
    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
    
    $ 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%)
    
    /project/xyz
    
    git filter-repo --path-rename OLD_NAME:NEW_NAME
    
    git filter-repo --replace-text expressions.txt
    
    git-filter-repo --message-callback 'return message.replace(b"OLD_NAME", b"NEW_NAME")'
    
    git filter-repo --path-rename ParentFolder/FolderwithContentOfInterest/:FolderwithContentOfInterest/ --force
    
    git remote add origin git@github.com:MyCompany/MyRepo.git
    
    git pull
    
    git branch --set-upstream-to=origin/history history
    
    git push
    
    git add .
    
    git status
    
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
            renamed:    old-folder/file.txt -> new-folder/file.txt