git-如何为git prune创建无法访问的提交?

时间:2018-09-30 22:23:46

标签: git

我正在为git写教学材料,我需要演示git prune删除“分离的对象”。我以为可以通过使用git reset将提交与分支历史记录分离来将提交置于分离状态。

这将触发git checkout将提交视为已分离,但是git prune不会在意。

我当前的分离提交模拟的设置如下:

~ $ mkdir git-prune-demo
~ $ cd git-prune-demo/
~/git-prune-demo $ git init .
Initialized empty Git repository in /Users/kev/Dropbox/git-prune-demo/.git/
~/git-prune-demo $ echo "hello git prune" > hello.txt
~/git-prune-demo $ git add hello.txt
~/git-prune-demo $ git commit -am "added hello.txt"
[master (root-commit) 994b122] added hello.txt
 1 file changed, 1 insertion(+)
 create mode 100644 hello.txt
~/git-prune-demo $ echo "this is second line txt" >> hello.txt
~/git-prune-demo $ git commit -am "added another line to hello.txt"
[master 5178bec] added another line to hello.txt
 1 file changed, 1 insertion(+)
~/git-prune-demo $ git reset --hard 994b122045cf4bf0b97139231b4dd52ea2643c7e
HEAD is now at 994b122 added hello.txt
~/git-prune-demo $ git prune -n
~/git-prune-demo $ nothing

是的,我知道git prune通常不用作独立命令,实际上是git gc的子级。

2 个答案:

答案 0 :(得分:4)

TL; DR

您将需要先运行git reflog expire --expire-unreachable=now,然后运行git prune --expire now。即使这样,也可能出错了,尽管对于这个特定的简单示例来说,这可能就足够了。

  

我正在为git编写教学材料,我需要演示git prune删除独立提交。

但这不是git prune 所做的。它的作用可以产生这种效果,但只能在特定条件下进行。重要的是, detached commit 在Git中不是定义明确的短语:Git为 detached HEAD 定义了一个概念,我们稍后再讲,但是自己提交是 reachable unreachable 。我认为您的意思是在这里谈论无法实现的提交。

重要的是,git prune处理的是对象,它比提交更通用。 Git有四种类型的对象:提交,树,blob和带注释的标签。只要满足其他几个条件,Git的git prune可以删除任何任何不可达对象。不过,在到达那里之前,让我们看一些其他项目。

纠正误解

  

我以为可以通过使用git reset将提交与分支历史记录分离来将提交置于分离状态。

根据定义,如果有一些外部名称可以直接将提交(或对象)本身命名,也可以命名其他可以通过它来实现给定提交的其他对象,则可以定义一个提交(或任何其他Git对象) 。 (有关此内容的更多信息,请参见Think Like (a) Git。)使用git reset,可以使仅通过当前分支名称可访问的提交变为不可访问。例如,如果只能通过当前分支名称(即,不能通过任何其他分支名称,也不能通过任何标记名称或其他非分支名称引用)来访问提交a123456...,则可以使用git reset来完成调整当前分支,使其排除a123456...,使该提交不可访问。

  

这将触发git checkout将提交视为已分离...

我认为您在这里谈论的是Git所谓的独立头。

分离的HEAD只是意味着Git的特殊HEAD引用存储为名为.git/HEAD的文件,其中包含提交的原始哈希ID。当.git/HEAD包含分支名称时,会发生相反的情况,我们可以称其为 attached HEAD ,因为这是 detached 的明显反义词。在这两种情况下,HEAD都指向当前提交;当HEAD包含分支名称时,HEAD也指代当前分支名称。 Git内部处理此问题的方式是,它具有不同的功能和程序来象征性地解决HEAD

$ git symbolic-ref HEAD
refs/heads/master

或哈希ID:

$ git rev-parse HEAD
c05048d43925ab8edcb36663752c2b4541911231

(对于分离的HEAD情况,git symbolic-ref会产生错误,因为没有分支名称。)

在以下情况下,git checkout命令将HEAD附加到某些指定的分支名称:

  • 您给它命名为分支名称,或者
  • 您使用它创建并附加到新的分支名称。

在以下情况下,它会分离HEAD:

  • 您为它提供了可以解析为哈希ID而不解析为分支名称的名称(例如,原始哈希ID或origin/master之类的远程跟踪名称),或者
  • 您可以使用--detach标志来强制分离的HEAD,即使它可以正常连接HEAD。

分离的HEAD模式并不意味着您正在使用不可访问的提交。实际上,将HEAD分离到否则无法实现的提交中,会使该提交突然变为,因为现在是HEAD提交。换句话说,将HEAD分离到任何提交都增加了另一种到达提交的方式,但是就修剪而言,有趣的问题不是有多少个名称到达所讨论的对象,而是数字不为零。一个名称,两个名称,十个名称或数百万个名称:所有这些与git prune相同。当我在这里说 names 时,我的意思不仅仅是参考名称加上可能分离的HEAD,但在添加下一个复杂功能之前,我们将从这些名称开始。

Git的对象模型,以及对对象的引用

Think Like (a) Git很好地描述了引用如何使提交可到达。但是,它并没有提到引用通常可以指定 any 对象的哈希ID,而不仅仅是提交。这是因为它与分支有关,而不仅仅是任何旧对象,并且分支名称(refs/heads/*)和远程跟踪名称(refs/remotes/*)都被约束为仅指向提交。它还没有涉及提交内部内容的详细信息,即Git如何存储文件和文件名。这是树和斑点对象进入的地方。

每个提交都包含单个树对象的哈希ID。树对象包含一系列三值项:模式,名称和哈希ID。该模式指定此树条目是用于文件,子树还是其他奇特项之一(符号链接和gitlinks)。该名称给出了要表示的实体的名称,例如README.txtsubdirfile.ext。哈希ID通常是blob对象或另一个树对象的ID:如果条目用于README.txt之类的文件,则它是blob哈希,并且用于subdir之类的子树。 ,这是子树的哈希ID。

如果我们将所有这些都绘制到一个提交中,则从最上面一行的右侧的分支名称开始,我们将得到如下内容:

... <-  commit a1234...   <-- branchname
               |
               v
        tree 07f39...: (100644, README.txt, 531c2...); (040000, subdir, ...)
                                               |                         |
                                               v                         |
                                blob 531c2...: data for README.txt       |
                                                                         |
                                                                         v
                                                               tree ...: ...
允许

带注释的标记对象指向任何其他对象(包括其他带注释的标记对象),尽管大多数情况下,它们仅指向提交对象。因此,在此图片中添加带注释的标签,我们通常只会看到诸如refs/tags/v1.0之类的标签引用指向带有一些哈希ID的带注释的标签对象,然后带注释的标签对象继续指向,例如,提交{ {1}}。这将为该提交提供另一个参考。如果我们还没有创建任何标签,则无需担心这些标签,但是它们对于完整图片很重要。

与提交一样,如果存在从某个外部名称引出的路径(对于Blob,是Git的 index 中存储的内部引用)导致的对象,则将引用任何对象。索引只能引用blob,因此,当我们仅对提交感兴趣时,可以忽略索引的引用,但是像标签一样,它们对于整个图片很重要。

无论如何,在上图中,我们可以看到名称a1234...使提交branchname可以访问。提交a1234...使树a1234...可以访问,这使blob和另一个子树可以访问,依此类推。由于这些都是可以访问的,因此07f39...肯定不会修剪它们。

重要的是,每个引用名称加上特殊的git prune名称,都有一个可选的 reflog ,用于为该引用存储引用的先前值。这些保存的值将有效一段时间,直到过期。 Git用于使过期保存的值失效的命令是HEAD,使用两个不同的命令行选项git reflog expire--expire=when

如果要显示--expire-unreachable=when删除对象,则需要确保该对象完全未被引用。这意味着您将需要删除任何直接记住(提交)或间接记住(树和blob)其哈希ID的reflog条目。这样做的简单方法(虽然具有破坏性)是使用:

git prune

(我们可以添加git reflog expire --expire-unreachable=now --all ,但是我们可以假定引用的当前值未达到reflog值,因此--expire=now设置就是适用的设置。)

这设置了必要的条件;现在该回到--expire-unreachable本身了。

所有事情都解决了,让我们回到git prune

git prune命令处理所有四种类型的对象。它的工作是删除未引用对象。从上面的内容中我们知道,在使用git prunegit branch -f或{{1}之类的命令之后,我们必须通过使可能记住它的所有reflog条目过期来确保提交未被引用。 },以确保没有分支名称也可以记住。

但是现在我们需要了解有关Git对象的两件事:

  • 它们可以是 loose packed ,并且
  • 他们有年龄,很像reflog条目。

松散的对象存储在文件系统中的单独文件中。这使得Git易于操作它,但是意味着它已被最小压缩。 Git将根据命令(或自动通过git branch -D)将许多单独的对象 pack 打包到一个 pack文件中。此时,文件系统中的一个文件包含许多对象:数十个(也许是几百万个)或介于两者之间的某个对象。

git reset命令将从不修剪一个打包的对象,因为这太难了。打包对象可能是其打包文件中增量压缩链的一部分。因此,git gc只会查看 loose 对象。一个单独的程序{prune –将重新打包对象,并将未引用的打包对象变回松散的对象(或完全丢弃它们)。

通常,对象不会立即打包,因此最近创建的对象可能会松动。但是,如果对象已被打包,并且现在未被引用,则需要运行git prune

与此同时,为了防止竞争的Git进程,git repack 还检查松散对象的时间戳。此时间戳必须足够旧,以允许git repack删除对象。这样做的原因是,当Git创建 new 对象(包括新提交)时,它将一次将这些对象一次(或仅几个)写入存储库数据库。 Git必须编写带有其Blob哈希值的最深子树,然后使用这些子树及其哈希值以及这些树中存在的任何Blob哈希值来编写下一层的树。一旦Git写出所有树并获得顶级树哈希以进行新的提交,Git才可以编写提交对象。在此之前,所有这些树都是未引用的。即使写入了提交,也不会引用 ,直到更新当前分支名称(或分离的git prune)以指向新创建的提交。

此过程需要时间。默认情况下,Git会给自己14天的时间来完成该过程。如果git prune需要14天以上才能完成,HEAD可能会删除其某些对象-但是14天应该是足够的时间。

如果您知道没有运行任何其他Git命令,则可以手动覆盖默认值:

git commit

表示任何未引用的松散对象都应删除,无论它们是多么新。因此,您需要做的就是确保您的提交未被引用,然后使用“现在”的到期时间进行修剪。

答案 1 :(得分:1)

修剪通常不会删除最近一个引用中可以到达的对象...我忘记了,一个月? -它检查本地reflog。通过--expire now可以禁用对象删除操作中的oops保护,但是如果您正在编写文档,则建议这样做确实是一个非常非常不好的习惯。