我想要重命名/移动 Git 中的项目子树,将其从...
/project/xyz
为了更好地处理这个请求,我需要更具体的上下文信息。请提供完整的 Stack Overflow 问题或回答以便我进行翻译。/components/xyz
如果我使用简单的git mv project components
,那么关于xyz项目
的所有提交历史记录都会丢失。是否有一种方法可以移动它并保留历史记录?
Git检测到重命名而不是将操作保留在提交中,因此无论您使用git mv
还是mv
都无所谓,只要移动操作与对文件的任何更改分开提交。
log
命令采用--follow
参数,在重命名操作之前继续历史记录,即使用启发式搜索查找相似内容。
要查找完整的历史记录,请使用以下命令:
git log --follow ./path/to/file
git config alias.logf "log --follow"
,然后只需写入git logf ./path/to/file
。 - Troels Thomsen简单回答,不可能在Git中重命名文件并保留历史记录。这非常麻烦。
有传言称使用 git log --follow
--find-copies-harder
可以解决问题,但对我无效,即使文件内容没有任何更改,并且移动是使用git mv
完成的。
(最初我使用Eclipse一步重命名和更新包,这可能会混淆Git。但那是非常普遍的事情。如果只执行了一个mv
然后commit
,而且mv
距离不太远,--follow
似乎可以工作。)
Linus说你应该全面理解软件项目的全部内容,不需要跟踪单个文件。但是,遗憾的是,我的小脑袋做不到这一点。
让人非常恼火的是,有很多人毫无思考地重复了Git自动跟踪移动的声明。他们浪费了我的时间。Git根本不会这样做。Git的设计是不跟踪移动。
我的解决方案是将文件重命名回其原始位置。让软件适应源代码控制。在Git中,您似乎需要一次性“git”正确。
很不幸,这会破坏Eclipse,因为它似乎使用--follow
。有时即使git log
可以显示全部历史记录,git log --follow
在处理具有复杂重命名历史的文件时也无法完全显示。(我不知道为什么。)
(有一些过于聪明的技巧可以回溯并重新提交旧作品,但它们相当可怕。请参阅GitHub-Gist: emiller/git-mv-with-history。)
简而言之:如果Subversion这样做是错误的,那么Git这样做也是错误的——这不是某种(误!)功能,而是一个错误。
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
。 - Gillespiegit 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
git log --follow [file]
会通过重命名显示历史记录。
git mv
实际上相当于执行了 git rm && git add
的操作。可以使用 -M90
或 --find-renames=90
等选项来判断文件是否被重命名,即当文件内容相似度达到90%时认为该文件已重命名。 - vdboor我做:
git mv {old} {new}
git add -u {new}
-A
的行为?再次参见此处:http://git-scm.com/docs/git-add - James M. Greenegit add -u
用途的问题。Git文档往往没有什么帮助,是我最不想看的地方。这里有一篇文章展示了git add -u
的使用:https://dev59.com/YnI-5IYBdhLWcg3w9tdw#2117202。 - Brent BradburnI would like to rename/move a project subtree in Git moving it from
/project/xyz
to
/components/xyz
If I use a plain
git mv project components
, then all the commit history for thexyz
project gets lost.
不会(8年后,Git 2.19,2018年第三季度),因为Git将会检测到目录重命名,并且这已经被更好地记录下来。
请参阅commit b00bf1c、commit 1634688、commit 0661e49、commit 4d34dff、commit 983f464、commit c840e1a、commit 9929430(2018年6月27日),以及commit d4e8062、commit 5dacd4a(2018年6月25日),作者为Elijah Newren(newren
)。gitster
--在commit 0ce5a69中合并,于2018年7月24日)
这在Documentation/technical/directory-rename-detection.txt
中有解释:x/a
,x/b
和x/c
都移动到z/a
,z/b
和z/c
时,很可能同时添加的x/d
也想通过获取整个目录“x
”移动到“z
”的提示来移动到“z/d
”。x -> z
重命名,而另一侧将某个文件重命名为x/e
,导致合并需要进行传递重命名。
- 如果在合并的两个分支上,某个目录仍然存在,则我们认为它没有被重命名。
- 如果要重命名的文件的子集中有一个文件或目录阻碍了路径(或彼此之间会相互阻碍),则对于这些特定子路径关闭目录重命名并向用户报告冲突。
- 如果历史的另一侧将目录重命名为您的历史记录重命名的路径,则忽略来自历史的另一侧的该特定重命名以进行任何隐式目录重命名(但向用户发出警告)。
您可以在t/t6043-merge-rename-directories.sh
中看到很多测试,这也指出:
- a) 如果重命名将一个目录拆分为两个或更多个目录,则具有最多重命名的目录“获胜”。
- b) 如果路径是合并的任一侧上的重命名源,则避免对该路径进行目录重命名检测。
- c) 仅在历史记录的另一侧执行重命名时,才对目录应用隐式目录重命名。
git log --pretty=email
将文件的提交历史转换为电子邮件补丁。git am
将这些文件(电子邮件)转换回Git提交,以保留历史记录。示例:提取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 生成命令的循环顺序:不要为每个文件运行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 '=>'
更多自定义:你可以使用选项--find-copies-harder
或--reverse
完善git log
命令。你也可以使用cut -f3-
删除前两列,并使用完整模式'{.* => .*}'
进行筛选。
find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'
我遇到了一个问题"重命名文件夹而不丢失历史记录"。要解决它,请运行:
$ git mv oldfolder temp && git mv temp newfolder
$ git commit
$ git push
我按照以下多步骤过程将代码移动到父目录并保留了历史记录:
步骤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
第三步:拉取仓库信息
git pull
步骤 4:将本地丢失的分支与源分支连接起来
git branch --set-upstream-to=origin/history history
步骤5:如有需要,请解决文件夹结构的合并冲突
步骤6:进行推送!!
git push
注意:修改的历史和移动的文件夹似乎已经提交。 输入代码
完成。代码移动到父目录/所需目录,保持历史记录完整!要重命名目录或文件(我不知道复杂情况,可能会有一些注意事项):
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: 'filter-repo' is not a git command. See 'git --help'
- alper
git mv
的作用是将文件重命名或移动到一个新位置,并在 Git 中提交该更改。使用git mv
命令可以自动完成重命名或移动操作,同时更新 Git 索引和工作树中的文件位置,而不需要手动执行多个命令来完成这些操作。 - cregoxgit-subtree
拆分存储库时,Git会赋予生成的子树一个比与其分离的项目不同的虚构历史。我认为Git试图确定涉及子树中任何文件的所有提交,并使用它们来拼接历史记录。此外,每次重新组合和重新拆分子树时,这些历史记录都会被重写。然而,子模块各自具有与父项目分开的独立历史记录。 - Nate T