我想在同一仓库中进行移动。
你可能无法完全得到你想要的,但如果你尝试一下,你可能会发现你得到了你所需要的。
你只需重命名目录并提交即可。如果目录不在当前分支的最新提交中,则您可能需要从其他提交中提取目录。也就是说,您可能需要进行初始操作:
git checkout otherbranch -- path/to/directory
git commit # optional, but see below
然后无论如何,运行:
git mv path/to/directory new/path/to/dir
git commit
结果。这并不是你想要的,但它可能是你需要的——特别是如果你进行第一次提交时没有重命名,这样你就有了两个相邻的提交,一个包含旧名称,一个包含新名称。
相反,您可能希望合并分支,提交合并,然后再重命名和提交。是否需要这样做以及原因需要详细说明。
这里有两件事很重要:
git log -p
显示的是补丁,即更改。你查看提交0a36ca1
,然后看到了对README.md
的一些更改。然后git log
继续提交0a36ca1
的父提交922bf37
,然后您会看到对README.md
和/或其他文件的另一个更改,以此类推。这是否意味着0a36ca1
只存储了对README.md
的更改?答案是:不,0a36ca1
存储了README.md
的完整副本和所有其他文件。Git通过检查922bf37
(0a36ca1
之前的提交)和0a36ca1
来显示更改。这两个提交都有每个文件的副本。Git比较了这两个提交的文件。那两个提交中的所有文件都匹配,除了README.md
。然后Git比较了这两个README.md
版本,以查看发生了什么变化,并向您展示了该文件中发生了什么变化。
git show
命令类似,但通常您需要给出一个提交的哈希 ID,git show
将打印提交的元数据(谁制作的、何时以及为什么——日志消息),然后将父级中的快照与该提交中的快照进行比较。不同的是,您所看到的文件。git log
请求历史记录时,Git 会执行以下操作:git log
或 git log master
:git log
),或者从master
的最后一次提交开始,并像 git show
一样显示该提交2。
2. 然后,移动回刚刚显示的提交的父级。在合并提交时,这变得复杂,但现在只需考虑一个简单的线性链即可。git log
的输出。假设存在一个简单的线性提交链,如下所示:A <-B <-C <-D <-E <-F <-G <-- master
G(按名称master 找到),然后移动到F并显示F,然后移动到E并显示E,以此类推。提交A是存储库中的第一个提交-它没有父提交;没有向左移动的箭头从A出来让Git向左移动-因此git show
将其显示为每个文件都是从头开始创建的。这意味着git log-p 以相同的方式显示它。当然,由于没有父提交,因此没有箭头可以向后跟随。
1技术上,目录转化为内部的树形对象,但Git不会存储一个空目录,因为你无法将目录放入Git的索引中,而Git不是从工作树,而是从索引中构建提交。更容易地,把Git想象成只存储文件,因为这是最终效果。
2当然,这假设您正在使用git log -p
。 git log
和git show
之间有几个重要的区别:首先,git log
进行反向行走; 其次,git log
默认情况下不显示补丁; 第三,git show
以不同的默认方式显示合并提交:git log -p
默认为自己说:啊,合并提交太难了:我只打印日志消息并继续前进,而不显示任何差异。 这里git show
的默认值是显示组合差异,它是针对多个父级的缩小形式的差异。
git log
可以显示历史记录的 子集
您可以运行以下命令,而不仅仅是运行 git log
或 git log master
:
git log master -- path/to/file.ext
看起来是关于path/to/file.ext
的历史记录。这里git log
的作用是像往常一样遍历提交历史,但是它不显示某些提交。也就是说,对于上面的简单线性链,git log
从提交G
开始。它比较(快照)F
和G
以查看哪些文件发生了变化。如果这些文件包括path/to/file.ext
,则git log
会显示提交G
。然后它会回到提交F
,即使它什么都没显示。
换句话说,git log
不仅可以显示它遍历的所有提交,还可以显示所选的提交。结果是,Git似乎有文件历史记录,但它实际上没有:它只是根据实际历史记录合成了一个子集历史记录,并在处理过程中工作。
这很重要,因为当Git进行合成文件历史创建时,git log
正在修改提交步骤。git log
文档将其称为历史简化,它很复杂。有大约半打git log
选项来控制如何执行历史简化。这意味着您使用git log
看到的“文件历史记录”取决于您传递给git log
的选项以及实际提交历史记录是什么。
(至少有一天要阅读和学习历史简化部分,因为其中有很多内容。我已经使用Git很长时间了,并且认为我对它了解很多,但即使如此,我也不得不参考此文档。特别是“TREESAME”的概念-在从每个提交中减去不需要的树组件后应用,并且在合并时跟随哪些提交尤其棘手。)
使用--follow
参数,git log
将尝试检测文件重命名操作
由于Git是逐个提交地向后遍历一系列提交,因此从父提交到子提交的差异可能显示某些文件已被重命名。例如,名为README
的文件可能已被重命名为README.md
。可以简单地使用以下命令:
git log master -- README.md
将向您展示如何逆向演变README.md
,但会在README.md
被命名为README
时停止,因为它正在寻找README.md
,而此后的提交不再具有README.md
。当您在git log
中添加--follow
时,它将跟踪该文件-它只适用于一个文件!-在重命名过程中,只需更改它正在查找的文件即可。例如,在检测到从提交-D
-到-E
边界时,现在称为README.md
的文件在提交D
中被称为README
,git log
停止寻找README.md
并开始从D
开始寻找名为README
的文件的更改。就是这么简单。--follow
对于您的用例太简单了。
这里的问题在于--follow
实在是太简单了,这种简单过度的情况导致它无法满足你的需求,原因有两个:
First, you're talking about copying files across some fairly large gap:
...--F--G--H <-- master
\
N--O--P <-- branch
If your directory-full-of-files is in commit H
on master
, and you're just now copying it to a new commit you'll make on branch
that comes after commit P
, well, there's no backwards link from P
to H
. That's why I proposed that you commit the files without renaming them, then rename them and commit again. The result will be:
...--F--G--H <-- master
\
N--O--P--Q--R <-- branch
where commit R
has the files renamed, and Q
has them not-renamed, just copied from H
. In the commit log message for Q
, you can state that the entire directory has been copied from branch master
at a time when it pointed to commit H
(use H
's real hash ID here—run git rev-parse master
to see what hash ID master
specifies right now). Then you rename the directory and commit again to make them show up as renames, whenever Git walks from commit R
back to commit Q
.
The git log --follow
option only works on one file. That is, given a commit that is or descends from R
, and therefore has the new directory name, you must run:
git log --follow [<commit-hash>] [--] new/path/to/dir/file.ext
which will eventually work its way to commit R
, show new/path/to/dir/file.ext
(because it is renamed in commit R
as compared to commit Q
), then move back to commit Q
and start looking for path/to/directory/file.ext
.
从这个检测到的重命名,再加上Q
和R
中的日志信息,你——一个聪明的人而不是一个只遵循非常简单规则的愚蠢的Git程序——可以得出结论,啊哈,所有这些文件都来自提交H
。
这就是你可能需要一个真正的合并的地方。你可以直接将提交Q
作为合并提交,将历史记录从提交Q
连接回到两个提交:P
和H
。也就是说,假设你最终得到:
...--F--G--H <-- master
\ \
N--O--P----Q--R <-- branch
现在,当Git遍历提交历史时,它会按顺序遍历R、Q、H和P、G和O、F和N等提交。也就是说,git log会逐个提交地遍历实际历史,通过一种复杂的跟踪方法来追踪历史中的分支,其中提交H和P合并形成提交Q。
这种合并的缺点显而易见:它是一个合并。默认情况下,它将带入自某个共同祖先以来的所有更改,自分支和主分支最终回到共享提交之前的提交N和提交F。您不一定要提交这些更改,甚至不需要任何更改:除了您想要的新目录外,您可以使提交Q的快照与提交P的快照相匹配。
有多种方法可以进行合并。如何实现是另一个完全不同的StackOverflow问题,已经得到了很好的解答。请参见(Git Merging) When to use 'ours' strategy, 'ours' option and 'theirs' option?,以及VonC's answer to a different question here。这里有许多选项,但您可能希望首先使用git merge -s ours --no-commit
,如果您想使用-s ours
,然后使用git checkout <commit> -- <path>
提取文件,最后将提交Q
作为合并。
合并的优点在于将历史记录绑定在一起,这样git log
就可以从合并Q
回到提交H
,这是(重命名前)文件实际历史的源头。缺点是它将历史记录绑定在一起,因此从那时起,Git认为将H
与P
合并的正确结果是Q
,即使您以后改变了主意。
如果合并不是您想要的,那么提交和日志消息可能就是您所需要的。