\nfoo@foo.foo",
+ When: when,
+ }
+}
+
func commitSignKey(c *C, decrypt bool) *openpgp.Entity {
s := strings.NewReader(armoredKeyRing)
es, err := openpgp.ReadArmoredKeyRing(s)
diff --git a/worktree_linux.go b/worktree_linux.go
index 6fcace2f9..f6b85fe3d 100644
--- a/worktree_linux.go
+++ b/worktree_linux.go
@@ -1,3 +1,4 @@
+//go:build linux
// +build linux
package git
@@ -21,6 +22,6 @@ func init() {
}
}
-func isSymlinkWindowsNonAdmin(err error) bool {
+func isSymlinkWindowsNonAdmin(_ error) bool {
return false
}
diff --git a/worktree_status.go b/worktree_status.go
index 61bb6f759..7870d138d 100644
--- a/worktree_status.go
+++ b/worktree_status.go
@@ -29,10 +29,23 @@ var (
// ErrGlobNoMatches in an AddGlob if the glob pattern does not match any
// files in the worktree.
ErrGlobNoMatches = errors.New("glob pattern did not match any files")
+ // ErrUnsupportedStatusStrategy occurs when an invalid StatusStrategy is used
+ // when processing the Worktree status.
+ ErrUnsupportedStatusStrategy = errors.New("unsupported status strategy")
)
// Status returns the working tree status.
func (w *Worktree) Status() (Status, error) {
+ return w.StatusWithOptions(StatusOptions{Strategy: defaultStatusStrategy})
+}
+
+// StatusOptions defines the options for Worktree.StatusWithOptions().
+type StatusOptions struct {
+ Strategy StatusStrategy
+}
+
+// StatusWithOptions returns the working tree status.
+func (w *Worktree) StatusWithOptions(o StatusOptions) (Status, error) {
var hash plumbing.Hash
ref, err := w.r.Head()
@@ -44,11 +57,14 @@ func (w *Worktree) Status() (Status, error) {
hash = ref.Hash()
}
- return w.status(hash)
+ return w.status(o.Strategy, hash)
}
-func (w *Worktree) status(commit plumbing.Hash) (Status, error) {
- s := make(Status)
+func (w *Worktree) status(ss StatusStrategy, commit plumbing.Hash) (Status, error) {
+ s, err := ss.new(w)
+ if err != nil {
+ return nil, err
+ }
left, err := w.diffCommitWithStaging(commit, false)
if err != nil {
@@ -74,7 +90,7 @@ func (w *Worktree) status(commit plumbing.Hash) (Status, error) {
}
}
- right, err := w.diffStagingWithWorktree(false)
+ right, err := w.diffStagingWithWorktree(false, true)
if err != nil {
return nil, err
}
@@ -113,7 +129,7 @@ func nameFromAction(ch *merkletrie.Change) string {
return name
}
-func (w *Worktree) diffStagingWithWorktree(reverse bool) (merkletrie.Changes, error) {
+func (w *Worktree) diffStagingWithWorktree(reverse, excludeIgnoredChanges bool) (merkletrie.Changes, error) {
idx, err := w.r.Storer.Index()
if err != nil {
return nil, err
@@ -138,7 +154,10 @@ func (w *Worktree) diffStagingWithWorktree(reverse bool) (merkletrie.Changes, er
return nil, err
}
- return w.excludeIgnoredChanges(c), nil
+ if excludeIgnoredChanges {
+ return w.excludeIgnoredChanges(c), nil
+ }
+ return c, nil
}
func (w *Worktree) excludeIgnoredChanges(changes merkletrie.Changes) merkletrie.Changes {
@@ -268,7 +287,7 @@ func diffTreeIsEquals(a, b noder.Hasher) bool {
// no error is returned. When path is a file, the blob.Hash is returned.
func (w *Worktree) Add(path string) (plumbing.Hash, error) {
// TODO(mcuadros): deprecate in favor of AddWithOption in v6.
- return w.doAdd(path, make([]gitignore.Pattern, 0))
+ return w.doAdd(path, make([]gitignore.Pattern, 0), false)
}
func (w *Worktree) doAddDirectory(idx *index.Index, s Status, directory string, ignorePattern []gitignore.Pattern) (added bool, err error) {
@@ -318,7 +337,7 @@ func (w *Worktree) AddWithOptions(opts *AddOptions) error {
}
if opts.All {
- _, err := w.doAdd(".", w.Excludes)
+ _, err := w.doAdd(".", w.Excludes, false)
return err
}
@@ -326,16 +345,11 @@ func (w *Worktree) AddWithOptions(opts *AddOptions) error {
return w.AddGlob(opts.Glob)
}
- _, err := w.Add(opts.Path)
+ _, err := w.doAdd(opts.Path, make([]gitignore.Pattern, 0), opts.SkipStatus)
return err
}
-func (w *Worktree) doAdd(path string, ignorePattern []gitignore.Pattern) (plumbing.Hash, error) {
- s, err := w.Status()
- if err != nil {
- return plumbing.ZeroHash, err
- }
-
+func (w *Worktree) doAdd(path string, ignorePattern []gitignore.Pattern, skipStatus bool) (plumbing.Hash, error) {
idx, err := w.r.Storer.Index()
if err != nil {
return plumbing.ZeroHash, err
@@ -345,6 +359,19 @@ func (w *Worktree) doAdd(path string, ignorePattern []gitignore.Pattern) (plumbi
var added bool
fi, err := w.Filesystem.Lstat(path)
+
+ // status is required for doAddDirectory
+ var s Status
+ var err2 error
+ if !skipStatus || fi == nil || fi.IsDir() {
+ s, err2 = w.Status()
+ if err2 != nil {
+ return plumbing.ZeroHash, err2
+ }
+ }
+
+ path = filepath.Clean(path)
+
if err != nil || !fi.IsDir() {
added, h, err = w.doAddFile(idx, s, path, ignorePattern)
} else {
@@ -418,8 +445,9 @@ func (w *Worktree) AddGlob(pattern string) error {
// doAddFile create a new blob from path and update the index, added is true if
// the file added is different from the index.
+// if s status is nil will skip the status check and update the index anyway
func (w *Worktree) doAddFile(idx *index.Index, s Status, path string, ignorePattern []gitignore.Pattern) (added bool, h plumbing.Hash, err error) {
- if s.File(path).Worktree == Unmodified {
+ if s != nil && s.File(path).Worktree == Unmodified {
return false, h, nil
}
if len(ignorePattern) > 0 {
@@ -478,7 +506,7 @@ func (w *Worktree) copyFileToStorage(path string) (hash plumbing.Hash, err error
return w.r.Storer.SetEncodedObject(obj)
}
-func (w *Worktree) fillEncodedObjectFromFile(dst io.Writer, path string, fi os.FileInfo) (err error) {
+func (w *Worktree) fillEncodedObjectFromFile(dst io.Writer, path string, _ os.FileInfo) (err error) {
src, err := w.Filesystem.Open(path)
if err != nil {
return err
@@ -493,7 +521,7 @@ func (w *Worktree) fillEncodedObjectFromFile(dst io.Writer, path string, fi os.F
return err
}
-func (w *Worktree) fillEncodedObjectFromSymlink(dst io.Writer, path string, fi os.FileInfo) error {
+func (w *Worktree) fillEncodedObjectFromSymlink(dst io.Writer, path string, _ os.FileInfo) error {
target, err := w.Filesystem.Readlink(path)
if err != nil {
return err
@@ -533,9 +561,11 @@ func (w *Worktree) doUpdateFileToIndex(e *index.Entry, filename string, h plumbi
return err
}
- if e.Mode.IsRegular() {
- e.Size = uint32(info.Size())
- }
+ // The entry size must always reflect the current state, otherwise
+ // it will cause go-git's Worktree.Status() to divert from "git status".
+ // The size of a symlink is the length of the path to the target.
+ // The size of Regular and Executable files is the size of the files.
+ e.Size = uint32(info.Size())
fillSystemInfo(e, info.Sys())
return nil
diff --git a/worktree_status_test.go b/worktree_status_test.go
new file mode 100644
index 000000000..629ebd5bf
--- /dev/null
+++ b/worktree_status_test.go
@@ -0,0 +1,89 @@
+package git
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "github.com/go-git/go-billy/v5/osfs"
+ "github.com/go-git/go-git/v5/plumbing/cache"
+ "github.com/go-git/go-git/v5/storage/filesystem"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// For additional context: #1159.
+func TestIndexEntrySizeUpdatedForNonRegularFiles(t *testing.T) {
+ w := osfs.New(t.TempDir(), osfs.WithBoundOS())
+ dot, err := w.Chroot(GitDirName)
+ require.NoError(t, err)
+
+ s := filesystem.NewStorage(dot, cache.NewObjectLRUDefault())
+ r, err := Init(s, w)
+ require.NoError(t, err)
+ require.NotNil(t, r)
+
+ wt, err := r.Worktree()
+ require.NoError(t, err)
+ require.NotNil(t, wt)
+
+ file := "LICENSE"
+ f, err := w.OpenFile(file, os.O_CREATE|os.O_WRONLY, 0o666)
+ require.NoError(t, err)
+ require.NotNil(t, f)
+
+ content := []byte(strings.Repeat("a\n", 1000))
+ _, err = f.Write(content)
+ require.NoError(t, err)
+ err = f.Close()
+ require.NoError(t, err)
+
+ _, err = wt.Add(file)
+ require.NoError(t, err)
+
+ _, err = wt.Commit("add file", &CommitOptions{})
+ require.NoError(t, err)
+
+ st, err := wt.StatusWithOptions(StatusOptions{Strategy: Preload})
+ require.NoError(t, err)
+ assert.Equal(t,
+ &FileStatus{Worktree: Unmodified, Staging: Unmodified},
+ st.File(file))
+
+ // Make the file not regular. The same would apply to a transition
+ // from regular file to symlink.
+ err = os.Chmod(filepath.Join(w.Root(), file), 0o777)
+ require.NoError(t, err)
+
+ f, err = w.OpenFile(file, os.O_APPEND|os.O_RDWR, 0o777)
+ require.NoError(t, err)
+ require.NotNil(t, f)
+
+ _, err = f.Write([]byte("\n\n"))
+ require.NoError(t, err)
+ err = f.Close()
+ require.NoError(t, err)
+
+ _, err = wt.Add(file)
+ assert.NoError(t, err)
+
+ // go-git's Status diverges from "git status", so this check does not
+ // fail, even when the issue is present. As at this point "git status"
+ // reports the unstaged file was modified while "git diff" would return
+ // empty, as the files are the same but the index has the incorrect file
+ // size.
+ st, err = wt.StatusWithOptions(StatusOptions{Strategy: Preload})
+ assert.NoError(t, err)
+ assert.Equal(t,
+ &FileStatus{Worktree: Unmodified, Staging: Modified},
+ st.File(file))
+
+ idx, err := wt.r.Storer.Index()
+ assert.NoError(t, err)
+ require.NotNil(t, idx)
+ require.Len(t, idx.Entries, 1)
+
+ // Check whether the index was updated with the two new line breaks.
+ assert.Equal(t, uint32(len(content)+2), idx.Entries[0].Size)
+}
diff --git a/worktree_test.go b/worktree_test.go
index 24d5bd50f..a3dbcfeb3 100644
--- a/worktree_test.go
+++ b/worktree_test.go
@@ -16,12 +16,16 @@ import (
fixtures "github.com/go-git/go-git-fixtures/v4"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
+ "github.com/go-git/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/plumbing/filemode"
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
"github.com/go-git/go-git/v5/plumbing/format/index"
"github.com/go-git/go-git/v5/plumbing/object"
+ "github.com/go-git/go-git/v5/storage/filesystem"
"github.com/go-git/go-git/v5/storage/memory"
+ "github.com/stretchr/testify/assert"
+ "github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-billy/v5/util"
@@ -29,6 +33,12 @@ import (
. "gopkg.in/check.v1"
)
+func defaultTestCommitOptions() *CommitOptions {
+ return &CommitOptions{
+ Author: &object.Signature{Name: "testuser", Email: "testemail"},
+ }
+}
+
type WorktreeSuite struct {
BaseSuite
}
@@ -60,8 +70,7 @@ func (s *WorktreeSuite) TestPullCheckout(c *C) {
}
func (s *WorktreeSuite) TestPullFastForward(c *C) {
- url, clean := s.TemporalDir()
- defer clean()
+ url := c.MkDir()
path := fixtures.Basic().ByTag("worktree").One().Worktree().Root()
@@ -70,8 +79,7 @@ func (s *WorktreeSuite) TestPullFastForward(c *C) {
})
c.Assert(err, IsNil)
- dir, clean := s.TemporalDir()
- defer clean()
+ dir := c.MkDir()
r, err := PlainClone(dir, false, &CloneOptions{
URL: url,
@@ -80,8 +88,9 @@ func (s *WorktreeSuite) TestPullFastForward(c *C) {
w, err := server.Worktree()
c.Assert(err, IsNil)
- err = os.WriteFile(filepath.Join(path, "foo"), []byte("foo"), 0755)
+ err = os.WriteFile(filepath.Join(url, "foo"), []byte("foo"), 0755)
c.Assert(err, IsNil)
+ w.Add("foo")
hash, err := w.Commit("foo", &CommitOptions{Author: defaultSignature()})
c.Assert(err, IsNil)
@@ -97,8 +106,7 @@ func (s *WorktreeSuite) TestPullFastForward(c *C) {
}
func (s *WorktreeSuite) TestPullNonFastForward(c *C) {
- url, clean := s.TemporalDir()
- defer clean()
+ url := c.MkDir()
path := fixtures.Basic().ByTag("worktree").One().Worktree().Root()
@@ -107,8 +115,7 @@ func (s *WorktreeSuite) TestPullNonFastForward(c *C) {
})
c.Assert(err, IsNil)
- dir, clean := s.TemporalDir()
- defer clean()
+ dir := c.MkDir()
r, err := PlainClone(dir, false, &CloneOptions{
URL: url,
@@ -117,15 +124,17 @@ func (s *WorktreeSuite) TestPullNonFastForward(c *C) {
w, err := server.Worktree()
c.Assert(err, IsNil)
- err = os.WriteFile(filepath.Join(path, "foo"), []byte("foo"), 0755)
+ err = os.WriteFile(filepath.Join(url, "foo"), []byte("foo"), 0755)
c.Assert(err, IsNil)
+ w.Add("foo")
_, err = w.Commit("foo", &CommitOptions{Author: defaultSignature()})
c.Assert(err, IsNil)
w, err = r.Worktree()
c.Assert(err, IsNil)
- err = os.WriteFile(filepath.Join(path, "bar"), []byte("bar"), 0755)
+ err = os.WriteFile(filepath.Join(dir, "bar"), []byte("bar"), 0755)
c.Assert(err, IsNil)
+ w.Add("bar")
_, err = w.Commit("bar", &CommitOptions{Author: defaultSignature()})
c.Assert(err, IsNil)
@@ -217,8 +226,7 @@ func (s *WorktreeSuite) TestPullProgressWithRecursion(c *C) {
path := fixtures.ByTag("submodule").One().Worktree().Root()
- dir, clean := s.TemporalDir()
- defer clean()
+ dir := c.MkDir()
r, _ := PlainInit(dir, false)
r.CreateRemote(&config.RemoteConfig{
@@ -278,7 +286,8 @@ func (s *RepositorySuite) TestPullAdd(c *C) {
func (s *WorktreeSuite) TestPullAlreadyUptodate(c *C) {
path := fixtures.Basic().ByTag("worktree").One().Worktree().Root()
- r, err := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{
+ fs := memfs.New()
+ r, err := Clone(memory.NewStorage(), fs, &CloneOptions{
URL: filepath.Join(path, ".git"),
})
@@ -286,8 +295,9 @@ func (s *WorktreeSuite) TestPullAlreadyUptodate(c *C) {
w, err := r.Worktree()
c.Assert(err, IsNil)
- err = os.WriteFile(filepath.Join(path, "bar"), []byte("bar"), 0755)
+ err = util.WriteFile(fs, "bar", []byte("bar"), 0755)
c.Assert(err, IsNil)
+ w.Add("bar")
_, err = w.Commit("bar", &CommitOptions{Author: defaultSignature()})
c.Assert(err, IsNil)
@@ -295,6 +305,55 @@ func (s *WorktreeSuite) TestPullAlreadyUptodate(c *C) {
c.Assert(err, Equals, NoErrAlreadyUpToDate)
}
+func (s *WorktreeSuite) TestPullDepth(c *C) {
+ r, err := Clone(memory.NewStorage(), memfs.New(), &CloneOptions{
+ URL: fixtures.Basic().One().URL,
+ Depth: 1,
+ })
+
+ c.Assert(err, IsNil)
+
+ w, err := r.Worktree()
+ c.Assert(err, IsNil)
+ err = w.Pull(&PullOptions{})
+ c.Assert(err, Equals, nil)
+}
+
+func (s *WorktreeSuite) TestPullAfterShallowClone(c *C) {
+ tempDir := c.MkDir()
+ remoteURL := filepath.Join(tempDir, "remote")
+ repoDir := filepath.Join(tempDir, "repo")
+
+ remote, err := PlainInit(remoteURL, false)
+ c.Assert(err, IsNil)
+ c.Assert(remote, NotNil)
+
+ _ = CommitNewFile(c, remote, "File1")
+ _ = CommitNewFile(c, remote, "File2")
+
+ repo, err := PlainClone(repoDir, false, &CloneOptions{
+ URL: remoteURL,
+ Depth: 1,
+ Tags: NoTags,
+ SingleBranch: true,
+ ReferenceName: "master",
+ })
+ c.Assert(err, IsNil)
+
+ _ = CommitNewFile(c, remote, "File3")
+ _ = CommitNewFile(c, remote, "File4")
+
+ w, err := repo.Worktree()
+ c.Assert(err, IsNil)
+
+ err = w.Pull(&PullOptions{
+ RemoteName: DefaultRemoteName,
+ SingleBranch: true,
+ ReferenceName: plumbing.NewBranchReferenceName("master"),
+ })
+ c.Assert(err, IsNil)
+}
+
func (s *WorktreeSuite) TestCheckout(c *C) {
fs := memfs.New()
w := &Worktree{
@@ -389,8 +448,7 @@ func (s *WorktreeSuite) TestCheckoutSymlink(c *C) {
c.Skip("git doesn't support symlinks by default in windows")
}
- dir, clean := s.TemporalDir()
- defer clean()
+ dir := c.MkDir()
r, err := PlainInit(dir, false)
c.Assert(err, IsNil)
@@ -420,7 +478,8 @@ func (s *WorktreeSuite) TestCheckoutSymlink(c *C) {
func (s *WorktreeSuite) TestCheckoutSparse(c *C) {
fs := memfs.New()
r, err := Clone(memory.NewStorage(), fs, &CloneOptions{
- URL: s.GetBasicLocalRepositoryURL(),
+ URL: s.GetBasicLocalRepositoryURL(),
+ NoCheckout: true,
})
c.Assert(err, IsNil)
@@ -453,8 +512,7 @@ func (s *WorktreeSuite) TestFilenameNormalization(c *C) {
c.Skip("windows paths may contain non utf-8 sequences")
}
- url, clean := s.TemporalDir()
- defer clean()
+ url := c.MkDir()
path := fixtures.Basic().ByTag("worktree").One().Worktree().Root()
@@ -646,8 +704,7 @@ func (s *WorktreeSuite) TestCheckoutIndexMem(c *C) {
}
func (s *WorktreeSuite) TestCheckoutIndexOS(c *C) {
- fs, clean := s.TemporalFilesystem()
- defer clean()
+ fs := s.TemporalFilesystem(c)
w := &Worktree{
r: s.Repository,
@@ -767,6 +824,30 @@ func (s *WorktreeSuite) TestCheckoutCreateMissingBranch(c *C) {
c.Assert(err, Equals, ErrCreateRequiresBranch)
}
+func (s *WorktreeSuite) TestCheckoutCreateInvalidBranch(c *C) {
+ w := &Worktree{
+ r: s.Repository,
+ Filesystem: memfs.New(),
+ }
+
+ for _, name := range []plumbing.ReferenceName{
+ "foo",
+ "-",
+ "-foo",
+ "refs/heads//",
+ "refs/heads/..",
+ "refs/heads/a..b",
+ "refs/heads/.",
+ } {
+ err := w.Checkout(&CheckoutOptions{
+ Create: true,
+ Branch: name,
+ })
+
+ c.Assert(err, Equals, plumbing.ErrInvalidReferenceName)
+ }
+}
+
func (s *WorktreeSuite) TestCheckoutTag(c *C) {
f := fixtures.ByTag("tags").One()
r := s.NewRepositoryWithEmptyWorktree(f)
@@ -804,6 +885,41 @@ func (s *WorktreeSuite) TestCheckoutTag(c *C) {
c.Assert(head.Name().String(), Equals, "HEAD")
}
+func (s *WorktreeSuite) TestCheckoutTagHash(c *C) {
+ f := fixtures.ByTag("tags").One()
+ r := s.NewRepositoryWithEmptyWorktree(f)
+ w, err := r.Worktree()
+ c.Assert(err, IsNil)
+
+ for _, hash := range []string{
+ "b742a2a9fa0afcfa9a6fad080980fbc26b007c69", // annotated tag
+ "ad7897c0fb8e7d9a9ba41fa66072cf06095a6cfc", // commit tag
+ "f7b877701fbf855b44c0a9e86f3fdce2c298b07f", // lightweight tag
+ } {
+ err = w.Checkout(&CheckoutOptions{
+ Hash: plumbing.NewHash(hash),
+ })
+ c.Assert(err, IsNil)
+ head, err := w.r.Head()
+ c.Assert(err, IsNil)
+ c.Assert(head.Name().String(), Equals, "HEAD")
+
+ status, err := w.Status()
+ c.Assert(err, IsNil)
+ c.Assert(status.IsClean(), Equals, true)
+ }
+
+ for _, hash := range []string{
+ "fe6cb94756faa81e5ed9240f9191b833db5f40ae", // blob tag
+ "152175bf7e5580299fa1f0ba41ef6474cc043b70", // tree tag
+ } {
+ err = w.Checkout(&CheckoutOptions{
+ Hash: plumbing.NewHash(hash),
+ })
+ c.Assert(err, NotNil)
+ }
+}
+
func (s *WorktreeSuite) TestCheckoutBisect(c *C) {
if testing.Short() {
c.Skip("skipping test in short mode.")
@@ -884,21 +1000,22 @@ func (s *WorktreeSuite) TestStatusCheckedInBeforeIgnored(c *C) {
c.Assert(err, IsNil)
_, err = w.Add("fileToIgnore")
c.Assert(err, IsNil)
- _, err = w.Commit("Added file that will be ignored later", &CommitOptions{})
+
+ _, err = w.Commit("Added file that will be ignored later", defaultTestCommitOptions())
c.Assert(err, IsNil)
err = util.WriteFile(fs, ".gitignore", []byte("fileToIgnore\nsecondIgnoredFile"), 0755)
c.Assert(err, IsNil)
_, err = w.Add(".gitignore")
c.Assert(err, IsNil)
- _, err = w.Commit("Added .gitignore", &CommitOptions{})
+ _, err = w.Commit("Added .gitignore", defaultTestCommitOptions())
c.Assert(err, IsNil)
status, err := w.Status()
c.Assert(err, IsNil)
c.Assert(status.IsClean(), Equals, true)
c.Assert(status, NotNil)
- err = util.WriteFile(fs, "secondIgnoredFile", []byte("Should be completly ignored"), 0755)
+ err = util.WriteFile(fs, "secondIgnoredFile", []byte("Should be completely ignored"), 0755)
c.Assert(err, IsNil)
status = nil
status, err = w.Status()
@@ -934,6 +1051,33 @@ func (s *WorktreeSuite) TestStatusEmptyDirty(c *C) {
c.Assert(status, HasLen, 1)
}
+func (s *WorktreeSuite) TestStatusUnmodified(c *C) {
+ fs := memfs.New()
+ w := &Worktree{
+ r: s.Repository,
+ Filesystem: fs,
+ }
+
+ err := w.Checkout(&CheckoutOptions{Force: true})
+ c.Assert(err, IsNil)
+
+ status, err := w.StatusWithOptions(StatusOptions{Strategy: Preload})
+ c.Assert(err, IsNil)
+ c.Assert(status.IsClean(), Equals, true)
+ c.Assert(status.IsUntracked("LICENSE"), Equals, false)
+
+ c.Assert(status.File("LICENSE").Staging, Equals, Unmodified)
+ c.Assert(status.File("LICENSE").Worktree, Equals, Unmodified)
+
+ status, err = w.StatusWithOptions(StatusOptions{Strategy: Empty})
+ c.Assert(err, IsNil)
+ c.Assert(status.IsClean(), Equals, true)
+ c.Assert(status.IsUntracked("LICENSE"), Equals, false)
+
+ c.Assert(status.File("LICENSE").Staging, Equals, Untracked)
+ c.Assert(status.File("LICENSE").Worktree, Equals, Untracked)
+}
+
func (s *WorktreeSuite) TestReset(c *C) {
fs := memfs.New()
w := &Worktree{
@@ -1097,6 +1241,111 @@ func (s *WorktreeSuite) TestResetHard(c *C) {
c.Assert(branch.Hash(), Equals, commit)
}
+func (s *WorktreeSuite) TestResetHardSubFolders(c *C) {
+ fs := memfs.New()
+ w := &Worktree{
+ r: s.Repository,
+ Filesystem: fs,
+ }
+
+ err := w.Checkout(&CheckoutOptions{})
+ c.Assert(err, IsNil)
+
+ err = fs.MkdirAll("dir", os.ModePerm)
+ c.Assert(err, IsNil)
+ tf, err := fs.Create("./dir/testfile.txt")
+ c.Assert(err, IsNil)
+ _, err = tf.Write([]byte("testfile content"))
+ c.Assert(err, IsNil)
+ err = tf.Close()
+ c.Assert(err, IsNil)
+ _, err = w.Add("dir/testfile.txt")
+ c.Assert(err, IsNil)
+ _, err = w.Commit("testcommit", &CommitOptions{Author: &object.Signature{Name: "name", Email: "email"}})
+ c.Assert(err, IsNil)
+
+ err = fs.Remove("dir/testfile.txt")
+ c.Assert(err, IsNil)
+
+ status, err := w.Status()
+ c.Assert(err, IsNil)
+ c.Assert(status.IsClean(), Equals, false)
+
+ err = w.Reset(&ResetOptions{Files: []string{"dir/testfile.txt"}, Mode: HardReset})
+ c.Assert(err, IsNil)
+
+ status, err = w.Status()
+ c.Assert(err, IsNil)
+ c.Assert(status.IsClean(), Equals, true)
+}
+
+func (s *WorktreeSuite) TestResetHardWithGitIgnore(c *C) {
+ fs := memfs.New()
+ w := &Worktree{
+ r: s.Repository,
+ Filesystem: fs,
+ }
+
+ err := w.Checkout(&CheckoutOptions{})
+ c.Assert(err, IsNil)
+
+ tf, err := fs.Create("newTestFile.txt")
+ c.Assert(err, IsNil)
+ _, err = tf.Write([]byte("testfile content"))
+ c.Assert(err, IsNil)
+ err = tf.Close()
+ c.Assert(err, IsNil)
+ _, err = w.Add("newTestFile.txt")
+ c.Assert(err, IsNil)
+ _, err = w.Commit("testcommit", &CommitOptions{Author: &object.Signature{Name: "name", Email: "email"}})
+ c.Assert(err, IsNil)
+
+ err = fs.Remove("newTestFile.txt")
+ c.Assert(err, IsNil)
+ f, err := fs.Create(".gitignore")
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("foo\n"))
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("newTestFile.txt\n"))
+ c.Assert(err, IsNil)
+ err = f.Close()
+ c.Assert(err, IsNil)
+
+ status, err := w.Status()
+ c.Assert(err, IsNil)
+ c.Assert(status.IsClean(), Equals, false)
+
+ err = w.Reset(&ResetOptions{Mode: HardReset})
+ c.Assert(err, IsNil)
+
+ status, err = w.Status()
+ c.Assert(err, IsNil)
+ c.Assert(status.IsClean(), Equals, true)
+}
+
+func (s *WorktreeSuite) TestResetSparsely(c *C) {
+ fs := memfs.New()
+ w := &Worktree{
+ r: s.Repository,
+ Filesystem: fs,
+ }
+
+ sparseResetDirs := []string{"php"}
+
+ err := w.ResetSparsely(&ResetOptions{Mode: HardReset}, sparseResetDirs)
+ c.Assert(err, IsNil)
+
+ files, err := fs.ReadDir("/")
+ c.Assert(err, IsNil)
+ c.Assert(files, HasLen, 1)
+ c.Assert(files[0].Name(), Equals, "php")
+
+ files, err = fs.ReadDir("/php")
+ c.Assert(err, IsNil)
+ c.Assert(files, HasLen, 1)
+ c.Assert(files[0].Name(), Equals, "crappy.php")
+}
+
func (s *WorktreeSuite) TestStatusAfterCheckout(c *C) {
fs := memfs.New()
w := &Worktree{
@@ -1114,8 +1363,7 @@ func (s *WorktreeSuite) TestStatusAfterCheckout(c *C) {
}
func (s *WorktreeSuite) TestStatusModified(c *C) {
- fs, clean := s.TemporalFilesystem()
- defer clean()
+ fs := s.TemporalFilesystem(c)
w := &Worktree{
r: s.Repository,
@@ -1206,8 +1454,7 @@ func (s *WorktreeSuite) TestStatusUntracked(c *C) {
}
func (s *WorktreeSuite) TestStatusDeleted(c *C) {
- fs, clean := s.TemporalFilesystem()
- defer clean()
+ fs := s.TemporalFilesystem(c)
w := &Worktree{
r: s.Repository,
@@ -1579,8 +1826,7 @@ func (s *WorktreeSuite) TestAddRemovedInDirectoryDot(c *C) {
}
func (s *WorktreeSuite) TestAddSymlink(c *C) {
- dir, clean := s.TemporalDir()
- defer clean()
+ dir := c.MkDir()
r, err := PlainInit(dir, false)
c.Assert(err, IsNil)
@@ -1761,6 +2007,66 @@ func (s *WorktreeSuite) TestAddGlob(c *C) {
c.Assert(file.Worktree, Equals, Unmodified)
}
+func (s *WorktreeSuite) TestAddFilenameStartingWithDot(c *C) {
+ fs := memfs.New()
+ w := &Worktree{
+ r: s.Repository,
+ Filesystem: fs,
+ }
+
+ err := w.Checkout(&CheckoutOptions{Force: true})
+ c.Assert(err, IsNil)
+
+ idx, err := w.r.Storer.Index()
+ c.Assert(err, IsNil)
+ c.Assert(idx.Entries, HasLen, 9)
+
+ err = util.WriteFile(w.Filesystem, "qux", []byte("QUX"), 0o755)
+ c.Assert(err, IsNil)
+ err = util.WriteFile(w.Filesystem, "baz", []byte("BAZ"), 0o755)
+ c.Assert(err, IsNil)
+ err = util.WriteFile(w.Filesystem, "foo/bar/baz", []byte("BAZ"), 0o755)
+ c.Assert(err, IsNil)
+
+ _, err = w.Add("./qux")
+ c.Assert(err, IsNil)
+
+ _, err = w.Add("./baz")
+ c.Assert(err, IsNil)
+
+ _, err = w.Add("foo/bar/../bar/./baz")
+ c.Assert(err, IsNil)
+
+ idx, err = w.r.Storer.Index()
+ c.Assert(err, IsNil)
+ c.Assert(idx.Entries, HasLen, 12)
+
+ e, err := idx.Entry("qux")
+ c.Assert(err, IsNil)
+ c.Assert(e.Mode, Equals, filemode.Executable)
+
+ e, err = idx.Entry("baz")
+ c.Assert(err, IsNil)
+ c.Assert(e.Mode, Equals, filemode.Executable)
+
+ status, err := w.Status()
+ c.Assert(err, IsNil)
+ c.Assert(status, HasLen, 3)
+
+ file := status.File("qux")
+ c.Assert(file.Staging, Equals, Added)
+ c.Assert(file.Worktree, Equals, Unmodified)
+
+ file = status.File("baz")
+ c.Assert(file.Staging, Equals, Added)
+ c.Assert(file.Worktree, Equals, Unmodified)
+
+ file = status.File("foo/bar/baz")
+ c.Assert(file.Staging, Equals, Added)
+ c.Assert(file.Worktree, Equals, Unmodified)
+
+}
+
func (s *WorktreeSuite) TestAddGlobErrorNoMatches(c *C) {
r, _ := Init(memory.NewStorage(), memfs.New())
w, _ := r.Worktree()
@@ -1769,6 +2075,166 @@ func (s *WorktreeSuite) TestAddGlobErrorNoMatches(c *C) {
c.Assert(err, Equals, ErrGlobNoMatches)
}
+func (s *WorktreeSuite) TestAddSkipStatusAddedPath(c *C) {
+ fs := memfs.New()
+ w := &Worktree{
+ r: s.Repository,
+ Filesystem: fs,
+ }
+
+ err := w.Checkout(&CheckoutOptions{Force: true})
+ c.Assert(err, IsNil)
+
+ idx, err := w.r.Storer.Index()
+ c.Assert(err, IsNil)
+ c.Assert(idx.Entries, HasLen, 9)
+
+ err = util.WriteFile(w.Filesystem, "file1", []byte("file1"), 0644)
+ c.Assert(err, IsNil)
+
+ err = w.AddWithOptions(&AddOptions{Path: "file1", SkipStatus: true})
+ c.Assert(err, IsNil)
+
+ idx, err = w.r.Storer.Index()
+ c.Assert(err, IsNil)
+ c.Assert(idx.Entries, HasLen, 10)
+
+ e, err := idx.Entry("file1")
+ c.Assert(err, IsNil)
+ c.Assert(e.Mode, Equals, filemode.Regular)
+
+ status, err := w.Status()
+ c.Assert(err, IsNil)
+ c.Assert(status, HasLen, 1)
+
+ file := status.File("file1")
+ c.Assert(file.Staging, Equals, Added)
+ c.Assert(file.Worktree, Equals, Unmodified)
+}
+
+func (s *WorktreeSuite) TestAddSkipStatusModifiedPath(c *C) {
+ fs := memfs.New()
+ w := &Worktree{
+ r: s.Repository,
+ Filesystem: fs,
+ }
+
+ err := w.Checkout(&CheckoutOptions{Force: true})
+ c.Assert(err, IsNil)
+
+ idx, err := w.r.Storer.Index()
+ c.Assert(err, IsNil)
+ c.Assert(idx.Entries, HasLen, 9)
+
+ err = util.WriteFile(w.Filesystem, "LICENSE", []byte("file1"), 0644)
+ c.Assert(err, IsNil)
+
+ err = w.AddWithOptions(&AddOptions{Path: "LICENSE", SkipStatus: true})
+ c.Assert(err, IsNil)
+
+ idx, err = w.r.Storer.Index()
+ c.Assert(err, IsNil)
+ c.Assert(idx.Entries, HasLen, 9)
+
+ e, err := idx.Entry("LICENSE")
+ c.Assert(err, IsNil)
+ c.Assert(e.Mode, Equals, filemode.Regular)
+
+ status, err := w.Status()
+ c.Assert(err, IsNil)
+ c.Assert(status, HasLen, 1)
+
+ file := status.File("LICENSE")
+ c.Assert(file.Staging, Equals, Modified)
+ c.Assert(file.Worktree, Equals, Unmodified)
+}
+
+func (s *WorktreeSuite) TestAddSkipStatusNonModifiedPath(c *C) {
+ fs := memfs.New()
+ w := &Worktree{
+ r: s.Repository,
+ Filesystem: fs,
+ }
+
+ err := w.Checkout(&CheckoutOptions{Force: true})
+ c.Assert(err, IsNil)
+
+ idx, err := w.r.Storer.Index()
+ c.Assert(err, IsNil)
+ c.Assert(idx.Entries, HasLen, 9)
+
+ err = w.AddWithOptions(&AddOptions{Path: "LICENSE", SkipStatus: true})
+ c.Assert(err, IsNil)
+
+ idx, err = w.r.Storer.Index()
+ c.Assert(err, IsNil)
+ c.Assert(idx.Entries, HasLen, 9)
+
+ e, err := idx.Entry("LICENSE")
+ c.Assert(err, IsNil)
+ c.Assert(e.Mode, Equals, filemode.Regular)
+
+ status, err := w.Status()
+ c.Assert(err, IsNil)
+ c.Assert(status, HasLen, 0)
+
+ file := status.File("LICENSE")
+ c.Assert(file.Staging, Equals, Untracked)
+ c.Assert(file.Worktree, Equals, Untracked)
+}
+
+func (s *WorktreeSuite) TestAddSkipStatusWithIgnoredPath(c *C) {
+ fs := memfs.New()
+ w := &Worktree{
+ r: s.Repository,
+ Filesystem: fs,
+ }
+
+ err := w.Checkout(&CheckoutOptions{Force: true})
+ c.Assert(err, IsNil)
+
+ idx, err := w.r.Storer.Index()
+ c.Assert(err, IsNil)
+ c.Assert(idx.Entries, HasLen, 9)
+
+ err = util.WriteFile(fs, ".gitignore", []byte("fileToIgnore\n"), 0755)
+ c.Assert(err, IsNil)
+ _, err = w.Add(".gitignore")
+ c.Assert(err, IsNil)
+ _, err = w.Commit("Added .gitignore", defaultTestCommitOptions())
+ c.Assert(err, IsNil)
+
+ err = util.WriteFile(fs, "fileToIgnore", []byte("file to ignore"), 0644)
+ c.Assert(err, IsNil)
+
+ status, err := w.Status()
+ c.Assert(err, IsNil)
+ c.Assert(status, HasLen, 0)
+
+ file := status.File("fileToIgnore")
+ c.Assert(file.Staging, Equals, Untracked)
+ c.Assert(file.Worktree, Equals, Untracked)
+
+ err = w.AddWithOptions(&AddOptions{Path: "fileToIgnore", SkipStatus: true})
+ c.Assert(err, IsNil)
+
+ idx, err = w.r.Storer.Index()
+ c.Assert(err, IsNil)
+ c.Assert(idx.Entries, HasLen, 10)
+
+ e, err := idx.Entry("fileToIgnore")
+ c.Assert(err, IsNil)
+ c.Assert(e.Mode, Equals, filemode.Regular)
+
+ status, err = w.Status()
+ c.Assert(err, IsNil)
+ c.Assert(status, HasLen, 1)
+
+ file = status.File("fileToIgnore")
+ c.Assert(file.Staging, Equals, Added)
+ c.Assert(file.Worktree, Equals, Unmodified)
+}
+
func (s *WorktreeSuite) TestRemove(c *C) {
fs := memfs.New()
w := &Worktree{
@@ -2076,34 +2542,40 @@ func (s *WorktreeSuite) TestCleanBare(c *C) {
c.Assert(err, IsNil)
}
-func (s *WorktreeSuite) TestAlternatesRepo(c *C) {
+func TestAlternatesRepo(t *testing.T) {
fs := fixtures.ByTag("alternates").One().Worktree()
// Open 1st repo.
rep1fs, err := fs.Chroot("rep1")
- c.Assert(err, IsNil)
+ assert.NoError(t, err)
rep1, err := PlainOpen(rep1fs.Root())
- c.Assert(err, IsNil)
+ assert.NoError(t, err)
// Open 2nd repo.
rep2fs, err := fs.Chroot("rep2")
- c.Assert(err, IsNil)
- rep2, err := PlainOpen(rep2fs.Root())
- c.Assert(err, IsNil)
+ assert.NoError(t, err)
+ d, _ := rep2fs.Chroot(GitDirName)
+ storer := filesystem.NewStorageWithOptions(d,
+ cache.NewObjectLRUDefault(), filesystem.Options{
+ AlternatesFS: fs,
+ })
+ rep2, err := Open(storer, rep2fs)
+
+ assert.NoError(t, err)
// Get the HEAD commit from the main repo.
h, err := rep1.Head()
- c.Assert(err, IsNil)
+ assert.NoError(t, err)
commit1, err := rep1.CommitObject(h.Hash())
- c.Assert(err, IsNil)
+ assert.NoError(t, err)
// Get the HEAD commit from the shared repo.
h, err = rep2.Head()
- c.Assert(err, IsNil)
+ assert.NoError(t, err)
commit2, err := rep2.CommitObject(h.Hash())
- c.Assert(err, IsNil)
+ assert.NoError(t, err)
- c.Assert(commit1.String(), Equals, commit2.String())
+ assert.Equal(t, commit1.String(), commit2.String())
}
func (s *WorktreeSuite) TestGrep(c *C) {
@@ -2294,8 +2766,7 @@ func (s *WorktreeSuite) TestGrep(c *C) {
path := fixtures.Basic().ByTag("worktree").One().Worktree().Root()
- dir, clean := s.TemporalDir()
- defer clean()
+ dir := c.MkDir()
server, err := PlainClone(dir, false, &CloneOptions{
URL: path,
@@ -2378,8 +2849,7 @@ func (s *WorktreeSuite) TestGrepBare(c *C) {
path := fixtures.Basic().ByTag("worktree").One().Worktree().Root()
- dir, clean := s.TemporalDir()
- defer clean()
+ dir := c.MkDir()
r, err := PlainClone(dir, true, &CloneOptions{
URL: path,
@@ -2427,8 +2897,7 @@ func (s *WorktreeSuite) TestGrepBare(c *C) {
}
func (s *WorktreeSuite) TestResetLingeringDirectories(c *C) {
- dir, clean := s.TemporalDir()
- defer clean()
+ dir := c.MkDir()
commitOpts := &CommitOptions{Author: &object.Signature{
Name: "foo",
@@ -2479,8 +2948,7 @@ func (s *WorktreeSuite) TestResetLingeringDirectories(c *C) {
func (s *WorktreeSuite) TestAddAndCommit(c *C) {
expectedFiles := 2
- dir, clean := s.TemporalDir()
- defer clean()
+ dir := c.MkDir()
repo, err := PlainInit(dir, false)
c.Assert(err, IsNil)
@@ -2522,8 +2990,7 @@ func (s *WorktreeSuite) TestAddAndCommit(c *C) {
}
func (s *WorktreeSuite) TestAddAndCommitEmpty(c *C) {
- dir, clean := s.TemporalDir()
- defer clean()
+ dir := c.MkDir()
repo, err := PlainInit(dir, false)
c.Assert(err, IsNil)
@@ -2616,3 +3083,310 @@ func (s *WorktreeSuite) TestLinkedWorktree(c *C) {
c.Assert(err, Equals, ErrRepositoryIncomplete)
}
}
+
+func TestValidPath(t *testing.T) {
+ type testcase struct {
+ path string
+ wantErr bool
+ }
+
+ tests := []testcase{
+ {".git", true},
+ {".git/b", true},
+ {".git\\b", true},
+ {"git~1", true},
+ {"a/../b", true},
+ {"a\\..\\b", true},
+ {"/", true},
+ {"", true},
+ {".gitmodules", false},
+ {".gitignore", false},
+ {"a..b", false},
+ {".", false},
+ {"a/.git", false},
+ {"a\\.git", false},
+ {"a/.git/b", false},
+ {"a\\.git\\b", false},
+ }
+
+ if runtime.GOOS == "windows" {
+ tests = append(tests, []testcase{
+ {"\\\\a\\b", true},
+ {"C:\\a\\b", true},
+ {".git . . .", true},
+ {".git . . ", true},
+ {".git ", true},
+ {".git.", true},
+ {".git::$INDEX_ALLOCATION", true},
+ }...)
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.path, func(t *testing.T) {
+ err := validPath(tc.path)
+ if tc.wantErr {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ }
+ })
+ }
+}
+
+func TestWindowsValidPath(t *testing.T) {
+ tests := []struct {
+ path string
+ want bool
+ }{
+ {".git", false},
+ {".git . . .", false},
+ {".git ", false},
+ {".git ", false},
+ {".git . .", false},
+ {".git . .", false},
+ {".git::$INDEX_ALLOCATION", false},
+ {".git:", false},
+ {"a", true},
+ {"a\\b", true},
+ {"a/b", true},
+ {".gitm", true},
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.path, func(t *testing.T) {
+ got := windowsValidPath(tc.path)
+ assert.Equal(t, tc.want, got)
+ })
+ }
+}
+
+var statusCodeNames = map[StatusCode]string{
+ Unmodified: "Unmodified",
+ Untracked: "Untracked",
+ Modified: "Modified",
+ Added: "Added",
+ Deleted: "Deleted",
+ Renamed: "Renamed",
+ Copied: "Copied",
+ UpdatedButUnmerged: "UpdatedButUnmerged",
+}
+
+func setupForRestore(c *C, s *WorktreeSuite) (fs billy.Filesystem, w *Worktree, names []string) {
+ fs = memfs.New()
+ w = &Worktree{
+ r: s.Repository,
+ Filesystem: fs,
+ }
+
+ err := w.Checkout(&CheckoutOptions{})
+ c.Assert(err, IsNil)
+
+ names = []string{"foo", "CHANGELOG", "LICENSE", "binary.jpg"}
+ verifyStatus(c, "Checkout", w, names, []FileStatus{
+ {Worktree: Untracked, Staging: Untracked},
+ {Worktree: Untracked, Staging: Untracked},
+ {Worktree: Untracked, Staging: Untracked},
+ {Worktree: Untracked, Staging: Untracked},
+ })
+
+ // Touch of bunch of files including create a new file and delete an exsiting file
+ for _, name := range names {
+ err = util.WriteFile(fs, name, []byte("Foo Bar"), 0755)
+ c.Assert(err, IsNil)
+ }
+ err = util.RemoveAll(fs, names[3])
+ c.Assert(err, IsNil)
+
+ // Confirm the status after doing the edits without staging anything
+ verifyStatus(c, "Edits", w, names, []FileStatus{
+ {Worktree: Untracked, Staging: Untracked},
+ {Worktree: Modified, Staging: Unmodified},
+ {Worktree: Modified, Staging: Unmodified},
+ {Worktree: Deleted, Staging: Unmodified},
+ })
+
+ // Stage all files and verify the updated status
+ for _, name := range names {
+ _, err = w.Add(name)
+ c.Assert(err, IsNil)
+ }
+ verifyStatus(c, "Staged", w, names, []FileStatus{
+ {Worktree: Unmodified, Staging: Added},
+ {Worktree: Unmodified, Staging: Modified},
+ {Worktree: Unmodified, Staging: Modified},
+ {Worktree: Unmodified, Staging: Deleted},
+ })
+
+ // Add secondary changes to a file to make sure we only restore the staged file
+ err = util.WriteFile(fs, names[1], []byte("Foo Bar:11"), 0755)
+ c.Assert(err, IsNil)
+ err = util.WriteFile(fs, names[2], []byte("Foo Bar:22"), 0755)
+ c.Assert(err, IsNil)
+
+ verifyStatus(c, "Secondary Edits", w, names, []FileStatus{
+ {Worktree: Unmodified, Staging: Added},
+ {Worktree: Modified, Staging: Modified},
+ {Worktree: Modified, Staging: Modified},
+ {Worktree: Unmodified, Staging: Deleted},
+ })
+
+ return
+}
+
+func verifyStatus(c *C, marker string, w *Worktree, files []string, statuses []FileStatus) {
+ c.Assert(len(files), Equals, len(statuses))
+
+ status, err := w.Status()
+ c.Assert(err, IsNil)
+
+ for i, file := range files {
+ current := status.File(file)
+ expected := statuses[i]
+ c.Assert(current.Worktree, Equals, expected.Worktree, Commentf("%s - [%d] : %s Worktree %s != %s", marker, i, file, statusCodeNames[current.Worktree], statusCodeNames[expected.Worktree]))
+ c.Assert(current.Staging, Equals, expected.Staging, Commentf("%s - [%d] : %s Staging %s != %s", marker, i, file, statusCodeNames[current.Staging], statusCodeNames[expected.Staging]))
+ }
+}
+
+func (s *WorktreeSuite) TestRestoreStaged(c *C) {
+ fs, w, names := setupForRestore(c, s)
+
+ // Attempt without files should throw an error like the git restore --staged
+ opts := RestoreOptions{Staged: true}
+ err := w.Restore(&opts)
+ c.Assert(err, Equals, ErrNoRestorePaths)
+
+ // Restore Staged files in 2 groups and confirm status
+ opts.Files = []string{names[0], "./" + names[1]}
+ err = w.Restore(&opts)
+ c.Assert(err, IsNil)
+ verifyStatus(c, "Restored First", w, names, []FileStatus{
+ {Worktree: Untracked, Staging: Untracked},
+ {Worktree: Modified, Staging: Unmodified},
+ {Worktree: Modified, Staging: Modified},
+ {Worktree: Unmodified, Staging: Deleted},
+ })
+
+ // Make sure the restore didn't overwrite our secondary changes
+ contents, err := util.ReadFile(fs, names[1])
+ c.Assert(err, IsNil)
+ c.Assert(string(contents), Equals, "Foo Bar:11")
+
+ opts.Files = []string{"./" + names[2], names[3]}
+ err = w.Restore(&opts)
+ c.Assert(err, IsNil)
+ verifyStatus(c, "Restored Second", w, names, []FileStatus{
+ {Worktree: Untracked, Staging: Untracked},
+ {Worktree: Modified, Staging: Unmodified},
+ {Worktree: Modified, Staging: Unmodified},
+ {Worktree: Deleted, Staging: Unmodified},
+ })
+
+ // Make sure the restore didn't overwrite our secondary changes
+ contents, err = util.ReadFile(fs, names[2])
+ c.Assert(err, IsNil)
+ c.Assert(string(contents), Equals, "Foo Bar:22")
+}
+
+func (s *WorktreeSuite) TestRestoreWorktree(c *C) {
+ _, w, names := setupForRestore(c, s)
+
+ // Attempt without files should throw an error like the git restore
+ opts := RestoreOptions{}
+ err := w.Restore(&opts)
+ c.Assert(err, Equals, ErrNoRestorePaths)
+
+ opts.Files = []string{names[0], names[1]}
+ err = w.Restore(&opts)
+ c.Assert(err, Equals, ErrRestoreWorktreeOnlyNotSupported)
+}
+
+func (s *WorktreeSuite) TestRestoreBoth(c *C) {
+ _, w, names := setupForRestore(c, s)
+
+ // Attempt without files should throw an error like the git restore --staged --worktree
+ opts := RestoreOptions{Staged: true, Worktree: true}
+ err := w.Restore(&opts)
+ c.Assert(err, Equals, ErrNoRestorePaths)
+
+ // Restore Staged files in 2 groups and confirm status
+ opts.Files = []string{names[0], names[1]}
+ err = w.Restore(&opts)
+ c.Assert(err, IsNil)
+ verifyStatus(c, "Restored First", w, names, []FileStatus{
+ {Worktree: Untracked, Staging: Untracked},
+ {Worktree: Untracked, Staging: Untracked},
+ {Worktree: Modified, Staging: Modified},
+ {Worktree: Unmodified, Staging: Deleted},
+ })
+
+ opts.Files = []string{names[2], names[3]}
+ err = w.Restore(&opts)
+ c.Assert(err, IsNil)
+ verifyStatus(c, "Restored Second", w, names, []FileStatus{
+ {Worktree: Untracked, Staging: Untracked},
+ {Worktree: Untracked, Staging: Untracked},
+ {Worktree: Untracked, Staging: Untracked},
+ {Worktree: Untracked, Staging: Untracked},
+ })
+}
+
+func TestFilePermissions(t *testing.T) {
+
+ // Initialize an in memory repository
+ remoteUrl := t.TempDir()
+
+ inMemoryFs := memfs.New()
+ remoteFs := osfs.New(remoteUrl)
+ remoteStorage := filesystem.NewStorage(remoteFs, cache.NewObjectLRUDefault())
+
+ remoteRepository, err := Init(remoteStorage, inMemoryFs)
+ assert.NoError(t, err)
+
+ err = util.WriteFile(inMemoryFs, "fileWithExecuteBit", []byte("Initial data"), 0755)
+ assert.NoError(t, err)
+
+ err = util.WriteFile(inMemoryFs, "regularFile", []byte("Initial data"), 0644)
+ assert.NoError(t, err)
+
+ remoteWorktree, err := remoteRepository.Worktree()
+ assert.NoError(t, err)
+
+ _, err = remoteWorktree.Add("fileWithExecuteBit")
+ assert.NoError(t, err)
+
+ _, err = remoteWorktree.Add("regularFile")
+ assert.NoError(t, err)
+
+ _, err = remoteWorktree.Commit("my commit", &CommitOptions{})
+ assert.NoError(t, err)
+
+ worktreePath := t.TempDir()
+
+ localRepo, err := PlainClone(worktreePath, false, &CloneOptions{URL: remoteUrl})
+ assert.NoError(t, err)
+
+ localWorktree, err := localRepo.Worktree()
+ assert.NoError(t, err)
+
+ idx, err := localWorktree.r.Storer.Index()
+ assert.NoError(t, err)
+
+ expectedEntries := []index.Entry{
+ {
+ Name: "fileWithExecuteBit",
+ Mode: filemode.Executable,
+ },
+ {
+ Name: "regularFile",
+ Mode: filemode.Regular,
+ },
+ }
+
+ assert.Len(t, idx.Entries, len(expectedEntries))
+
+ for i, expectedEntry := range expectedEntries {
+ assert.Equal(t, expectedEntry.Name, idx.Entries[i].Name)
+ assert.Equal(t, expectedEntry.Mode, idx.Entries[i].Mode)
+ }
+
+}
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy