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, &params)
   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  }