Skip to content
Open
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
operations: fix rmdirs failing on directories which only look empty - f…
…ixes #9907

Google Drive leaves out of its listings any Google Doc it has no export
format for, such as a .gform. rmdirs therefore saw a directory holding
only such a file as empty, tried to remove it, and failed the whole run
with "directory not empty".

A directory which turns out not to be empty is not a failure for rmdirs,
whose job is to remove the empty ones, so skip it and carry on. This also
covers a file arriving between the listing and the removal. sync already
ignores this error when it prunes empty directories.

The drive backend reported this case as a bare error rather than
fs.ErrorDirectoryNotEmpty, so it now returns the standard error like the
other backends, which is what makes it recognisable here.
  • Loading branch information
Karthikvanabhojana1 committed Sep 30, 2026
commit 4b54841962255a92043c1f3740ea23f8bc92caff
2 changes: 1 addition & 1 deletion backend/drive/drive.go
Original file line number Diff line number Diff line change
Expand Up @@ -2784,7 +2784,7 @@ func (f *Fs) purgeCheck(ctx context.Context, dir string, check bool) error {
return err
}
if found {
return fmt.Errorf("directory not empty")
return fs.ErrorDirectoryNotEmpty
}
}
if root != "" {
Expand Down
8 changes: 8 additions & 0 deletions fs/operations/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,14 @@ func Rmdirs(ctx context.Context, f fs.Fs, dir string, leaveRoot bool) error {
dir := dir
g.Go(func() error {
err := TryRmdir(gCtx, f, dir)
if errors.Is(err, fs.ErrorDirectoryNotEmpty) {
// The listing above showed this directory as empty but the
// backend disagrees, either because it hides some files from
// listings or because something was written in the meantime.
// Leaving it in place is what was asked for.
fs.Infof(dir, "Not removing directory as it is not empty")
return nil
}
if err != nil {
err = fs.CountError(ctx, err)
fs.Errorf(dir, "Failed to rmdir: %v", err)
Expand Down
35 changes: 35 additions & 0 deletions fs/operations/operations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,41 @@ func TestRmdirsWithFilter(t *testing.T) {
)
}

// dirNotEmptyFs is an Fs whose directories always refuse to be removed,
// standing in for a backend which hides some files from its listings.
type dirNotEmptyFs struct {
fs.Fs
}

// Rmdir reports the directory as not empty
func (f dirNotEmptyFs) Rmdir(ctx context.Context, dir string) error {
return fs.ErrorDirectoryNotEmpty
}

func TestRmdirsDirectoryNotEmpty(t *testing.T) {
ctx := context.Background()
r := fstest.NewRun(t)
r.Mkdir(ctx, r.Fremote)

r.ForceMkdir(ctx, r.Fremote)

require.NoError(t, operations.Mkdir(ctx, r.Fremote, "A1"))

accounting.Stats(ctx).ResetErrors()
require.NoError(t, operations.Rmdirs(ctx, dirNotEmptyFs{r.Fremote}, "", false))
assert.Equal(t, int64(0), accounting.Stats(ctx).GetErrors())

fstest.CheckListingWithPrecision(
t,
r.Fremote,
[]fstest.Item{},
[]string{
"A1",
},
fs.GetModifyWindow(ctx, r.Fremote),
)
}

func TestCopyURL(t *testing.T) {
ctx := context.Background()
ctx, ci := fs.AddConfig(ctx)
Expand Down
Loading