| 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..c0f96b3553144d991dae9645b08fef5dce517624
|
| --- /dev/null
|
| +++ b/go/src/infra/tools/drover/disambiguate.go
|
| @@ -0,0 +1,167 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +package main
|
| +
|
| +import (
|
| + "encoding/json"
|
| + "errors"
|
| + "fmt"
|
| + "net/http"
|
| + "reflect"
|
| + "sort"
|
| + "strings"
|
| +
|
| + "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, err := gitiles.JSON(map[string]map[string]string{}, "+refs")
|
| + if err != nil {
|
| + return
|
| + }
|
| + refs := *result.(*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
|
| + candidates := make(sortableRefCandidates, 0, 10)
|
| +
|
| + 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(iannucci): 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
|
| +}
|
|
|