6

正如标题所暗示的,我试图弄清楚如何使用go-git与 Git CLI 命令相同的结果来创建本地分支git branch <branchname>

据我所知,git branch <branchname>(没有明确的<start-point>论点)做了两件事:

  1. 创建.git/refs/heads/<branchname>指向当前HEAD提交
  2. .git/logs/refs/heads/<branchname>用单行记录分支的创建。

它可能会做得更多,但我知道这两件事是肯定的。(如果您知道它的更多功能,请分享!)

接下来的大部分内容都记录了我在研究我的选择时的发现之旅,我想我现在可能已经掌握了上面的#1。不过,对于#2,我开始认为我可能是 SOL,至少使用go-git.

第一个想法:Repository.CreateBranch

我最初的幼稚想法是打电话Repository.CreateBranch,并且有一个类似的 SO 问题的答案(“如何使用 go-git 结帐一个新的本地分支?”),这似乎可以证明这个想法。但是一旦我开始研究细节,事情就变得非常混乱。

首先,Repository.CreateBranch将 aconfig.Config作为输入(为什么?),并且似乎还修改了存储库的.git/config文件(再次,为什么?)。我已经验证该git branch <branchname>命令不会触及 repo 的配置,当我调用该命令时,我当然不需要提及任何有关配置的内容。

其次,我在上面链接的 SO 答案引用了go-git'srepository_test.go中的代码,该代码执行以下操作:

r, _ := Init(memory.NewStorage(), nil) // init repo
testBranch := &config.Branch{
    Name:   "foo",
    Remote: "origin",
    Merge:  "refs/heads/foo",
}
err := r.CreateBranch(testBranch)

但是 的定义config.Branch是:

type Branch struct {
    // Name of branch
    Name string
    // Remote name of remote to track
    Remote string
    // Merge is the local refspec for the branch <=== ???
    Merge plumbing.ReferenceName
    ...
}

并且"refs/heads/foo" 不是 refspec(因为 refspec:将其srcdst组件分开)。

经过大量的头疼和代码阅读后,我得出了一个(非常)初步的结论,即评论中的“refspec”这个词一定是错误的,而应该只是“ref”。但我对此完全不确定:如果我是对的,那么为什么这个字段被命名Merge而不是 just Ref

另一个初步结论是,这Repository.CreateBranch并不是真正用于创建纯粹的本地分支,而是用于创建与远程分支有某种关系的本地分支——例如,如果我从遥控器。

实际上,在重新阅读Repository.CreateBranch方法时,我根本不相信它确实创建了一个分支(也就是说,它创建了一个分支.git/refs/heads/<branchname>)。除非我遗漏了某些东西(完全有可能),否则它似乎所做的[branch "<name>"] 只是.git/config. 但如果这是真的,为什么它是一种方法Repository呢?为什么它不是一种方法config.Config

同样,还有一个相关的功能:

func (r *Repository) Branch(name string) (*config.Branch, error)

那只会从配置中返回分支信息。然而,文档中的下一个功能Repository是:

func (r *Repository) Branches() (storer.ReferenceIter, error) 

它确实返回了一个遍历.git/refs/heads/.

这非常令人困惑,并且文档(例如)无济于事。无论如何,除非有人能说服我,否则我很确定这对实际创建一个分支CreateBranch没有太大帮助。

工作树结帐???

一些额外的网络搜索从旧d-src/go-git回购中发现了这两个问题:

这两篇文章都提出了创建本地分支的基本方法:

wt, err := repo.Worktree()                                                                                                                                                                                                                           
if err != nil {                                                                                                                                                                                                                                  
        // deal with it                                                                                                                                                                                                                                   
}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
err = w.Checkout(&git.CheckoutOptions{                                                                                                                                                                                                           
        Create: true,                                                                                                                                                                                                                            
        Force:  false,                                                                                                                                                                                                                           
        Branch: plumbing.ReferenceName("refs/heads/<branchname>"),                                                                                                                                                                                
})

除了这个检查新分支的事实之外git branch <branchname>,它也没有创建.git/logs/refs/heads/<branchname>.

此外——作为一个潜在的非常令人讨厌的意外——它会清除工作树中所有未跟踪的文件。默认情况下,git checkout 保留对工作树中文件的本地修改,但go-git您需要明确指定Keep: true,即使您已指定Force: false.

绝对违反了“最小惊讶原则”。值得庆幸的是,在我测试过的本地仓库中,它们都是旧的编辑器备份文件或我早就放弃的旧项目的片段。

存储器.ReferenceStorer

碰巧的是,其中一位go-git作者/维护者回应了第二个问题,并建议:

为了创建和删除独立于工作树的引用,您应该使用storer.ReferenceStorer.

请看一下分支示例:https ://github.com/src-d/go-git/blob/master/_examples/branch/main.go

这很好,很简单,但它只解决了分支 ref 的创建。

我能够在go-git源代码中找到的所有“日志”一词似乎都指的是提交日志,而不是参考日志。鉴于 reflog 条目看起来不像.git树中的其他工件,我想创建/更新它们需要一种不同类型的存储器 - 并且现有存储器看起来都不像(对我而言)他们做那。

所以...

关于我应该如何获得适当的 reflog 以配合 ref 的任何建议?

(或者,也许我严重误解了,除了我上面列出的那些之外,还有一些方法可以在 中创建分支go-git,这可以满足我的需求。)

4

2 回答 2

2

首先,我没有足够的声誉来评论 Pedro 的回答,但他的方法在Checkout阶段失败了,因为实际上没有在存储上创建分支(Storer从未调用过 repo)。

其次,这是我第一次听说.git/logdir,所以不,git branch不会为该 dir 中的分支创建记录。

这使我得到了实际的解决方案,它是作为go-git repo分支示例提供的解决方案。

  • 要创建一个分支(离开 HEAD):
Info("git branch test")
branchName := plumbing.NewBranchReferenceName("test")
headRef, err := r.Head()
CheckIfError(err)
ref := plumbing.NewHashReference(branchName, headRef.Hash())
err = r.Storer.SetReference(ref)
CheckIfError(err)
  • 结帐分支
Info("git checkout test")
w, err := r.Worktree()
CheckIfError(err)
err = w.Checkout(&git.CheckoutOptions{Branch: ref.Name()})
CheckIfError(err)

然而,这样一来,这个分支就没有配置了.git/config,所以应该调用repo.Branch函数,但这真的很不直观。

于 2021-11-16T11:10:15.180 回答
1

我是怎么做的:

创建对新分支的本地引用

branchName := "new-branch"
localRef := plumbing.NewBranchReferenceName(branchName)

创建分支

opts := &gitConfig.Branch{
    Name:   branchName,
    Remote: "origin",
    Merge:  localRef,
}

if err := repo.CreateBranch(opts); err != nil {
    return err
}

如果您确实需要更改到该分支...只需进行结帐(不记得它是否实际上通过创建更改为创建的分支)

获取工作树

w, err := repo.Worktree()
if err != nil {
    return rest.InternalServerError(err.Error())
}

查看

if err := w.Checkout(&git.CheckoutOptions{Branch: plumbing.ReferenceName(localRef.String())}); err != nil {
    return nil
}

如果您想跟踪远程分支

创建远程引用

remoteRef := plumbing.NewRemoteReferenceName("origin", branchName)

远程跟踪

newReference := plumbing.NewSymbolicReference(localRef, remoteRef)

if err := repo.Storer.SetReference(newReference); err != nil {
   return err
}
于 2021-06-18T16:10:23.247 回答