是否可以在 Git 中移动/重命名文件并维护其历史记录?

发布于 2024-08-22 03:50:54 字数 196 浏览 7 评论 0 原文

我想在 Git 中重命名/移动项目子树,将其从 移动

/project/xyz

/components/xyz

如果我使用普通的 git mv 项目组件,那么 xyz 项目的所有提交历史记录都会获取丢失的。有没有办法移动这个以便保留历史?

I 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 the xyz project gets lost. Is there a way to move this such that the history is maintained?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

独留℉清风醉 2024-08-29 03:50:54

Git 检测重命名而不是通过提交持久化操作,因此无论您使用 git mv 还是 mv 并不重要,只要移动操作与任何操作分开提交即可。对文件的更改。

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

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

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

Git detects renames rather than persisting the operation with the commit, so whether you use git mv or mv doesn't matter, as long as the move operation is committed separately from any changes to the file.

The log command takes a --follow argument that continues history before a rename operation, i.e., it searches for similar content using heuristics.

To lookup the full history, use the following command:

git log --follow ./path/to/file
猫瑾少女 2024-08-29 03:50:54

不。

简短的回答是。在 Git 中重命名文件并记住历史记录是不可能的。这是一种痛苦。

有传言称 git log --follow --find-copies-harder 会起作用,但它对我不起作用,即使文件内容的更改为零,并且这些动作是通过 git mv 进行的。

(最初我使用 Eclipse 在一次操作中重命名和更新包,这可能会让 Git 感到困惑。但这是一件很常见的事情。如果只有一个 --follow 似乎确实可以工作mv 被执行,然后是 commit 并且 mv 并不太远。)

Linus 说你应该全面理解一个软件项目的全部内容,不需要跟踪单个文件。好吧,遗憾的是,我的小脑袋做不到这一点。

这么多人漫不经心地重复 Git 自动跟踪移动的说法,这真的很烦人。他们浪费了我的时间。 Git 没有做这样的事情。 按照设计(!)Git 根本不跟踪移动。

我的解决方案是将文件重命名回原来的位置。更改软件以适应源代码管理。使用 Git,您似乎只需要第一次就“git”正确。

不幸的是,这破坏了 Eclipse,它似乎使用 --followgit log --follow 有时不会显示具有复杂重命名历史的文件的完整历史记录,即使 git log 会显示。 (我不知道为什么。)

(有一些过于聪明的 hack 会返回并重新提交旧的工作,但它们相当可怕。请参阅 GitHub-Gist:emiller/git-mv-with-history。)

简而言之:如果 Subversion 这样做 是错误的,那么 Git 这样做也是错误的 - 这样做不是某种(错误!)功能,这是一个错误。

No.

The short answer is NO. It is not possible to rename a file in Git and remember the history. And it is a pain.

Rumor has it that git log --follow --find-copies-harder will work, but it does not work for me, even if there are zero changes to the file contents, and the moves have been made with git mv.

(Initially I used Eclipse to rename and update packages in one operation, which may have confused Git. But that is a very common thing to do. --follow does seem to work if only a mv is performed and then a commit and the mv is not too far.)

Linus says that you are supposed to understand the entire contents of a software project holistically, not needing to track individual files. Well, sadly, my small brain cannot do that.

It is really annoying that so many people have mindlessly repeated the statement that Git automatically tracks moves. They have wasted my time. Git does no such thing. By design(!) Git does not track moves at all.

My solution is to rename the files back to their original locations. Change the software to fit the source control. With Git you just seem to need to "git" it right the first time.

Unfortunately, that breaks Eclipse, which seems to use --follow. git log --follow sometimes does not show the full history of files with complicated rename histories even though git log does. (I do not know why.)

(There are some too clever hacks that go back and recommit old work, but they are rather frightening. See GitHub-Gist: emiller/git-mv-with-history.)

In short: if Subversion doing this is wrong, then Git doing this is also wrong - doing this isn't some (mis!)feature, it's a mistake.

揽月 2024-08-29 03:50:54

可以重命名文件并保持历史记录完整,尽管这会导致文件在存储库的整个历史记录中被重命名。这可能仅适用于痴迷于 git-log 的爱好者,并且会产生一些严重的影响,包括:

  • 您可能会重写共享历史记录,这是使用 Git 时最重要的“不要”。如果其他人克隆了存储库,那么您这样做会破坏它。他们将不得不重新克隆以避免头痛。如果重命名足够重要,这可能没问题,但您需要仔细考虑这一点 - 您最终可能会惹恼整个开源社区!
  • 如果您在存储库历史记录中使用较早的旧名称引用了该文件,那么您实际上破坏了早期版本。为了解决这个问题,你必须多做一些跳圈运动。这并非不可能,只是乏味而且可能不值得。

现在,既然您仍然和我在一起,您可能是一个单独的开发人员,正在重命名一个完全独立的文件。让我们使用 filter-tree 移动文件!

假设您要将文件 old 移动到文件夹 dir 中,并将其命名为 new

这可以使用 git 来完成mv 旧目录/新 && git add -u dir/new,但这打破了历史。

相反:

git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD

重做分支中的每个提交,在每次迭代的刻度中执行命令。当你这样做时,很多事情都可能出错。我通常会测试该文件是否存在(否则它还没有移动),然后执行必要的步骤以按照我的喜好将树拔掉。在这里,您可以通过文件来改变对文件的引用等等。把自己打垮! :)

完成后,文件将被移动并且日志完好无损。你感觉就像一个忍者海盗。

还;当然,仅当您将文件移动到新文件夹时, mkdir dir 才是必需的。 if 将避免在历史上早于您的文件存在的时间创建此文件夹。

It is possible to rename a file and keep the history intact, although it causes the file to be renamed throughout the entire history of the repository. This is probably only for the obsessive git-log-lovers, and has some serious implications, including these:

  • You could be rewriting a shared history, which is the most important DON'T while using Git. If someone else has cloned the repository, you'll break it doing this. They will have to re-clone to avoid headaches. This might be OK if the rename is important enough, but you'll need to consider this carefully -- you might end up upsetting an entire opensource community!
  • If you've referenced the file using it's old name earlier in the repository history, you're effectively breaking earlier versions. To remedy this, you'll have to do a bit more hoop jumping. It's not impossible, just tedious and possibly not worth it.

Now, since you're still with me, you're a probably solo developer renaming a completely isolated file. Let's move a file using filter-tree!

Assume you're going to move a file old into a folder dir and give it the name new

This could be done with git mv old dir/new && git add -u dir/new, but that breaks history.

Instead:

git filter-branch --tree-filter 'if [ -f old ]; then mkdir dir && mv old dir/new; fi' HEAD

will redo every commit in the branch, executing the command in the ticks for each iteration. Plenty of stuff can go wrong when you do this. I normally test to see if the file is present (otherwise it's not there yet to move) and then perform the necessary steps to shoehorn the tree to my liking. Here you might sed through files to alter references to the file and so on. Knock yourself out! :)

When completed, the file is moved and the log is intact. You feel like a ninja pirate.

Also; The mkdir dir is only necessary if you move the file to a new folder, of course. The if will avoid the creation of this folder earlier in history than your file exists.

深府石板幽径 2024-08-29 03:50:54
git log --follow [file]

将通过重命名向您展示历史记录。

git log --follow [file]

will show you the history through renames.

空城之時有危險 2024-08-29 03:50:54

我愿意:

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

I do:

git mv {old} {new}
git add -u {new}
梦在深巷 2024-08-29 03:50:54

我想在 Git 中重命名/移动项目子树,将其从

<前><代码>/项目/xyz

/组件/xyz

如果我使用普通的 git mv 项目组件,那么 xyz 项目的所有提交历史记录都会丢失。

否(8 年后,Git 2.19,2018 年第 3 季度),因为 Git 会检测目录重命名,并且现在对此进行了更好的记录。

请参阅 提交 b00bf1c提交 1634688提交 0661e49提交 4d34dff提交 983f464, 提交 c840e1a 提交 9929430(2018 年 6 月 27 日)和 提交 d4e8062提交 5dacd4a(2018 年 6 月 25 日),作者:Elijah Newren (newren )
(由 Junio C Hamano -- gitster -- 合并于 提交 0ce5a69,2018 年 7 月 24 日)

现在在 文档/technical/directory-rename-detection.txt

示例:

x/ax/bx/c全部移动到z/a时>、z/bz/c,同时添加的 x/d 很可能也想移动到 z/d 通过
接受整个目录“x”移动到“z”的提示。

但还有许多其他情况,例如:

历史的一侧重命名x -> z,另一个将某些文件重命名为
x/e,导致合并需要进行传递重命名。

为了简化目录重命名检测,这些规则由 Git 强制执行:

一些基本规则限制了何时
目录重命名检测适用:

  1. 如果给定目录在合并双方仍然存在,我们不认为它已被重命名。
  2. 如果要重命名的文件子集有文件或目录妨碍(或相互妨碍),则“关闭”这些特定子路径的目录重命名并报告冲突给用户。
  3. 如果历史记录的另一端将目录重命名为您的历史记录一侧已重命名的路径,则对于任何隐式目录重命名,忽略历史记录另一侧的特定重命名(但警告用户)。

您可以在 很多测试“noreferrer”>t/t6043-merge-rename-directories.sh,其中还指出:

  • a) 如果重命名将一个目录分成两个或多个其他目录,则重命名次数最多的目录“获胜”。
  • b) 如果该路径是合并两侧重命名的源,请避免对该路径进行目录重命名检测。
  • c) 仅将隐式目录重命名应用于目录,如果另一方
    历史的创造者是进行重命名的人。

I 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 the xyz project gets lost.

No (8 years later, Git 2.19, Q3 2018), because Git will detect the directory rename, and this is now better documented.

See commit b00bf1c, commit 1634688, commit 0661e49, commit 4d34dff, commit 983f464, commit c840e1a, commit 9929430 (27 Jun 2018), and commit d4e8062, commit 5dacd4a (25 Jun 2018) by Elijah Newren (newren).
(Merged by Junio C Hamano -- gitster -- in commit 0ce5a69, 24 Jul 2018)

That is now explained in Documentation/technical/directory-rename-detection.txt:

Example:

When all of x/a, x/b and x/c have moved to z/a, z/b and z/c, it is likely that x/d added in the meantime would also want to move to z/d by
taking the hint that the entire directory 'x' moved to 'z'.

But they are many other cases, like:

one side of history renames x -> z, and the other renames some file to
x/e, causing the need for the merge to do a transitive rename.

To simplify directory rename detection, those rules are enforced by Git:

a couple basic rules limit when
directory rename detection applies:

  1. If a given directory still exists on both sides of a merge, we do not consider it to have been renamed.
  2. If a subset of to-be-renamed files have a file or directory in the way (or would be in the way of each other), "turn off" the directory rename for those specific sub-paths and report the conflict to the user.
  3. If the other side of history did a directory rename to a path that your side of history renamed away, then ignore that particular rename from the other side of history for any implicit directory renames (but warn the user).

You can see a lot of tests in t/t6043-merge-rename-directories.sh, which also point out that:

  • a) If renames split a directory into two or more others, the directory with the most renames, "wins".
  • b) Avoid directory-rename-detection for a path, if that path is the source of a rename on either side of a merge.
  • c) Only apply implicit directory renames to directories if the other side
    of history is the one doing the renaming.
剧终人散尽 2024-08-29 03:50:54

  1. 您使用 git log --pretty=email 将文件的提交历史记录转换为电子邮件补丁
  2. 您在新目录中重新组织这些文件并重命名它们
  3. 您将这些文件(电子邮件)转换回 Git 提交以保留使用 git am 的历史记录。

限制

  • 不保留标签和分支
  • 历史记录在路径文件重命名(目录重命名)上被剪切

通过示例逐步说明

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

示例:提取 file3file4< 的历史记录/code> 和 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 日志生成命令的循环:而不是运行 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

--committer-date-is-author-date< /code> 保留原始提交时间戳(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- 和 grep 完整模式 '{.* =>; 删除前两列。 .*}'

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

Yes

  1. You convert the commit history of files into email patches using git log --pretty=email
  2. You reorganize these files in new directories and rename them
  3. You convert back these files (emails) to Git commits to keep the history using git am.

Limitation

  • Tags and branches are not kept
  • History is cut on path file rename (directory rename)

Step by step explanation with examples

1. Extract history in email format

Example: Extract history of file3, file4 and file5

my_repo
├── dirA
│   ├── file1
│   └── file2
├── dirB            ^
│   ├── subdir      | To be moved
│   │   ├── file3   | with history
│   │   └── file4   | 
│   └── file5       v
└── dirC
    ├── file6
    └── file7

Set/clean the destination

export historydir=/tmp/mail/dir       # Absolute path
rm -rf "$historydir"    # Caution when cleaning the folder

Extract history of each file in email format

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

Unfortunately option --follow or --find-copies-harder cannot be combined with --reverse. This is why history is cut when file is renamed (or when a parent directory is renamed).

Temporary history in email format:

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

Dan Bonachea suggests to invert the loops of the git log generation command in this first step: rather than running git log once per file, run it exactly once with a list of files on the command line and generate a single unified log. This way commits that modify multiple files remain a single commit in the result, and all the new commits maintain their original relative order. Note this also requires changes in second step below when rewriting filenames in the (now unified) log.


2. Reorganize file tree and update filenames

Suppose you want to move these three files in this other repo (can be the same repo).

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

Therefore reorganize your files:

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

Your temporary history is now:

/tmp/mail/dir
    └── dirB
        ├── dirB1
        │   ├── file33
        │   └── file44
        └── dirB2
             └── file5

Change also filenames within the history:

cd "$historydir"
find * -type f -exec bash -c 'sed "/^diff --git a\|^--- a\|^+++ b/s:\( [ab]\)/[^ ]*:\1/$0:g" -i "$0"' {} ';'

3. Apply new history

Your other repo is:

my_other_repo
├── dirF
│   ├── file55
│   └── file56
└── dirH
    └── file77

Apply commits from temporary history files:

cd my_other_repo
find "$historydir" -type f -exec cat {} + | git am --committer-date-is-author-date

--committer-date-is-author-date preserves the original commit time-stamps (Dan Bonachea's comment).

Your other repo is now:

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

Use git status to see amount of commits ready to be pushed :-)


Extra trick: Check renamed/moved files within your repo

To list the files having been renamed:

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

More customizations: You can complete the command git log using options --find-copies-harder or --reverse. You can also remove the first two columns using cut -f3- and grepping complete pattern '{.* => .*}'.

find -name .git -prune -o -exec git log --pretty=tformat:'' --numstat --follow --find-copies-harder --reverse {} ';' | cut -f3- | grep '{.* => .*}'
葬花如无物 2024-08-29 03:50:54

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

第 0 步:从“master”创建一个分支“历史记录”以进行保管

第 1 步:使用 git-filter- repo 重写历史的工具。下面的命令将文件夹“FolderwithContentOfInterest”移动到上一级并修改相关提交历史记录

git filter-repo --path-rename ParentFolder/FolderwithContentOfInterest/:FolderwithContentOfInterest/ --force

第 2 步:此时 GitHub 存储库丢失了其远程存储库路径。添加远程引用

git remote add origin [email protected]:MyCompany/MyRepo.git

步骤 3:在存储库上拉取信息

git pull

步骤 4:将本地丢失的分支与原始分支连接

git branch --set-upstream-to=origin/history history

步骤 5:如果出现提示,请解决文件夹结构的合并冲突

步骤 6:推送

git push

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

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

I followed this multi-step process to move code to the parent directory and retained history.

Step 0: Created a branch 'history' from 'master' for safekeeping

Step 1: Used git-filter-repo tool to rewrite history. This command below moved folder 'FolderwithContentOfInterest' to one level up and modified the relevant commit history

git filter-repo --path-rename ParentFolder/FolderwithContentOfInterest/:FolderwithContentOfInterest/ --force

Step 2: By this time the GitHub repository lost its remote repository path. Added remote reference

git remote add origin [email protected]:MyCompany/MyRepo.git

Step 3: Pull information on repository

git pull

Step 4: Connect the local lost branch with the origin branch

git branch --set-upstream-to=origin/history history

Step 5: Address merge conflict for the folder structure if prompted

Step 6: Push!!

git push

Note: The modified history and moved folder appear to already be committed. enter code here

Done. Code moves to the parent / desired directory keeping history intact!

枕花眠 2024-08-29 03:50:54

我遇到了“重命名文件夹而不丢失历史记录”的问题。要修复它,请运行:

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

I have faced the issue "Renaming the folder without loosing history". To fix it, run:

$ git mv oldfolder temp && git mv temp newfolder
$ git commit
$ git push
月亮是我掰弯的 2024-08-29 03:50:54

重命名目录或文件(我对复杂情况不太了解,因此可能有一些警告):

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-参考删除不添加

To rename a directory or file (I don't know much about complex cases, so there might be some caveats):

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

To rename a directory in files that mention it (it's possible to use callbacks, but I don't know how):

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

expressions.txt is a file filled with lines like literal:OLD_NAME==>NEW_NAME (it's possible to use Python's RE with regex: or glob with glob:).

To rename a directory in messages of commits:

git-filter-repo --message-callback 'return message.replace(b"OLD_NAME", b"NEW_NAME")'

Python's regular expressions are also supported, but they must be written in Python, manually.

If the repository is original, without remote, you will have to add --force to force a rewrite. (You may want to create a backup of your repository before doing this.)

If you do not want to preserve refs (they will be displayed in the branch history of Git GUI), you will have to add --replace-refs delete-no-add.

南烟 2024-08-29 03:50:54

只需移动文件并使用以下命令进行暂存:

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 帮助页面

Simply move the file and stage with:

git add .

Before commit you can check the status:

git status

That will show:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    old-folder/file.txt -> new-folder/file.txt

I tested with Git version 2.26.1.

Extracted from GitHub Help Page.

御守 2024-08-29 03:50:54

虽然 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 <[email protected]>
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 <[email protected]>
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,这都有效,而不仅仅是使用 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 版本可能是正确的。

While the core of Git, the Git plumbing doesn't keep track of renames, the history you display with the Git log "porcelain" can detect them if you like.

For a given git log use the -M option:

git log -p -M

With a current version of Git.

This works for other commands like git diff as well.

There are options to make the comparisons more or less rigorous. If you rename a file without making significant changes to the file at the same time it makes it easier for Git log and friends to detect the rename. For this reason some people rename files in one commit and change them in another.

There's a cost in CPU use whenever you ask Git to find where files have been renamed, so whether you use it or not, and when, is up to you.

If you would like to always have your history reported with rename detection in a particular repository you can use:

git config diff.renames 1

Files moving from one directory to another is detected. Here's an example:

commit c3ee8dfb01e357eba1ab18003be1490a46325992
Author: John S. Gruber <[email protected]>
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 <[email protected]>
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

Please note that this works whenever you are using diff, not just with git log. For example:

$ 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

As a trial I made a small change in one file in a feature branch and committed it and then in the master branch I renamed the file, committed, and then made a small change in another part of the file and committed that. When I went to feature branch and merged from master the merge renamed the file and merged the changes. Here's the output from the merge:

 $ 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%)

The result was a working directory with the file renamed and both text changes made. So it's possible for Git to do the right thing despite the fact that it doesn't explicitly track renames.

This is an late answer to an old question so the other answers may have been correct for the Git version at the time.

心在旅行 2024-08-29 03:50:54

首先创建一个仅重命名的独立提交。

然后对文件内容的任何最终更改都会放入单独的提交中。

First create a standalone commit with just a rename.

Then any eventual changes to the file content put in the separate commit.

稀香 2024-08-29 03:50:54

就我而言,我将两个文件从“resources”目录移动到“src/main/resources”。如以下代码所示,它们显示为“已删除”。

但是,当我将重定位的文件添加到暂存区域,然后添加已删除的文件后,系统将它们识别为“重命名”。

当我检查这两个文件的历史记录时,它完好无损,并且它们的永久链接仍然有效。所以,一切都如我们所希望的那样进行。

myaddress (master *)$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    resources/myaddress-schemas.sql
        deleted:    resources/select-sangdo-ro-60.sql

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .gitignore
        pom.xml
        src/

myaddress (master *)$ git add src/main/resources/*.sql
myaddress (master *+)$ git add `git ls-files --deleted`

myaddress (master +)$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    resources/myaddress-schemas.sql -> src/main/resources/myaddress-schemas.sql
        renamed:    resources/select-sangdo-ro-60.sql -> src/main/resources/select-sangdo-ro-60.sql

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .gitignore
        pom.xml
        src/main/java/
        src/main/webapp/

myaddress (master +)$
myaddress (master +)$ git commit -m "two resource files moved to src/main"
[master 0832839] two resource files moved to src/main
 2 files changed, 0 insertions(+), 0 deletions(-)
 rename {resources => src/main/resources}/myaddress-schemas.sql (100%)
 rename {resources => src/main/resources}/select-sangdo-ro-60.sql (100%)
myaddress (master)$

In my case, I moved two files from a 'resources' directory into 'src/main/resources'. As following code shows they appeared as 'deleted'.

However, after I added the relocated files into staging area and then added the deleted ones, the system recognized them as "renamed".

When I check the history of both files, it was intact and their permanent links were still working. So, everything works as we might have hoped.

myaddress (master *)$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        deleted:    resources/myaddress-schemas.sql
        deleted:    resources/select-sangdo-ro-60.sql

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .gitignore
        pom.xml
        src/

myaddress (master *)$ git add src/main/resources/*.sql
myaddress (master *+)$ git add `git ls-files --deleted`

myaddress (master +)$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    resources/myaddress-schemas.sql -> src/main/resources/myaddress-schemas.sql
        renamed:    resources/select-sangdo-ro-60.sql -> src/main/resources/select-sangdo-ro-60.sql

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .gitignore
        pom.xml
        src/main/java/
        src/main/webapp/

myaddress (master +)$
myaddress (master +)$ git commit -m "two resource files moved to src/main"
[master 0832839] two resource files moved to src/main
 2 files changed, 0 insertions(+), 0 deletions(-)
 rename {resources => src/main/resources}/myaddress-schemas.sql (100%)
 rename {resources => src/main/resources}/select-sangdo-ro-60.sql (100%)
myaddress (master)$
凡尘雨 2024-08-29 03:50:54

是的
你可以:

  1. 移动文件=>提交
  2. 重命名文件=>提交
    (不要在同一提交中移动和重命名)
    现在该文件将保留其 git 历史记录

Yes
you can:

  1. move the file => commit
  2. rename the file => commit
    (dont move and rename in the same commit)
    now the file will keep their git history
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文