Created
October 23, 2018 12:43
-
-
Save codeflitting/ac4e11f4e6b37413711fa4e3f371dcb9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Adding a file directly to index (stage area), without using working tree. | |
// | |
// $ go build index-add.go && yes | mv index-add ~/code/workspace/bin | |
// $ mkdir -p /tmp/sample && cd /tmp/sample | |
// $ index-add | |
package main | |
import ( | |
"bytes" | |
"fmt" | |
"path" | |
"sort" | |
"strconv" | |
"strings" | |
"time" | |
"golang.org/x/crypto/openpgp" | |
"gopkg.in/src-d/go-billy.v4" | |
"gopkg.in/src-d/go-billy.v4/osfs" | |
"gopkg.in/src-d/go-git.v4" | |
. "gopkg.in/src-d/go-git.v4/_examples" | |
"gopkg.in/src-d/go-git.v4/plumbing" | |
"gopkg.in/src-d/go-git.v4/plumbing/filemode" | |
"gopkg.in/src-d/go-git.v4/plumbing/format/index" | |
"gopkg.in/src-d/go-git.v4/plumbing/object" | |
"gopkg.in/src-d/go-git.v4/storage" | |
fsfs "gopkg.in/src-d/go-git.v4/storage/filesystem" | |
) | |
// 获取git log | |
func gitLog(r *git.Repository) { | |
// ... retrieves the branch pointed by HEAD | |
ref, err := r.Head() | |
CheckIfError(err) | |
// ... retrieves the commit history | |
cIter, err := r.Log(&git.LogOptions{From: ref.Hash()}) | |
CheckIfError(err) | |
// ... just iterates over the commits, printing it | |
err = cIter.ForEach(func(c *object.Commit) error { | |
fmt.Println(c) | |
return nil | |
}) | |
CheckIfError(err) | |
} | |
// 创建一个新的 bare repository | |
func gitInit(repoPath string) *git.Repository { | |
s := fsfs.NewStorage(osfs.New(repoPath), nil) | |
r, err := git.Init(s, nil) | |
CheckIfError(err) | |
return r | |
} | |
// 打开一个已经存在的 repository | |
func gitPlainOpen(repoPath string) *git.Repository { | |
r, err := git.PlainOpen(repoPath) | |
CheckIfError(err) | |
return r | |
} | |
// 添加文件到 index cache | |
func gitAdd(r *git.Repository, path string, content []byte) { | |
// Find index from Storer, here we are using filesystem. | |
idx, err := r.Storer.Index() | |
CheckIfError(err) | |
// Get an object, write data into the object, then save to object storage. | |
obj := r.Storer.NewEncodedObject() | |
obj.SetType(plumbing.BlobObject) | |
obj.SetSize(int64(len(content))) | |
// The implementation of "obj.Writer.Write()" is MemoryObject, which | |
// makes a copy of the object. | |
writer, err := obj.Writer() | |
CheckIfError(err) | |
_, err = writer.Write(content) | |
CheckIfError(err) | |
writer.Close() | |
// Here we again copy the object from "obj" to underline storage. Once | |
// saved, it is officially considered part of git database. | |
// ** Improvement Needed to avoid Double Copy** | |
h, err := r.Storer.SetEncodedObject(obj) | |
CheckIfError(err) | |
e := idx.Add(path) | |
// Add a new entry (we can use "idx.Entry(path)" to check if path exists). | |
e.Hash = h | |
e.Mode = filemode.Regular | |
// Set index, which will be translated to tree object once we commit. | |
r.Storer.SetIndex(idx) | |
} | |
func gitDelete(r *git.Repository, filePath string) { | |
// Find index from Storer, here we are using filesystem. | |
idx, err := r.Storer.Index() | |
CheckIfError(err) | |
_, err = idx.Remove(filePath) | |
CheckIfError(err) | |
err = r.Storer.SetIndex(idx) | |
CheckIfError(err) | |
} | |
// 发布变更 | |
func gitCommit(r *git.Repository, msg string, opts *git.CommitOptions) (plumbing.Hash, error) { | |
if err := opts.Validate(r); err != nil { | |
return plumbing.ZeroHash, err | |
} | |
idx, err := r.Storer.Index() | |
if err != nil { | |
return plumbing.ZeroHash, err | |
} | |
h := &buildTreeHelper{ | |
s: r.Storer, | |
} | |
tree, err := h.BuildTree(idx) | |
if err != nil { | |
return plumbing.ZeroHash, err | |
} | |
commit, err := buildCommitObject(r, msg, opts, tree) | |
if err != nil { | |
return plumbing.ZeroHash, err | |
} | |
// updateHEAD | |
head, err := r.Storer.Reference(plumbing.HEAD) | |
if err != nil { | |
return commit, err | |
} | |
name := plumbing.HEAD | |
if head.Type() != plumbing.HashReference { | |
name = head.Target() | |
} | |
ref := plumbing.NewHashReference(name, commit) | |
return commit, r.Storer.SetReference(ref) | |
} | |
func main() { | |
// repoPath = "/tmp/interesting" | |
// gitInit or gitPlainOpen | |
r := gitInit("/tmp/interesting") | |
// gitAdd | |
for i := 0; i < 10; i++ { | |
content := []byte(strconv.Itoa(i)) | |
filePath := strconv.Itoa(i) | |
gitAdd(r, filePath, content) | |
} | |
// gitCommit | |
_, err := gitCommit(r, "add files", &git.CommitOptions{ | |
Author: &object.Signature{ | |
Name: "John Doe", | |
Email: "[email protected]", | |
When: time.Now(), | |
}, | |
Parents: []plumbing.Hash{}, | |
}) | |
CheckIfError(err) | |
// gitDelete | |
gitDelete(r, "3") | |
// gitCommit | |
_, err = gitCommit(r, "remove file", &git.CommitOptions{ | |
Author: &object.Signature{ | |
Name: "John Doe", | |
Email: "[email protected]", | |
When: time.Now(), | |
}, | |
Parents: []plumbing.Hash{}, | |
}) | |
CheckIfError(err) | |
// gitLog | |
gitLog(r) | |
} | |
///////////////////////////////////////////////////////////////////// | |
///////////////// /////////////// | |
///////////////// The following copy from work tree /////////////// | |
///////////////// /////////////// | |
///////////////////////////////////////////////////////////////////// | |
// buildTreeHelper converts a given index.Index file into multiple git objects | |
// reading the blobs from the given filesystem and creating the trees from the | |
// index structure. The created objects are pushed to a given Storer. | |
type buildTreeHelper struct { | |
fs billy.Filesystem | |
s storage.Storer | |
trees map[string]*object.Tree | |
entries map[string]*object.TreeEntry | |
} | |
// BuildTree builds the tree objects and push its to the storer, the hash | |
// of the root tree is returned. | |
func (h *buildTreeHelper) BuildTree(idx *index.Index) (plumbing.Hash, error) { | |
const rootNode = "" | |
h.trees = map[string]*object.Tree{rootNode: {}} | |
h.entries = map[string]*object.TreeEntry{} | |
for _, e := range idx.Entries { | |
if err := h.commitIndexEntry(e); err != nil { | |
return plumbing.ZeroHash, err | |
} | |
} | |
return h.copyTreeToStorageRecursive(rootNode, h.trees[rootNode]) | |
} | |
func (h *buildTreeHelper) commitIndexEntry(e *index.Entry) error { | |
parts := strings.Split(e.Name, "/") | |
var fullpath string | |
for _, part := range parts { | |
parent := fullpath | |
fullpath = path.Join(fullpath, part) | |
h.doBuildTree(e, parent, fullpath) | |
} | |
return nil | |
} | |
func (h *buildTreeHelper) doBuildTree(e *index.Entry, parent, fullpath string) { | |
if _, ok := h.trees[fullpath]; ok { | |
return | |
} | |
if _, ok := h.entries[fullpath]; ok { | |
return | |
} | |
te := object.TreeEntry{Name: path.Base(fullpath)} | |
if fullpath == e.Name { | |
te.Mode = e.Mode | |
te.Hash = e.Hash | |
} else { | |
te.Mode = filemode.Dir | |
h.trees[fullpath] = &object.Tree{} | |
} | |
h.trees[parent].Entries = append(h.trees[parent].Entries, te) | |
} | |
type sortableEntries []object.TreeEntry | |
func (sortableEntries) sortName(te object.TreeEntry) string { | |
if te.Mode == filemode.Dir { | |
return te.Name + "/" | |
} | |
return te.Name | |
} | |
func (se sortableEntries) Len() int { return len(se) } | |
func (se sortableEntries) Less(i int, j int) bool { return se.sortName(se[i]) < se.sortName(se[j]) } | |
func (se sortableEntries) Swap(i int, j int) { se[i], se[j] = se[j], se[i] } | |
func (h *buildTreeHelper) copyTreeToStorageRecursive(parent string, t *object.Tree) (plumbing.Hash, error) { | |
sort.Sort(sortableEntries(t.Entries)) | |
for i, e := range t.Entries { | |
if e.Mode != filemode.Dir && !e.Hash.IsZero() { | |
continue | |
} | |
path := path.Join(parent, e.Name) | |
var err error | |
e.Hash, err = h.copyTreeToStorageRecursive(path, h.trees[path]) | |
if err != nil { | |
return plumbing.ZeroHash, err | |
} | |
t.Entries[i] = e | |
} | |
o := h.s.NewEncodedObject() | |
if err := t.Encode(o); err != nil { | |
return plumbing.ZeroHash, err | |
} | |
return h.s.SetEncodedObject(o) | |
} | |
func buildCommitObject(r *git.Repository, msg string, opts *git.CommitOptions, tree plumbing.Hash) (plumbing.Hash, error) { | |
commit := &object.Commit{ | |
Author: *opts.Author, | |
Committer: *opts.Committer, | |
Message: msg, | |
TreeHash: tree, | |
ParentHashes: opts.Parents, | |
} | |
if opts.SignKey != nil { | |
sig, err := buildCommitSignature(commit, opts.SignKey) | |
if err != nil { | |
return plumbing.ZeroHash, err | |
} | |
commit.PGPSignature = sig | |
} | |
obj := r.Storer.NewEncodedObject() | |
if err := commit.Encode(obj); err != nil { | |
return plumbing.ZeroHash, err | |
} | |
return r.Storer.SetEncodedObject(obj) | |
} | |
func buildCommitSignature(commit *object.Commit, signKey *openpgp.Entity) (string, error) { | |
encoded := &plumbing.MemoryObject{} | |
if err := commit.Encode(encoded); err != nil { | |
return "", err | |
} | |
r, err := encoded.Reader() | |
if err != nil { | |
return "", err | |
} | |
var b bytes.Buffer | |
if err := openpgp.ArmoredDetachSign(&b, signKey, r, nil); err != nil { | |
return "", err | |
} | |
return b.String(), nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment