Skip to content

Commit 228e5d2

Browse files
committed
maintner: fix GerritMeta.Hashtags to look at earlier meta parents for answer
The Gerrit meta commit graph is a linear history. The most recent meta with a "Hashtags: " footer line has the complete set. We just have to go back and look for it. Fixes golang/go#28318 Updates golang/go#28510 (fixes after gopherbot re-deployed) Updates golang/go#28320 (fixes after gopherbot re-deployed) Change-Id: I43705075800ae3d353c1c8f60ab7685883ea5602 Reviewed-on: https://go-review.googlesource.com/c/152779 Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent 36a2830 commit 228e5d2

File tree

3 files changed

+135
-20
lines changed

3 files changed

+135
-20
lines changed

maintner/gerrit.go

+49-19
Original file line numberDiff line numberDiff line change
@@ -364,27 +364,29 @@ func (cl *GerritCL) Branch() string { return cl.branch }
364364
func (cl *GerritCL) updateBranch() {
365365
for i := len(cl.Metas) - 1; i >= 0; i-- {
366366
mc := cl.Metas[i]
367-
branch, _ := lineValue(mc.Commit.Msg, "Branch:")
367+
branch := lineValue(mc.Commit.Msg, "Branch:")
368368
if branch != "" {
369369
cl.branch = strings.TrimPrefix(branch, "refs/heads/")
370370
return
371371
}
372372
}
373373
}
374374

375-
// lineValue extracts a value from an RFC 822-style "key: value" series of lines.
375+
// lineValueOK extracts a value from an RFC 822-style "key: value" series of lines.
376376
// If all is,
377377
// foo: bar
378378
// bar: baz
379379
// lineValue(all, "foo:") returns "bar". It trims any whitespace.
380380
// The prefix is case sensitive and must include the colon.
381-
func lineValue(all, prefix string) (value, rest string) {
381+
// The ok value reports whether a line with such a prefix is found, even if its
382+
// value is empty. If ok is true, the rest value contains the subsequent lines.
383+
func lineValueOK(all, prefix string) (value, rest string, ok bool) {
382384
orig := all
383385
consumed := 0
384386
for {
385387
i := strings.Index(all, prefix)
386388
if i == -1 {
387-
return "", ""
389+
return "", "", false
388390
}
389391
if i > 0 && all[i-1] != '\n' && all[i-1] != '\r' {
390392
all = all[i+len(prefix):]
@@ -399,17 +401,26 @@ func lineValue(all, prefix string) (value, rest string) {
399401
} else {
400402
consumed = len(orig)
401403
}
402-
return strings.TrimSpace(val), orig[consumed:]
404+
return strings.TrimSpace(val), orig[consumed:], true
403405
}
404406
}
405407

408+
func lineValue(all, prefix string) string {
409+
value, _, _ := lineValueOK(all, prefix)
410+
return value
411+
}
412+
413+
func lineValueRest(all, prefix string) (value, rest string) {
414+
value, rest, _ = lineValueOK(all, prefix)
415+
return
416+
}
417+
406418
// WorkInProgress reports whether the CL has its Work-in-progress bit set, per
407419
// https://gerrit-review.googlesource.com/Documentation/intro-user.html#wip
408420
func (cl *GerritCL) WorkInProgress() bool {
409421
var wip bool
410422
for _, m := range cl.Metas {
411-
v, _ := lineValue(m.Commit.Msg, "Work-in-progress:")
412-
switch v {
423+
switch lineValue(m.Commit.Msg, "Work-in-progress:") {
413424
case "true":
414425
wip = true
415426
case "false":
@@ -438,8 +449,7 @@ func (cl *GerritCL) Footer(key string) string {
438449
panic("Footer key does not end in colon")
439450
}
440451
// TODO: git footers are treated as multimaps. Account for this.
441-
v, _ := lineValue(cl.Commit.Msg, key)
442-
return v
452+
return lineValue(cl.Commit.Msg, key)
443453
}
444454

445455
// OwnerID returns the ID of the CL’s owner. It will return -1 on error.
@@ -1292,7 +1302,6 @@ func (gp *GerritProject) check() error {
12921302
type GerritMeta struct {
12931303
// Commit points up to the git commit for this Gerrit NoteDB meta commit.
12941304
Commit *GitCommit
1295-
12961305
// CL is the Gerrit CL this metadata is for.
12971306
CL *GerritCL
12981307

@@ -1324,17 +1333,39 @@ func (m *GerritMeta) Footer() string {
13241333
return m.Commit.Msg[i+2:]
13251334
}
13261335

1327-
// Hashtags returns the current set of hashtags.
1336+
// Hashtags returns the set of hashtags on m's CL as of the time of m.
13281337
func (m *GerritMeta) Hashtags() GerritHashtags {
1329-
tags, _ := lineValue(m.Footer(), "Hashtags: ")
1330-
return GerritHashtags(tags)
1338+
// If this GerritMeta set hashtags, use it.
1339+
tags, _, ok := lineValueOK(m.Footer(), "Hashtags: ")
1340+
if ok {
1341+
return GerritHashtags(tags)
1342+
}
1343+
1344+
// Otherwise, look at older metas (from most recent to oldest)
1345+
// to find most recent value. Ignore anything that's newer
1346+
// than m.
1347+
sawThisMeta := false // whether we've seen 'm'
1348+
metas := m.CL.Metas
1349+
for i := len(metas) - 1; i >= 0; i-- {
1350+
mp := metas[i]
1351+
if mp.Commit.Hash == m.Commit.Hash {
1352+
sawThisMeta = true
1353+
continue
1354+
}
1355+
if !sawThisMeta {
1356+
continue
1357+
}
1358+
if tags, _, ok := lineValueOK(mp.Footer(), "Hashtags: "); ok {
1359+
return GerritHashtags(tags)
1360+
}
1361+
}
1362+
return ""
13311363
}
13321364

13331365
// ActionTag returns the Gerrit "Tag" value from the meta commit.
13341366
// These are of the form "autogenerated:gerrit:setHashtag".
13351367
func (m *GerritMeta) ActionTag() string {
1336-
v, _ := lineValue(m.Footer(), "Tag: ")
1337-
return v
1368+
return lineValue(m.Footer(), "Tag: ")
13381369
}
13391370

13401371
// HashtagEdits returns the hashtags added and removed by this meta commit,
@@ -1354,7 +1385,7 @@ func (m *GerritMeta) HashtagEdits() (added, removed GerritHashtags, ok bool) {
13541385
// Hashtag added: bar
13551386
// Hashtags added: foo, bar
13561387
for len(msg) > 0 {
1357-
value, rest := lineValue(msg, "Hash")
1388+
value, rest := lineValueRest(msg, "Hash")
13581389
msg = rest
13591390
colon := strings.IndexByte(value, ':')
13601391
if colon != -1 {
@@ -1419,8 +1450,7 @@ func (m *GerritMeta) LabelVotes() map[string]map[string]int8 {
14191450
isNew := strings.Contains(footer, "\nTag: autogenerated:gerrit:newPatchSet\n")
14201451
email := mc.Commit.Author.Email()
14211452
if isNew {
1422-
commit, _ := lineValue(footer, "Commit: ")
1423-
if commit != "" {
1453+
if commit := lineValue(footer, "Commit: "); commit != "" {
14241454
// TODO: implement Gerrit's vote copying. For example,
14251455
// label.Label-Name.copyAllScoresIfNoChange defaults to true (as it is with Go's server)
14261456
// https://gerrit-review.googlesource.com/Documentation/config-labels.html#label_copyAllScoresIfNoChange
@@ -1447,7 +1477,7 @@ func (m *GerritMeta) LabelVotes() map[string]map[string]int8 {
14471477
remain := footer
14481478
for len(remain) > 0 {
14491479
var labelEqVal string
1450-
labelEqVal, remain = lineValue(remain, "Label: ")
1480+
labelEqVal, remain = lineValueRest(remain, "Label: ")
14511481
if labelEqVal != "" {
14521482
label, value, whose := parseGerritLabelValue(labelEqVal)
14531483
if label != "" {

maintner/git.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ func (c *Corpus) processGitCommit(commit *maintpb.GitCommit) (*GitCommit, error)
364364
// Patch-set: 1
365365
// Reviewer: Ian Lance Taylor <5206@62eb7196-b449-3ce5-99f1-c037f21e1705>
366366
// Label: Code-Review=+2
367-
if reviewer, _ := lineValue(c.strb(msg), "Reviewer: "); reviewer != "" {
367+
if reviewer := lineValue(c.strb(msg), "Reviewer: "); reviewer != "" {
368368
gc.Reviewer = &GitPerson{Str: reviewer}
369369
}
370370

maintner/godata/godata_test.go

+85
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ import (
88
"bytes"
99
"context"
1010
"fmt"
11+
"os"
12+
"sort"
1113
"strings"
1214
"sync"
1315
"testing"
1416

17+
"cloud.google.com/go/compute/metadata"
18+
"golang.org/x/build/gerrit"
1519
"golang.org/x/build/maintner"
1620
)
1721

@@ -272,3 +276,84 @@ removed "bar" = "quux,blarf"
272276
t.Errorf("got:\n%s\n\nwant prefix:\n%s", got, want)
273277
}
274278
}
279+
280+
func getGerritAuth() (username string, password string, err error) {
281+
var slurp string
282+
if metadata.OnGCE() {
283+
for _, key := range []string{"gopherbot-gerrit-token", "maintner-gerrit-token", "gobot-password"} {
284+
slurp, err = metadata.ProjectAttributeValue(key)
285+
if err != nil || slurp == "" {
286+
continue
287+
}
288+
break
289+
}
290+
}
291+
if slurp == "" {
292+
slurp = os.Getenv("TEST_GERRIT_AUTH")
293+
}
294+
f := strings.SplitN(strings.TrimSpace(slurp), ":", 2)
295+
if len(f) == 1 {
296+
// assume the whole thing is the token
297+
return "git-gobot.golang.org", f[0], nil
298+
}
299+
if len(f) != 2 || f[0] == "" || f[1] == "" {
300+
return "", "", fmt.Errorf("Expected Gerrit token %q to be of form <git-email>:<token>", slurp)
301+
}
302+
return f[0], f[1], nil
303+
}
304+
305+
// Hit the Gerrit API and compare its computation of CLs' hashtags against what maintner thinks.
306+
// Off by default unless $TEST_GERRIT_AUTH is defined with "user:token", or we're running in the
307+
// prod project.
308+
func TestGerritHashtags(t *testing.T) {
309+
if testing.Short() {
310+
t.Skip("skipping in short mode")
311+
}
312+
c := getGoData(t)
313+
user, pass, err := getGerritAuth()
314+
if err != nil {
315+
t.Skipf("no Gerrit auth defined, skipping: %v", err)
316+
}
317+
gc := gerrit.NewClient("https://go-review.googlesource.com", gerrit.BasicAuth(user, pass))
318+
ctx := context.Background()
319+
more := true
320+
n := 0
321+
for more {
322+
// We search Gerrit for "hashtag", which seems to also
323+
// search auto-generated gerrit meta (notedb) texts,
324+
// so this has the effect of searching for all Gerrit
325+
// changes that have ever had hashtags added or
326+
// removed:
327+
cis, err := gc.QueryChanges(ctx, "hashtag", gerrit.QueryChangesOpt{
328+
Start: n,
329+
})
330+
if err != nil {
331+
t.Fatal(err)
332+
}
333+
for _, ci := range cis {
334+
n++
335+
cl := c.Gerrit().Project("go.googlesource.com", ci.Project).CL(int32(ci.ChangeNumber))
336+
if cl == nil {
337+
t.Logf("Ignoring not-in-maintner %s/%v", ci.Project, ci.ChangeNumber)
338+
continue
339+
}
340+
sort.Strings(ci.Hashtags)
341+
want := strings.Join(ci.Hashtags, ", ")
342+
got := canonicalTagList(string(cl.Meta.Hashtags()))
343+
if got != want {
344+
t.Errorf("ci: https://golang.org/cl/%d (%s) -- maintner = %q; want gerrit value %q", ci.ChangeNumber, ci.Project, got, want)
345+
}
346+
more = ci.MoreChanges
347+
}
348+
}
349+
t.Logf("N = %v", n)
350+
}
351+
352+
func canonicalTagList(s string) string {
353+
var sl []string
354+
for _, v := range strings.Split(s, ",") {
355+
sl = append(sl, strings.TrimSpace(v))
356+
}
357+
sort.Strings(sl)
358+
return strings.Join(sl, ", ")
359+
}

0 commit comments

Comments
 (0)