aboutsummaryrefslogtreecommitdiffstats
path: root/worktree_commit.go
diff options
context:
space:
mode:
Diffstat (limited to 'worktree_commit.go')
-rw-r--r--worktree_commit.go243
1 files changed, 243 insertions, 0 deletions
diff --git a/worktree_commit.go b/worktree_commit.go
new file mode 100644
index 0000000..bec75b2
--- /dev/null
+++ b/worktree_commit.go
@@ -0,0 +1,243 @@
+package git
+
+import (
+ "io"
+ "path/filepath"
+ "strings"
+
+ "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"
+ "gopkg.in/src-d/go-git.v4/utils/ioutil"
+
+ "gopkg.in/src-d/go-billy.v2"
+)
+
+// Commit stores the current contents of the index in a new commit along with
+// a log message from the user describing the changes.
+func (w *Worktree) Commit(msg string, opts *CommitOptions) (plumbing.Hash, error) {
+ if err := opts.Validate(w.r); err != nil {
+ return plumbing.ZeroHash, err
+ }
+
+ if opts.All {
+ if err := w.autoAddModifiedAndDeleted(); err != nil {
+ return plumbing.ZeroHash, err
+ }
+ }
+
+ idx, err := w.r.Storer.Index()
+ if err != nil {
+ return plumbing.ZeroHash, err
+ }
+
+ h := &commitIndexHelper{
+ fs: w.fs,
+ s: w.r.Storer,
+ }
+
+ tree, err := h.buildTreeAndBlobObjects(idx)
+ if err != nil {
+ return plumbing.ZeroHash, err
+ }
+
+ commit, err := w.buildCommitObject(msg, opts, tree)
+ if err != nil {
+ return plumbing.ZeroHash, err
+ }
+
+ return commit, w.updateHEAD(commit)
+}
+
+func (w *Worktree) autoAddModifiedAndDeleted() error {
+ s, err := w.Status()
+ if err != nil {
+ return err
+ }
+
+ for path, fs := range s {
+ if fs.Worktree != Modified && fs.Worktree != Deleted {
+ continue
+ }
+
+ if _, err := w.Add(path); err != nil {
+ return err
+ }
+
+ }
+
+ return nil
+}
+
+func (w *Worktree) updateHEAD(commit plumbing.Hash) error {
+ head, err := w.r.Storer.Reference(plumbing.HEAD)
+ if err != nil {
+ return err
+ }
+
+ name := plumbing.HEAD
+ if head.Type() != plumbing.HashReference {
+ name = head.Target()
+ }
+
+ ref := plumbing.NewHashReference(name, commit)
+ return w.r.Storer.SetReference(ref)
+}
+
+func (w *Worktree) buildCommitObject(msg string, opts *CommitOptions, tree plumbing.Hash) (plumbing.Hash, error) {
+ commit := &object.Commit{
+ Author: *opts.Author,
+ Committer: *opts.Committer,
+ Message: msg,
+ TreeHash: tree,
+ ParentHashes: opts.Parents,
+ }
+
+ obj := w.r.Storer.NewEncodedObject()
+ if err := commit.Encode(obj); err != nil {
+ return plumbing.ZeroHash, err
+ }
+ return w.r.Storer.SetEncodedObject(obj)
+}
+
+// commitIndexHelper 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 commitIndexHelper struct {
+ fs billy.Filesystem
+ s storage.Storer
+
+ trees map[string]*object.Tree
+ entries map[string]*object.TreeEntry
+}
+
+// buildTreesAndBlobs builds the objects and push its to the storer, the hash
+// of the root tree is returned.
+func (h *commitIndexHelper) buildTreeAndBlobObjects(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 *commitIndexHelper) commitIndexEntry(e *index.Entry) error {
+ parts := strings.Split(e.Name, string(filepath.Separator))
+
+ var path string
+ for _, part := range parts {
+ parent := path
+ path = filepath.Join(path, part)
+
+ if !h.buildTree(e, parent, path) {
+ continue
+ }
+
+ if err := h.copyIndexEntryToStorage(e); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (h *commitIndexHelper) buildTree(e *index.Entry, parent, path string) bool {
+ if _, ok := h.trees[path]; ok {
+ return false
+ }
+
+ if _, ok := h.entries[path]; ok {
+ return false
+ }
+
+ te := object.TreeEntry{Name: filepath.Base(path)}
+
+ if path == e.Name {
+ te.Mode = e.Mode
+ te.Hash = e.Hash
+ } else {
+ te.Mode = filemode.Dir
+ h.trees[path] = &object.Tree{}
+ }
+
+ h.trees[parent].Entries = append(h.trees[parent].Entries, te)
+ return true
+}
+
+func (h *commitIndexHelper) copyIndexEntryToStorage(e *index.Entry) error {
+ _, err := h.s.EncodedObject(plumbing.BlobObject, e.Hash)
+ if err == nil {
+ return nil
+ }
+
+ if err != plumbing.ErrObjectNotFound {
+ return err
+ }
+
+ return h.doCopyIndexEntryToStorage(e)
+}
+
+func (h *commitIndexHelper) doCopyIndexEntryToStorage(e *index.Entry) (err error) {
+ fi, err := h.fs.Stat(e.Name)
+ if err != nil {
+ return err
+ }
+
+ obj := h.s.NewEncodedObject()
+ obj.SetType(plumbing.BlobObject)
+ obj.SetSize(fi.Size())
+
+ reader, err := h.fs.Open(e.Name)
+ if err != nil {
+ return err
+ }
+
+ defer ioutil.CheckClose(reader, &err)
+
+ writer, err := obj.Writer()
+ if err != nil {
+ return err
+ }
+
+ defer ioutil.CheckClose(writer, &err)
+
+ if _, err := io.Copy(writer, reader); err != nil {
+ return err
+ }
+
+ _, err = h.s.SetEncodedObject(obj)
+ return err
+}
+
+func (h *commitIndexHelper) copyTreeToStorageRecursive(parent string, t *object.Tree) (plumbing.Hash, error) {
+ for i, e := range t.Entries {
+ if e.Mode != filemode.Dir && !e.Hash.IsZero() {
+ continue
+ }
+
+ path := filepath.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)
+}