github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/cmd/log.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 10 "github.com/qri-io/ioes" 11 "github.com/qri-io/qri/base/params" 12 "github.com/qri-io/qri/dsref" 13 "github.com/qri-io/qri/errors" 14 "github.com/qri-io/qri/lib" 15 "github.com/qri-io/qri/repo" 16 "github.com/spf13/cobra" 17 ) 18 19 // NewLogCommand creates a new `qri log` cobra command 20 func NewLogCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command { 21 o := &LogOptions{IOStreams: ioStreams} 22 cmd := &cobra.Command{ 23 Use: "log [DATASET]", 24 Aliases: []string{"history"}, 25 Short: "show log of dataset commits", 26 Long: "`qri log`" + ` lists dataset commits over time. Each entry in the log is a 27 snapshot of a dataset taken at the moment it was saved that keeps exact details 28 about how that dataset looked at at that point in time. 29 30 We call these snapshots versions. Each version has an author (the peer that 31 created the version) and a message explaining what changed. Log prints these 32 details in order of occurrence, starting with the most recent known version, 33 working backwards in time. 34 35 The log command can get the list of versions for a local dataset or a dataset 36 on the network at a remote. 37 `, 38 Example: ` # Show log for the local dataset b5/precip: 39 $ qri log b5/precip 40 41 # Show log for a dataset on the Qri Cloud registry called ramfox/league_stats 42 $ qri log ramfox/league_stats 43 44 # Show log for a dataset chriswhong/nyc_parking_tickets on a remote named "nycdatacollection" 45 $ qri log chriswhong/nyc_parking_tickets --source nycdatacollection`, 46 Annotations: map[string]string{ 47 "group": "dataset", 48 }, 49 Args: cobra.MaximumNArgs(1), 50 RunE: func(cmd *cobra.Command, args []string) error { 51 if err := o.Complete(f, args); err != nil { 52 return err 53 } 54 return o.Run() 55 }, 56 } 57 58 // cmd.Flags().StringVarP(&o.Format, "format", "f", "", "set output format [json]") 59 cmd.Flags().IntVar(&o.Offset, "offset", 0, "skip this number of records from the results, default 0") 60 cmd.Flags().IntVar(&o.Limit, "limit", 25, "size of results, default 25") 61 cmd.Flags().StringVarP(&o.Source, "source", "", "", "name of source to fetch from, disables local actions. `registry` will search the default qri registry") 62 cmd.Flags().BoolVarP(&o.Local, "local", "l", false, "only fetch local logs, disables network actions") 63 cmd.Flags().BoolVarP(&o.Pull, "pull", "p", false, "fetch the latest logs from the network") 64 65 return cmd 66 } 67 68 // LogOptions encapsulates state for the log command 69 type LogOptions struct { 70 ioes.IOStreams 71 72 Offset int 73 Limit int 74 Refs *RefSelect 75 Local bool 76 Pull bool 77 78 // remote fetching specific flags 79 Source string 80 Unfetch bool 81 NoRegistry bool 82 NoPin bool 83 84 Instance *lib.Instance 85 } 86 87 // Complete adds any missing configuration that can only be added just before calling Run 88 func (o *LogOptions) Complete(f Factory, args []string) (err error) { 89 if o.Instance, err = f.Instance(); err != nil { 90 return err 91 } 92 93 if o.Local && (o.Source != "" || o.Pull) { 94 return errors.New(err, "cannot use 'local' flag with either the 'source' or 'pull' flags") 95 } 96 97 if o.Refs, err = GetCurrentRefSelect(f, args, AnyNumberOfReferences); err != nil { 98 if err == repo.ErrEmptyRef { 99 return errors.New(err, "please provide a dataset reference") 100 } 101 } 102 103 return err 104 } 105 106 // Run executes the log command 107 func (o *LogOptions) Run() error { 108 109 ctx := context.TODO() 110 p := &lib.ActivityParams{ 111 Ref: o.Refs.Ref(), 112 Pull: o.Pull, 113 List: params.List{ 114 Offset: o.Offset, 115 Limit: o.Limit, 116 }, 117 } 118 119 res, err := o.Instance.WithSource(o.Source).Dataset().Activity(ctx, p) 120 if err != nil { 121 return err 122 } 123 124 makeItemsAndPrint(res, o.Out, o.Offset) 125 return nil 126 } 127 128 func makeItemsAndPrint(refs []dsref.VersionInfo, out io.Writer, offset int) { 129 items := make([]fmt.Stringer, len(refs)) 130 for i, r := range refs { 131 items[i] = dslogItemStringer(r) 132 } 133 134 printItems(out, items, offset) 135 } 136 137 // NewLogbookCommand creates a `qri logbook` cobra command 138 func NewLogbookCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command { 139 o := &LogbookOptions{IOStreams: ioStreams} 140 cmd := &cobra.Command{ 141 Use: "logbook [DATASET]", 142 Short: "show a detailed list of changes on a dataset name", 143 Example: ` # Show log for the dataset bob/precip: 144 $ qri logbook bob/precip`, 145 Long: `Logbooks are records of changes to a dataset. The logbook is more detailed 146 than a dataset history, recording the steps taken to construct a dataset history 147 without including dataset data. Logbooks can be synced with other users. 148 149 The logbook command shows entries for a dataset, from newest to oldest.`, 150 Annotations: map[string]string{ 151 "group": "dataset", 152 }, 153 RunE: func(cmd *cobra.Command, args []string) error { 154 if err := o.Complete(f, args); err != nil { 155 return err 156 } 157 if o.Raw { 158 return o.RawLogs() 159 } else if o.Summary { 160 return o.LogbookSummary() 161 } 162 return o.LogEntries() 163 }, 164 } 165 166 cmd.Flags().IntVar(&o.Offset, "offset", 0, "skip this number of records from the results, default 0") 167 cmd.Flags().IntVar(&o.Limit, "limit", 25, "size of results, default 25") 168 cmd.Flags().BoolVar(&o.Raw, "raw", false, "full logbook in raw JSON format. overrides all other flags") 169 cmd.Flags().BoolVar(&o.Summary, "summary", false, "print one oplog per line in the format 'MODEL ID OPCOUNT NAME'. overrides all other flags") 170 171 return cmd 172 } 173 174 // LogbookOptions encapsulates state for the log command 175 type LogbookOptions struct { 176 ioes.IOStreams 177 178 Offset int 179 Limit int 180 Refs *RefSelect 181 Raw, Summary bool 182 183 Instance *lib.Instance 184 } 185 186 // Complete adds any missing configuration that can only be added just before calling Run 187 func (o *LogbookOptions) Complete(f Factory, args []string) (err error) { 188 if o.Instance, err = f.Instance(); err != nil { 189 return err 190 } 191 192 if o.Raw && o.Summary { 193 return fmt.Errorf("cannot use summary & raw flags at once") 194 } 195 196 if o.Raw || o.Summary { 197 if len(args) != 0 { 198 return fmt.Errorf("can't use dataset reference. the raw flag shows the entire logbook") 199 } 200 } else { 201 if o.Refs, err = GetCurrentRefSelect(f, args, 1); err != nil { 202 return err 203 } 204 } 205 206 return err 207 } 208 209 // LogEntries gets entries from the logbook 210 func (o *LogbookOptions) LogEntries() error { 211 212 p := &lib.RefListParams{ 213 Ref: o.Refs.Ref(), 214 Offset: o.Offset, 215 Limit: o.Limit, 216 } 217 218 ctx := context.TODO() 219 res, err := o.Instance.Log().Log(ctx, p) 220 if err != nil { 221 if err == repo.ErrEmptyRef { 222 return errors.New(err, "please provide a dataset reference") 223 } 224 return err 225 } 226 227 // print items in reverse 228 items := make([]fmt.Stringer, len(res)) 229 j := len(items) 230 for _, r := range res { 231 j-- 232 items[j] = logEntryStringer(r) 233 } 234 235 printItems(o.Out, items, o.Offset) 236 return nil 237 } 238 239 // RawLogs executes the rawlogs variant of the logbook command 240 func (o *LogbookOptions) RawLogs() error { 241 ctx := context.TODO() 242 res, err := o.Instance.Log().RawLogbook(ctx, &lib.RawLogbookParams{}) 243 if err != nil { 244 return err 245 } 246 247 data, err := json.Marshal(res) 248 if err != nil { 249 return err 250 } 251 252 printToPager(o.Out, bytes.NewBuffer(data)) 253 return nil 254 } 255 256 // LogbookSummary prints a logbook overview 257 func (o *LogbookOptions) LogbookSummary() error { 258 ctx := context.TODO() 259 res, err := o.Instance.Log().LogbookSummary(ctx, &struct{}{}) 260 if err != nil { 261 return err 262 } 263 264 printToPager(o.Out, bytes.NewBufferString(*res)) 265 return nil 266 }