我想在Git中重命名/移动项目子树,将其从
/project/xyz
至
/components/xyz
如果我使用plain git mv project components
,则xyz project
丢失的所有提交历史记录。有什么办法可以移动历史记录吗?
我想在Git中重命名/移动项目子树,将其从
/project/xyz
至
/components/xyz
如果我使用plain git mv project components
,则xyz project
丢失的所有提交历史记录。有什么办法可以移动历史记录吗?
Answers:
Git会检测到重命名,而不是通过提交保留操作,因此无论您使用git mv
还是mv
不重要。
该log
命令采用的--follow
参数在重命名操作之前延续历史记录,即,它使用试探法搜索相似的内容:
http://git-scm.com/docs/git-log
要查找完整的历史记录,请使用以下命令:
git log --follow ./path/to/file
git config alias.logf "log --follow"
然后写入git logf ./path/to/file
。
它可能重命名文件,并保持完好的历史,但它会导致整个仓库的整个历史要重命名的文件。这可能仅适用于痴迷的git-log-lovers,并具有一些严重的含义,包括:
现在,由于您仍然与我在一起,您可能是一个重命名完全隔离的文件的单独开发人员。让我们使用来移动文件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
将重做分支中的每个提交,在每次迭代的滴答声中执行命令。当您这样做时,很多东西都会出错。我通常会测试文件是否存在(否则,文件尚不存在),然后执行必要的步骤来拔出我喜欢的树。在这里,您可能浏览文件以更改对该文件的引用,依此类推。把自己打昏!:)
完成后,文件将被移动并且日志保持不变。你觉得自己像个忍者海盗。
也; 当然,只有将文件移动到新文件夹时,才需要mkdir dir。该如果将避免在历史上较早的该文件夹的创建比你的文件存在。
--index-filter
for重命名会更快,因为不必在每次提交时都将树检出并返回。--index-filter
直接作用于每个提交索引。
简短的答案是“ 否”。不能在Git中重命名文件并记住历史记录。这是一种痛苦。
谣言说它git log --follow
--find-copies-harder
可以工作,但是即使文件内容的更改为零,并且使用进行了移动,它也对我不起作用git mv
。
(最初,我使用Eclipse在一个操作中重命名和更新软件包,这可能会使Git感到困惑。但这是很常见的事情。--follow
如果仅mv
执行a然后执行a commit
并且the mv
距离不太远,似乎确实可行。)
Linus说,您应该全面了解软件项目的全部内容,而无需跟踪单个文件。好吧,可悲的是,我的小脑无法做到这一点。
如此多的人无意识地重复了Git自动跟踪移动的说法,这真是令人讨厌。他们浪费了我的时间。Git没有这样的事情。根据设计(!),Git根本不会跟踪移动。
我的解决方案是将文件重命名为其原始位置。更改软件以适合源代码管理。使用Git,您似乎只需要在第一时间正确进行“ git”操作即可。
不幸的是,这破坏了Eclipse的效用,后者似乎在使用--follow
。git log --follow
有时甚至不会显示具有复杂重命名历史记录的文件的完整历史记录git log
。(我不知道为什么。)
(有一些非常聪明的hacks可以追溯到以前并重新提交旧的工作,但是它们相当令人恐惧。请参阅GitHub-Gist:emiller / git-mv-with-history。)
git log --follow [file]
将通过重命名显示历史记录。
git mv
基本上做一个git rm && git add
。有类似-M90
/的选项--find-renames=90
,可以在文件90%相同时考虑重命名该文件。
我做:
git mv {old} {new}
git add -u {new}
-A
代替它的行为?同样,请参见此处:git-scm.com/docs/git-add
我想在Git中重命名/移动项目子树,将其从
/project/xyz
至
/ components / xyz
如果我使用plain
git mv project components
,则该xyz
项目的所有提交历史记录都将丢失。
否(8年后,Git 2.19,2018年第三季度),因为Git将检测目录更名,现在对此进行了更好的记录。
请参阅Elijah Newren()的提交b00bf1c,提交1634688,提交0661e49,提交4d34dff,提交983f464,提交c840e1a,提交9929430(2018年6月27日)和提交d4e8062,提交5dacd4a(2018年6月25日)。(由Junio C Hamano合并--在commit 0ce5a69中,2018年7月24日)newren
gitster
现在在中进行解释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强制执行:
应用目录重命名检测时有几个基本规则限制:
- 如果给定目录仍然存在于合并的两侧,则我们不认为该目录已被重命名。
- 如果要重命名的文件的子集有一个文件或目录(或互相干扰),请“关闭”这些特定子路径的目录重命名并将冲突报告给用户。
- 如果历史记录的另一侧确实将目录重命名为您的历史记录的另一侧重命名的路径,则对于任何隐式目录重命名,请从历史记录的另一侧忽略该特定重命名(但警告用户)。
您可以在中看到很多测试t/t6043-merge-rename-directories.sh
,这些测试还指出:
- a)如果重命名将目录分为两个或多个其他目录,则重命名最多的目录为“ wins”。
- b)避免对目录进行目录重命名检测,如果该路径是合并任一侧的重命名源。
- c)如果历史的另一端是进行重命名的人员,则仅将隐式目录重命名应用于目录。
git log --pretty=email -p --reverse --full-index --binary
cat extracted-history | git am --committer-date-is-author-date
例如:提取物的历史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"' {} ';'
不幸的是选项--follow
或--find-copies-harder
不能与组合使用--reverse
。这就是为什么在重命名文件(或重命名父目录)时剪切历史记录的原因。
电子邮件格式的临时历史记录:
/tmp/mail/dir
├── subdir
│ ├── file3
│ └── file4
└── file5
Dan Bonachea建议在第一步中反转git log generation命令的循环:与其每个文件运行git log一次,不如在命令行中使用文件列表对它运行一次,并生成一个统一的日志。这样,修改多个文件的提交将在结果中保留为单个提交,并且所有新提交均保持其原始相对顺序。请注意,在(现在已统一)日志中重写文件名时,这还需要在下面的第二步中进行更改。
假设您要将这三个文件移到另一个仓库中(可以是相同的仓库)。
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
--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 '=>'
更多自定义:您可以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 '{.* => .*}'
我遵循了此多步骤过程,将代码移至父目录和保留的历史记录。
步骤0:从“ master”创建分支“ history”以进行保管
步骤1:使用git-filter-repo工具重写历史记录。下面的此命令将文件夹“ FolderwithContentOfInterest”上移到一个级别,并修改了相关的提交历史记录
git filter-repo --path-rename ParentFolder/FolderwithContentOfInterest/:FolderwithContentOfInterest/ --force
第2步:此时,GitHub存储库丢失了其远程存储库路径。添加了远程参考
git remote add origin git@github.com:MyCompany/MyRepo.git
步骤3:提取信息库中的信息
git pull
步骤4:将本地丢失的分支与原始分支连接
git branch --set-upstream-to=origin/history history
步骤5:如果出现提示,则文件夹结构的地址合并冲突
步骤6:推!
git push
注意:修改后的历史记录和移动的文件夹似乎已经提交。 enter code here
做完了 代码移至父目录/所需目录,保持历史记录完整!
尽管Git的核心(Git管道)无法跟踪重命名,但是您可以通过Git日志“瓷”显示的历史记录可以检测到它们。
对于给定的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还是仅使用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仍可能做正确的事情。
这是一个旧问题的较晚答案,因此其他答案对于当时的Git版本可能是正确的。
首先创建一个仅带有重命名的独立提交。
然后,对文件内容的任何最终更改将放入单独的提交中。
重命名目录或文件(对于复杂的情况我不太了解,因此可能有一些警告):
git filter-repo --path-rename OLD_NAME:NEW_NAME
要在提到它的文件中重命名目录(可以使用回调,但我不知道如何):
git filter-repo --replace-text expressions.txt
expressions.txt
是一个充满以下行的文件literal:OLD_NAME==>NEW_NAME
(可以将Python的RE与regex:
或与glob一起使用glob:
)。
要在提交消息中重命名目录:
git-filter-repo --message-callback 'return message.replace(b"OLD_NAME", b"NEW_NAME")'
也支持Python的正则表达式,但必须手动用Python编写。
如果存储库是原始存储库,没有远程存储库,则必须添加该存储库--force
以强制进行重写。(在执行此操作之前,您可能需要创建存储库的备份。)
如果不想保留引用(它们将显示在Git GUI的分支历史记录中),则必须添加--replace-refs delete-no-add
。
只需移动文件并执行以下操作即可:
git add .
提交之前,您可以检查状态:
git status
这将显示:
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: old-folder/file.txt -> new-folder/file.txt
我使用Git 2.26.1版本进行了测试。
从GitHub帮助页面提取。
git mv
:stackoverflow.com/questions/1094269/whats-the-purpose-of-git-mv