Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit 932ced9

Browse files
authored
Merge pull request #339 from mcuadros/status
worktree, status and reset implementation based on merkletrie
2 parents 9b45f46 + 5bcf802 commit 932ced9

19 files changed

+1288
-292
lines changed

_examples/checkout/main.go

+19-7
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,37 @@ import (
1111

1212
// Basic example of how to checkout a specific commit.
1313
func main() {
14-
CheckArgs("<url>", "<directory>", "<commit-ref>")
15-
url, directory, commitRef := os.Args[1], os.Args[2], os.Args[3]
14+
CheckArgs("<url>", "<directory>", "<commit>")
15+
url, directory, commit := os.Args[1], os.Args[2], os.Args[3]
1616

1717
// Clone the given repository to the given directory
1818
Info("git clone %s %s", url, directory)
19-
2019
r, err := git.PlainClone(directory, false, &git.CloneOptions{
2120
URL: url,
2221
})
2322

2423
CheckIfError(err)
2524

26-
Info("git checkout %s", commitRef)
25+
// ... retrieving the commit being pointed by HEAD
26+
Info("git show-ref --head HEAD")
27+
ref, err := r.Head()
28+
CheckIfError(err)
29+
fmt.Println(ref.Hash())
2730

2831
w, err := r.Worktree()
29-
3032
CheckIfError(err)
3133

32-
CheckIfError(w.Checkout(plumbing.NewHash(commitRef)))
34+
// ... checking out to commit
35+
Info("git checkout %s", commit)
36+
err = w.Checkout(&git.CheckoutOptions{
37+
Hash: plumbing.NewHash(commit),
38+
})
39+
CheckIfError(err)
3340

34-
fmt.Println("voila")
41+
// ... retrieving the commit being pointed by HEAD, it's shows that the
42+
// repository is poiting to the giving commit in detached mode
43+
Info("git show-ref --head HEAD")
44+
ref, err = r.Head()
45+
CheckIfError(err)
46+
fmt.Println(ref.Hash())
3547
}

_examples/common_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313

1414
var examplesTest = flag.Bool("examples", false, "run the examples tests")
1515

16-
var defaultURL = "https://github.com/mcuadros/basic.git"
16+
var defaultURL = "https://github.com/git-fixtures/basic.git"
1717

1818
var args = map[string][]string{
1919
"checkout": []string{defaultURL, tempFolder(), "35e85108805c84807bc66a02d91535e1e24b38b9"},

options.go

+66
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,72 @@ type SubmoduleUpdateOptions struct {
178178
RecurseSubmodules SubmoduleRescursivity
179179
}
180180

181+
// CheckoutOptions describes how a checkout 31operation should be performed.
182+
type CheckoutOptions struct {
183+
// Hash to be checked out, if used HEAD will in detached mode. Branch and
184+
// Hash are mutual exclusive.
185+
Hash plumbing.Hash
186+
// Branch to be checked out, if Branch and Hash are empty is set to `master`.
187+
Branch plumbing.ReferenceName
188+
// Force, if true when switching branches, proceed even if the index or the
189+
// working tree differs from HEAD. This is used to throw away local changes
190+
Force bool
191+
}
192+
193+
// Validate validates the fields and sets the default values.
194+
func (o *CheckoutOptions) Validate() error {
195+
if o.Branch == "" {
196+
o.Branch = plumbing.Master
197+
}
198+
199+
return nil
200+
}
201+
202+
// ResetMode defines the mode of a reset operation.
203+
type ResetMode int8
204+
205+
const (
206+
// HardReset resets the index and working tree. Any changes to tracked files
207+
// in the working tree are discarded.
208+
HardReset ResetMode = iota
209+
// MixedReset resets the index but not the working tree (i.e., the changed
210+
// files are preserved but not marked for commit) and reports what has not
211+
// been updated. This is the default action.
212+
MixedReset
213+
// MergeReset resets the index and updates the files in the working tree
214+
// that are different between Commit and HEAD, but keeps those which are
215+
// different between the index and working tree (i.e. which have changes
216+
// which have not been added).
217+
//
218+
// If a file that is different between Commit and the index has unstaged
219+
// changes, reset is aborted.
220+
MergeReset
221+
)
222+
223+
// ResetOptions describes how a reset operation should be performed.
224+
type ResetOptions struct {
225+
// Commit, if commit is pressent set the current branch head (HEAD) to it.
226+
Commit plumbing.Hash
227+
// Mode, form resets the current branch head to Commit and possibly updates
228+
// the index (resetting it to the tree of Commit) and the working tree
229+
// depending on Mode. If empty MixedReset is used.
230+
Mode ResetMode
231+
}
232+
233+
// Validate validates the fields and sets the default values.
234+
func (o *ResetOptions) Validate(r *Repository) error {
235+
if o.Commit == plumbing.ZeroHash {
236+
ref, err := r.Head()
237+
if err != nil {
238+
return err
239+
}
240+
241+
o.Commit = ref.Hash()
242+
}
243+
244+
return nil
245+
}
246+
181247
// LogOptions describes how a log action should be performed.
182248
type LogOptions struct {
183249
// When the From option is set the log will only contain commits

plumbing/format/index/index.go

+26
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package index
22

33
import (
44
"errors"
5+
"fmt"
56
"time"
67

8+
"bytes"
9+
710
"gopkg.in/src-d/go-git.v4/plumbing"
811
"gopkg.in/src-d/go-git.v4/plumbing/filemode"
912
)
@@ -47,6 +50,16 @@ type Index struct {
4750
ResolveUndo *ResolveUndo
4851
}
4952

53+
// String is equivalent to `git ls-files --stage --debug`
54+
func (i *Index) String() string {
55+
buf := bytes.NewBuffer(nil)
56+
for _, e := range i.Entries {
57+
buf.WriteString(e.String())
58+
}
59+
60+
return buf.String()
61+
}
62+
5063
// Entry represents a single file (or stage of a file) in the cache. An entry
5164
// represents exactly one stage of a file. If a file path is unmerged then
5265
// multiple Entry instances may appear for the same path name.
@@ -78,6 +91,19 @@ type Entry struct {
7891
IntentToAdd bool
7992
}
8093

94+
func (e Entry) String() string {
95+
buf := bytes.NewBuffer(nil)
96+
97+
fmt.Fprintf(buf, "%06o %s %d\t%s\n", e.Mode, e.Hash, e.Stage, e.Name)
98+
fmt.Fprintf(buf, " ctime: %d:%d\n", e.CreatedAt.Unix(), e.CreatedAt.Nanosecond())
99+
fmt.Fprintf(buf, " mtime: %d:%d\n", e.ModifiedAt.Unix(), e.ModifiedAt.Nanosecond())
100+
fmt.Fprintf(buf, " dev: %d\tino: %d\n", e.Dev, e.Inode)
101+
fmt.Fprintf(buf, " uid: %d\tgid: %d\n", e.UID, e.GID)
102+
fmt.Fprintf(buf, " size: %d\tflags: %x\n", e.Size, 0)
103+
104+
return buf.String()
105+
}
106+
81107
// Tree contains pre-computed hashes for trees that can be derived from the
82108
// index. It helps speed up tree object generation from index for a new commit.
83109
type Tree struct {

plumbing/object/difftree.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import (
1010
// DiffTree compares the content and mode of the blobs found via two
1111
// tree objects.
1212
func DiffTree(a, b *Tree) (Changes, error) {
13-
from := newTreeNoder(a)
14-
to := newTreeNoder(b)
13+
from := NewTreeRootNode(a)
14+
to := NewTreeRootNode(b)
1515

1616
hashEqual := func(a, b noder.Hasher) bool {
1717
return bytes.Equal(a.Hash(), b.Hash())

plumbing/object/tree.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ type TreeEntry struct {
6767
// File returns the hash of the file identified by the `path` argument.
6868
// The path is interpreted as relative to the tree receiver.
6969
func (t *Tree) File(path string) (*File, error) {
70-
e, err := t.findEntry(path)
70+
e, err := t.FindEntry(path)
7171
if err != nil {
7272
return nil, ErrFileNotFound
7373
}
@@ -86,7 +86,7 @@ func (t *Tree) File(path string) (*File, error) {
8686
// Tree returns the tree identified by the `path` argument.
8787
// The path is interpreted as relative to the tree receiver.
8888
func (t *Tree) Tree(path string) (*Tree, error) {
89-
e, err := t.findEntry(path)
89+
e, err := t.FindEntry(path)
9090
if err != nil {
9191
return nil, ErrDirectoryNotFound
9292
}
@@ -109,7 +109,8 @@ func (t *Tree) TreeEntryFile(e *TreeEntry) (*File, error) {
109109
return NewFile(e.Name, e.Mode, blob), nil
110110
}
111111

112-
func (t *Tree) findEntry(path string) (*TreeEntry, error) {
112+
// FindEntry search a TreeEntry in this tree or any subtree.
113+
func (t *Tree) FindEntry(path string) (*TreeEntry, error) {
113114
pathParts := strings.Split(path, "/")
114115

115116
var tree *Tree
@@ -146,6 +147,7 @@ func (t *Tree) entry(baseName string) (*TreeEntry, error) {
146147
if t.m == nil {
147148
t.buildMap()
148149
}
150+
149151
entry, ok := t.m[baseName]
150152
if !ok {
151153
return nil, errEntryNotFound

plumbing/object/tree_test.go

+6
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ func (s *TreeSuite) TestFiles(c *C) {
107107
c.Assert(count, Equals, 9)
108108
}
109109

110+
func (s *TreeSuite) TestFindEntry(c *C) {
111+
e, err := s.Tree.FindEntry("vendor/foo.go")
112+
c.Assert(err, IsNil)
113+
c.Assert(e.Name, Equals, "foo.go")
114+
}
115+
110116
// This plumbing.EncodedObject implementation has a reader that only returns 6
111117
// bytes at a time, this should simulate the conditions when a read
112118
// returns less bytes than asked, for example when reading a hash which

plumbing/object/treenoder.go

+10-16
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
package object
22

3-
// A treenoder is a helper type that wraps git trees into merkletrie
4-
// noders.
5-
//
6-
// As a merkletrie noder doesn't understand the concept of modes (e.g.
7-
// file permissions), the treenoder includes the mode of the git tree in
8-
// the hash, so changes in the modes will be detected as modifications
9-
// to the file contents by the merkletrie difftree algorithm. This is
10-
// consistent with how the "git diff-tree" command works.
113
import (
124
"io"
135

@@ -16,6 +8,14 @@ import (
168
"gopkg.in/src-d/go-git.v4/utils/merkletrie/noder"
179
)
1810

11+
// A treenoder is a helper type that wraps git trees into merkletrie
12+
// noders.
13+
//
14+
// As a merkletrie noder doesn't understand the concept of modes (e.g.
15+
// file permissions), the treenoder includes the mode of the git tree in
16+
// the hash, so changes in the modes will be detected as modifications
17+
// to the file contents by the merkletrie difftree algorithm. This is
18+
// consistent with how the "git diff-tree" command works.
1919
type treeNoder struct {
2020
parent *Tree // the root node is its own parent
2121
name string // empty string for the root node
@@ -24,7 +24,8 @@ type treeNoder struct {
2424
children []noder.Noder // memoized
2525
}
2626

27-
func newTreeNoder(t *Tree) *treeNoder {
27+
// NewTreeRootNode returns the root node of a Tree
28+
func NewTreeRootNode(t *Tree) noder.Noder {
2829
if t == nil {
2930
return &treeNoder{}
3031
}
@@ -45,13 +46,6 @@ func (t *treeNoder) String() string {
4546
return "treeNoder <" + t.name + ">"
4647
}
4748

48-
// The hash of a treeNoder is the result of concatenating the hash of
49-
// its contents and its mode; that way the difftree algorithm will
50-
// detect changes in the contents of files and also in their mode.
51-
//
52-
// Files with Regular and Deprecated file modes are considered the same
53-
// for the purpose of difftree, so Regular will be used as the mode for
54-
// Deprecated files here.
5549
func (t *treeNoder) Hash() []byte {
5650
if t.mode == filemode.Deprecated {
5751
return append(t.hash[:], filemode.Regular.Bytes()...)

repository.go

+11-9
Original file line numberDiff line numberDiff line change
@@ -340,11 +340,11 @@ func (r *Repository) clone(o *CloneOptions) error {
340340
return err
341341
}
342342

343-
if _, err := r.updateReferences(c.Fetch, o.ReferenceName, head); err != nil {
343+
if _, err := r.updateReferences(c.Fetch, head); err != nil {
344344
return err
345345
}
346346

347-
if err := r.updateWorktree(); err != nil {
347+
if err := r.updateWorktree(head.Name()); err != nil {
348348
return err
349349
}
350350

@@ -429,7 +429,7 @@ func (r *Repository) updateRemoteConfig(remote *Remote, o *CloneOptions,
429429
}
430430

431431
func (r *Repository) updateReferences(spec []config.RefSpec,
432-
headName plumbing.ReferenceName, resolvedHead *plumbing.Reference) (updated bool, err error) {
432+
resolvedHead *plumbing.Reference) (updated bool, err error) {
433433

434434
if !resolvedHead.IsBranch() {
435435
// Detached HEAD mode
@@ -534,7 +534,7 @@ func (r *Repository) Pull(o *PullOptions) error {
534534
return err
535535
}
536536

537-
refsUpdated, err := r.updateReferences(remote.c.Fetch, o.ReferenceName, head)
537+
refsUpdated, err := r.updateReferences(remote.c.Fetch, head)
538538
if err != nil {
539539
return err
540540
}
@@ -547,7 +547,7 @@ func (r *Repository) Pull(o *PullOptions) error {
547547
return NoErrAlreadyUpToDate
548548
}
549549

550-
if err := r.updateWorktree(); err != nil {
550+
if err := r.updateWorktree(head.Name()); err != nil {
551551
return err
552552
}
553553

@@ -560,22 +560,24 @@ func (r *Repository) Pull(o *PullOptions) error {
560560
return nil
561561
}
562562

563-
func (r *Repository) updateWorktree() error {
563+
func (r *Repository) updateWorktree(branch plumbing.ReferenceName) error {
564564
if r.wt == nil {
565565
return nil
566566
}
567567

568-
w, err := r.Worktree()
568+
b, err := r.Reference(branch, true)
569569
if err != nil {
570570
return err
571571
}
572572

573-
h, err := r.Head()
573+
w, err := r.Worktree()
574574
if err != nil {
575575
return err
576576
}
577577

578-
return w.Checkout(h.Hash())
578+
return w.Reset(&ResetOptions{
579+
Commit: b.Hash(),
580+
})
579581
}
580582

581583
// Fetch fetches changes from a remote repository.

0 commit comments

Comments
 (0)