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