|
8 | 8 | package redirect // import "golang.org/x/tools/godoc/redirect"
|
9 | 9 |
|
10 | 10 | import (
|
| 11 | + "context" |
11 | 12 | "fmt"
|
| 13 | + "html/template" |
12 | 14 | "net/http"
|
13 | 15 | "os"
|
14 | 16 | "regexp"
|
15 | 17 | "strconv"
|
16 | 18 | "strings"
|
| 19 | + "sync" |
| 20 | + "time" |
| 21 | + |
| 22 | + "golang.org/x/net/context/ctxhttp" |
17 | 23 | )
|
18 | 24 |
|
19 | 25 | // Register registers HTTP handlers that redirect old godoc paths to their new
|
@@ -193,18 +199,82 @@ func clHandler(w http.ResponseWriter, r *http.Request) {
|
193 | 199 | target := ""
|
194 | 200 |
|
195 | 201 | if n, err := strconv.Atoi(id); err == nil && isRietveldCL(n) {
|
196 |
| - // TODO: Issue 28836: if this Rietveld CL happens to |
| 202 | + // Issue 28836: if this Rietveld CL happens to |
197 | 203 | // also be a Gerrit CL, render a disambiguation HTML
|
198 |
| - // page with two links instead. We'll need to make an |
199 |
| - // RPC (to maintner?) to figure that out. For now just |
200 |
| - // redirect to rietveld. |
| 204 | + // page with two links instead. We need to make a |
| 205 | + // Gerrit API call to figure that out, but we cache |
| 206 | + // known Gerrit CLs so it's done at most once per CL. |
| 207 | + if ok, err := isGerritCL(r.Context(), n); err == nil && ok { |
| 208 | + w.Header().Set("Content-Type", "text/html; charset=utf-8") |
| 209 | + clDisambiguationHTML.Execute(w, n) |
| 210 | + return |
| 211 | + } |
| 212 | + |
201 | 213 | target = "https://codereview.appspot.com/" + id
|
202 | 214 | } else {
|
203 | 215 | target = "https://go-review.googlesource.com/" + id
|
204 | 216 | }
|
205 | 217 | http.Redirect(w, r, target, http.StatusFound)
|
206 | 218 | }
|
207 | 219 |
|
| 220 | +var clDisambiguationHTML = template.Must(template.New("").Parse(`<!DOCTYPE html> |
| 221 | +<html lang="en"> |
| 222 | + <head> |
| 223 | + <title>Go CL {{.}} Disambiguation</title> |
| 224 | + <meta name="viewport" content="width=device-width"> |
| 225 | + </head> |
| 226 | + <body> |
| 227 | + CL number {{.}} exists in both Gerrit (the current code review system) |
| 228 | + and Rietveld (the previous code review system). Please make a choice: |
| 229 | +
|
| 230 | + <ul> |
| 231 | + <li><a href="https://go-review.googlesource.com/{{.}}">Gerrit CL {{.}}</a></li> |
| 232 | + <li><a href="https://codereview.appspot.com/{{.}}">Rietveld CL {{.}}</a></li> |
| 233 | + </ul> |
| 234 | + </body> |
| 235 | +</html>`)) |
| 236 | + |
| 237 | +// isGerritCL reports whether a Gerrit CL with the specified numeric change ID (e.g., "4247") |
| 238 | +// is known to exist by querying the Gerrit API at https://go-review.googlesource.com. |
| 239 | +// isGerritCL uses gerritCLCache as a cache of Gerrit CL IDs that exist. |
| 240 | +func isGerritCL(ctx context.Context, id int) (bool, error) { |
| 241 | + // Check cache first. |
| 242 | + gerritCLCache.Lock() |
| 243 | + ok := gerritCLCache.exist[id] |
| 244 | + gerritCLCache.Unlock() |
| 245 | + if ok { |
| 246 | + return true, nil |
| 247 | + } |
| 248 | + |
| 249 | + // Query the Gerrit API Get Change endpoint, as documented at |
| 250 | + // https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-change. |
| 251 | + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) |
| 252 | + defer cancel() |
| 253 | + resp, err := ctxhttp.Get(ctx, nil, fmt.Sprintf("https://go-review.googlesource.com/changes/%d", id)) |
| 254 | + if err != nil { |
| 255 | + return false, err |
| 256 | + } |
| 257 | + resp.Body.Close() |
| 258 | + switch resp.StatusCode { |
| 259 | + case http.StatusOK: |
| 260 | + // A Gerrit CL with this ID exists. Add it to cache. |
| 261 | + gerritCLCache.Lock() |
| 262 | + gerritCLCache.exist[id] = true |
| 263 | + gerritCLCache.Unlock() |
| 264 | + return true, nil |
| 265 | + case http.StatusNotFound: |
| 266 | + // A Gerrit CL with this ID doesn't exist. It may get created in the future. |
| 267 | + return false, nil |
| 268 | + default: |
| 269 | + return false, fmt.Errorf("unexpected status code: %v", resp.Status) |
| 270 | + } |
| 271 | +} |
| 272 | + |
| 273 | +var gerritCLCache = struct { |
| 274 | + sync.Mutex |
| 275 | + exist map[int]bool // exist is a set of Gerrit CL IDs that are known to exist. |
| 276 | +}{exist: make(map[int]bool)} |
| 277 | + |
208 | 278 | var changeMap *hashMap
|
209 | 279 |
|
210 | 280 | // LoadChangeMap loads the specified map of Mercurial to Git revisions,
|
|
0 commit comments