예제 #1
0
파일: confirm.go 프로젝트: axw/lingo
// 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
}
예제 #2
0
파일: review.go 프로젝트: axw/lingo
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)
	}
}