\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 dd9b2439c..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 {
@@ -354,6 +370,8 @@ func (w *Worktree) doAdd(path string, ignorePattern []gitignore.Pattern, skipSta
}
}
+ path = filepath.Clean(path)
+
if err != nil || !fi.IsDir() {
added, h, err = w.doAddFile(idx, s, path, ignorePattern)
} else {
@@ -488,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
@@ -503,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
@@ -543,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 668c30a70..a3dbcfeb3 100644
--- a/worktree_test.go
+++ b/worktree_test.go
@@ -4,7 +4,6 @@ import (
"bytes"
"context"
"errors"
- "fmt"
"io"
"os"
"path/filepath"
@@ -26,6 +25,7 @@ import (
"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"
@@ -33,9 +33,11 @@ import (
. "gopkg.in/check.v1"
)
-var (
- defaultTestCommitOptions = &CommitOptions{Author: &object.Signature{Name: "testuser", Email: "testemail"}}
-)
+func defaultTestCommitOptions() *CommitOptions {
+ return &CommitOptions{
+ Author: &object.Signature{Name: "testuser", Email: "testemail"},
+ }
+}
type WorktreeSuite struct {
BaseSuite
@@ -68,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()
@@ -78,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,
@@ -88,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)
@@ -105,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()
@@ -115,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,
@@ -125,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)
@@ -225,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{
@@ -286,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"),
})
@@ -294,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)
@@ -318,8 +320,7 @@ func (s *WorktreeSuite) TestPullDepth(c *C) {
}
func (s *WorktreeSuite) TestPullAfterShallowClone(c *C) {
- tempDir, clean := s.TemporalDir()
- defer clean()
+ tempDir := c.MkDir()
remoteURL := filepath.Join(tempDir, "remote")
repoDir := filepath.Join(tempDir, "repo")
@@ -447,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)
@@ -478,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)
@@ -511,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()
@@ -704,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,
@@ -1002,14 +1001,14 @@ func (s *WorktreeSuite) TestStatusCheckedInBeforeIgnored(c *C) {
_, err = w.Add("fileToIgnore")
c.Assert(err, IsNil)
- _, err = w.Commit("Added file that will be ignored later", defaultTestCommitOptions)
+ _, 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", defaultTestCommitOptions)
+ _, err = w.Commit("Added .gitignore", defaultTestCommitOptions())
c.Assert(err, IsNil)
status, err := w.Status()
c.Assert(err, IsNil)
@@ -1052,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{
@@ -1215,6 +1241,44 @@ 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{
@@ -1241,6 +1305,7 @@ func (s *WorktreeSuite) TestResetHardWithGitIgnore(c *C) {
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()
@@ -1258,6 +1323,29 @@ func (s *WorktreeSuite) TestResetHardWithGitIgnore(c *C) {
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{
@@ -1275,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,
@@ -1367,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,
@@ -1740,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)
@@ -1922,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()
@@ -2056,7 +2201,7 @@ func (s *WorktreeSuite) TestAddSkipStatusWithIgnoredPath(c *C) {
c.Assert(err, IsNil)
_, err = w.Add(".gitignore")
c.Assert(err, IsNil)
- _, err = w.Commit("Added .gitignore", defaultTestCommitOptions)
+ _, err = w.Commit("Added .gitignore", defaultTestCommitOptions())
c.Assert(err, IsNil)
err = util.WriteFile(fs, "fileToIgnore", []byte("file to ignore"), 0644)
@@ -2621,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,
@@ -2705,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,
@@ -2754,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",
@@ -2806,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)
@@ -2849,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)
@@ -2982,7 +3122,7 @@ func TestValidPath(t *testing.T) {
}
for _, tc := range tests {
- t.Run(fmt.Sprintf("%s", tc.path), func(t *testing.T) {
+ t.Run(tc.path, func(t *testing.T) {
err := validPath(tc.path)
if tc.wantErr {
assert.Error(t, err)
@@ -3013,9 +3153,240 @@ func TestWindowsValidPath(t *testing.T) {
}
for _, tc := range tests {
- t.Run(fmt.Sprintf("%s", tc.path), func(t *testing.T) {
+ 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