// TODO(waigani) screen diff tenet side - see other diff comment. func newInDiffFunc(diff *diffparser.Diff) (func(*api.Issue) bool, error) { diffChanges := diff.Changed() return func(issue *api.Issue) bool { start := int(issue.Position.Start.Line) end := start if endLine := int(issue.Position.End.Line); endLine != 0 { end = endLine } filename := GetDiffRootPath(issue.Position.Start.Filename) if newLines, ok := diffChanges[filename]; ok { for _, lineNo := range newLines { if lineNo >= start && lineNo <= end { return true } } } return false }, nil }
func reviewAction(ctx *cli.Context) { // TODO: file args input, as files and dirs var err error var diff *diffparser.Diff // create new diff to filter issues by if ctx.Bool("diff") { diff, err = diffparser.Parse(rawDiff()) if err != nil { oserrf(err.Error()) return } } fileArgs := ctx.Args() var changed *map[string][]int = nil if diff != nil { // if we are diffing, add all files in diff for _, f := range diff.Files { if f.Mode != diffparser.DELETED { fileArgs = append(fileArgs, f.NewName) } } // TODO(waigani) find a better place for this to live. c := diff.Changed() for i, f := range diff.Files { newDiffRootPath := review.GetDiffRootPath(f.NewName) origDiffRootPath := review.GetDiffRootPath(f.OrigName) diff.Files[i].NewName = newDiffRootPath f.OrigName = origDiffRootPath // Need untransformed names to stay in changeset to use diff information in reviewQueue if origDiffRootPath != f.NewName { c[origDiffRootPath] = c[f.NewName] delete(c, f.NewName) } } changed = &c } else if len(fileArgs) == 0 { fileArgs = []string{"."} } // Get this first as it might fail, we want to avoid all other work in that case cfm, err := review.NewConfirmer(ctx, diff) if err != nil { oserrf(err.Error()) return } // Map of project config filenames -> directories they control cfgList := []cfgMap{} // TODO: This loop is now pipelinable too, if we need to further reduce time-to-first-review for _, f := range fileArgs { // Specifically asking for a file that can't be found/read is fatal file, err := os.Open(f) if err != nil { oserrf(err.Error()) return } fi, err := file.Stat() if err != nil { oserrf(err.Error()) return } if fi.IsDir() { filepath.Walk(f, func(relPath string, info os.FileInfo, err error) error { if info.IsDir() { // Ignore folders beginning with '.', except search root // TODO: Flag to turn this behaviour off if len(relPath) > 1 && info.Name()[0] == '.' { return filepath.SkipDir } // TODO: Faster technique for finding cfgPath taking advantage of Walk's depth-first search // This implementation recurses upwards for each found dir cfgPath, _ := tenetCfgPathRecusive(path.Join(relPath, defaultTenetCfgPath)) cfgList = append(cfgList, cfgMap{ path: cfgPath, cfg: nil, dirs: []string{relPath}, files: []string{}, }) } return nil }) } else { cfgPath, _ := tenetCfgPathRecusive(path.Join(filepath.Dir(f), defaultTenetCfgPath)) cfgList = append(cfgList, cfgMap{ path: cfgPath, cfg: nil, dirs: []string{}, files: []string{f}, }) } } // Receiver for errors that can occur during pipeline stages errc := make(chan error, 100000) // Use a channel to read configs with directory mapping configDirs := readCfgs(cfgList, errc) rc, cancelledc := reviewQueue(ctx, configDirs, changed, errc) var count int keptIssuesc := make(chan *api.Issue) // collectedIssues has a huge buffer so we can store all the found issues, // allowing the tenet instances to be stopped. If this buffer is filled, // tenets will not be stopped. They will hang around until there is room // to offload their issues. collectedIssuesc := make(chan *api.Issue, 100000) allIssuesWG := &sync.WaitGroup{} go func() { for i := range collectedIssuesc { if cfm.Confirm(0, i) { keptIssuesc <- i } } close(keptIssuesc) close(errc) }() z: for { select { case r, open := <-rc: if !open && r == nil { break z } allIssuesWG.Add(1) go func(r *tenetReview) { defer r.issuesWG.Done() l: for { select { case i, ok := <-r.issuesc: if !ok && i == nil { allIssuesWG.Done() break l } count++ select { case <-cancelledc: case collectedIssuesc <- i: } } } }(r) } } // Wait for all issues to be read. allIssuesWG.Wait() // then close our collection chan. close(collectedIssuesc) var issues []*api.Issue for i := range keptIssuesc { issues = append(issues, i) } errors := []error{} for err := range errc { errors = append(errors, err) } outputFmt := review.OutputFormat(ctx.String("output-fmt")) // Print errors if any occured if len(errors) > 0 { fmt.Println("The following errors were encounted:") for _, err := range errors { fmt.Printf("%v\n", err) } if outputFmt != "none" { var options string fmt.Println("Do you still wish to output the found issues? [y]es [N]o") fmt.Scanln(&options) switch options { case "y", "Y", "yes": default: return } } } if outputFmt != "none" { output := review.Output(outputFmt, ctx.String("output"), issues) fmt.Print(output) } else { // TODO(waigani) make more informative // TODO(waigani) if !ctx.String("quiet") fmt.Printf("Done! Found %d issues \n", count) } }