| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // The following enables go generate to generate the doc.go file. |
| //go:generate go run $JIRI_ROOT/release/go/src/v.io/x/lib/cmdline/testdata/gendoc.go -env=CMDLINE_PREFIX=jiri . |
| |
| package main |
| |
| import ( |
| "encoding/xml" |
| "fmt" |
| "path/filepath" |
| "regexp" |
| "sort" |
| "strconv" |
| "strings" |
| |
| "v.io/jiri" |
| "v.io/jiri/collect" |
| "v.io/jiri/gitutil" |
| "v.io/jiri/project" |
| "v.io/jiri/tool" |
| "v.io/x/devtools/tooldata" |
| "v.io/x/lib/cmdline" |
| "v.io/x/lib/set" |
| ) |
| |
| const ( |
| aliasesFileName = "aliases.v1.xml" |
| ) |
| |
| var ( |
| countFlag bool |
| aliasesFlag string |
| ) |
| |
| func init() { |
| cmdContributorsList.Flags.BoolVar(&countFlag, "n", false, "Show number of contributions.") |
| cmdContributorsList.Flags.StringVar(&aliasesFlag, "aliases", "", "Path to the aliases file.") |
| } |
| |
| // cmdContributors represents the "jiri contributors" command. |
| var cmdContributors = &cmdline.Command{ |
| Name: "contributors", |
| Short: "List project contributors", |
| Long: "List project contributors.", |
| Children: []*cmdline.Command{cmdContributorsList}, |
| } |
| |
| // cmdContributorsList represents the "jiri contributors list" command. |
| var cmdContributorsList = &cmdline.Command{ |
| Runner: jiri.RunnerFunc(runContributorsList), |
| Name: "contributors", |
| Short: "List project contributors", |
| Long: ` |
| Lists project contributors. Projects to consider can be specified as |
| an argument. If no projects are specified, all projects in the current |
| manifest are considered by default. |
| `, |
| ArgsName: "<projects>", |
| ArgsLong: "<projects> is a list of projects to consider.", |
| } |
| |
| type contributor struct { |
| count int |
| email string |
| name string |
| } |
| |
| var ( |
| contributorRE = regexp.MustCompile("^(.*)\t(.*) <(.*)>$") |
| ) |
| |
| type aliasesSchema struct { |
| XMLName xml.Name `xml:"aliases"` |
| Names []nameSchema `xml:"name"` |
| Emails []emailSchema `xml:"email"` |
| } |
| |
| type nameSchema struct { |
| Canonical string `xml:"canonical"` |
| Aliases []string `xml:"alias"` |
| } |
| |
| type emailSchema struct { |
| Canonical string `xml:"canonical"` |
| Aliases []string `xml:"alias"` |
| } |
| |
| type aliasMaps struct { |
| emails map[string]string |
| names map[string]string |
| } |
| |
| func canonicalize(aliases *aliasMaps, email, name string) (string, string) { |
| canonicalEmail, canonicalName := email, name |
| if email, ok := aliases.emails[email]; ok { |
| canonicalEmail = email |
| } |
| if name, ok := aliases.names[name]; ok { |
| canonicalName = name |
| } |
| return canonicalEmail, canonicalName |
| } |
| |
| func loadAliases(jirix *jiri.X) (*aliasMaps, error) { |
| aliasesFile := aliasesFlag |
| if aliasesFile == "" { |
| dataDir, err := tooldata.DataDirPath(jirix, tool.Name) |
| if err != nil { |
| return nil, err |
| } |
| aliasesFile = filepath.Join(dataDir, aliasesFileName) |
| } |
| bytes, err := jirix.NewSeq().ReadFile(aliasesFile) |
| if err != nil { |
| return nil, err |
| } |
| var data aliasesSchema |
| if err := xml.Unmarshal(bytes, &data); err != nil { |
| return nil, fmt.Errorf("Unmarshal(%v) failed: %v", string(bytes), err) |
| } |
| aliases := &aliasMaps{ |
| emails: map[string]string{}, |
| names: map[string]string{}, |
| } |
| for _, email := range data.Emails { |
| for _, alias := range email.Aliases { |
| aliases.emails[alias] = email.Canonical |
| } |
| } |
| for _, name := range data.Names { |
| for _, alias := range name.Aliases { |
| aliases.names[alias] = name.Canonical |
| } |
| } |
| return aliases, nil |
| } |
| |
| func runContributorsList(jirix *jiri.X, args []string) error { |
| localProjects, err := project.LocalProjects(jirix, project.FastScan) |
| if err != nil { |
| return err |
| } |
| projectNames := map[string]struct{}{} |
| if len(args) != 0 { |
| projectNames = set.String.FromSlice(args) |
| } else { |
| for _, p := range localProjects { |
| projectNames[p.Name] = struct{}{} |
| } |
| } |
| |
| aliases, err := loadAliases(jirix) |
| if err != nil { |
| return err |
| } |
| contributors := map[string]*contributor{} |
| for name, _ := range projectNames { |
| projects := localProjects.Find(name) |
| if len(projects) == 0 { |
| continue |
| } |
| |
| for _, project := range projects { |
| if err := jirix.NewSeq().Chdir(project.Path).Done(); err != nil { |
| return err |
| } |
| switch project.Protocol { |
| case "git": |
| lines, err := listCommitters(jirix) |
| if err != nil { |
| return err |
| } |
| for _, line := range lines { |
| matches := contributorRE.FindStringSubmatch(line) |
| if got, want := len(matches), 4; got != want { |
| return fmt.Errorf("unexpected length of %v: got %v, want %v", matches, got, want) |
| } |
| count, err := strconv.Atoi(strings.TrimSpace(matches[1])) |
| if err != nil { |
| return fmt.Errorf("Atoi(%v) failed: %v", strings.TrimSpace(matches[1]), err) |
| } |
| c := &contributor{ |
| count: count, |
| email: strings.TrimSpace(matches[3]), |
| name: strings.TrimSpace(matches[2]), |
| } |
| if c.email == "jenkins.veyron@gmail.com" || c.email == "jenkins.veyron.rw@gmail.com" { |
| continue |
| } |
| c.email, c.name = canonicalize(aliases, c.email, c.name) |
| if existing, ok := contributors[c.name]; ok { |
| existing.count += c.count |
| } else { |
| contributors[c.name] = c |
| } |
| } |
| } |
| } |
| } |
| names := []string{} |
| for name, _ := range contributors { |
| names = append(names, name) |
| } |
| sort.Strings(names) |
| for _, name := range names { |
| c := contributors[name] |
| if countFlag { |
| fmt.Fprintf(jirix.Stdout(), "%4d ", c.count) |
| } |
| fmt.Fprintf(jirix.Stdout(), "%v <%v>\n", c.name, c.email) |
| } |
| return nil |
| } |
| |
| func listCommitters(jirix *jiri.X) (_ []string, e error) { |
| branch, err := gitutil.New(jirix.NewSeq()).CurrentBranchName() |
| if err != nil { |
| return nil, err |
| } |
| stashed, err := gitutil.New(jirix.NewSeq()).Stash() |
| if err != nil { |
| return nil, err |
| } |
| if stashed { |
| defer collect.Error(func() error { return gitutil.New(jirix.NewSeq()).StashPop() }, &e) |
| } |
| if err := gitutil.New(jirix.NewSeq()).CheckoutBranch("master"); err != nil { |
| return nil, err |
| } |
| defer collect.Error(func() error { return gitutil.New(jirix.NewSeq()).CheckoutBranch(branch) }, &e) |
| return gitutil.New(jirix.NewSeq()).Committers() |
| } |
| |
| func main() { |
| cmdline.Main(cmdContributors) |
| } |