Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions status.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package git

import "fmt"
import "bytes"
import (
"bytes"
"fmt"
"path/filepath"
)

// Status represents the current status of a Worktree.
// The key of the map is the path of the file.
Expand All @@ -17,6 +20,12 @@ func (s Status) File(path string) *FileStatus {
return s[path]
}

// IsUntracked checks if file for given path is 'Untracked'
func (s Status) IsUntracked(path string) bool {
stat, ok := (s)[filepath.ToSlash(path)]
return ok && stat.Worktree == Untracked
}

// IsClean returns true if all the files aren't in Unmodified status.
func (s Status) IsClean() bool {
for _, status := range s {
Expand Down
54 changes: 36 additions & 18 deletions storage/filesystem/dotgit/dotgit.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ var (
// type is not zero-value-safe, use the New function to initialize it.
type DotGit struct {
fs billy.Filesystem

// incoming object directory information
incomingChecked bool
incomingDirName string
}

// New returns a DotGit value ready to be used. The path argument must
Expand Down Expand Up @@ -279,33 +283,47 @@ func (d *DotGit) objectPath(h plumbing.Hash) string {
return d.fs.Join(objectsPath, hash[0:2], hash[2:40])
}

//incomingObjectPath is intended to add support for a git pre-recieve hook to be written
//it adds support for go-git to find objects in an "incoming" directory, so that the library
//can be used to write a pre-recieve hook that deals with the incoming objects.
//More on git hooks found here : https://git-scm.com/docs/githooks
//More on 'quarantine'/incoming directory here : https://git-scm.com/docs/git-receive-pack
// incomingObjectPath is intended to add support for a git pre-receive hook
// to be written it adds support for go-git to find objects in an "incoming"
// directory, so that the library can be used to write a pre-receive hook
// that deals with the incoming objects.
//
// More on git hooks found here : https://git-scm.com/docs/githooks
// More on 'quarantine'/incoming directory here:
// https://git-scm.com/docs/git-receive-pack
func (d *DotGit) incomingObjectPath(h plumbing.Hash) string {
hString := h.String()
directoryContents, err := d.fs.ReadDir(objectsPath)
if err != nil {

if d.incomingDirName == "" {
return d.fs.Join(objectsPath, hString[0:2], hString[2:40])
}
var incomingDirName string
for _, file := range directoryContents {
if strings.Split(file.Name(), "-")[0] == "incoming" && file.IsDir() {
incomingDirName = file.Name()

return d.fs.Join(objectsPath, d.incomingDirName, hString[0:2], hString[2:40])
}

// hasIncomingObjects searches for an incoming directory and keeps its name
// so it doesn't have to be found each time an object is accessed.
func (d *DotGit) hasIncomingObjects() bool {
if !d.incomingChecked {
directoryContents, err := d.fs.ReadDir(objectsPath)
if err == nil {
for _, file := range directoryContents {
if strings.HasPrefix(file.Name(), "incoming-") && file.IsDir() {
d.incomingDirName = file.Name()
}
}
}

d.incomingChecked = true
}
if incomingDirName == "" {
return d.fs.Join(objectsPath, hString[0:2], hString[2:40])
}
return d.fs.Join(objectsPath, incomingDirName, hString[0:2], hString[2:40])

return d.incomingDirName != ""
}

// Object returns a fs.File pointing the object file, if exists
func (d *DotGit) Object(h plumbing.Hash) (billy.File, error) {
obj1, err1 := d.fs.Open(d.objectPath(h))
if os.IsNotExist(err1) {
if os.IsNotExist(err1) && d.hasIncomingObjects() {
obj2, err2 := d.fs.Open(d.incomingObjectPath(h))
if err2 != nil {
return obj1, err1
Expand All @@ -318,7 +336,7 @@ func (d *DotGit) Object(h plumbing.Hash) (billy.File, error) {
// ObjectStat returns a os.FileInfo pointing the object file, if exists
func (d *DotGit) ObjectStat(h plumbing.Hash) (os.FileInfo, error) {
obj1, err1 := d.fs.Stat(d.objectPath(h))
if os.IsNotExist(err1) {
if os.IsNotExist(err1) && d.hasIncomingObjects() {
obj2, err2 := d.fs.Stat(d.incomingObjectPath(h))
if err2 != nil {
return obj1, err1
Expand All @@ -331,7 +349,7 @@ func (d *DotGit) ObjectStat(h plumbing.Hash) (os.FileInfo, error) {
// ObjectDelete removes the object file, if exists
func (d *DotGit) ObjectDelete(h plumbing.Hash) error {
err1 := d.fs.Remove(d.objectPath(h))
if os.IsNotExist(err1) {
if os.IsNotExist(err1) && d.hasIncomingObjects() {
err2 := d.fs.Remove(d.incomingObjectPath(h))
if err2 != nil {
return err1
Expand Down
56 changes: 42 additions & 14 deletions worktree.go
Original file line number Diff line number Diff line change
Expand Up @@ -713,29 +713,54 @@ func (w *Worktree) readGitmodulesFile() (*config.Modules, error) {
}

// Clean the worktree by removing untracked files.
// An empty dir could be removed - this is what `git clean -f -d .` does.
func (w *Worktree) Clean(opts *CleanOptions) error {
s, err := w.Status()
if err != nil {
return err
}

// Check Worktree status to be Untracked, obtain absolute path and delete.
for relativePath, status := range s {
// Check if the path contains a directory and if Dir options is false,
// skip the path.
if relativePath != filepath.Base(relativePath) && !opts.Dir {
root := ""
files, err := w.Filesystem.ReadDir(root)
if err != nil {
return err
}
return w.doClean(s, opts, root, files)
}

func (w *Worktree) doClean(status Status, opts *CleanOptions, dir string, files []os.FileInfo) error {
for _, fi := range files {
if fi.Name() == ".git" {
continue
}

// Remove the file only if it's an untracked file.
if status.Worktree == Untracked {
absPath := filepath.Join(w.Filesystem.Root(), relativePath)
if err := os.Remove(absPath); err != nil {
// relative path under the root
path := filepath.Join(dir, fi.Name())
if fi.IsDir() {
if !opts.Dir {
continue
}

subfiles, err := w.Filesystem.ReadDir(path)
if err != nil {
return err
}
err = w.doClean(status, opts, path, subfiles)
if err != nil {
return err
}
} else {
if status.IsUntracked(path) {
if err := w.Filesystem.Remove(path); err != nil {
return err
}
}
}
}

if opts.Dir {
return doCleanDirectories(w.Filesystem, dir)
}
return nil
}

Expand Down Expand Up @@ -881,15 +906,18 @@ func rmFileAndDirIfEmpty(fs billy.Filesystem, name string) error {
return err
}

path := filepath.Dir(name)
files, err := fs.ReadDir(path)
dir := filepath.Dir(name)
return doCleanDirectories(fs, dir)
}

// doCleanDirectories removes empty subdirs (without files)
func doCleanDirectories(fs billy.Filesystem, dir string) error {
files, err := fs.ReadDir(dir)
if err != nil {
return err
}

if len(files) == 0 {
fs.Remove(path)
return fs.Remove(dir)
}

return nil
}
9 changes: 9 additions & 0 deletions worktree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,10 @@ func (s *WorktreeSuite) TestClean(c *C) {

c.Assert(len(status), Equals, 1)

fi, err := fs.Lstat("pkgA")
c.Assert(err, IsNil)
c.Assert(fi.IsDir(), Equals, true)

// Clean with Dir: true.
err = wt.Clean(&CleanOptions{Dir: true})
c.Assert(err, IsNil)
Expand All @@ -1599,6 +1603,11 @@ func (s *WorktreeSuite) TestClean(c *C) {
c.Assert(err, IsNil)

c.Assert(len(status), Equals, 0)

// An empty dir should be deleted, as well.
_, err = fs.Lstat("pkgA")
c.Assert(err, ErrorMatches, ".*(no such file or directory.*|.*file does not exist)*.")

}

func (s *WorktreeSuite) TestAlternatesRepo(c *C) {
Expand Down