是否可以在Git中移动/重命名文件并维护其历史记录?
我想重命名/移动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参数,该参数在重命名操作之前继续历史记录,即,它使用启发式搜索类似内容: 要查找
/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