github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/cmd/remove.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 8 "github.com/qri-io/ioes" 9 "github.com/qri-io/qri/dsref" 10 qerr "github.com/qri-io/qri/errors" 11 "github.com/qri-io/qri/lib" 12 "github.com/qri-io/qri/repo" 13 "github.com/spf13/cobra" 14 ) 15 16 // NewRemoveCommand creates a new `qri remove` cobra command for removing datasets from a local repository 17 func NewRemoveCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command { 18 o := &RemoveOptions{IOStreams: ioStreams} 19 cmd := &cobra.Command{ 20 Use: "remove [DATASET]", 21 Aliases: []string{"rm", "delete"}, 22 Short: "remove a dataset from your local repository", 23 Long: `Remove deletes datasets from qri. 24 25 For read-only datasets you've pulled from others, Remove gets rid of a dataset 26 from your qri node. After running remove, qri will no longer list that dataset 27 as being available locally, and may free up the storage space. 28 29 For datasets you can edit, remove deletes commits from a dataset history. 30 Use delete to "correct the record" by erasing commits. Running remove on 31 writable datasets requires a '--revisions' flag, specifying the number of 32 commits to delete. Remove always starts from the latest (HEAD) commit, working 33 backwards toward the first commit. 34 35 Remove can also be used to ask remotes to delete datasets with the '--remote' 36 flag. Passing the remote flag will run the operation as a network request, 37 reporting the results of attempting to remove on the destination remote. 38 The remote flag can only be used to completely remove a dataset from a remote. 39 To edit history on a remote, run delete locally and use 'qri push' to send the 40 updated history to the remote. Any command run with the remote flag has no 41 effect on local data.`, 42 Example: ` # delete a dataset cloned from another user 43 $ qri remove user/world_bank_population 44 45 # delete the latest commit from annual_pop 46 $ qri remove me/annual_pop --revisions 1 47 48 # delete the latest two versions from history 49 $ qri remove me/annual_pop --revisions 2 50 51 # destroy a dataset named 'annual_pop' 52 $ qri remove --all me/annual_pop 53 54 # ask the registry to delete a dataset 55 $ qri remove --remote registry me/annual_pop`, 56 Annotations: map[string]string{ 57 "group": "dataset", 58 }, 59 // Use *max* so we can print a nicer message for no or malformed args 60 Args: cobra.MaximumNArgs(1), 61 RunE: func(cmd *cobra.Command, args []string) error { 62 if err := o.Complete(f, args); err != nil { 63 return err 64 } 65 if err := o.Validate(); err != nil { 66 return err 67 } 68 return o.Run() 69 }, 70 } 71 72 cmd.Flags().StringVarP(&o.RevisionsText, "revisions", "r", "", "revisions to delete") 73 cmd.Flags().BoolVarP(&o.All, "all", "a", false, "synonym for --revisions=all") 74 cmd.Flags().BoolVarP(&o.Force, "force", "f", false, "remove files even if a working directory is dirty") 75 cmd.Flags().StringVar(&o.Remote, "remote", "", "remote address to remove from") 76 77 return cmd 78 } 79 80 // RemoveOptions encapsulates state for the remove command 81 type RemoveOptions struct { 82 ioes.IOStreams 83 84 Refs *RefSelect 85 86 Remote string 87 RevisionsText string 88 Revision *dsref.Rev 89 All bool 90 Force bool 91 92 inst *lib.Instance 93 } 94 95 // Complete adds any missing configuration that can only be added just before calling Run 96 func (o *RemoveOptions) Complete(f Factory, args []string) (err error) { 97 if o.inst, err = f.Instance(); err != nil { 98 return 99 } 100 101 if o.Refs, err = GetCurrentRefSelect(f, args, 1); err != nil { 102 // This error will be handled during validation 103 if err != repo.ErrEmptyRef { 104 return 105 } 106 err = nil 107 } 108 if o.All { 109 o.Revision = dsref.NewAllRevisions() 110 } else { 111 if o.RevisionsText == "" { 112 return fmt.Errorf("need --all or --revisions to specify how many revisions to remove") 113 } 114 revisions, err := dsref.ParseRevs(o.RevisionsText) 115 if err != nil { 116 return err 117 } 118 if len(revisions) != 1 { 119 return fmt.Errorf("need exactly 1 revision parameter to remove") 120 } 121 if revisions[0] == nil { 122 return fmt.Errorf("invalid nil revision") 123 } 124 o.Revision = revisions[0] 125 } 126 return 127 } 128 129 // Validate checks that all user input is valid 130 func (o *RemoveOptions) Validate() error { 131 if o.Refs.Ref() == "" { 132 return qerr.New(lib.ErrBadArgs, "please specify a dataset path or name you would like to remove from your qri node") 133 } 134 return nil 135 } 136 137 // Run executes the remove command 138 func (o *RemoveOptions) Run() (err error) { 139 if o.Remote != "" { 140 return o.RemoveRemote() 141 } 142 143 params := lib.RemoveParams{ 144 Ref: o.Refs.Ref(), 145 Revision: o.Revision, 146 Force: o.Force, 147 } 148 149 ctx := context.TODO() 150 res, err := o.inst.Dataset().Remove(ctx, ¶ms) 151 if err != nil { 152 // TODO(b5): move this error handling down into lib 153 if errors.Is(err, dsref.ErrRefNotFound) { 154 return qerr.New(err, fmt.Sprintf("could not find dataset '%s'", o.Refs.Ref())) 155 } 156 if err == lib.ErrCantRemoveDirectoryDirty { 157 printErr(o.ErrOut, err) 158 printErr(o.ErrOut, fmt.Errorf("use either --keep-files, or --force")) 159 return fmt.Errorf("dataset not removed") 160 } 161 return err 162 } 163 164 if res.NumDeleted == dsref.AllGenerations { 165 printSuccess(o.Out, "removed entire dataset '%s'", res.Ref) 166 } else if res.NumDeleted != 0 { 167 printSuccess(o.Out, "removed %d revisions of dataset '%s'", res.NumDeleted, res.Ref) 168 } else if res.Message != "" { 169 printSuccess(o.Out, "removed remains of dataset from %s", res.Message) 170 } 171 if res.Unlinked { 172 printSuccess(o.Out, "removed dataset link") 173 } 174 return nil 175 } 176 177 // RemoveRemote runs the remove command as a network request to a remote 178 func (o *RemoveOptions) RemoveRemote() error { 179 ctx := context.TODO() 180 res, err := o.inst.Remote().Remove(ctx, &lib.PushParams{ 181 Ref: o.Refs.Ref(), 182 Remote: o.Remote, 183 }) 184 if err != nil { 185 return fmt.Errorf("dataset not removed") 186 } 187 188 // remove profileID info for cleaner output 189 res.ProfileID = "" 190 printSuccess(o.Out, "removed dataset %s from remote %s", res, o.Remote) 191 return nil 192 }