-
Notifications
You must be signed in to change notification settings - Fork 66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(fsi): qri remove --all --force should not fail on low value files #1203
Conversation
d641225
to
4c0d472
Compare
fsi/fsi.go
Outdated
err := removeLowValueFiles(dirPath) | ||
if err != nil { | ||
log.Errorf("removing directory: %s", err.Error()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be better if it lived here: https://github.com/qri-io/qri/blob/master/lib/datasets.go#L793. For one, this behavior isn't directly needed by fsi.Unlink
, only by lib.Remove
. If a user runs qri fsi unlink
then this function also gets called, via the Unlink
function in lib/fsi.go
, but that shouldn't be deleting files. Furthermore, having this functionality here means that it doesn't respect the --keep-files
flag, which if true shouldn't modify any files at all.
Would also be great to have a test for this in cmd/
, which creates a directory, runs qri init
, runs save
, writes a .DS_Store
file to that directory, then runs remove --all
to make sure the whole thing is deleted. Copying TestRemoveOneRevisionFromWorkingDirectory
then modifying it would be a great start.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed on moving the call site to https://github.com/qri-io/qri/blob/master/lib/datasets.go#L793, but I think the logic of what we mean by RemoveAll
should live in the fsi
package. To me the best of both worlds is if fsi
had an exported function:
package fsi
// RemoveAll is like os.RemoveAll, but it also removes low-value files
func RemoveAll(path string) error {
// ...
}
Then call that function from lib.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a bit of a split approach with datastore
retaining control but moving the execution logic to fsi
.
fsi/fsi.go
Outdated
os.Remove(path) | ||
} | ||
} | ||
return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My only other question here: should we also be removing directories if they are empty after removing low-value files? Personally, I think we should. when I run qri remove --all --force
I'm trying to blow the directory away.
We could save that for another PR, but if we can get it knocked out now, I'd be all for it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got covered as part of the update :)
4c0d472
to
fd1c082
Compare
fd1c082
to
7d0a3c5
Compare
Still pending on adding the tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test TestRemoveAllVersionsWorkingDirectoryLowValueFiles
is awesome, excellent work. I have a design concern with --force
, however, that I think we need to figure out.
cmd/fsi_integration_test.go
Outdated
subDirPath := filepath.Join(run.WorkPath, subdir) | ||
err := os.MkdirAll(subDirPath, os.ModePerm) | ||
if err != nil { | ||
panic(err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of panic'ing, pass a testing.T
to CreateSubDir
and call t.Fatal(err)
here. See how MustWriteFile
does it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, lot's of these small bits we're just taken out of other examples. I'll look over it and appropriate this + if I find this setup anywhere else.
cmd/remove_integration_test.go
Outdated
} | ||
|
||
// Remove all versions | ||
run.MustExec(t, "qri remove --all=1") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small style suggestion, just do --all
without the =1
. It's just a boolean flag, this makes it read as though it requires an integer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the above, seen this in several places. For this PR will fix just this case but will open another to clean up the rest.
cmd/remove_integration_test.go
Outdated
run.MustWriteFile(t, "test.sh", "echo test\n") | ||
|
||
// Remove all with force | ||
run.MustExec(t, "qri remove --revisions=all --force") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest that, even with --force
, we shouldn't try and delete files that we don't own. See my comments on the fsi.RemoveAll
function for full justification.
fsi/fsi.go
Outdated
if err := fsi.Unlink(dirPath, refStr); err != nil { | ||
return err | ||
} | ||
if err := os.RemoveAll(dirPath); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wary of ever calling RemoveAll
, because it's potentially very destructive, especially when taking unknown / arbitrary input as a parameter. The only places we use it in the codebase right now are for tests that construct temporary directories, and one or two places that are part of rarely used commands, like a user deleting their whole repository. Since we're a data storage system, when it comes to deletes, I'd rather err on the side of being cautious over convenient, and whitelist the things we delete, rather than doing a RemoveAll
.
In fact, this makes me realize, if a user at some point runs qri init
in their home directory, and then later run remove --all --force
, this codepath would try and delete their entire home directory. That alone is reason enough why we shouldn't make --force
mean "remove even unknown files". The current semantics of --force
are "delete things even if we get errors, including the working directory not being clean", and I think it should stay that way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree on RemoveAll being very agressive and some bad input could wreak havoc.
On the alternative side, doing just RemoveAll and Remove will essentially result in the same behavior, unless Remove no longer removes low value files and it just triggers on --force
which technically is the purpose of this PR.
Think the above might be a best blend between the two things while remaining cautious.
lib/datasets.go
Outdated
if p.Force { | ||
err = r.inst.fsi.RemoveAll(ref.FSIPath, ref.AliasString()) | ||
} else if !p.KeepFiles { | ||
err = r.inst.fsi.Remove(ref.FSIPath, ref.AliasString()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This call is happening before fsi.DeleteComponentFiles
below, which is what removes "body.csv", "structure.json", "meta.json" etc. That means in nearly all cases, the working directory will not be empty, and the call to os.Remove
within fsi.Remove
will fail.
There's already a call to os.Remove
below on the next line (after fsi.DeleteComponentFiles
). I think the logic of removing low-value files should be happening over there, instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Currently only works due to os.RemoveAll
from above.
fsi/fsi.go
Outdated
"^__MACOSX$", // Resource fork | ||
|
||
// linux specific files | ||
"~$", // Backup file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not fan of this one pattern. Any file ending with a ~
will be considered low-value, that's too broad and easy to get false positives.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy to remove it, mostly picked it up from some other list of "low value files"
} | ||
|
||
func getLowValueFiles(files *[]string) filepath.WalkFunc { | ||
return func(path string, f os.FileInfo, err error) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure to run go fmt ./...
in order to apply the standard golang code style guide.
a4dba27
to
7437209
Compare
3f292d2
to
bb26d7c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Addresses issue #1190.