github.com/developest/gtm-enhanced@v1.0.4-0.20220111132249-cc80a3372c3f/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/DEVELOPEST/gtm-core/project"
    17  	"github.com/DEVELOPEST/gtm-core/report"
    18  	"github.com/DEVELOPEST/gtm-core/scm"
    19  	"github.com/DEVELOPEST/gtm-core/util"
    20  	"github.com/briandowns/spinner"
    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    -app-off=false             Exclude time spent in apps
    50    -force-color=false         Always output color even if no terminal is detected, i.e 'gtm report -color | less -R'
    51    -testing=false             This is used for automated testing to force default test path
    52  
    53    Commit Limiting:
    54  
    55    -n int=0                   Limit output, 0 is no limit
    56    -from-date=yyyy-mm-dd      Show commits starting from this date
    57    -to-date=yyyy-mm-dd        Show commits thru the end of this date
    58    -author=""                 Show commits which contain author substring
    59    -message=""                Show commits which contain message substring
    60    -subdir=""                 Show commits that are in subdirectory
    61    -today=false               Show commits for today
    62    -yesterday=false           Show commits for yesterday
    63    -this-week=false           Show commits for this week
    64    -last-week=false           Show commits for last week
    65    -this-month=false          Show commits for this month
    66    -last-month=false          Show commits for last month
    67    -this-year=false           Show commits for this year
    68    -last-year=false           Show commits for last year
    69  
    70    Multi-Project Reporting:
    71  
    72    -tags=""                   Project tags to report on, i.e --tags tag1,tag2
    73    -all=false                 Show commits for all projects
    74  `
    75  	return strings.TrimSpace(helpText)
    76  }
    77  
    78  // Run executes report command with args
    79  func (c ReportCmd) Run(args []string) int {
    80  	var limit int
    81  	var color, terminalOff, appOff, fullMessage, testing bool
    82  	var today, yesterday, thisWeek, lastWeek, thisMonth, lastMonth, thisYear, lastYear, all bool
    83  	var fromDate, toDate, message, author, subdir, tags, format string
    84  	cmdFlags := flag.NewFlagSet("report", flag.ContinueOnError)
    85  	cmdFlags.BoolVar(&color, "force-color", false, "")
    86  	cmdFlags.BoolVar(&terminalOff, "terminal-off", false, "")
    87  	cmdFlags.BoolVar(&appOff, "app-off", false, "")
    88  	cmdFlags.StringVar(&format, "format", "commits", "")
    89  	cmdFlags.IntVar(&limit, "n", 0, "")
    90  	cmdFlags.BoolVar(&fullMessage, "full-message", false, "")
    91  	cmdFlags.StringVar(&fromDate, "from-date", "", "")
    92  	cmdFlags.StringVar(&toDate, "to-date", "", "")
    93  	cmdFlags.BoolVar(&today, "today", false, "")
    94  	cmdFlags.BoolVar(&yesterday, "yesterday", false, "")
    95  	cmdFlags.BoolVar(&thisWeek, "this-week", false, "")
    96  	cmdFlags.BoolVar(&lastWeek, "last-week", false, "")
    97  	cmdFlags.BoolVar(&thisMonth, "this-month", false, "")
    98  	cmdFlags.BoolVar(&lastMonth, "last-month", false, "")
    99  	cmdFlags.BoolVar(&thisYear, "this-year", false, "")
   100  	cmdFlags.BoolVar(&lastYear, "last-year", false, "")
   101  	cmdFlags.StringVar(&author, "author", "", "")
   102  	cmdFlags.StringVar(&message, "message", "", "")
   103  	cmdFlags.StringVar(&subdir, "subdir", "", "")
   104  	cmdFlags.StringVar(&tags, "tags", "", "")
   105  	cmdFlags.BoolVar(&all, "all", false, "")
   106  	cmdFlags.BoolVar(&testing, "testing", false, "")
   107  	cmdFlags.Usage = func() { c.UI.Output(c.Help()) }
   108  	if err := cmdFlags.Parse(args); err != nil {
   109  		return 1
   110  	}
   111  
   112  	if !util.StringInSlice([]string{"summary", "commits", "timeline-hours", "files", "timeline-commits", "project"}, format) {
   113  		c.UI.Error(fmt.Sprintf("report --format=%s not valid\n", format))
   114  		return 1
   115  	}
   116  
   117  	var (
   118  		commits []string
   119  		out     string
   120  		err     error
   121  	)
   122  
   123  	const invalidSHA1 = "\nNot a valid commit SHA-1 %s\n"
   124  
   125  	// if running from within a MINGW console isatty detection does not work
   126  	// https://github.com/mintty/mintty/issues/482
   127  	isMinGW := strings.HasPrefix(os.Getenv("MSYSTEM"), "MINGW")
   128  
   129  	sha1Regex := regexp.MustCompile(`\A([0-9a-f]{40})\z`)
   130  
   131  	var projCommits []report.ProjectCommits
   132  
   133  	switch {
   134  	case !testing && !isMinGW && !isatty.IsTerminal(os.Stdin.Fd()):
   135  		scanner := bufio.NewScanner(os.Stdin)
   136  		for scanner.Scan() {
   137  			if !sha1Regex.MatchString(scanner.Text()) {
   138  				c.UI.Error(fmt.Sprintf("%s %s", invalidSHA1, scanner.Text()))
   139  				return 1
   140  			}
   141  			commits = append(commits, scanner.Text())
   142  		}
   143  		curProjPath, err := scm.GitRepoPath()
   144  		if err != nil {
   145  			c.UI.Error(err.Error())
   146  			return 1
   147  		}
   148  		curProjPath, err = scm.Workdir(curProjPath)
   149  		if err != nil {
   150  			c.UI.Error(err.Error())
   151  			return 1
   152  		}
   153  
   154  		projCommits = append(projCommits, report.ProjectCommits{Path: curProjPath, Commits: commits})
   155  
   156  	case !testing && len(cmdFlags.Args()) > 0:
   157  		for _, a := range cmdFlags.Args() {
   158  			if !sha1Regex.MatchString(a) {
   159  				c.UI.Error(fmt.Sprintf("%s %s", invalidSHA1, a))
   160  				return 1
   161  			}
   162  			commits = append(commits, a)
   163  		}
   164  		curProjPath, err := scm.GitRepoPath()
   165  		if err != nil {
   166  			c.UI.Error(err.Error())
   167  			return 1
   168  		}
   169  		curProjPath, err = scm.Workdir(curProjPath)
   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  		var 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  		limiter, err := scm.NewCommitLimiter(
   195  			limit, fromDate, toDate, author, message,
   196  			today, yesterday, thisWeek, lastWeek,
   197  			thisMonth, lastMonth, thisYear, lastYear)
   198  
   199  		if err != nil {
   200  			c.UI.Error(err.Error())
   201  			return 1
   202  		}
   203  
   204  		limit = limiter.Max
   205  
   206  		for _, p := range projects {
   207  			commits, err = scm.CommitIDs(limiter, p)
   208  			if err != nil {
   209  				c.UI.Error(err.Error())
   210  				return 1
   211  			}
   212  			projCommits = append(projCommits, report.ProjectCommits{Path: p, Commits: commits})
   213  		}
   214  	}
   215  
   216  	options := report.OutputOptions{
   217  		FullMessage: fullMessage,
   218  		TerminalOff: terminalOff,
   219  		AppOff:      appOff,
   220  		Color:       color,
   221  		Limit:       limit,
   222  		Subdir:      subdir}
   223  
   224  	s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
   225  	s.Start()
   226  
   227  	switch format {
   228  	case "project":
   229  		out, err = report.ProjectSummary(projCommits, options)
   230  	case "summary":
   231  		out, err = report.CommitSummary(projCommits, options)
   232  	case "commits":
   233  		out, err = report.Commits(projCommits, options)
   234  	case "files":
   235  		out, err = report.Files(projCommits, options)
   236  	case "timeline-hours":
   237  		out, err = report.Timeline(projCommits, options)
   238  	case "timeline-commits":
   239  		out, err = report.TimelineCommits(projCommits, options)
   240  	}
   241  
   242  	s.Stop()
   243  
   244  	if err != nil {
   245  		c.UI.Error(err.Error())
   246  		return 1
   247  	}
   248  	c.UI.Output(out)
   249  
   250  	return 0
   251  }
   252  
   253  // Synopsis return help for report command
   254  func (c ReportCmd) Synopsis() string {
   255  	return "Display reports for git repositories"
   256  }