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

/project/xyz

to

/components/xyz

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


当前回答

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

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。

其他回答

我愿意:

git mv {old} {new}
git add -u {new}

可以重命名文件并保持历史记录不变,尽管这会导致文件在存储库的整个历史记录中被重命名。这可能只适用于痴迷于git日志的爱好者,并有一些严重的影响,包括:

您可以重写共享历史,这是使用Git时最重要的“不要”。如果其他人克隆了存储库,您将通过此操作将其破坏。他们将不得不重新克隆以避免头痛。如果重命名足够重要,这可能是可以的,但您需要仔细考虑这一点——您可能会扰乱整个开源社区!如果您在存储库历史记录的早期使用文件的旧名称引用了该文件,那么实际上就是在破坏早期版本。为了解决这个问题,你必须多跳一点环跳。这不是不可能的,只是乏味而且可能不值得。

现在,既然你还和我在一起,你可能是一个单独的开发人员,正在重命名一个完全独立的文件。让我们使用过滤器树移动文件!

假设您要将一个旧文件移动到文件夹目录中,并将其命名为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 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检测重命名,而不是通过提交持久化操作,因此使用gitmv还是mv并不重要。

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

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

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