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

Commit

Permalink
Merge pull request #339 from mcuadros/status
Browse files Browse the repository at this point in the history
worktree, status and reset implementation based on merkletrie
  • Loading branch information
mcuadros authored Apr 12, 2017
2 parents 9b45f46 + 5bcf802 commit 932ced9
Show file tree
Hide file tree
Showing 19 changed files with 1,288 additions and 292 deletions.
26 changes: 19 additions & 7 deletions _examples/checkout/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,37 @@ import (

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

// Clone the given repository to the given directory
Info("git clone %s %s", url, directory)

r, err := git.PlainClone(directory, false, &git.CloneOptions{
URL: url,
})

CheckIfError(err)

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

w, err := r.Worktree()

CheckIfError(err)

CheckIfError(w.Checkout(plumbing.NewHash(commitRef)))
// ... checking out to commit
Info("git checkout %s", commit)
err = w.Checkout(&git.CheckoutOptions{
Hash: plumbing.NewHash(commit),
})
CheckIfError(err)

fmt.Println("voila")
// ... retrieving the commit being pointed by HEAD, it's shows that the
// repository is poiting to the giving commit in detached mode
Info("git show-ref --head HEAD")
ref, err = r.Head()
CheckIfError(err)
fmt.Println(ref.Hash())
}
2 changes: 1 addition & 1 deletion _examples/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

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

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

var args = map[string][]string{
"checkout": []string{defaultURL, tempFolder(), "35e85108805c84807bc66a02d91535e1e24b38b9"},
Expand Down
66 changes: 66 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,72 @@ type SubmoduleUpdateOptions struct {
RecurseSubmodules SubmoduleRescursivity
}

// CheckoutOptions describes how a checkout 31operation should be performed.
type CheckoutOptions struct {
// Hash to be checked out, if used HEAD will in detached mode. Branch and
// Hash are mutual exclusive.
Hash plumbing.Hash
// Branch to be checked out, if Branch and Hash are empty is set to `master`.
Branch plumbing.ReferenceName
// Force, if true when switching branches, proceed even if the index or the
// working tree differs from HEAD. This is used to throw away local changes
Force bool
}

// Validate validates the fields and sets the default values.
func (o *CheckoutOptions) Validate() error {
if o.Branch == "" {
o.Branch = plumbing.Master
}

return nil
}

// ResetMode defines the mode of a reset operation.
type ResetMode int8

const (
// HardReset resets the index and working tree. Any changes to tracked files
// in the working tree are discarded.
HardReset ResetMode = iota
// MixedReset resets the index but not the working tree (i.e., the changed
// files are preserved but not marked for commit) and reports what has not
// been updated. This is the default action.
MixedReset
// MergeReset resets the index and updates the files in the working tree
// that are different between Commit and HEAD, but keeps those which are
// different between the index and working tree (i.e. which have changes
// which have not been added).
//
// If a file that is different between Commit and the index has unstaged
// changes, reset is aborted.
MergeReset
)

// ResetOptions describes how a reset operation should be performed.
type ResetOptions struct {
// Commit, if commit is pressent set the current branch head (HEAD) to it.
Commit plumbing.Hash
// Mode, form resets the current branch head to Commit and possibly updates
// the index (resetting it to the tree of Commit) and the working tree
// depending on Mode. If empty MixedReset is used.
Mode ResetMode
}

// Validate validates the fields and sets the default values.
func (o *ResetOptions) Validate(r *Repository) error {
if o.Commit == plumbing.ZeroHash {
ref, err := r.Head()
if err != nil {
return err
}

o.Commit = ref.Hash()
}

return nil
}

// LogOptions describes how a log action should be performed.
type LogOptions struct {
// When the From option is set the log will only contain commits
Expand Down
26 changes: 26 additions & 0 deletions plumbing/format/index/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package index

import (
"errors"
"fmt"
"time"

"bytes"

"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/filemode"
)
Expand Down Expand Up @@ -47,6 +50,16 @@ type Index struct {
ResolveUndo *ResolveUndo
}

// String is equivalent to `git ls-files --stage --debug`
func (i *Index) String() string {
buf := bytes.NewBuffer(nil)
for _, e := range i.Entries {
buf.WriteString(e.String())
}

return buf.String()
}

// Entry represents a single file (or stage of a file) in the cache. An entry
// represents exactly one stage of a file. If a file path is unmerged then
// multiple Entry instances may appear for the same path name.
Expand Down Expand Up @@ -78,6 +91,19 @@ type Entry struct {
IntentToAdd bool
}

func (e Entry) String() string {
buf := bytes.NewBuffer(nil)

fmt.Fprintf(buf, "%06o %s %d\t%s\n", e.Mode, e.Hash, e.Stage, e.Name)
fmt.Fprintf(buf, " ctime: %d:%d\n", e.CreatedAt.Unix(), e.CreatedAt.Nanosecond())
fmt.Fprintf(buf, " mtime: %d:%d\n", e.ModifiedAt.Unix(), e.ModifiedAt.Nanosecond())
fmt.Fprintf(buf, " dev: %d\tino: %d\n", e.Dev, e.Inode)
fmt.Fprintf(buf, " uid: %d\tgid: %d\n", e.UID, e.GID)
fmt.Fprintf(buf, " size: %d\tflags: %x\n", e.Size, 0)

return buf.String()
}

// Tree contains pre-computed hashes for trees that can be derived from the
// index. It helps speed up tree object generation from index for a new commit.
type Tree struct {
Expand Down
4 changes: 2 additions & 2 deletions plumbing/object/difftree.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
// DiffTree compares the content and mode of the blobs found via two
// tree objects.
func DiffTree(a, b *Tree) (Changes, error) {
from := newTreeNoder(a)
to := newTreeNoder(b)
from := NewTreeRootNode(a)
to := NewTreeRootNode(b)

hashEqual := func(a, b noder.Hasher) bool {
return bytes.Equal(a.Hash(), b.Hash())
Expand Down
8 changes: 5 additions & 3 deletions plumbing/object/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ type TreeEntry struct {
// File returns the hash of the file identified by the `path` argument.
// The path is interpreted as relative to the tree receiver.
func (t *Tree) File(path string) (*File, error) {
e, err := t.findEntry(path)
e, err := t.FindEntry(path)
if err != nil {
return nil, ErrFileNotFound
}
Expand All @@ -86,7 +86,7 @@ func (t *Tree) File(path string) (*File, error) {
// Tree returns the tree identified by the `path` argument.
// The path is interpreted as relative to the tree receiver.
func (t *Tree) Tree(path string) (*Tree, error) {
e, err := t.findEntry(path)
e, err := t.FindEntry(path)
if err != nil {
return nil, ErrDirectoryNotFound
}
Expand All @@ -109,7 +109,8 @@ func (t *Tree) TreeEntryFile(e *TreeEntry) (*File, error) {
return NewFile(e.Name, e.Mode, blob), nil
}

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

var tree *Tree
Expand Down Expand Up @@ -146,6 +147,7 @@ func (t *Tree) entry(baseName string) (*TreeEntry, error) {
if t.m == nil {
t.buildMap()
}

entry, ok := t.m[baseName]
if !ok {
return nil, errEntryNotFound
Expand Down
6 changes: 6 additions & 0 deletions plumbing/object/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ func (s *TreeSuite) TestFiles(c *C) {
c.Assert(count, Equals, 9)
}

func (s *TreeSuite) TestFindEntry(c *C) {
e, err := s.Tree.FindEntry("vendor/foo.go")
c.Assert(err, IsNil)
c.Assert(e.Name, Equals, "foo.go")
}

// This plumbing.EncodedObject implementation has a reader that only returns 6
// bytes at a time, this should simulate the conditions when a read
// returns less bytes than asked, for example when reading a hash which
Expand Down
26 changes: 10 additions & 16 deletions plumbing/object/treenoder.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
package object

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

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

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

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

// The hash of a treeNoder is the result of concatenating the hash of
// its contents and its mode; that way the difftree algorithm will
// detect changes in the contents of files and also in their mode.
//
// Files with Regular and Deprecated file modes are considered the same
// for the purpose of difftree, so Regular will be used as the mode for
// Deprecated files here.
func (t *treeNoder) Hash() []byte {
if t.mode == filemode.Deprecated {
return append(t.hash[:], filemode.Regular.Bytes()...)
Expand Down
20 changes: 11 additions & 9 deletions repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,11 +340,11 @@ func (r *Repository) clone(o *CloneOptions) error {
return err
}

if _, err := r.updateReferences(c.Fetch, o.ReferenceName, head); err != nil {
if _, err := r.updateReferences(c.Fetch, head); err != nil {
return err
}

if err := r.updateWorktree(); err != nil {
if err := r.updateWorktree(head.Name()); err != nil {
return err
}

Expand Down Expand Up @@ -429,7 +429,7 @@ func (r *Repository) updateRemoteConfig(remote *Remote, o *CloneOptions,
}

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

if !resolvedHead.IsBranch() {
// Detached HEAD mode
Expand Down Expand Up @@ -534,7 +534,7 @@ func (r *Repository) Pull(o *PullOptions) error {
return err
}

refsUpdated, err := r.updateReferences(remote.c.Fetch, o.ReferenceName, head)
refsUpdated, err := r.updateReferences(remote.c.Fetch, head)
if err != nil {
return err
}
Expand All @@ -547,7 +547,7 @@ func (r *Repository) Pull(o *PullOptions) error {
return NoErrAlreadyUpToDate
}

if err := r.updateWorktree(); err != nil {
if err := r.updateWorktree(head.Name()); err != nil {
return err
}

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

func (r *Repository) updateWorktree() error {
func (r *Repository) updateWorktree(branch plumbing.ReferenceName) error {
if r.wt == nil {
return nil
}

w, err := r.Worktree()
b, err := r.Reference(branch, true)
if err != nil {
return err
}

h, err := r.Head()
w, err := r.Worktree()
if err != nil {
return err
}

return w.Checkout(h.Hash())
return w.Reset(&ResetOptions{
Commit: b.Hash(),
})
}

// Fetch fetches changes from a remote repository.
Expand Down
Loading

0 comments on commit 932ced9

Please sign in to comment.