···6161// type is not zero-value-safe, use the New function to initialize it.
6262type DotGit struct {
6363 fs billy.Filesystem
6464+6565+ // incoming object directory information
6666+ incomingChecked bool
6767+ incomingDirName string
6468}
65696670// New returns a DotGit value ready to be used. The path argument must
···279283 return d.fs.Join(objectsPath, hash[0:2], hash[2:40])
280284}
281285282282-//incomingObjectPath is intended to add support for a git pre-recieve hook to be written
283283-//it adds support for go-git to find objects in an "incoming" directory, so that the library
284284-//can be used to write a pre-recieve hook that deals with the incoming objects.
285285-//More on git hooks found here : https://git-scm.com/docs/githooks
286286-//More on 'quarantine'/incoming directory here : https://git-scm.com/docs/git-receive-pack
286286+// incomingObjectPath is intended to add support for a git pre-receive hook
287287+// to be written it adds support for go-git to find objects in an "incoming"
288288+// directory, so that the library can be used to write a pre-receive hook
289289+// that deals with the incoming objects.
290290+//
291291+// More on git hooks found here : https://git-scm.com/docs/githooks
292292+// More on 'quarantine'/incoming directory here:
293293+// https://git-scm.com/docs/git-receive-pack
287294func (d *DotGit) incomingObjectPath(h plumbing.Hash) string {
288295 hString := h.String()
289289- directoryContents, err := d.fs.ReadDir(objectsPath)
290290- if err != nil {
296296+297297+ if d.incomingDirName == "" {
291298 return d.fs.Join(objectsPath, hString[0:2], hString[2:40])
292299 }
293293- var incomingDirName string
294294- for _, file := range directoryContents {
295295- if strings.Split(file.Name(), "-")[0] == "incoming" && file.IsDir() {
296296- incomingDirName = file.Name()
300300+301301+ return d.fs.Join(objectsPath, d.incomingDirName, hString[0:2], hString[2:40])
302302+}
303303+304304+// hasIncomingObjects searches for an incoming directory and keeps its name
305305+// so it doesn't have to be found each time an object is accessed.
306306+func (d *DotGit) hasIncomingObjects() bool {
307307+ if !d.incomingChecked {
308308+ directoryContents, err := d.fs.ReadDir(objectsPath)
309309+ if err == nil {
310310+ for _, file := range directoryContents {
311311+ if strings.HasPrefix(file.Name(), "incoming-") && file.IsDir() {
312312+ d.incomingDirName = file.Name()
313313+ }
314314+ }
297315 }
316316+317317+ d.incomingChecked = true
298318 }
299299- if incomingDirName == "" {
300300- return d.fs.Join(objectsPath, hString[0:2], hString[2:40])
301301- }
302302- return d.fs.Join(objectsPath, incomingDirName, hString[0:2], hString[2:40])
319319+320320+ return d.incomingDirName != ""
303321}
304322305323// Object returns a fs.File pointing the object file, if exists
306324func (d *DotGit) Object(h plumbing.Hash) (billy.File, error) {
307325 obj1, err1 := d.fs.Open(d.objectPath(h))
308308- if os.IsNotExist(err1) {
326326+ if os.IsNotExist(err1) && d.hasIncomingObjects() {
309327 obj2, err2 := d.fs.Open(d.incomingObjectPath(h))
310328 if err2 != nil {
311329 return obj1, err1
···318336// ObjectStat returns a os.FileInfo pointing the object file, if exists
319337func (d *DotGit) ObjectStat(h plumbing.Hash) (os.FileInfo, error) {
320338 obj1, err1 := d.fs.Stat(d.objectPath(h))
321321- if os.IsNotExist(err1) {
339339+ if os.IsNotExist(err1) && d.hasIncomingObjects() {
322340 obj2, err2 := d.fs.Stat(d.incomingObjectPath(h))
323341 if err2 != nil {
324342 return obj1, err1
···331349// ObjectDelete removes the object file, if exists
332350func (d *DotGit) ObjectDelete(h plumbing.Hash) error {
333351 err1 := d.fs.Remove(d.objectPath(h))
334334- if os.IsNotExist(err1) {
352352+ if os.IsNotExist(err1) && d.hasIncomingObjects() {
335353 err2 := d.fs.Remove(d.incomingObjectPath(h))
336354 if err2 != nil {
337355 return err1
+44-14
worktree.go
···713713}
714714715715// Clean the worktree by removing untracked files.
716716+// An empty dir could be removed - this is what `git clean -f -d .` does.
716717func (w *Worktree) Clean(opts *CleanOptions) error {
717718 s, err := w.Status()
718719 if err != nil {
719720 return err
720721 }
721722722722- // Check Worktree status to be Untracked, obtain absolute path and delete.
723723- for relativePath, status := range s {
724724- // Check if the path contains a directory and if Dir options is false,
725725- // skip the path.
726726- if relativePath != filepath.Base(relativePath) && !opts.Dir {
723723+ root := ""
724724+ files, err := w.Filesystem.ReadDir(root)
725725+ if err != nil {
726726+ return err
727727+ }
728728+ return w.doClean(s, opts, root, files)
729729+}
730730+731731+func (w *Worktree) doClean(status Status, opts *CleanOptions, dir string, files []os.FileInfo) error {
732732+ for _, fi := range files {
733733+ if fi.Name() == ".git" {
727734 continue
728735 }
729736730730- // Remove the file only if it's an untracked file.
731731- if status.Worktree == Untracked {
732732- absPath := filepath.Join(w.Filesystem.Root(), relativePath)
733733- if err := os.Remove(absPath); err != nil {
737737+ // relative path under the root
738738+ path := filepath.Join(dir, fi.Name())
739739+ if fi.IsDir() {
740740+ if !opts.Dir {
741741+ continue
742742+ }
743743+744744+ subfiles, err := w.Filesystem.ReadDir(path)
745745+ if err != nil {
746746+ return err
747747+ }
748748+ err = w.doClean(status, opts, path, subfiles)
749749+ if err != nil {
734750 return err
735751 }
752752+ } else {
753753+ // check if file is 'Untracked'
754754+ s, ok := (status)[filepath.ToSlash(path)]
755755+ if ok && s.Worktree == Untracked {
756756+ if err := w.Filesystem.Remove(path); err != nil {
757757+ return err
758758+ }
759759+ }
736760 }
737761 }
738762763763+ if opts.Dir {
764764+ return doCleanDirectories(w.Filesystem, dir)
765765+ }
739766 return nil
740767}
741768···881908 return err
882909 }
883910884884- path := filepath.Dir(name)
885885- files, err := fs.ReadDir(path)
911911+ dir := filepath.Dir(name)
912912+ return doCleanDirectories(fs, dir)
913913+}
914914+915915+// doCleanDirectories removes empty subdirs (without files)
916916+func doCleanDirectories(fs billy.Filesystem, dir string) error {
917917+ files, err := fs.ReadDir(dir)
886918 if err != nil {
887919 return err
888920 }
889889-890921 if len(files) == 0 {
891891- fs.Remove(path)
922922+ return fs.Remove(dir)
892923 }
893893-894924 return nil
895925}
+9
worktree_test.go
···1591159115921592 c.Assert(len(status), Equals, 1)
1593159315941594+ fi, err := fs.Lstat("pkgA")
15951595+ c.Assert(err, IsNil)
15961596+ c.Assert(fi.IsDir(), Equals, true)
15971597+15941598 // Clean with Dir: true.
15951599 err = wt.Clean(&CleanOptions{Dir: true})
15961600 c.Assert(err, IsNil)
···15991603 c.Assert(err, IsNil)
1600160416011605 c.Assert(len(status), Equals, 0)
16061606+16071607+ // An empty dir should be deleted, as well.
16081608+ _, err = fs.Lstat("pkgA")
16091609+ c.Assert(err, ErrorMatches, ".*(no such file or directory.*|.*file does not exist)*.")
16101610+16021611}
1603161216041613func (s *WorktreeSuite) TestAlternatesRepo(c *C) {