git copy file,而不是`git mv`

时间:2017-11-20 22:01:36

标签: git cp git-mv

我意识到git通过区分文件的内容来工作。我有一些我想复制的文件。为了绝对防止git混淆,是否有一些git命令可用于将文件复制到不同的目录(不是mv,但是cp),并且还可以暂存文件?

1 个答案:

答案 0 :(得分:16)

简短的回答只是" no"。但还有更多要知道;它只需要一些背景知识。 (和JDB suggests in a comment一样,为方便起见,我会提到为什么git mv存在。)

稍微长一点:你是对的,Git会对文件进行区分,但是当 Git执行这些文件差异时,你可能会错误

Git的内部存储模型建议每次提交都是该提交中所有文件的独立快照。进入新提交的每个文件的版本,即该路径的快照中的数据,是您运行git commit时该路径下的索引中的任何内容。 1

第一级的实际实现是每个快照文件都以压缩形式捕获为Git数据库中的 blob对象。 blob对象完全独立于该文件的每个先前版本和后续版本,除了一个特殊情况:如果您进行新的提交,其中 no 数据已更改,您将重新使用旧blob 。因此,当您连续进行两次提交,每次提交包含100个文件,并且只更改一个文件时,第二次提交将重新使用99个先前的blob,并且只需要将一个实际文件快照到新的blob中。 2功能

因此,Git将差异文件的事实根本没有进入提交。除了存储先前提交的哈希ID之外,没有提交依赖于先前的提交(并且可能重新使用精确匹配的blob,但是它们的副作用完全匹配,而不是花哨运行git commit时的计算。

现在,所有这些独立的blob对象最终都会占用过多的空间。 此时,Git可以"打包"将对象转换为.pack文件。它会将每个对象与一些选定的其他对象进行比较 - 它们可能在历史中更早或更晚,并且具有相同的文件名或不同的文件名,理论上Git甚至可以针对blob对象压缩提交对象,反之亦然(虽然在实践中它并没有) - 并尝试找到一些方法来使用更少的磁盘空间来表示许多blob。但结果至少在逻辑上仍然是一系列独立的对象,使用它们的哈希ID以原始形式完整地检索。因此,即使此时使用的磁盘空间量下降(我们希望!),所有对象都与以前完全相同。

那么当 Git比较文件时?答案是:只有当你提出要求时才会这样做。"问时间"就是当你直接运行git diff时:

git diff commit1 commit2

或间接:

git show commit  # roughly, `git diff commit^@ commmit`
git log -p       # runs `git show commit`, more or less, on each commit

关于此问题有一些细微之处 - 特别是git show会在合并提交时生成Git所谓的组合差异,而git log -p通常只是向右跳过在合并提交的差异上 - 但是这些以及其他一些重要的情况是Git运行git diff时。

当Git运行git diff 时,你可以(有时)要求它找到或不找到副本。 -C标志也拼写为--find-copies=<number>,要求Git查找副本。 --find-copies-harder标志(Git文档调用&#34;计算成本高昂&#34;)看起来比普通-C标志更难。 -B(中断不合适的配对)选项会影响-C-M又称--find-renames=<number>选项也会影响-C。可以告诉git merge命令调整其重命名检测级别,但至少目前不能告诉他们找到副本,也不能打破不合适的配对。

(一个命令,git blame,有一些不同的复制查找,上述内容并不完全适用于它。)

1 如果您运行git commit --include <paths>git commit --only <paths>git commit <paths>git commit -a,请在运行{{1}之前将其视为修改索引}}。在git commit的特殊情况下,Git使用临时索引,这有点复杂,但它仍然从 索引提交 - 它只使用特殊的临时索引而不是正常的索引一。要创建临时索引,Git会复制--only提交中的所有文件,然后覆盖那些包含您列出的HEAD文件的文件。对于其他情况,Git只是将工作树文件复制到常规索引中,然后继续像往常一样从索引进行提交。

2 实际上,将blob存储到存储库中的实际快照发生在--only期间。这会使git add秘密变得更快,因为您通常不会注意在启动git commit之前运行git add所需的额外时间。

为什么git commit存在

git mv的作用是什么,非常

git mv old new

第一步显而易见:我们需要重命名文件的工作树版本。第二步是类似的:我们需要将文件的索引版本放在适当的位置。然而,第三个是很奇怪:我们为什么要添加&#34;我们刚删除的文件?好吧,mv old new git add new git add old 并不总是添加文件:相反,在这种情况下,它会检测到索引中的文件 并且不再存在。

我们还可以将第三步拼写为:

git add

我们所做的只是将旧名称从索引中删除。

但这里有一个问题,这就是为什么我说&#34; 非常大致&#34;。索引具有每个文件的副本,这些文件将在您下次运行git rm --cached old 时提交。 该副本可能与工作树中的副本不匹配。实际上,如果git commit中有一个副本,它可能与HEAD中的副本不匹配。

例如,在:

之后
HEAD

文件echo I am a foo > foo git add foo 存在于工作树和索引中。工作树内容和索引内容匹配。但现在让我们改变工作树版本:

foo

现在索引和工作树不同了。假设我们要将基础文件从echo I am a bar > foo 移动到foo,但由于某些奇怪的原因 3 - 我们希望保持索引内容不变。如果我们运行:

bar

我们会在新索引文件中获得mv foo bar git add bar 。如果我们从索引中删除旧版I am a bar,则会完全丢失foo版本。

所以,I am a foo并没有真正移动和添加两次,或者移动 - 添加 - 删除。相反,它重命名工作树文件重命名索引中的副本。如果原始文件的索引副本与工作树文件不同,则重命名的索引副本仍然与重命名的工作树副本不同。

如果没有git mv foo bar等前端命令,很难做到这一点。 4 当然,如果你计划git mv一切,你就不要# 39;首先需要所有这些东西。并且,值得注意的是,如果存在git add,则在制作索引副本时,它可能复制索引版本,而不是工作树版本。所以git cp确实应该存在。还应该有一个git cp选项,一个Mercurial&#39; git mv --after。两个都应该存在,但目前还没有。 (但是,对于这些中的任何一个都没有那么多,而不是直接hg mv --after,在我看来。)

3 对于这个例子,它有点愚蠢和毫无意义。但是如果你使用git mv为中间提交仔细准备一个补丁,然后再与补丁一起决定,你想重命名该文件,那么能够做到这一点绝对是有用的。弄乱你精心修补的中间版本。

4 这不是不可能的:git add -p会立即从索引中获取您需要的信息,git ls-index --stage可以让您制作任意更改索引。您可以将这两者以及一些复杂的shell脚本编程或编程用更好的语言组合在一起,以构建实现git update-indexgit mv --after的内容。