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