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  }