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

sga*_*gan 630 git rename mv

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

/project/xyz
Run Code Online (Sandbox Code Playgroud)

/components/xyz
Run Code Online (Sandbox Code Playgroud)

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

Tro*_*sen 622

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

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

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

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

git log --follow ./path/to/file
Run Code Online (Sandbox Code Playgroud)

  • 这个答案有点误导.Git确实"检测重命名",但在游戏中很晚; 问题是询问你如何确保Git轨道重命名,而阅读此内容的人可以很容易地推断出Git会自动检测到它并记下它.它不是.Git没有真正的重命名处理,而是有合并/日志工具试图弄清楚发生了什么 - 并且很少做对.Linus有一个错误但激烈的论点,为什么git永远不应该以正确的方式去做并明确地跟踪重命名.所以,我们被困在这里. (119认同)
  • 我怀疑这是一个性能考虑因素.如果您不需要完整的历史记录,则扫描内容肯定会花费更长的时间.最简单的方法是设置一个别名`git config alias.logf"log --follow"`并编写`git logf./ path/to/file`. (60认同)
  • 虽然Linus可能犯了很少的错误,但这似乎确实是一个错误.只需重命名文件夹就可以将大量增量上传到GitHub.这使我对重命名我的文件夹持谨慎态度......但对于程序员来说,这是一个相当大的直接夹克.偶尔,我必须重新定义某事物的含义,或改变事物的分类方式.Linus:"换句话说,我是对的.我总是对的,但有时我比其他时候更正确.而且,当我说'文件无所谓'时,我真的很对( Tm值)." ......我怀疑那个. (41认同)
  • 重要提示:如果重命名目录,例如在重命名Java包期间,请确保执行两次提交,首先执行'git mv {old} {new}'命令,第二次执行所有引用Java文件的Java文件的更新更改了包目录.否则,即使使用--follow参数,git也无法跟踪单个文件. (29认同)
  • @TroelsThomsen [本电子邮件](http://article.gmane.org/gmane.comp.version-control.git/217)由Linus Torvalds撰写,链接自[此答案](http://stackoverflow.com/ a/10130671/833844),表明它是Git的故意设计选择,因为它据称比跟踪重命名等更强大. (13认同)
  • 不需要--follow会很好,但我想这将是必须的.感谢您的回答. (10认同)
  • @GarretWilson:如果文件的至少50%仍然相同,`git log --follow`将遵循历史记录.您可以随时更改该百分比(使用`-M`),因为它是跟随相似性的`git log`,而不是一般的磁盘上的git.(通常不可能解决重命名+编辑应被视为"同一文件".) (5认同)
  • @GabeHalsmer:简单地重命名文件夹不会导致大量增量(除非包含其他更改,例如对文件中文件夹名称的引用).当然,有可能GitHub _shows_重命名为一个巨大的三角洲. (5认同)
  • 所以我从Subversion转向Git,我想澄清一些事情.我知道Git没有添加任何文件重命名元数据.我得到'git log`将使用启发式方法来猜测文件移动的时间.我知道,如果我在Git中移动文件,我应该在修改文件之前提交移动,以帮助Git在将来跟踪历史记录.但我对现实场景有疑问:当我移动某些内容时,我通常会重构源代码,因此例如.java文件不会只是移动---至少指示包将会移动更改.这会阻止'git log'跟踪历史记录吗? (4认同)
  • 您应该只使用新路径.它的工作方式是Git遍历历史记录并在检测到重命名时切换到旧路径. (3认同)
  • 请注意,"git log --follow"仅适用于单个文件. (2认同)
  • 我不认为你在所有情况下都使用`git mv`或只是`mv`并不重要.简单的重命名或移动似乎被检测到很好,但例如将文件移动到子文件夹(是的,有理由这样做)在我的情况下没有被检测到. (2认同)
  • 我看到其他人问了同样的问题:http://stackoverflow.com/q/3520023/421049.但到目前为止答案尚不清楚. (2认同)
  • 一场很长的对话正在进行,但只是对其中一条评论的评论:如果您重命名一个目录,则不会涉及_massive_ delta。它实际上意味着**树对象**将被**重用**并放置在构成以下修订版的树中的不同位置。当然,差异可能是_巨大_(实际上你会在差异中得到很多重命名,至少现在是这样),但这并不意味着它实际上是发送到 github 的重命名文件大小的两倍与推操作。 (2认同)

Øys*_*ler 90

可能重命名文件,并保持完好的历史,但它会导致整个仓库的整个历史要重命名的文件.这可能只适用于强迫性的git-log爱好者,并且有一些严重的影响,包括:

  • 您可以重写共享历史记录,这是使用Git时最重要的事情.如果有其他人克隆了存储库,那么你就会破坏它.他们将不得不重新克隆以避免头痛.如果重命名非常重要,这可能没问题,但是你需要仔细考虑这一点 - 你最终可能会破坏整个开源社区!
  • 如果您在存储库历史记录中使用它的旧名称引用了该文件,那么您实际上是在破坏早期版本.为了解决这个问题,你必须做更多的箍跳.这不是不可能的,只是单调乏味,可能不值得.

现在,既然你还在和我在一起,那么你可能是一个独立开发人员重命名一个完全孤立的文件.让我们使用移动文件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
Run Code Online (Sandbox Code Playgroud)

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

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

也; 仅当您将文件移动到新文件夹时,才需要mkdir目录.该如果将避免该文件夹的创建较早的历史比你的文件存在.

  • 作为一个痴迷的git-log-lover,我不会这样做.这些文件没有在那些时间点被命名,因此历史反映了一种永不存在的情况.谁知道过去哪些测试可能会破坏!打破早期版本的风险几乎在每种情况下都不值得. (54认同)
  • @Vincent你是完全正确的,我试图尽可能清楚这个解决方案的不可靠性.在这种情况下,我也认为我们正在讨论"历史"这个词的两个含义,我对此表示赞赏. (7认同)
  • 我发现有些人可能需要这个.假设我在自己的个人分支中开发了一些东西,我现在想在上游合并.但我发现,文件名不合适,所以我将其更改为我的整个个人分支.通过这种方式,我可以保持一个干净的正确历史,并从一开始就拥有正确的名称. (5认同)
  • @ user2291758这是我的用例.这些更强大的git命令是危险的,但这并不意味着如果你知道自己在做什么,它们就没有非常引人注目的用例! (2认同)
  • 如果可能的话,使用“--index-filter”进行重命名会更快,因为不必在每次提交时都检出并返回树。`--index-filter` 直接作用于每个提交索引。 (2认同)
  • @MattiJokipii:`mv` 命令用于在整个存储库历史记录中的每次提交之前移动文件,因此使用普通的 unix `mv` 是正确的。我什至不确定如果你使用 `git mv` 会发生什么。如果您使用的是 Windows,则应该使用 `move` 命令。 (2认同)

Tun*_*ble 81

没有.

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

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

(最初我使用Eclipse在一个操作中重命名和更新包,这可能会让git感到困惑.但这是一件非常常见的事情.--follow如果只mv执行a然后执行a commit并且mv不是太远就会起作用.)

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

这是真的很烦,所以很多人都盲目地重复声明git的自动追踪移动.他们浪费了我的时间.Git没有这样的事情.按设计(!)Git根本不跟踪移动.

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

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

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

  • git -1,subversion +1 (5认同)
  • `git log --follow` 对我有用,但前提是 `git mv` 将文件移动到未跟踪的位置。如果您尝试执行“rm a.txt && git mv b.txt a.txt”,那么 b.txt 的历史记录将被破坏。如果你想让“git log --follow”工作,你必须先“git rm a.txt”然后提交,然后“git mv b.txt a.txt”。 (3认同)
  • 我相信你是对的。我只是想使用php-cs-fixer重新格式化Laravel 5项目的源,但它坚持要更改名称空间子句的大小写以匹配app文件夹的小写值。但是名称空间(或作曲家自动加载)仅适用于CamelCase。我需要将文件夹的大小写更改为App,但这会使我的更改丢失。这是最琐碎的示例,但显示了git启发式方法如何也无法遵循最简单的名称更改(--follow和--find-copies-harder应该是规则,而不是例外)。 (2认同)
  • 这仍然是真的吗?这也是我现在继续使用 tfs 的更多原因,在大型项目中必须保留移动/重命名文件的历史记录。 (2认同)
  • 简短的回答是肯定的。Git 当前版本也支持“git log --follow”。我同意@MohammadDehghan (2认同)

Eri*_*ink 41

git log --follow [file]
Run Code Online (Sandbox Code Playgroud)

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

  • 看来这需要您在开始修改文件之前只提交重命名.如果您移动文件(在shell中)然后更改它,则所有投注都将关闭. (26认同)
  • @yoyo:那是因为git不跟踪重命名,它会检测到它们.`git mv`基本上是`git rm && git add`.有一些选项,如`-M90` /`--find-renames = 90`,可以考虑在90%相同的情况下重命名文件. (21认同)

Jam*_*ene 18

我做:

git mv {old} {new}
git add -u {new}
Run Code Online (Sandbox Code Playgroud)

  • 我做了一个复杂的重构,移动了一个include目录(使用mv,而不是git mv),然后在重命名的文件中更改了许多#include路径。git找不到足够的相似性来跟踪历史记录。但是git add -u只是我需要的东西。git status现在指示“重命名”,之前显示“已删除”和“新文件”。 (3认同)
  • -u似乎对我没有任何作用,它是否想更新历史记录? (2认同)
  • SO 有很多问题涉及“git add -u”的目的。Git 文档往往没有什么帮助,也是我最不想看的地方。这是一篇展示“git add -u”实际操作的帖子:http://stackoverflow.com/a/2117202。 (2认同)

Von*_*onC 16

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

/project/xyz
Run Code Online (Sandbox Code Playgroud)

/组件/ XYZ

如果我使用plain git mv project components,那么项目的所有提交历史xyz都会丢失.

不(8年后,Git 2.19,Q8 2018),因为Git会检测目录重命名,现在这个记录更好了.

请参阅提交b00bf1c,提交1634688,提交0661e49,提交4d34dff,提交983f464,提交c840e1a,提交9929430(2018年6月27日),并提交d4e8062,提交5dacd4a(2018年6月25日),由Elijah Newren(newren)提交.
(由Junio C gitsterHamano合并- -提交0ce5a69,2017年7月24日)

现在解释Documentation/technical/directory-rename-detection.txt如下:

例:

当所有的x/a,x/b并且x/c已经移动到z/a,z/b并且z/c,x/d在此期间添加的可能性也可能z/d通过采取整个目录' x'移动到' z' 的提示来移动.

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

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

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

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

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

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

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


oli*_*bre 15

目的

  • 使用(灵感来自Smar,借鉴Exherbo)git am
  • 添加复制/移动文件的提交历史记录
  • 从一个目录到另一个目录
  • 或从一个存储库到另一个存储库

局限性

  • 不保留标签和分支
  • 在路径文件重命名(目录重命名)上剪切历史记录

摘要

  1. 使用以电子邮件格式提取历史记录
    git log --pretty=email -p --reverse --full-index --binary
  2. 重新组织文件树并更新文件名
  3. 使用附加新历史记录
    cat extracted-history | git am --committer-date-is-author-date

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

例如:提取的历史file3,file4file5

my_repo
??? dirA
?   ??? file1
?   ??? file2
??? dirB            ^
?   ??? subdir      | To be moved
?   ?   ??? file3   | with history
?   ?   ??? file4   | 
?   ??? file5       v
??? dirC
    ??? file6
    ??? file7
Run Code Online (Sandbox Code Playgroud)

设置/清除目的地

export historydir=/tmp/mail/dir       # Absolute path
rm -rf "$historydir"    # Caution when cleaning the folder
Run Code Online (Sandbox Code Playgroud)

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

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"' {} ';'
Run Code Online (Sandbox Code Playgroud)

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

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

/tmp/mail/dir
    ??? subdir
    ?   ??? file3
    ?   ??? file4
    ??? file5
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

因此重新组织您的文件:

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
Run Code Online (Sandbox Code Playgroud)

您的临时历史记录现在是:

/tmp/mail/dir
    ??? dirB
        ??? dirB1
        ?   ??? file33
        ?   ??? file44
        ??? dirB2
             ??? file5
Run Code Online (Sandbox Code Playgroud)

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

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

3.应用新历史记录

你的其他回购是:

my_other_repo
??? dirF
?   ??? file55
?   ??? file56
??? dirH
    ??? file77
Run Code Online (Sandbox Code Playgroud)

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

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am --committer-date-is-author-date
Run Code Online (Sandbox Code Playgroud)

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

你的其他回购现在是:

my_other_repo
??? dirF
?   ??? file55
?   ??? file56
??? dirB
?   ??? dirB1
?   ?   ??? file33
?   ?   ??? file44
?   ??? dirB2
?        ??? file5
??? dirH
    ??? file77
Run Code Online (Sandbox Code Playgroud)

使用git status看量的承诺准备推:-)


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

列出已重命名的文件:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'
Run Code Online (Sandbox Code Playgroud)

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

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'
Run Code Online (Sandbox Code Playgroud)

  • 注意:这种技术将提交的内容分成两个或多个文件转换为单独的碎片提交,然后通过对文件名进行排序来扰乱它们的顺序(因此一个原始提交的片段在线性历史记录中不会出现相邻).因此,生成的历史记录仅在逐个文件的基础上"正确".如果要移动多个文件,则生成的历史记录中的NONE新提交表示原始存储库历史记录中存在的已移动文件的一致快照. (4认同)
  • 嗨@DanBonachea。感谢您的有趣反馈。我已经使用此技术成功迁移了一些包含几个文件的存储库(即使使用重命名的文件和跨目录移动的文件)。您建议对此答案进行哪些更改。您是否认为我们应该在此答案的顶部添加一个警告标语,以说明该技术的局限性?干杯 (2认同)
  • 我通过在步骤1中反转git log generation命令的循环来适应该技术,从而避免了该问题。与其对每个文件运行一次git log,不如在命令行上使用文件列表对它运行一次,然后生成一个统一的日志。这样,修改2个或更多文件的提交将在结果中保留为单个提交,并且所有新提交均保持其原始相对顺序。请注意,在(现在已统一)日志中重写文件名时,这也需要在步骤2中进行更改。我还使用了git am --committer-date-is-author-date来保留原始的提交时间戳。 (2认同)
  • 感谢您的实验和分享。我已经为其他读者更新了一些答案。不过我花了一些时间来测试你的处理过程。如果您想提供命令行示例,请随时编辑此答案。干杯;) (2认同)

小智 10

我遇到了“在不丢失历史记录的情况下重命名文件夹”的问题。要修复它,请运行:

$ git mv oldfolder temp && git mv temp newfolder
$ git commit
$ git push
Run Code Online (Sandbox Code Playgroud)

  • 为什么这比“git mv oldfolder newfolder”更好? (4认同)
  • 这应该标记为正确答案。将文件从一个文件夹移动到同一存储库中的另一个文件夹对我来说完全有效。我什至不需要做“临时”的事情。git mv olddir/file newdir/file 对我有用。 (2认同)

8da*_*day 9

重命名目录或文件(我对复杂情况了解不多,所以可能会有一些警告):

git filter-repo --path-rename OLD_NAME:NEW_NAME
Run Code Online (Sandbox Code Playgroud)

在提到它的文件中重命名目录(可以使用回调,但我不知道如何):

git filter-repo --replace-text expressions.txt
Run Code Online (Sandbox Code Playgroud)

expressions.txt是一个填充有类似行的文件literal:OLD_NAME==>NEW_NAME(可以使用 Python 的 RE withregex:或 glob with glob:)。

要重命名提交消息中的目录:

git-filter-repo --message-callback 'return message.replace(b"OLD_NAME", b"NEW_NAME")'
Run Code Online (Sandbox Code Playgroud)

也支持 Python 的正则表达式,但它们必须用 Python 手动编写。

如果存储库是原始的,没有远程,则必须添加--force以强制重写。(在执行此操作之前,您可能希望创建存储库的备份。)

如果您不想保留 refs(它们将显示在 Git GUI 的分支历史记录中),则必须添加--replace-refs delete-no-add.


小智 7

我按照这个多步骤过程将代码移动到父目录并保留历史记录。

第 0 步:从 'master' 创建一个分支 'history' 用于保管

第 1 步:使用git-filter-repo工具重写历史记录。下面的这个命令将文件夹 'FolderwithContentOfInterest' 移动到一个级别并修改了相关的提交历史

git filter-repo --path-rename ParentFolder/FolderwithContentOfInterest/:FolderwithContentOfInterest/ --force
Run Code Online (Sandbox Code Playgroud)

第 2 步:此时 GitHub 存储库丢失了其远程存储库路径。添加远程参考

git remote add origin git@github.com:MyCompany/MyRepo.git
Run Code Online (Sandbox Code Playgroud)

第 3 步:在存储库上拉取信息

git pull
Run Code Online (Sandbox Code Playgroud)

第四步:连接本地丢失分支和源分支

git branch --set-upstream-to=origin/history history
Run Code Online (Sandbox Code Playgroud)

步骤 5:如果出现提示,解决文件夹结构的合并冲突

第 6 步:!!

git push
Run Code Online (Sandbox Code Playgroud)

注意:修改后的历史记录和移动的文件夹似乎已经提交。 enter code here

完毕。代码移动到父目录/所需目录保持历史完整!


小智 7

只需使用以下命令移动文件和舞台:

git add .
Run Code Online (Sandbox Code Playgroud)

在提交之前您可以检查状态:

git status
Run Code Online (Sandbox Code Playgroud)

这将显示:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    old-folder/file.txt -> new-folder/file.txt
Run Code Online (Sandbox Code Playgroud)

我使用 Git 版本 2.26.1 进行测试。

摘自GitHub 帮助页面


归档时间:

查看次数:

315606 次

最近记录:

6 年,8 月 前