我有一个裸存储库,需要在其中添加并提交一组文件。据我了解,将文件添加到索引需要一个工作树。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”。
我不是 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。