github.com/kilpkonn/gtm-enhanced@v1.3.5/command/report.go (about) 1 // Copyright 2016 Michael Schenk. All rights reserved. 2 // Use of this source code is governed by a MIT-style 3 // license that can be found in the LICENSE file. 4 5 package command 6 7 import ( 8 "bufio" 9 "flag" 10 "fmt" 11 "os" 12 "regexp" 13 "strings" 14 "time" 15 16 "github.com/briandowns/spinner" 17 "github.com/git-time-metric/gtm/project" 18 "github.com/git-time-metric/gtm/report" 19 "github.com/git-time-metric/gtm/scm" 20 "github.com/git-time-metric/gtm/util" 21 isatty "github.com/mattn/go-isatty" 22 "github.com/mitchellh/cli" 23 ) 24 25 // ReportCmd contains methods for report command 26 type ReportCmd struct { 27 UI cli.Ui 28 } 29 30 // NewReport create new ReportCmd struct 31 func NewReport() (cli.Command, error) { 32 return ReportCmd{}, nil 33 } 34 35 // Help returns help for report command 36 func (c ReportCmd) Help() string { 37 helpText := ` 38 Usage: gtm report [options] <Commit-ID>... 39 40 Display reports for one or more git repositories. 41 42 Options: 43 44 Report Formats: 45 46 -format=commits Specify report format [summary|project|commits|files|timeline-hours|timeline-commits] (default commits) 47 -full-message=false Include full commit message 48 -terminal-off=false Exclude time spent in terminal (Terminal plug-in is required) 49 -force-color=false Always output color even if no terminal is detected, i.e 'gtm report -color | less -R' 50 -testing=false This is used for automated testing to force default test path 51 52 Commit Limiting: 53 54 -n int=1 Limit output, 0 is no limits, defaults to 1 when no limiting flags otherwise defaults to 0 55 -from-date=yyyy-mm-dd Show commits starting from this date 56 -to-date=yyyy-mm-dd Show commits thru the end of this date 57 -author="" Show commits which contain author substring 58 -message="" Show commits which contain message substring 59 -today=false Show commits for today 60 -yesterday=false Show commits for yesterday 61 -this-week=false Show commits for this week 62 -last-week=false Show commits for last week 63 -this-month=false Show commits for this month 64 -last-month=false Show commits for last month 65 -this-year=false Show commits for this year 66 -last-year=false Show commits for last year 67 68 Multi-Project Reporting: 69 70 -tags="" Project tags to report on, i.e --tags tag1,tag2 71 -all=false Show commits for all projects 72 ` 73 return strings.TrimSpace(helpText) 74 } 75 76 // Run executes report command with args 77 func (c ReportCmd) Run(args []string) int { 78 var limit int 79 var color, terminalOff, fullMessage, testing bool 80 var today, yesterday, thisWeek, lastWeek, thisMonth, lastMonth, thisYear, lastYear, all bool 81 var fromDate, toDate, message, author, tags, format string 82 cmdFlags := flag.NewFlagSet("report", flag.ContinueOnError) 83 cmdFlags.BoolVar(&color, "force-color", false, "") 84 cmdFlags.BoolVar(&terminalOff, "terminal-off", false, "") 85 cmdFlags.StringVar(&format, "format", "commits", "") 86 cmdFlags.IntVar(&limit, "n", 0, "") 87 cmdFlags.BoolVar(&fullMessage, "full-message", false, "") 88 cmdFlags.StringVar(&fromDate, "from-date", "", "") 89 cmdFlags.StringVar(&toDate, "to-date", "", "") 90 cmdFlags.BoolVar(&today, "today", false, "") 91 cmdFlags.BoolVar(&yesterday, "yesterday", false, "") 92 cmdFlags.BoolVar(&thisWeek, "this-week", false, "") 93 cmdFlags.BoolVar(&lastWeek, "last-week", false, "") 94 cmdFlags.BoolVar(&thisMonth, "this-month", false, "") 95 cmdFlags.BoolVar(&lastMonth, "last-month", false, "") 96 cmdFlags.BoolVar(&thisYear, "this-year", false, "") 97 cmdFlags.BoolVar(&lastYear, "last-year", false, "") 98 cmdFlags.StringVar(&author, "author", "", "") 99 cmdFlags.StringVar(&message, "message", "", "") 100 cmdFlags.StringVar(&tags, "tags", "", "") 101 cmdFlags.BoolVar(&all, "all", false, "") 102 cmdFlags.BoolVar(&testing, "testing", false, "") 103 cmdFlags.Usage = func() { c.UI.Output(c.Help()) } 104 if err := cmdFlags.Parse(args); err != nil { 105 return 1 106 } 107 108 if !util.StringInSlice([]string{"summary", "commits", "timeline-hours", "files", "timeline-commits", "project"}, format) { 109 c.UI.Error(fmt.Sprintf("report --format=%s not valid\n", format)) 110 return 1 111 } 112 113 var ( 114 commits []string 115 out string 116 err error 117 ) 118 119 const invalidSHA1 = "\nNot a valid commit SHA-1 %s\n" 120 121 // if running from within a MINGW console isatty detection does not work 122 // https://github.com/mintty/mintty/issues/482 123 isMinGW := strings.HasPrefix(os.Getenv("MSYSTEM"), "MINGW") 124 125 sha1Regex := regexp.MustCompile(`\A([0-9a-f]{40})\z`) 126 127 projCommits := []report.ProjectCommits{} 128 129 switch { 130 case !testing && !isMinGW && !isatty.IsTerminal(os.Stdin.Fd()): 131 scanner := bufio.NewScanner(os.Stdin) 132 for scanner.Scan() { 133 if !sha1Regex.MatchString(scanner.Text()) { 134 c.UI.Error(fmt.Sprintf("%s %s", invalidSHA1, scanner.Text())) 135 return 1 136 } 137 commits = append(commits, scanner.Text()) 138 } 139 curProjPath, err := scm.GitRepoPath() 140 if err != nil { 141 c.UI.Error(err.Error()) 142 return 1 143 } 144 curProjPath, err = scm.Workdir(curProjPath) 145 if err != nil { 146 c.UI.Error(err.Error()) 147 return 1 148 } 149 150 projCommits = append(projCommits, report.ProjectCommits{Path: curProjPath, Commits: commits}) 151 152 case !testing && len(cmdFlags.Args()) > 0: 153 for _, a := range cmdFlags.Args() { 154 if !sha1Regex.MatchString(a) { 155 c.UI.Error(fmt.Sprintf("%s %s", invalidSHA1, a)) 156 return 1 157 } 158 commits = append(commits, a) 159 } 160 curProjPath, err := scm.GitRepoPath() 161 if err != nil { 162 c.UI.Error(err.Error()) 163 return 1 164 } 165 curProjPath, err = scm.Workdir(curProjPath) 166 if err != nil { 167 c.UI.Error(err.Error()) 168 return 1 169 } 170 if err != nil { 171 c.UI.Error(err.Error()) 172 return 1 173 } 174 175 projCommits = append(projCommits, report.ProjectCommits{Path: curProjPath, Commits: commits}) 176 177 default: 178 index, err := project.NewIndex() 179 if err != nil { 180 c.UI.Error(err.Error()) 181 return 1 182 } 183 184 tagList := []string{} 185 if tags != "" { 186 tagList = util.Map(strings.Split(tags, ","), strings.TrimSpace) 187 } 188 projects, err := index.Get(tagList, all) 189 if err != nil { 190 c.UI.Error(err.Error()) 191 return 1 192 } 193 194 // hack, if project format we want all commits for the project 195 if format == "project" && limit == 0 { 196 // set max to absurdly high value for number of possible commits 197 limit = 2147483647 198 } 199 200 limiter, err := scm.NewCommitLimiter( 201 limit, fromDate, toDate, author, message, 202 today, yesterday, thisWeek, lastWeek, 203 thisMonth, lastMonth, thisYear, lastYear) 204 205 if err != nil { 206 c.UI.Error(err.Error()) 207 return 1 208 } 209 210 limit = limiter.Max 211 212 for _, p := range projects { 213 commits, err = scm.CommitIDs(limiter, p) 214 if err != nil { 215 c.UI.Error(err.Error()) 216 return 1 217 } 218 projCommits = append(projCommits, report.ProjectCommits{Path: p, Commits: commits}) 219 } 220 } 221 222 options := report.OutputOptions{ 223 FullMessage: fullMessage, 224 TerminalOff: terminalOff, 225 Color: color, 226 Limit: limit} 227 228 s := spinner.New(spinner.CharSets[9], 100*time.Millisecond) 229 s.Start() 230 231 switch format { 232 case "project": 233 out, err = report.ProjectSummary(projCommits, options) 234 case "summary": 235 out, err = report.CommitSummary(projCommits, options) 236 case "commits": 237 out, err = report.Commits(projCommits, options) 238 case "files": 239 out, err = report.Files(projCommits, options) 240 case "timeline-hours": 241 out, err = report.Timeline(projCommits, options) 242 case "timeline-commits": 243 out, err = report.TimelineCommits(projCommits, options) 244 } 245 246 s.Stop() 247 248 if err != nil { 249 c.UI.Error(err.Error()) 250 return 1 251 } 252 c.UI.Output(out) 253 254 return 0 255 } 256 257 // Synopsis return help for report command 258 func (c ReportCmd) Synopsis() string { 259 return "Display reports for git repositories" 260 }