Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 1 | // Copyright 2016 Google Inc. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | // Package gitiles is a client library for the Gitiles source viewer. |
| 16 | package gitiles |
| 17 | |
Han-Wen Nienhuys | f92f01c | 2017-12-04 10:44:14 +0100 | [diff] [blame] | 18 | // The gitiles command set is defined here: |
| 19 | // |
| 20 | // https://gerrit.googlesource.com/gitiles/+/7c07a4a68ece6009909206482e0728dbbf0be77d/java/com/google/gitiles/ViewFilter.java#47 |
| 21 | |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 22 | import ( |
| 23 | "bytes" |
| 24 | "encoding/base64" |
| 25 | "encoding/json" |
Han-Wen Nienhuys | 21c9742 | 2016-07-22 15:23:18 +0200 | [diff] [blame] | 26 | "flag" |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 27 | "fmt" |
Han-Wen Nienhuys | f4bca23 | 2017-12-04 10:11:59 +0100 | [diff] [blame] | 28 | "io" |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 29 | "io/ioutil" |
| 30 | "log" |
| 31 | "net/http" |
| 32 | "net/url" |
| 33 | "path" |
Han-Wen Nienhuys | 1e2b8af | 2016-11-23 13:58:25 +0100 | [diff] [blame] | 34 | "strings" |
Han-Wen Nienhuys | abc8d15 | 2016-07-04 17:26:59 +0200 | [diff] [blame] | 35 | |
Han-Wen Nienhuys | 0213eb6 | 2016-07-19 13:52:51 +0200 | [diff] [blame] | 36 | "github.com/google/slothfs/cookie" |
Han-Wen Nienhuys | abc8d15 | 2016-07-04 17:26:59 +0200 | [diff] [blame] | 37 | "golang.org/x/net/context" |
| 38 | "golang.org/x/time/rate" |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 39 | ) |
| 40 | |
| 41 | // Service is a client for the Gitiles JSON interface. |
| 42 | type Service struct { |
Han-Wen Nienhuys | abc8d15 | 2016-07-04 17:26:59 +0200 | [diff] [blame] | 43 | limiter *rate.Limiter |
| 44 | addr url.URL |
Han-Wen Nienhuys | 3677ba9 | 2016-07-07 18:55:51 +0200 | [diff] [blame] | 45 | client http.Client |
| 46 | agent string |
Han-Wen Nienhuys | 1e2b8af | 2016-11-23 13:58:25 +0100 | [diff] [blame] | 47 | debug bool |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 48 | } |
| 49 | |
| 50 | // Addr returns the address of the gitiles service. |
| 51 | func (s *Service) Addr() string { |
| 52 | return s.addr.String() |
| 53 | } |
| 54 | |
Han-Wen Nienhuys | 21c9742 | 2016-07-22 15:23:18 +0200 | [diff] [blame] | 55 | // Options configures the the Gitiles service. |
Han-Wen Nienhuys | abc8d15 | 2016-07-04 17:26:59 +0200 | [diff] [blame] | 56 | type Options struct { |
Han-Wen Nienhuys | 21c9742 | 2016-07-22 15:23:18 +0200 | [diff] [blame] | 57 | // A URL for the Gitiles service. |
| 58 | Address string |
| 59 | |
Han-Wen Nienhuys | abc8d15 | 2016-07-04 17:26:59 +0200 | [diff] [blame] | 60 | BurstQPS int |
| 61 | SustainedQPS float64 |
Han-Wen Nienhuys | 3677ba9 | 2016-07-07 18:55:51 +0200 | [diff] [blame] | 62 | |
Han-Wen Nienhuys | 21c9742 | 2016-07-22 15:23:18 +0200 | [diff] [blame] | 63 | // Path to a Netscape/Mozilla style cookie file. |
| 64 | CookieJar string |
Han-Wen Nienhuys | 3677ba9 | 2016-07-07 18:55:51 +0200 | [diff] [blame] | 65 | |
| 66 | // UserAgent defines how we present ourself to the server. |
| 67 | UserAgent string |
Han-Wen Nienhuys | 1e2b8af | 2016-11-23 13:58:25 +0100 | [diff] [blame] | 68 | |
Billy Lynch | ecdd255 | 2017-01-12 18:31:36 -0500 | [diff] [blame] | 69 | // HTTPClient allows callers to present their own http.Client instead of the default. |
| 70 | HTTPClient http.Client |
| 71 | |
Han-Wen Nienhuys | 1e2b8af | 2016-11-23 13:58:25 +0100 | [diff] [blame] | 72 | Debug bool |
Han-Wen Nienhuys | abc8d15 | 2016-07-04 17:26:59 +0200 | [diff] [blame] | 73 | } |
| 74 | |
Han-Wen Nienhuys | 21c9742 | 2016-07-22 15:23:18 +0200 | [diff] [blame] | 75 | var defaultOptions Options |
Han-Wen Nienhuys | 0213eb6 | 2016-07-19 13:52:51 +0200 | [diff] [blame] | 76 | |
Han-Wen Nienhuys | 21c9742 | 2016-07-22 15:23:18 +0200 | [diff] [blame] | 77 | // DefineFlags sets up standard command line flags, and returns the |
| 78 | // options struct in which the values are put. |
| 79 | func DefineFlags() *Options { |
Han-Wen Nienhuys | fa03949 | 2016-08-10 12:42:29 +0200 | [diff] [blame] | 80 | flag.StringVar(&defaultOptions.Address, "gitiles_url", "https://android.googlesource.com", "Set the URL of the Gitiles service.") |
| 81 | flag.StringVar(&defaultOptions.CookieJar, "gitiles_cookies", "", "Set path to cURL-style cookie jar file.") |
| 82 | flag.StringVar(&defaultOptions.UserAgent, "gitiles_agent", "slothfs", "Set the User-Agent string to report to Gitiles.") |
| 83 | flag.Float64Var(&defaultOptions.SustainedQPS, "gitiles_qps", 4, "Set the maximum QPS to send to Gitiles.") |
Han-Wen Nienhuys | 1e2b8af | 2016-11-23 13:58:25 +0100 | [diff] [blame] | 84 | flag.BoolVar(&defaultOptions.Debug, "gitiles_debug", false, "Print URLs as they are fetched.") |
Han-Wen Nienhuys | 21c9742 | 2016-07-22 15:23:18 +0200 | [diff] [blame] | 85 | return &defaultOptions |
Han-Wen Nienhuys | 0213eb6 | 2016-07-19 13:52:51 +0200 | [diff] [blame] | 86 | } |
| 87 | |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 88 | // NewService returns a new Gitiles JSON client. |
Han-Wen Nienhuys | 21c9742 | 2016-07-22 15:23:18 +0200 | [diff] [blame] | 89 | func NewService(opts Options) (*Service, error) { |
| 90 | var jar http.CookieJar |
| 91 | if nm := opts.CookieJar; nm != "" { |
| 92 | var err error |
| 93 | jar, err = cookie.NewJar(nm) |
| 94 | if err != nil { |
| 95 | return nil, err |
| 96 | } |
| 97 | if err := cookie.WatchJar(jar, nm); err != nil { |
| 98 | return nil, err |
| 99 | } |
| 100 | } |
| 101 | |
Han-Wen Nienhuys | abc8d15 | 2016-07-04 17:26:59 +0200 | [diff] [blame] | 102 | if opts.SustainedQPS == 0.0 { |
Han-Wen Nienhuys | 965dcb3 | 2016-08-01 14:09:41 +0200 | [diff] [blame] | 103 | opts.SustainedQPS = 4 |
| 104 | } |
| 105 | if opts.BurstQPS == 0 { |
| 106 | opts.BurstQPS = int(10.0 * opts.SustainedQPS) |
| 107 | } else if float64(opts.BurstQPS) < opts.SustainedQPS { |
| 108 | opts.BurstQPS = int(opts.SustainedQPS) + 1 |
Han-Wen Nienhuys | abc8d15 | 2016-07-04 17:26:59 +0200 | [diff] [blame] | 109 | } |
| 110 | |
Han-Wen Nienhuys | 21c9742 | 2016-07-22 15:23:18 +0200 | [diff] [blame] | 111 | url, err := url.Parse(opts.Address) |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 112 | if err != nil { |
| 113 | return nil, err |
| 114 | } |
Han-Wen Nienhuys | 3677ba9 | 2016-07-07 18:55:51 +0200 | [diff] [blame] | 115 | s := &Service{ |
Han-Wen Nienhuys | abc8d15 | 2016-07-04 17:26:59 +0200 | [diff] [blame] | 116 | limiter: rate.NewLimiter(rate.Limit(opts.SustainedQPS), opts.BurstQPS), |
| 117 | addr: *url, |
Han-Wen Nienhuys | 3677ba9 | 2016-07-07 18:55:51 +0200 | [diff] [blame] | 118 | agent: opts.UserAgent, |
Billy Lynch | ecdd255 | 2017-01-12 18:31:36 -0500 | [diff] [blame] | 119 | client: opts.HTTPClient, |
Han-Wen Nienhuys | 3677ba9 | 2016-07-07 18:55:51 +0200 | [diff] [blame] | 120 | } |
| 121 | |
Han-Wen Nienhuys | 21c9742 | 2016-07-22 15:23:18 +0200 | [diff] [blame] | 122 | s.client.Jar = jar |
Han-Wen Nienhuys | 3677ba9 | 2016-07-07 18:55:51 +0200 | [diff] [blame] | 123 | s.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { |
| 124 | req.Header.Set("User-Agent", s.agent) |
| 125 | return nil |
| 126 | } |
Han-Wen Nienhuys | 1e2b8af | 2016-11-23 13:58:25 +0100 | [diff] [blame] | 127 | s.debug = opts.Debug |
Han-Wen Nienhuys | 3677ba9 | 2016-07-07 18:55:51 +0200 | [diff] [blame] | 128 | return s, nil |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 129 | } |
| 130 | |
Han-Wen Nienhuys | f4bca23 | 2017-12-04 10:11:59 +0100 | [diff] [blame] | 131 | func (s *Service) stream(u *url.URL) (*http.Response, error) { |
Han-Wen Nienhuys | abc8d15 | 2016-07-04 17:26:59 +0200 | [diff] [blame] | 132 | ctx := context.Background() |
| 133 | |
| 134 | if err := s.limiter.Wait(ctx); err != nil { |
| 135 | return nil, err |
| 136 | } |
Han-Wen Nienhuys | 3677ba9 | 2016-07-07 18:55:51 +0200 | [diff] [blame] | 137 | req, err := http.NewRequest("GET", u.String(), nil) |
| 138 | if err != nil { |
| 139 | return nil, err |
| 140 | } |
| 141 | req.Header.Add("User-Agent", s.agent) |
| 142 | resp, err := s.client.Do(req) |
Han-Wen Nienhuys | abc8d15 | 2016-07-04 17:26:59 +0200 | [diff] [blame] | 143 | |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 144 | if err != nil { |
| 145 | return nil, err |
| 146 | } |
Han-Wen Nienhuys | f4bca23 | 2017-12-04 10:11:59 +0100 | [diff] [blame] | 147 | |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 148 | if resp.StatusCode != 200 { |
Han-Wen Nienhuys | f4bca23 | 2017-12-04 10:11:59 +0100 | [diff] [blame] | 149 | resp.Body.Close() |
Han-Wen Nienhuys | 39ace57 | 2016-06-02 18:30:51 +0200 | [diff] [blame] | 150 | return nil, fmt.Errorf("%s: %s", u.String(), resp.Status) |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 151 | } |
Han-Wen Nienhuys | 1e2b8af | 2016-11-23 13:58:25 +0100 | [diff] [blame] | 152 | |
| 153 | if s.debug { |
| 154 | log.Printf("%s %s: %d", req.Method, req.URL, resp.StatusCode) |
| 155 | } |
Han-Wen Nienhuys | 3677ba9 | 2016-07-07 18:55:51 +0200 | [diff] [blame] | 156 | if got := resp.Request.URL.String(); got != u.String() { |
Han-Wen Nienhuys | f4bca23 | 2017-12-04 10:11:59 +0100 | [diff] [blame] | 157 | resp.Body.Close() |
Han-Wen Nienhuys | 3677ba9 | 2016-07-07 18:55:51 +0200 | [diff] [blame] | 158 | // We accept redirects, but only for authentication. |
| 159 | // If we get a 200 from a different page than we |
| 160 | // requested, it's probably some sort of login page. |
| 161 | return nil, fmt.Errorf("got URL %s, want %s", got, u.String()) |
| 162 | } |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 163 | |
Han-Wen Nienhuys | f4bca23 | 2017-12-04 10:11:59 +0100 | [diff] [blame] | 164 | return resp, nil |
| 165 | } |
| 166 | |
| 167 | func (s *Service) get(u *url.URL) ([]byte, error) { |
| 168 | resp, err := s.stream(u) |
| 169 | if err != nil { |
| 170 | return nil, err |
| 171 | } |
| 172 | |
| 173 | defer resp.Body.Close() |
| 174 | |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 175 | c, err := ioutil.ReadAll(resp.Body) |
| 176 | if err != nil { |
| 177 | return nil, err |
| 178 | } |
Han-Wen Nienhuys | ae6d11c | 2016-07-11 13:50:45 +0200 | [diff] [blame] | 179 | |
Han-Wen Nienhuys | 163ec59 | 2016-07-11 14:34:17 +0200 | [diff] [blame] | 180 | if resp.Header.Get("Content-Type") == "text/plain; charset=UTF-8" { |
Han-Wen Nienhuys | ae6d11c | 2016-07-11 13:50:45 +0200 | [diff] [blame] | 181 | out := make([]byte, base64.StdEncoding.DecodedLen(len(c))) |
| 182 | n, err := base64.StdEncoding.Decode(out, c) |
| 183 | return out[:n], err |
| 184 | } |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 185 | return c, nil |
| 186 | } |
| 187 | |
| 188 | var xssTag = []byte(")]}'\n") |
| 189 | |
Han-Wen Nienhuys | f065f14 | 2016-07-11 14:03:57 +0200 | [diff] [blame] | 190 | func (s *Service) getJSON(u *url.URL, dest interface{}) error { |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 191 | c, err := s.get(u) |
| 192 | if err != nil { |
| 193 | return err |
| 194 | } |
| 195 | |
| 196 | if !bytes.HasPrefix(c, xssTag) { |
| 197 | return fmt.Errorf("Gitiles JSON %s missing XSS tag: %q", u, c) |
| 198 | } |
| 199 | c = c[len(xssTag):] |
| 200 | |
| 201 | err = json.Unmarshal(c, dest) |
| 202 | if err != nil { |
| 203 | err = fmt.Errorf("Unmarshal(%s): %v", u, err) |
| 204 | } |
| 205 | return err |
| 206 | } |
| 207 | |
| 208 | // List retrieves the list of projects. |
Han-Wen Nienhuys | 8b96081 | 2016-07-13 18:15:57 +0200 | [diff] [blame] | 209 | func (s *Service) List(branches []string) (map[string]*Project, error) { |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 210 | listURL := s.addr |
| 211 | listURL.RawQuery = "format=JSON" |
Han-Wen Nienhuys | 8b96081 | 2016-07-13 18:15:57 +0200 | [diff] [blame] | 212 | for _, b := range branches { |
| 213 | listURL.RawQuery += "&b=" + b |
| 214 | } |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 215 | |
| 216 | projects := map[string]*Project{} |
Han-Wen Nienhuys | f065f14 | 2016-07-11 14:03:57 +0200 | [diff] [blame] | 217 | err := s.getJSON(&listURL, &projects) |
Han-Wen Nienhuys | d42875c | 2016-11-30 19:07:27 +0100 | [diff] [blame] | 218 | for k, v := range projects { |
| 219 | if k != v.Name { |
| 220 | return nil, fmt.Errorf("gitiles: key %q had project name %q", k, v.Name) |
| 221 | } |
| 222 | } |
| 223 | |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 224 | return projects, err |
| 225 | } |
| 226 | |
Han-Wen Nienhuys | a501c04 | 2016-07-21 16:28:49 +0200 | [diff] [blame] | 227 | // NewRepoService creates a service for a specific repository on a Gitiles server. |
Han-Wen Nienhuys | 39ace57 | 2016-06-02 18:30:51 +0200 | [diff] [blame] | 228 | func (s *Service) NewRepoService(name string) *RepoService { |
| 229 | return &RepoService{ |
| 230 | Name: name, |
| 231 | service: s, |
| 232 | } |
| 233 | } |
| 234 | |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 235 | // RepoService is a JSON client for the functionality of a specific |
| 236 | // respository. |
| 237 | type RepoService struct { |
| 238 | Name string |
| 239 | service *Service |
| 240 | } |
| 241 | |
Han-Wen Nienhuys | 27dde40 | 2016-06-03 17:44:33 +0200 | [diff] [blame] | 242 | // Get retrieves a single project. |
| 243 | func (s *RepoService) Get() (*Project, error) { |
| 244 | jsonURL := s.service.addr |
| 245 | jsonURL.Path = path.Join(jsonURL.Path, s.Name) |
| 246 | jsonURL.RawQuery = "format=JSON" |
| 247 | |
| 248 | var p Project |
Han-Wen Nienhuys | f065f14 | 2016-07-11 14:03:57 +0200 | [diff] [blame] | 249 | err := s.service.getJSON(&jsonURL, &p) |
Han-Wen Nienhuys | 27dde40 | 2016-06-03 17:44:33 +0200 | [diff] [blame] | 250 | return &p, err |
| 251 | } |
| 252 | |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 253 | // GetBlob fetches a blob. |
| 254 | func (s *RepoService) GetBlob(branch, filename string) ([]byte, error) { |
| 255 | blobURL := s.service.addr |
| 256 | |
| 257 | blobURL.Path = path.Join(blobURL.Path, s.Name, "+show", branch, filename) |
| 258 | blobURL.RawQuery = "format=TEXT" |
| 259 | |
| 260 | // TODO(hanwen): invent a more structured mechanism for logging. |
| 261 | log.Println(blobURL.String()) |
Han-Wen Nienhuys | f065f14 | 2016-07-11 14:03:57 +0200 | [diff] [blame] | 262 | return s.service.get(&blobURL) |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 263 | } |
| 264 | |
Han-Wen Nienhuys | f4bca23 | 2017-12-04 10:11:59 +0100 | [diff] [blame] | 265 | // Archive formats for +archive. JGit also supports some shorthands. |
| 266 | const ( |
| 267 | ArchiveTbz = "tar.bz2" |
| 268 | ArchiveTgz = "tar.gz" |
| 269 | ArchiveTar = "tar" |
| 270 | ArchiveTxz = "tar.xz" |
| 271 | |
| 272 | // the Gitiles source code claims .tar.xz and .tar are |
| 273 | // supported, but googlesource.com doesn't support it, |
| 274 | // apparently. In addition, JGit provides ZipFormat, but |
| 275 | // gitiles doesn't support it. |
| 276 | ) |
| 277 | |
| 278 | // GetArchive downloads an archive of the project. Format is one |
| 279 | // ArchivXxx formats. dirPrefix, if given, restricts to the given |
| 280 | // subpath, and strips the path prefix from the files in the resulting |
| 281 | // tar archive. revision is a git revision, either a branch/tag name |
| 282 | // ("master") or a hex commit SHA1. |
| 283 | func (s *RepoService) GetArchive(revision, dirPrefix, format string) (io.ReadCloser, error) { |
| 284 | u := s.service.addr |
| 285 | u.Path = path.Join(u.Path, s.Name, "+archive", revision) |
| 286 | if dirPrefix != "" { |
| 287 | u.Path = path.Join(u.Path, dirPrefix) |
| 288 | } |
| 289 | u.Path += "." + format |
| 290 | resp, err := s.service.stream(&u) |
| 291 | if err != nil { |
| 292 | return nil, err |
| 293 | } |
| 294 | return resp.Body, err |
| 295 | } |
| 296 | |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 297 | // GetTree fetches a tree. The dir argument may not point to a |
| 298 | // blob. If recursive is given, the server recursively expands the |
| 299 | // tree. |
| 300 | func (s *RepoService) GetTree(branch, dir string, recursive bool) (*Tree, error) { |
| 301 | jsonURL := s.service.addr |
| 302 | jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+", branch, dir) |
Han-Wen Nienhuys | 1e2b8af | 2016-11-23 13:58:25 +0100 | [diff] [blame] | 303 | if !strings.HasSuffix(jsonURL.Path, "/") { |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 304 | jsonURL.Path += "/" |
| 305 | } |
| 306 | jsonURL.RawQuery = "format=JSON&long=1" |
| 307 | |
| 308 | if recursive { |
| 309 | jsonURL.RawQuery += "&recursive=1" |
| 310 | } |
| 311 | |
| 312 | var tree Tree |
Han-Wen Nienhuys | f065f14 | 2016-07-11 14:03:57 +0200 | [diff] [blame] | 313 | err := s.service.getJSON(&jsonURL, &tree) |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 314 | return &tree, err |
| 315 | } |
| 316 | |
| 317 | // GetCommit gets the data of a commit in a branch. |
| 318 | func (s *RepoService) GetCommit(branch string) (*Commit, error) { |
| 319 | jsonURL := s.service.addr |
| 320 | jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+", branch) |
| 321 | jsonURL.RawQuery = "format=JSON" |
| 322 | |
| 323 | var c Commit |
Han-Wen Nienhuys | f065f14 | 2016-07-11 14:03:57 +0200 | [diff] [blame] | 324 | err := s.service.getJSON(&jsonURL, &c) |
Han-Wen Nienhuys | 4bf7fcc | 2016-06-02 15:03:59 +0200 | [diff] [blame] | 325 | return &c, err |
| 326 | } |
Han-Wen Nienhuys | f92f01c | 2017-12-04 10:44:14 +0100 | [diff] [blame] | 327 | |
| 328 | // Options for Describe. |
| 329 | const ( |
| 330 | // Return a ref that contains said commmit |
| 331 | DescribeContains = "contains" |
| 332 | |
| 333 | // Return any type of ref |
| 334 | DescribeAll = "all" |
| 335 | |
| 336 | // Only return a tag ref |
| 337 | DescribeTags = "tags" |
| 338 | |
| 339 | // The default for 'contains': return annotated tags |
| 340 | DescribeAnnotatedTags = "" |
| 341 | ) |
| 342 | |
| 343 | // Describe describes a possibly shortened commit hash as a ref that |
| 344 | // is visible to the caller. Currently, only the 'contains' flavor is |
| 345 | // implemented, so options must always include 'contains'. |
| 346 | func (s *RepoService) Describe(revision string, options ...string) (string, error) { |
| 347 | jsonURL := s.service.addr |
| 348 | jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+describe", revision) |
| 349 | jsonURL.RawQuery = "format=JSON&" + strings.Join(options, "&") |
| 350 | |
| 351 | result := map[string]string{} |
| 352 | err := s.service.getJSON(&jsonURL, &result) |
| 353 | if err != nil { |
| 354 | return "", err |
| 355 | } |
| 356 | |
| 357 | if len(result) != 1 { |
| 358 | return "", fmt.Errorf("gitiles: got map %v, want just one entry", result) |
| 359 | } |
| 360 | |
| 361 | for _, v := range result { |
| 362 | return v, nil |
| 363 | } |
| 364 | |
| 365 | panic("unreachable.") |
| 366 | } |
Han-Wen Nienhuys | 85e1ea1 | 2017-12-04 10:58:26 +0100 | [diff] [blame] | 367 | |
| 368 | // Refs returns the refs of a repository, optionally filtered by prefix. |
| 369 | func (s *RepoService) Refs(prefix string) (map[string]*RefData, error) { |
| 370 | |
| 371 | jsonURL := s.service.addr |
| 372 | jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+refs") |
| 373 | if prefix != "" { |
| 374 | jsonURL.Path = path.Join(jsonURL.Path, prefix) |
| 375 | } |
| 376 | jsonURL.RawQuery = "format=JSON" |
| 377 | |
| 378 | result := map[string]*RefData{} |
| 379 | err := s.service.getJSON(&jsonURL, &result) |
| 380 | if err != nil { |
| 381 | return nil, err |
| 382 | } |
| 383 | |
| 384 | return result, err |
| 385 | } |