[英]How do I mimic `git --work-tree ...` with `go-git` in go?
我有一个裸存储库,我需要在其中添加和提交一组文件。 据我了解,将文件添加到索引需要一个worktree 。 在命令行上使用git
,我会将git-dir
选项设置为指向裸目录,同时将work-tree
选项设置为指向要添加到索引的文件所在的工作树。 像这样:
$ git --git-dir /path/to/.git --work-tree /path/to/worktree add ...
值得一提的是,“.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
并使用go-git
:
r, err := git.PlainOpen("/path/to/.notgit")
panicOnError(err)
c, err := r.Config()
panicOnError(err)
fmt.Println(c.Core.IsBare) // true
c.Core.Worktree = "/path/to/worktree"
err = r.SetConfig(c)
panicOnError(err)
_, err = r.Worktree() // panic: worktree not available in a bare repository
panicOnError(err)
我的一个想法是依靠git.PlainOpenWithOptions
function 希望允许我提供一个工作树作为一个选项。 然而,看看git.PlainOpenOptions
结构类型,它很快就崩溃了。
type PlainOpenOptions struct {
// DetectDotGit defines whether parent directories should be
// walked until a .git directory or file is found.
DetectDotGit bool
// Enable .git/commondir support (see https://git-scm.com/docs/gitrepository-layout#Documentation/gitrepository-layout.txt).
// NOTE: This option will only work with the filesystem storage.
EnableDotGitCommonDir bool
}
我如何模仿git --work-tree...
与go-git
?
编辑 1:解释说“.git”并不完全命名为“.git”。
当您使用git.Open()
,它基本上将存储库结构中的worktree
字段设置为nil
,因为它PlainOpenWithOptions
内部使用PlainOpenWithOptions
的默认值DetectDotGit
为false
。 如果使用以下构造函数,您将看到,未跟踪的文件将成功添加。
r, err := git.PlainOpenWithOptions("/path/to/.git",&git.PlainOpenOptions{DetectDotGit: true})
panicOnError(err)
c, err := r.Config()
panicOnError(err)
fmt.Println(c.Core.IsBare) // true
c.Core.Worktree = "/path/to/worktree"
err = r.SetConfig(c)
panicOnError(err)
_, err = r.Worktree() // panic: worktree not available in a bare repository
panicOnError(err)
// added this part for test
workTree, werr := r.Worktree()
panicOnError(werr)
hash, hashErr := workTree.Add("a.txt")
if hashErr != nil {
log.Fatal(hashErr)
}
fmt.Println(hash)
我不是 Git 方面的专家,但我一直在玩 go-git,我已经能够创建一个裸存储库并使用 Git 管道命令向其中添加一个文件。 它有点冗长,但一旦你理解了它的要点,实际上就很简单了。 要意识到的主要事情是 Git 有许多不同的 object 类型用于执行其工作,我们只需要创建这些对象中的每一个,这是下面代码的主体。
以下代码将在/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)
}
运行此代码后,要查看它是否正常工作,您可以使用git
的命令行版本clone
生成的 bar 存储库。 假设当前目录是/tmp
,这很简单:
/tmp $ git clone example.git
Cloning into 'example'...
done.
这将在/tmp/example
目录中创建一个工作树,您可以将其 cd 到:
/tmp $ cd example
/tmp/example $ ls
README.md
您可以使用类似的技术将新文件添加到裸存储库,而无需工作目录。 以下代码将名为“example.md”的文件添加到存储库。 (请注意,此代码是幼稚的;如果您运行它两次,它将为同一个文件创建两个条目,您通常不应该这样做;请参阅 API 的 go-git 文档以查找 TreeEntry 而不是添加一个):
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"
"io"
"os"
"time"
)
func panicIf(err error) {
if err != nil {
panic(err)
}
}
func getRepo() string {
return "/tmp/example.git"
}
// Add or replace a single file in a bare repository.
// This creates a new commit, containing the file.
// You can change the file or add a new file.
//
func main() {
dir := getRepo()
repo, err := git.PlainOpen(dir)
if err != nil {
panic(err)
}
// Get a reference to head of the "main" branch.
mainRef, err := repo.Reference(plumbing.ReferenceName("refs/heads/main"), true)
panicIf(err)
commit, err := repo.CommitObject(mainRef.Hash())
panicIf(err)
// Get the tree referred to in the commit.
tree, err := repo.TreeObject(commit.TreeHash)
panicIf(err)
// Copy the file into the repository
fileObject := plumbing.MemoryObject{}
fileObject.SetType(plumbing.BlobObject)
w, err := fileObject.Writer()
panicIf(err)
file, err := os.Open("example.md")
panicIf(err)
_, err = io.Copy(w, file)
panicIf(err)
err = w.Close()
panicIf(err)
fileHash, err := repo.Storer.SetEncodedObject(&fileObject)
panicIf(err)
// Add a new entry to the tree, and save it into storage.
newTreeEntry := object.TreeEntry{
Name: "example.md",
Mode: filemode.Regular,
Hash: fileHash,
}
tree.Entries = append(tree.Entries, newTreeEntry)
treeObject := plumbing.MemoryObject{}
err = tree.Encode(&treeObject)
panicIf(err)
treeHash, err := repo.Storer.SetEncodedObject(&treeObject)
panicIf(err)
// Next, create a commit that references the previous commit, as well as the new tree
newCommit := object.Commit{
Author: object.Signature{"Alice", "alice@example.com", time.Now()},
Committer: object.Signature{"Alice", "alice@example.com", time.Now()},
Message: "second commit",
TreeHash: treeHash,
ParentHashes: []plumbing.Hash{commit.Hash},
}
commitObject := plumbing.MemoryObject{}
err = newCommit.Encode(&commitObject)
panicIf(err)
commitHash, err := repo.Storer.SetEncodedObject(&commitObject)
panicIf(err)
// Now, point the "main" branch to the newly-created commit
ref := plumbing.NewHashReference("refs/heads/main", commitHash)
err = repo.Storer.SetReference(ref)
panicIf(err)
}
要运行它,您需要在您的工作目录中创建一个名为“example.md”的文件,可能如下所示:
$ echo "# An example file" > example.md
$ go build
$ ./add
运行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
您可以看到该文件现在存在:
/tmp/example $ ls
README.md example.md
/tmp/example $ cat example.md
# An example file
/tmp/example $
它的工作方式是手动操作 Git 本身使用的数据结构。 我们存储文件 (blob),创建包含该文件的树,并创建指向树的提交。 更新文件或删除文件应该同样容易,但是每个更改分支头的操作都需要创建树的副本并提交它,类似于此处add
的方式。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.