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
69 return nil
70}
71
72func (w *Worktree) updateHEAD(commit plumbing.Hash) error {
73 head, err := w.r.Storer.Reference(plumbing.HEAD)
74 if err != nil {
75 return err
76 }
77
78 name := plumbing.HEAD
79 if head.Type() != plumbing.HashReference {
80 name = head.Target()
81 }
82
83 ref := plumbing.NewHashReference(name, commit)
84 return w.r.Storer.SetReference(ref)
85}
86
87func (w *Worktree) buildCommitObject(msg string, opts *CommitOptions, tree plumbing.Hash) (plumbing.Hash, error) {
88 commit := &object.Commit{
89 Author: *opts.Author,
90 Committer: *opts.Committer,
91 Message: msg,
92 TreeHash: tree,
93 ParentHashes: opts.Parents,
94 }
95
96 obj := w.r.Storer.NewEncodedObject()
97 if err := commit.Encode(obj); err != nil {
98 return plumbing.ZeroHash, err
99 }
100 return w.r.Storer.SetEncodedObject(obj)
101}
102
103// buildTreeHelper converts a given index.Index file into multiple git objects
104// reading the blobs from the given filesystem and creating the trees from the
105// index structure. The created objects are pushed to a given Storer.
106type buildTreeHelper struct {
107 fs billy.Filesystem
108 s storage.Storer
109
110 trees map[string]*object.Tree
111 entries map[string]*object.TreeEntry
112}
113
114// BuildTree builds the tree objects and push its to the storer, the hash
115// of the root tree is returned.
116func (h *buildTreeHelper) BuildTree(idx *index.Index) (plumbing.Hash, error) {
117 const rootNode = ""
118 h.trees = map[string]*object.Tree{rootNode: {}}
119 h.entries = map[string]*object.TreeEntry{}
120
121 for _, e := range idx.Entries {
122 if err := h.commitIndexEntry(e); err != nil {
123 return plumbing.ZeroHash, err
124 }
125 }
126
127 return h.copyTreeToStorageRecursive(rootNode, h.trees[rootNode])
128}
129
130func (h *buildTreeHelper) commitIndexEntry(e *index.Entry) error {
131 parts := strings.Split(e.Name, "/")
132
133 var fullpath string
134 for _, part := range parts {
135 parent := fullpath
136 fullpath = path.Join(fullpath, part)
137
138 h.doBuildTree(e, parent, fullpath)
139 }
140
141 return nil
142}
143
144func (h *buildTreeHelper) doBuildTree(e *index.Entry, parent, fullpath string) {
145 if _, ok := h.trees[fullpath]; ok {
146 return
147 }
148
149 if _, ok := h.entries[fullpath]; ok {
150 return
151 }
152
153 te := object.TreeEntry{Name: path.Base(fullpath)}
154
155 if fullpath == e.Name {
156 te.Mode = e.Mode
157 te.Hash = e.Hash
158 } else {
159 te.Mode = filemode.Dir
160 h.trees[fullpath] = &object.Tree{}
161 }
162
163 h.trees[parent].Entries = append(h.trees[parent].Entries, te)
164}
165
166func (h *buildTreeHelper) copyTreeToStorageRecursive(parent string, t *object.Tree) (plumbing.Hash, error) {
167 for i, e := range t.Entries {
168 if e.Mode != filemode.Dir && !e.Hash.IsZero() {
169 continue
170 }
171
172 path := path.Join(parent, e.Name)
173
174 var err error
175 e.Hash, err = h.copyTreeToStorageRecursive(path, h.trees[path])
176 if err != nil {
177 return plumbing.ZeroHash, err
178 }
179
180 t.Entries[i] = e
181 }
182
183 o := h.s.NewEncodedObject()
184 if err := t.Encode(o); err != nil {
185 return plumbing.ZeroHash, err
186 }
187
188 return h.s.SetEncodedObject(o)
189}