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