Chromium Code Reviews| Index: go/src/infra/tools/drover/disambiguate.go |
| diff --git a/go/src/infra/tools/drover/disambiguate.go b/go/src/infra/tools/drover/disambiguate.go |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..83fdd521490f6920362e69303bc4649fdc46a624 |
| --- /dev/null |
| +++ b/go/src/infra/tools/drover/disambiguate.go |
| @@ -0,0 +1,163 @@ |
| +package main |
| + |
| +import "encoding/json" |
| +import "errors" |
| +import "fmt" |
| +import "net/http" |
| +import "reflect" |
| +import "sort" |
| +import "strings" |
| + |
| +import "infra/libs/gitiles" |
| + |
| +const crRevAPI = "https://cr-rev.appspot.com/_ah/api/crrev/v1/" |
| + |
| +/* Takes a user-suppiled commit specifier, and resolves it to a commit hash and |
| +repo url. |
| + |
| +Possible input formats: |
| + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" |
| + "29345" # Cr-Commit-Position assuming refs/heads/master and src.git |
| + |
| +The disambiguation is done by crrev.com, and so this function may return an |
| +error if the service is not available. |
| + |
| +Additionally, this function may return an error if the commit specifier is |
| +not unambiguous. |
| +*/ |
| +func disambiguateCommit(commitSpec string) (commit, url string, err error) { |
| + commit = commitSpec |
| + url = "" |
| + |
| + resp, err := http.Get(crRevAPI + "redirect/" + commitSpec) |
| + if err != nil { |
| + return |
| + } |
| + defer resp.Body.Close() |
| + |
| + // TODO(iannucci,stip): generate this from endpoints source code |
| + jsonData := make(map[string]string) |
| + if err = json.NewDecoder(resp.Body).Decode(&jsonData); err != nil { |
| + return |
| + } |
| + commit, ok := jsonData["git_sha"] |
| + if !ok { |
| + err = errors.New("disambiguateCommit: expected 'git_sha' key") |
| + return |
| + } |
| + |
| + rawURL, ok := jsonData["redirect_url"] |
| + if !ok { |
| + err = errors.New("disambiguateCommit: expected 'redirect_url' key") |
| + return |
| + } |
| + |
| + url = strings.Split(rawURL, "+")[0] |
| + return |
| +} |
| + |
| +type sortKey struct { |
| + inexactMatch bool |
| + partialMatch bool |
| + nonHead bool |
| + refLength int |
| +} |
| + |
| +// Sorts refCandidate's so that the least one is the best one. |
| +func (s *sortKey) less(other sortKey) bool { |
| + return ((!s.inexactMatch && other.inexactMatch) || |
| + (!s.partialMatch && other.partialMatch) || |
| + (!s.nonHead && other.nonHead) || |
| + (s.refLength < other.refLength)) |
| +} |
| + |
| +type refCandidate struct { |
| + Ref string |
| + Confident bool |
| + SortKey sortKey |
| +} |
| + |
| +type sortableRefCandidates []refCandidate |
| + |
| +func (r sortableRefCandidates) Len() int { return len(r) } |
| +func (r sortableRefCandidates) Swap(i, j int) { r[i], r[j] = r[j], r[i] } |
| +func (r sortableRefCandidates) Less(i, j int) bool { |
| + return r[i].SortKey.less(r[j].SortKey) |
| +} |
| + |
| +func disambiguateRef(gitiles *gitiles.Gitiles, commit, refFragment string) (ref string, err error) { |
| + result := <-gitiles.JSON(map[string]map[string]string{}, "+refs") |
| + if result.Err != nil { |
| + err = result.Err |
| + return |
| + } |
| + refs := *result.DataPtr.(*map[string]map[string]string) |
| + |
| + reverse := func(s []string) (ret []string) { |
| + ret = make([]string, len(s)) |
| + for idx, el := range s { |
| + ret[len(ret)-idx-1] = el |
| + } |
| + return |
| + } |
| + |
| + targetParts := reverse(strings.Split(refFragment, "/")) |
| + |
| + // guess that we'll have about 10 |
| + var candidates sortableRefCandidates = make([]refCandidate, 0, 10) |
|
M-A Ruel
2014/10/18 00:47:06
candidates := make(sortableRefCandidates, 0, 10)
iannucci
2014/10/20 21:11:58
Der... dunno what I was thinking.
|
| + |
| + for ref := range refs { |
| + if strings.HasSuffix(ref, refFragment) { |
| + refParts := reverse(strings.Split(ref, "/")) |
| + |
| + exactMatch := reflect.DeepEqual(refParts, targetParts) |
| + wholeMatch := true |
| + shortest := -1 |
| + if len(refParts) < len(targetParts) { |
| + shortest = len(refParts) |
| + } else { |
| + shortest = len(targetParts) |
| + } |
| + for i := 0; i < shortest; i++ { |
| + if refParts[i] != targetParts[i] { |
| + wholeMatch = false |
| + break |
| + } |
| + } |
| + hasHeads := strings.Contains(ref, "heads") |
| + |
| + confident := exactMatch || (wholeMatch && hasHeads) |
| + |
| + candidates = append(candidates, refCandidate{ |
| + Ref: ref, |
| + Confident: confident, |
| + SortKey: sortKey{ |
| + inexactMatch: !exactMatch, |
| + partialMatch: !wholeMatch, |
| + nonHead: !hasHeads, |
| + refLength: len(ref), |
| + }, |
| + }) |
| + } |
| + } |
| + |
| + if len(candidates) == 0 { |
| + err = fmt.Errorf("disambiguateRef: no matching ref for %s", refFragment) |
| + return |
| + } |
| + |
| + sort.Sort(candidates) |
| + // TODO(riannucci): if we have logging, it would be good to dump all |
| + // candidates in debug mode. |
| + if !candidates[0].Confident { |
| + fmt.Printf("No confident matches for refs given '%s'. Candidates:\n", |
| + refFragment) |
| + for _, c := range candidates { |
| + fmt.Printf(" %s\n", c.Ref) |
| + } |
| + err = errors.New("disambiguateRef: no confident refs") |
| + } |
| + |
| + ref = candidates[0].Ref |
| + return |
| +} |