fork of go-git with some jj specific features
at v4.4.0 4.3 kB view raw
1package git 2 3import ( 4 "path" 5 "strings" 6 7 "gopkg.in/src-d/go-git.v4/plumbing" 8 "gopkg.in/src-d/go-git.v4/plumbing/filemode" 9 "gopkg.in/src-d/go-git.v4/plumbing/format/index" 10 "gopkg.in/src-d/go-git.v4/plumbing/object" 11 "gopkg.in/src-d/go-git.v4/storage" 12 13 "gopkg.in/src-d/go-billy.v4" 14) 15 16// Commit stores the current contents of the index in a new commit along with 17// a log message from the user describing the changes. 18func (w *Worktree) Commit(msg string, opts *CommitOptions) (plumbing.Hash, error) { 19 if err := opts.Validate(w.r); err != nil { 20 return plumbing.ZeroHash, err 21 } 22 23 if opts.All { 24 if err := w.autoAddModifiedAndDeleted(); err != nil { 25 return plumbing.ZeroHash, err 26 } 27 } 28 29 idx, err := w.r.Storer.Index() 30 if err != nil { 31 return plumbing.ZeroHash, err 32 } 33 34 h := &buildTreeHelper{ 35 fs: w.Filesystem, 36 s: w.r.Storer, 37 } 38 39 tree, err := h.BuildTree(idx) 40 if err != nil { 41 return plumbing.ZeroHash, err 42 } 43 44 commit, err := w.buildCommitObject(msg, opts, tree) 45 if err != nil { 46 return plumbing.ZeroHash, err 47 } 48 49 return commit, w.updateHEAD(commit) 50} 51 52func (w *Worktree) autoAddModifiedAndDeleted() error { 53 s, err := w.Status() 54 if err != nil { 55 return err 56 } 57 58 for path, fs := range s { 59 if fs.Worktree != Modified && fs.Worktree != Deleted { 60 continue 61 } 62 63 if _, err := w.Add(path); err != nil { 64 return err 65 } 66 } 67 68 return nil 69} 70 71func (w *Worktree) updateHEAD(commit plumbing.Hash) error { 72 head, err := w.r.Storer.Reference(plumbing.HEAD) 73 if err != nil { 74 return err 75 } 76 77 name := plumbing.HEAD 78 if head.Type() != plumbing.HashReference { 79 name = head.Target() 80 } 81 82 ref := plumbing.NewHashReference(name, commit) 83 return w.r.Storer.SetReference(ref) 84} 85 86func (w *Worktree) buildCommitObject(msg string, opts *CommitOptions, tree plumbing.Hash) (plumbing.Hash, error) { 87 commit := &object.Commit{ 88 Author: *opts.Author, 89 Committer: *opts.Committer, 90 Message: msg, 91 TreeHash: tree, 92 ParentHashes: opts.Parents, 93 } 94 95 obj := w.r.Storer.NewEncodedObject() 96 if err := commit.Encode(obj); err != nil { 97 return plumbing.ZeroHash, err 98 } 99 return w.r.Storer.SetEncodedObject(obj) 100} 101 102// buildTreeHelper converts a given index.Index file into multiple git objects 103// reading the blobs from the given filesystem and creating the trees from the 104// index structure. The created objects are pushed to a given Storer. 105type buildTreeHelper struct { 106 fs billy.Filesystem 107 s storage.Storer 108 109 trees map[string]*object.Tree 110 entries map[string]*object.TreeEntry 111} 112 113// BuildTree builds the tree objects and push its to the storer, the hash 114// of the root tree is returned. 115func (h *buildTreeHelper) BuildTree(idx *index.Index) (plumbing.Hash, error) { 116 const rootNode = "" 117 h.trees = map[string]*object.Tree{rootNode: {}} 118 h.entries = map[string]*object.TreeEntry{} 119 120 for _, e := range idx.Entries { 121 if err := h.commitIndexEntry(e); err != nil { 122 return plumbing.ZeroHash, err 123 } 124 } 125 126 return h.copyTreeToStorageRecursive(rootNode, h.trees[rootNode]) 127} 128 129func (h *buildTreeHelper) commitIndexEntry(e *index.Entry) error { 130 parts := strings.Split(e.Name, "/") 131 132 var fullpath string 133 for _, part := range parts { 134 parent := fullpath 135 fullpath = path.Join(fullpath, part) 136 137 h.doBuildTree(e, parent, fullpath) 138 } 139 140 return nil 141} 142 143func (h *buildTreeHelper) doBuildTree(e *index.Entry, parent, fullpath string) { 144 if _, ok := h.trees[fullpath]; ok { 145 return 146 } 147 148 if _, ok := h.entries[fullpath]; ok { 149 return 150 } 151 152 te := object.TreeEntry{Name: path.Base(fullpath)} 153 154 if fullpath == e.Name { 155 te.Mode = e.Mode 156 te.Hash = e.Hash 157 } else { 158 te.Mode = filemode.Dir 159 h.trees[fullpath] = &object.Tree{} 160 } 161 162 h.trees[parent].Entries = append(h.trees[parent].Entries, te) 163} 164 165func (h *buildTreeHelper) copyTreeToStorageRecursive(parent string, t *object.Tree) (plumbing.Hash, error) { 166 for i, e := range t.Entries { 167 if e.Mode != filemode.Dir && !e.Hash.IsZero() { 168 continue 169 } 170 171 path := path.Join(parent, e.Name) 172 173 var err error 174 e.Hash, err = h.copyTreeToStorageRecursive(path, h.trees[path]) 175 if err != nil { 176 return plumbing.ZeroHash, err 177 } 178 179 t.Entries[i] = e 180 } 181 182 o := h.s.NewEncodedObject() 183 if err := t.Encode(o); err != nil { 184 return plumbing.ZeroHash, err 185 } 186 187 return h.s.SetEncodedObject(o) 188}