Skip to content

Commit

Permalink
Remove empty dirs when cleaning with Dir opt.
Browse files Browse the repository at this point in the history
Signed-off-by: kuba-- <[email protected]>
  • Loading branch information
kuba-- committed Aug 29, 2018
1 parent 005d5dc commit 0167dab
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 32 deletions.
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
58 changes: 44 additions & 14 deletions worktree.go
Original file line number Diff line number Diff line change
Expand Up @@ -713,29 +713,56 @@ 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 {
// check if file is 'Untracked'
s, ok := (status)[filepath.ToSlash(path)]
if ok && s.Worktree == Untracked {
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 +908,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

0 comments on commit 0167dab

Please sign in to comment.