diff options
Diffstat (limited to 'worktree_commit.go')
-rw-r--r-- | worktree_commit.go | 243 |
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) +} |