如何在 go 中使用 `go-git` 来模仿 `git --work-tree ...` ?

Emi*_*ser 9 git go go-git

我有一个裸存储库,需要在其中添加并提交一组文件。据我了解,将文件添加到索引需要一个工作树git在命令行上使用时,我会将git-dir选项设置为指向裸目录,同时将选项设置work-tree为指向要添加到索引的文件所在的工作树。就像这样:

$ git --git-dir /path/to/.git --work-tree /path/to/worktree add ...
Run Code Online (Sandbox Code Playgroud)

值得一提的是,“.git”目录不是、也不可能简单地命名为“.git”。它实际上是一个“自定义”“.git”目录。喜欢git --git-dir /path/to/.notgit ...

我尝试设置core.worktree配置选项。然而,core.bare设置为true这个会导致致命错误。两者都来自命令行:

$ git --git-dir /path/to/.notgit config core.worktree /path/to/worktree
$ git --git-dir /path/to/.notgit add ...
warning: core.bare and core.worktree do not make sense
fatal: unable to set up work tree using invalid config
Run Code Online (Sandbox Code Playgroud)

并使用go-git

$ git --git-dir /path/to/.git --work-tree /path/to/worktree add ...
Run Code Online (Sandbox Code Playgroud)

我的一个想法是依靠该git.PlainOpenWithOptions功能,希望能够让我提供工作树作为一种选择。然而,看看git.PlainOpenOptions结构类型,这很快就崩溃了。

$ git --git-dir /path/to/.notgit config core.worktree /path/to/worktree
$ git --git-dir /path/to/.notgit add ...
warning: core.bare and core.worktree do not make sense
fatal: unable to set up work tree using invalid config
Run Code Online (Sandbox Code Playgroud)

我如何git --work-tree ...模仿go-git


编辑1:解释“.git”并不完全命名为“.git”。

Doc*_*val 1

我不是 Git 专家,但我一直在使用 go-git,并且我已经能够创建一个裸存储库并使用 Git 管道命令向其中添加文件。虽然有点冗长,但一旦掌握了要点,实际上就很简单了。需要意识到的主要一点是,Git 有许多不同的对象类型用于执行其工作,我们只需要创建每个对象,这就是下面的大部分代码。

以下代码将在 中创建一个新的裸存储库/tmp/example.git,并向其中添加一个名为“README.md”的文件,而不需要任何工作目录。它确实需要创建我们想要存储的文件的内存表示,但该表示只是一个字节缓冲区,而不是文件系统。(此代码还将默认分支名称从“master”更改为“main”):

package main

import (
    "github.com/go-git/go-git/v5"
    "github.com/go-git/go-git/v5/plumbing"
    "github.com/go-git/go-git/v5/plumbing/filemode"
    "github.com/go-git/go-git/v5/plumbing/object"
    "os"
    "time"
)

func panicIf(err error) {
    if err != nil {
        panic(err)
    }
}

func getRepo() string {
    return "/tmp/example.git"
}

func main() {

    dir := getRepo()
    err := os.Mkdir(dir, 0700)
    panicIf(err)

    // Create a new repo
    r, err := git.PlainInit(dir, true)
    panicIf(err)

    // Change it to use "main" instead of "master"
    h := plumbing.NewSymbolicReference(plumbing.HEAD, "refs/heads/main")
    err = r.Storer.SetReference(h)
    panicIf(err)

    // Create a file in storage. It's identified by its hash.
    fileObject := plumbing.MemoryObject{}
    fileObject.SetType(plumbing.BlobObject)
    w, err := fileObject.Writer()
    panicIf(err)

    _, err = w.Write([]byte("# My Story\n"))
    panicIf(err)

    err = w.Close()
    panicIf(err)

    fileHash, err := r.Storer.SetEncodedObject(&fileObject)
    panicIf(err)

    // Create and store a Tree that contains the stored object.
    // Give it the name "README.md".

    treeEntry := object.TreeEntry{
        Name: "README.md",
        Mode: filemode.Regular,
        Hash: fileHash,
    }

    tree := object.Tree{
        Entries: []object.TreeEntry{treeEntry},
    }

    treeObject := plumbing.MemoryObject{}
    err = tree.Encode(&treeObject)
    panicIf(err)

    treeHash, err := r.Storer.SetEncodedObject(&treeObject)
    panicIf(err)

    // Next, create a commit that references the tree
    // A commit is just metadata about a tree.

    commit := object.Commit{
        Author:    object.Signature{"Bob", "bob@example.com", time.Now()},
        Committer: object.Signature{"Bob", "bob@example.com", time.Now()},
        Message:   "first commit",
        TreeHash:  treeHash,
    }

    commitObject := plumbing.MemoryObject{}
    err = commit.Encode(&commitObject)
    panicIf(err)

    commitHash, err := r.Storer.SetEncodedObject(&commitObject)
    panicIf(err)

    // Now, point the "main" branch to the newly-created commit

    ref := plumbing.NewHashReference("refs/heads/main", commitHash)
    err = r.Storer.SetReference(ref)

    cfg, err := r.Config()
    panicIf(err)

    // Tell Git that the default branch name is "main".

    cfg.Init.DefaultBranch = "main"
    err = r.SetConfig(cfg)
    panicIf(err)
}
Run Code Online (Sandbox Code Playgroud)

运行此代码后,要查看它是否正常工作,您可以clone使用命令行版本的git. 假设当前目录是/tmp,这很简单:

/tmp $ git clone example.git
Cloning into 'example'...
done.
Run Code Online (Sandbox Code Playgroud)

这将在目录中创建一个工作树/tmp/example,您可以 cd 到该目录:

/tmp $ cd example
/tmp/example $ ls
README.md
Run Code Online (Sandbox Code Playgroud)

您可以使用类似的技术将新文件添加到裸存储库,而不需要工作目录。以下代码将一个名为“example.md”的文件添加到存储库中。(注意,这段代码很幼稚;如果您运行它两次,它将为同一个文件创建两个条目,而您通常不应该这样做;请参阅 go-git 文档以获取 API 来查找 TreeEntry 而不是添加一个):

/tmp $ git clone example.git
Cloning into 'example'...
done.
Run Code Online (Sandbox Code Playgroud)

要运行它,您需要在工作目录中创建一个名为“example.md”的文件,可能如下所示:

$ echo "# An example file" > example.md
$ go build
$ ./add
Run Code Online (Sandbox Code Playgroud)

运行add命令后,您可以git pull在工作目录中:

/tmp/example $ git pull
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 266 bytes | 266.00 KiB/s, done.
From /tmp/example
   6f234cc..c248a9d  main       -> origin/main
Updating 6f234cc..c248a9d
Fast-forward
 example.md | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 example.md
Run Code Online (Sandbox Code Playgroud)

您可以看到该文件现在存在:

/tmp/example $ ls
README.md   example.md
/tmp/example $ cat example.md
# An example file
/tmp/example $
Run Code Online (Sandbox Code Playgroud)

其工作方式是手动操作 Git 本身使用的数据结构。我们存储文件(blob),创建包含该文件的树,并创建指向该树的提交。更新文件或删除文件应该同样容易,但是每个更改分支头的操作都需要创建树的副本并提交它,与此处的操作类似add