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

/project/xyz

to

/components/xyz

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


当前回答

我曾面临“重命名文件夹而不丢失历史记录”的问题。要修复此问题,请运行:

$ git mv oldfolder temp && git mv temp newfolder
$ git commit
$ git push

其他回答

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

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

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

git log --follow ./path/to/file

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

步骤0:从“master”创建了分支“history”以进行安全保管

步骤1:使用gitfilter 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

注意:修改的历史记录和移动的文件夹似乎已提交。在此处输入代码

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

要重命名目录或文件(我不太了解复杂的情况,因此可能需要注意):

git filter-repo --path-rename OLD_NAME:NEW_NAME

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

git filter-repo --replace-text expressions.txt

expressions.txt是一个充满了文字: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以强制重写。(在执行此操作之前,您可能需要创建存储库的备份。)

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

Yes

您可以使用git-log--prey=email将文件的提交历史转换为电子邮件补丁在新目录中重新组织这些文件并重命名它们您可以将这些文件(电子邮件)转换回Git提交,以使用gitam保存历史记录。

限制

未保留标记和分支路径文件重命名(目录重命名)时剪切历史记录


用示例逐步解释

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

示例:提取文件3、文件4和文件5的历史记录

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 hard不能与--reverse组合使用。这就是为什么重命名文件(或重命名父目录)时会剪切历史记录。

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

/tmp/mail/dir
    ├── subdir
    │   ├── file3
    │   └── file4
    └── file5

Dan Bonachea建议在第一步中反转git日志生成命令的循环:与其在每个文件中运行一次git日志,不如在命令行上使用文件列表运行一次,生成一个统一的日志。这样,修改多个文件的提交在结果中保持为一次提交,所有新的提交都保持其原始的相对顺序。注意,在重写(现在统一的)日志中的文件名时,这也需要在下面的第二步中进行更改。


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"' {} ';'

3.应用新历史记录

您的其他回购是:

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

--提交日期是作者日期,保留原始提交时间戳(Dan Bonachea的评论)。

您的其他回购现在是:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
├── dirB
│   ├── dirB1
│   │   ├── file33
│   │   └── file44
│   └── dirB2
│        └── file5
└── dirH
    └── file77

使用gitstatus查看准备推送的提交数量:-)


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

要列出已重命名的文件,请执行以下操作:

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow {} ';' | grep '=>'

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

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

只需移动文件并使用:

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帮助页中提取。