git相当于“hg strip”

时间:2015-11-18 23:18:36

标签: git mercurial

这个问题 - 或基本相似的“如何撤消git fetch” - 之前曾被问过几次,但答案总是过于简单化。特别是,最流行的答案(git reset --hard)根本不会触及存储库。

我正在寻找的答案应该留下本地git repo(不是工作目录,而不是索引;我真的不关心那些最终处于什么状态)处于同一状态(bit-for-bit,理想情况下,它是在取之前。我会接受重新克隆上游或修改本地回购。我可以使用忽略多个分支,多个遥控器等的解决方案,但是一个更成熟的解决方案会很好。

我一直在使用的测试是:

  • git show显示$want
  • git log$want
  • 开始
  • git show $latest错误
  • git tag -l $latesttagname没有显示任何内容
  • git show $latesttag错误
  • git fetch -v清楚地下载了我剥离的所有东西(它应该说“最新”)

其中$latest是我尝试撤消的提取后头部的提交哈希值,$latesttag是需要剥离的提交集中最新标记提交的提交哈希值,$latesttagname是该标记的名称,$want是旧头部的提交哈希值,一旦剥离其后代,我想成为新头部。

我尝试过的事情:

  • git reset --hard $want:仅通过前两个测试

  • 添加'git prune -n​​v'没有帮助

  • git gc --prune=now

  • 也没有
  • 如果我在执行reset后克隆了回购,那么在孩子中第三次测试通过,第六次出现(在将远程URL设置到适当的上游之后)

  • 通过所有测试的原因是在克隆之前在我的本地父母中运行git tag -d $(git tag --contains $want | tail +2);特别是,之前由标签引用的提交不再被引用,所以我猜他们会被抛在脑后。但它表明我可能忽略了其他参考类型。

  • 我带出了大锤子,做了一些似乎接近hg strip所做的事情(为那里的zshisms道歉):

    tags=( $(git tag --contains $want | tail +2) )
    mv .git/objects/pack .
    git unpack-objects < pack/pack*.pack
    rm -rf pack
    git log --pretty=format:%H $want..HEAD | while read i; do rm .git/objects/$i[1,2]/$i[3,-1]; done
    git update-ref refs/remotes/origin/master $want
    git update-ref refs/heads/master $want
    for i in refs/tags/$^tags; do git update-ref -d $i; done
    git repack -ad
    

    几乎完成了你期望锤子做的事情 - 打破一堆事情:git fsck --unreachable抱怨一堆无效的reflog条目和无法访问的blob(和一个悬空提交),但克隆使得这些消失了。

在我看来,reset +标记删除+ clone是我在这里最好的选择,但是a)我需要删除/重新命名的其他参考,以及b)是否存在一种避免需要克隆的方法?还是完全有另一种方式?

道歉的长度。我希望尽可能清楚地了解我正在寻找的东西(以及我没有找到的东西)以及我已经发现的失败(以及为什么)。如果你走得这么远,谢谢你。

1 个答案:

答案 0 :(得分:0)

TL; DR回答:你的重置,删除任何指向重置区域的标签,克隆方法有效,可能就是这样。

如果你想清理一个现有的仓库,你(至少可能)需要git for-each-ref和reflog清理(每个ref的reflog,然后是HEAD

在没有克隆的情况下可以(但显然更难)做到这一点,并且您正在使用git gc --prune=now(或--prune=all正确的轨道,正如文档所述;尽管它可能更好地使用显式git repackgit prune,您可能还需要清除一些reflog条目。

&#34;大锤子&#34;方法可能过于激进,因为它可以 2 删除所需的对象,因为它们与您保留的提交中的对象相同。 (在意识到你只是删除提交对象之后编辑:并且,它可能会留下对象,尽管它们在正常使用中不会被看到。这是否是一个大问题取决于删除这些对象的重要性。请参阅下面的一般理论部分。)

克隆更容易,因为克隆只会带来从它带来的引用中可以访问的那些对象。也就是说,您可以控制添加的内容,而不是必须避免的内容。只要您使用智能协议和/或展开任何包(例如,请参阅下面的包部分),您的方法(删除任何剩余标签并确保没有分支包含您想要的任何提交)就足够了。

什么被复制

有点形式上,假设整个存储库图是 G (这是存储在存储库中的所有对象提交,标记,树和blob)。克隆通过从克隆的分支名称开始查找 g 的子集 G 1 查找对象这些名称,然后递归添加从这些对象可到达的任何对象。因此,如果refs/heads/master指向某个提交,则会获得该提交,其树,其树中的任何子树以及与这些树相关联的所有blob;加上作为其父母,他们的树,子树和blob的提交,等等。对refs/heads/中的每个名称重复一次,您就拥有基本的子图 g

(A fetch使用相同的过程,除了接收者告诉发件人他有哪些SHA-1 ID作为现有分支提示。发送者可以使用它来排除从这些SHA-1可到达的对象,前提是发送者拥有那些SHA-1。这只适用于&#34;智能&#34;协议,但通常你应该使用智能协议。)

默认情况下,clonefetch都会对标记采用混合方法。如果添加--tags,除了分支名称之外,他们只使用远程提供的所有标记(顺便说一句,您可以使用git ls-remote查看远程提供的所有引用)。如果您使用--no-tags,则会完全忽略这些标记。否则,他们最初会忽略这些标记,像往常一样构建子图 g ,然后添加指向 g 。对于轻量级标记,这不会添加任何对象,但是对于每个带注释的标记,这会将带注释的标记对象添加到 g (如果标记指向除提交之外的其他内容,例如另一个tag - 你也可以在这里获得更多的对象。)

一旦Git构建了克隆或获取图形 g ,它就会提取对象,从中创建一个新包,并发送该包。这意味着您不必担心现有的包含过时&#34;陈旧&#34;对象(例如,可能包含敏感数据)。

没有克隆

但是,如果你不想克隆,你必须考虑Git可以看到的所有引用。 git gcgit prunegit gc为您运行)都将删除未引用的松散对象。显然这意味着&#34;被引用&#34;这是一个关键。但是,对于松散的物体,还有另一个豁免检定:如果他们非常年轻,Git不会删除它们。 (这可以保护在引用插入仓库之前生成的对象。)使用--prune=all(或--expire now)将终止这些对象。除此之外,还有一个单独的问题&#34; packed&#34;对象 - 但让我们暂时离开。

除了分支和标记名称之外,对象的其他标准引用是:

  • 远程跟踪分支(refs/remotes/中的任何内容)
  • 备注(refs/notes中的任何内容)
  • 藏匿(refs/stash
  • Git的各种特例案例:HEADFETCH_HEADORIG_HEADMERGE_HEADCHERRY_PICK_HEAD(这些是唯一正常的但.git中的任何文件都可以作为参考:请参阅gitrevisions

但为了安全起见,您应该查看.git/refs.git/packed-refs,或者更好,更简单,使用git for-each-ref。对于发现的每个引用,请查看它是否指向您要丢弃的提交;如果是这样,删除(或重新指向)该ref。这还包括远程跟踪分支origin/foo(仅refs/remotes/origin/foo)的情况,当您在foo上删除或倒回origin并且不在refs/remotes/origin/foo时。我希望那些提交出现。

接下来,您需要查看reflog。如果您删除一个引用,Git(当前)也会删除它的reflog,因此这仅适用于第一次传递中未删除的引用。 reflog基本上是每个ref的平坦历史。例如,1234567...可能在取消您正在执行/剥离的提取之前命名了提交fedcb9a...,现在名称提交1234567...。其reflog将包含git reflog delete ref@{number}。如果您希望剥离该提交,则需要删除此reflog条目。您可以使用HEAD执行此操作;见the git reflog documentation。通常,每个分支和每个远程跟踪分支都有一个reflog,加上git unpack-objects的一个。

看起来您已经使用git repack -a -d处理了此问题,但我会完成此事。

即使清除了所有引用,仍然存在打包对象问题:Git可能会在包中保留未引用的对象。要摆脱它们,您必须使用.keep强制Git创建一个新的单包文件并删除任何现有的包文件。我不确定这是否会删除包含相关git gc文件的包文件(git gc赢了&t 39和git repack大多只是运行其他命令,所以它&# 39; --tags可能会赢得其中任何一项。

1 有时是标签(如果您使用refs/heads/)。通常,分支名称选择是&#34;所有分支名称&#34; - git clone中的所有内容 - 但您可以指示--single-branch仅使用{{1}}克隆一个分支。

2 这种可能性取决于对象的年龄:重复使用的最旧的对象会在包文件中结束,如果您没有将它们解包,那么它们将是安全的。 :-)编辑:另外,我最初误读了这个例子:你只是剥离提交对象,所以只要他们不在其他分支中共享你应该没问题