正如标题所示,我试图弄清楚如何使用go-git与 Git CLI 命令给出相同结果的方式创建本地分支git branch <branchname>。
据我所知,git branch <branchname>(没有明确的<start-point>论证)做了两件事:
.git/refs/heads/<branchname>指向当前HEAD提交.git/logs/refs/heads/<branchname>使用一行记录分支的创建进行创建。它可能会做更多的事情,但我知道它肯定会做这两件事。(如果你知道更多它的作用,请分享!)
接下来的大部分内容记录了我在研究我的选择时的发现之旅,我想我现在可能已经掌握了上面的#1。不过,对于#2,我开始认为我可能是 SOL,至少使用go-git.
我最初天真的想法是直接打电话Repository.CreateBranch,并且有一个类似的 SO 问题的答案(“如何使用 go-git 检查新的本地分支?”),这似乎为这个想法提供了可信度。但一旦我开始研究细节,事情就变得非常混乱。
首先,Repository.CreateBranch采用 aconfig.Config作为输入(为什么?),并且似乎还修改了存储库的.git/config文件(同样,为什么?)。我已经验证该git branch <branchname>命令不会触及存储库的配置,并且当我调用该命令时,我当然不需要提及有关配置的任何内容。
其次,我上面链接的 SO 答案引用了 中的代码,go-git该repository_test.go代码执行以下操作:
r, _ := Init(memory.NewStorage(), nil) // init repo
testBranch := &config.Branch{
Name: "foo",
Remote: "origin",
Merge: "refs/heads/foo",
}
err := r.CreateBranch(testBranch)
Run Code Online (Sandbox Code Playgroud)
但 的定义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
...
}
Run Code Online (Sandbox Code Playgroud)
并且"refs/heads/foo" 不是 refspec(因为 refspec 具有:分离它的src和dst组件)。
经过大量的绞尽脑汁和代码阅读之后,我得出了(非常)初步的结论:注释中的“refspec”一词一定是错误的,而应该只是“ref”。但我对此完全不确定:如果我是对的,那么为什么这个字段被命名Merge而不是仅仅被命名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)
Run Code Online (Sandbox Code Playgroud)
只会从配置返回分支信息。然而,文档中的下一个函数Repository是:
func (r *Repository) Branches() (storer.ReferenceIter, error)
Run Code Online (Sandbox Code Playgroud)
它确实返回了 中所有条目的迭代器.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>"),
})
Run Code Online (Sandbox Code Playgroud)
除了这会检查新分支(这git branch <branchname>不起作用)之外,它也无法创建.git/logs/refs/heads/<branchname>.
另外,作为一个潜在的非常令人讨厌的意外,它会清除工作树中所有未跟踪的文件。默认情况下,git checkout 保留对工作树中文件的本地修改,但go-git您需要显式指定Keep: true,即使您已指定Force: false。
绝对违反了“最小惊讶原则”。值得庆幸的是,在我测试过的本地存储库中,它们都是旧的编辑器备份文件或我很久以前就放弃的旧项目的片段。
碰巧的是,其中一位go-git作者/维护者回应了第二个问题,并建议:
为了独立于工作树创建和删除引用,您应该使用
storer.ReferenceStorer.请查看分支示例:https://github.com/src-d/go-git/blob/master/_examples/branch/main.go
这很好而且很简单,但它只解决了分支引用的创建问题。
我在源代码中找到的所有“日志”一词go-git似乎都是指提交日志,而不是引用日志。鉴于引用日志条目看起来与树中的其他工件完全不同.git,我想需要一种不同类型的存储器来创建/更新它们——并且现有的存储器看起来(对我来说)都不像它们那样那。
关于如何获得适当的参考日志以配合参考有什么建议吗?
(或者,也许我误解得很严重,除了我上面列出的那些之外,还有一些在 中创建分支的方法go-git,这可以满足我的要求。)
小智 6
首先,我没有足够的声誉来评论 Pedro 的答案,但他的方法在该Checkout阶段失败,因为实际上没有在存储上创建分支(存储库Storer从未被调用)。
其次,这是我第一次听说.git/logdir,所以不,git branch不会为该目录中的分支创建记录。
这引导我找到实际的解决方案,该解决方案是作为go-git repo分支示例提供的解决方案。
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)
Run Code Online (Sandbox Code Playgroud)
Info("git checkout test")
w, err := r.Worktree()
CheckIfError(err)
err = w.Checkout(&git.CheckoutOptions{Branch: ref.Name()})
CheckIfError(err)
Run Code Online (Sandbox Code Playgroud)
然而,这样一来,这个分支就没有配置了.git/config,所以应该调用函数repo.Branch,但这确实是滑稽的不直观。
我是如何做到的:
创建对新分支的本地引用
branchName := "new-branch"
localRef := plumbing.NewBranchReferenceName(branchName)
Run Code Online (Sandbox Code Playgroud)
创建分支
opts := &gitConfig.Branch{
Name: branchName,
Remote: "origin",
Merge: localRef,
}
if err := repo.CreateBranch(opts); err != nil {
return err
}
Run Code Online (Sandbox Code Playgroud)
如果您实际上需要更改到该分支...只需进行结帐(不记得它是否实际上通过创建更改为创建的分支)
获取工作树
w, err := repo.Worktree()
if err != nil {
return rest.InternalServerError(err.Error())
}
Run Code Online (Sandbox Code Playgroud)
查看
if err := w.Checkout(&git.CheckoutOptions{Branch: plumbing.ReferenceName(localRef.String())}); err != nil {
return nil
}
Run Code Online (Sandbox Code Playgroud)
如果你想跟踪远程分支
创建远程引用
remoteRef := plumbing.NewRemoteReferenceName("origin", branchName)
Run Code Online (Sandbox Code Playgroud)
跟踪遥控
newReference := plumbing.NewSymbolicReference(localRef, remoteRef)
if err := repo.Storer.SetReference(newReference); err != nil {
return err
}
Run Code Online (Sandbox Code Playgroud)