Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 package main | |
| 2 | |
| 3 import "encoding/json" | |
| 4 import "errors" | |
| 5 import "fmt" | |
| 6 import "net/http" | |
| 7 import "reflect" | |
| 8 import "sort" | |
| 9 import "strings" | |
| 10 | |
| 11 import "infra/libs/gitiles" | |
| 12 | |
| 13 const crRevAPI = "https://cr-rev.appspot.com/_ah/api/crrev/v1/" | |
| 14 | |
| 15 /* Takes a user-suppiled commit specifier, and resolves it to a commit hash and | |
| 16 repo url. | |
| 17 | |
| 18 Possible input formats: | |
| 19 "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef" | |
| 20 "29345" # Cr-Commit-Position assuming refs/heads/master and src.git | |
| 21 | |
| 22 The disambiguation is done by crrev.com, and so this function may return an | |
| 23 error if the service is not available. | |
| 24 | |
| 25 Additionally, this function may return an error if the commit specifier is | |
| 26 not unambiguous. | |
| 27 */ | |
| 28 func disambiguateCommit(commitSpec string) (commit, url string, err error) { | |
| 29 commit = commitSpec | |
| 30 url = "" | |
| 31 | |
| 32 resp, err := http.Get(crRevAPI + "redirect/" + commitSpec) | |
| 33 if err != nil { | |
| 34 return | |
| 35 } | |
| 36 defer resp.Body.Close() | |
| 37 | |
| 38 // TODO(iannucci,stip): generate this from endpoints source code | |
| 39 jsonData := make(map[string]string) | |
| 40 if err = json.NewDecoder(resp.Body).Decode(&jsonData); err != nil { | |
| 41 return | |
| 42 } | |
| 43 commit, ok := jsonData["git_sha"] | |
| 44 if !ok { | |
| 45 err = errors.New("disambiguateCommit: expected 'git_sha' key") | |
| 46 return | |
| 47 } | |
| 48 | |
| 49 rawURL, ok := jsonData["redirect_url"] | |
| 50 if !ok { | |
| 51 err = errors.New("disambiguateCommit: expected 'redirect_url' ke y") | |
| 52 return | |
| 53 } | |
| 54 | |
| 55 url = strings.Split(rawURL, "+")[0] | |
| 56 return | |
| 57 } | |
| 58 | |
| 59 type sortKey struct { | |
| 60 inexactMatch bool | |
| 61 partialMatch bool | |
| 62 nonHead bool | |
| 63 refLength int | |
| 64 } | |
| 65 | |
| 66 // Sorts refCandidate's so that the least one is the best one. | |
| 67 func (s *sortKey) less(other sortKey) bool { | |
| 68 return ((!s.inexactMatch && other.inexactMatch) || | |
| 69 (!s.partialMatch && other.partialMatch) || | |
| 70 (!s.nonHead && other.nonHead) || | |
| 71 (s.refLength < other.refLength)) | |
| 72 } | |
| 73 | |
| 74 type refCandidate struct { | |
| 75 Ref string | |
| 76 Confident bool | |
| 77 SortKey sortKey | |
| 78 } | |
| 79 | |
| 80 type sortableRefCandidates []refCandidate | |
| 81 | |
| 82 func (r sortableRefCandidates) Len() int { return len(r) } | |
| 83 func (r sortableRefCandidates) Swap(i, j int) { r[i], r[j] = r[j], r[i] } | |
| 84 func (r sortableRefCandidates) Less(i, j int) bool { | |
| 85 return r[i].SortKey.less(r[j].SortKey) | |
| 86 } | |
| 87 | |
| 88 func disambiguateRef(gitiles *gitiles.Gitiles, commit, refFragment string) (ref string, err error) { | |
| 89 result := <-gitiles.JSON(map[string]map[string]string{}, "+refs") | |
| 90 if result.Err != nil { | |
| 91 err = result.Err | |
| 92 return | |
| 93 } | |
| 94 refs := *result.DataPtr.(*map[string]map[string]string) | |
| 95 | |
| 96 reverse := func(s []string) (ret []string) { | |
| 97 ret = make([]string, len(s)) | |
| 98 for idx, el := range s { | |
| 99 ret[len(ret)-idx-1] = el | |
| 100 } | |
| 101 return | |
| 102 } | |
| 103 | |
| 104 targetParts := reverse(strings.Split(refFragment, "/")) | |
| 105 | |
| 106 // guess that we'll have about 10 | |
| 107 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.
| |
| 108 | |
| 109 for ref := range refs { | |
| 110 if strings.HasSuffix(ref, refFragment) { | |
| 111 refParts := reverse(strings.Split(ref, "/")) | |
| 112 | |
| 113 exactMatch := reflect.DeepEqual(refParts, targetParts) | |
| 114 wholeMatch := true | |
| 115 shortest := -1 | |
| 116 if len(refParts) < len(targetParts) { | |
| 117 shortest = len(refParts) | |
| 118 } else { | |
| 119 shortest = len(targetParts) | |
| 120 } | |
| 121 for i := 0; i < shortest; i++ { | |
| 122 if refParts[i] != targetParts[i] { | |
| 123 wholeMatch = false | |
| 124 break | |
| 125 } | |
| 126 } | |
| 127 hasHeads := strings.Contains(ref, "heads") | |
| 128 | |
| 129 confident := exactMatch || (wholeMatch && hasHeads) | |
| 130 | |
| 131 candidates = append(candidates, refCandidate{ | |
| 132 Ref: ref, | |
| 133 Confident: confident, | |
| 134 SortKey: sortKey{ | |
| 135 inexactMatch: !exactMatch, | |
| 136 partialMatch: !wholeMatch, | |
| 137 nonHead: !hasHeads, | |
| 138 refLength: len(ref), | |
| 139 }, | |
| 140 }) | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 if len(candidates) == 0 { | |
| 145 err = fmt.Errorf("disambiguateRef: no matching ref for %s", refF ragment) | |
| 146 return | |
| 147 } | |
| 148 | |
| 149 sort.Sort(candidates) | |
| 150 // TODO(riannucci): if we have logging, it would be good to dump all | |
| 151 // candidates in debug mode. | |
| 152 if !candidates[0].Confident { | |
| 153 fmt.Printf("No confident matches for refs given '%s'. Candidates :\n", | |
| 154 refFragment) | |
| 155 for _, c := range candidates { | |
| 156 fmt.Printf(" %s\n", c.Ref) | |
| 157 } | |
| 158 err = errors.New("disambiguateRef: no confident refs") | |
| 159 } | |
| 160 | |
| 161 ref = candidates[0].Ref | |
| 162 return | |
| 163 } | |
| OLD | NEW |