internal/task: make Tag idempotent
Proceed if we're trying to write a tag that already exists and matches
the desired commit. That way, if we fail just after writing a tag, or
need to entirely restart a workflow for whatever reason, we can keep
going.
For golang/go#51797.
Change-Id: Ib2fdb13eea6f8cd6d3dd83cdc6cf7d97f12ca6f5
Reviewed-on: https://go-review.googlesource.com/c/build/+/411195
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Run-TryBot: Heschi Kreinick <heschi@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Auto-Submit: Heschi Kreinick <heschi@google.com>
diff --git a/gerrit/gerrit.go b/gerrit/gerrit.go
index 3ced0aa..ed707a8 100644
--- a/gerrit/gerrit.go
+++ b/gerrit/gerrit.go
@@ -774,15 +774,14 @@
return c.do(ctx, nil, "POST", "/changes/"+changeID+"/edit:publish", wantResStatus(http.StatusNoContent))
}
-// ErrProjectNotExist is returned when a project doesn't exist.
+// ErrXNotExist is returned when the requested X doesn't exist.
// It is not necessarily returned unless a method is documented as
// returning it.
-var ErrProjectNotExist = errors.New("gerrit: requested project does not exist")
-
-// ErrChangeNotExist is returned when a change doesn't exist.
-// It is not necessarily returned unless a method is documented as
-// returning it.
-var ErrChangeNotExist = errors.New("gerrit: requested change does not exist")
+var (
+ ErrProjectNotExist = errors.New("gerrit: requested project does not exist")
+ ErrChangeNotExist = errors.New("gerrit: requested change does not exist")
+ ErrTagNotExist = errors.New("gerrit: requested tag does not exist")
+)
// GetProjectInfo returns info about a project.
// If the project doesn't exist, the error will be ErrProjectNotExist.
@@ -883,6 +882,19 @@
return m, nil
}
+// GetTag returns a particular tag on project. If the tag doesn't exist, the
+// error will be ErrTagNotExist.
+//
+// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#get-tag.
+func (c *Client) GetTag(ctx context.Context, project, tag string) (TagInfo, error) {
+ var res TagInfo
+ err := c.do(ctx, &res, "GET", fmt.Sprintf("/projects/%s/tags/%s", project, tag))
+ if he, ok := err.(*HTTPError); ok && he.Res.StatusCode == 404 {
+ return TagInfo{}, ErrTagNotExist
+ }
+ return res, err
+}
+
// TagInput contains information for creating a tag.
// See https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#tag-input
type TagInput struct {
diff --git a/internal/task/gerrit.go b/internal/task/gerrit.go
index 8b4dcee..16bf0d2 100644
--- a/internal/task/gerrit.go
+++ b/internal/task/gerrit.go
@@ -85,7 +85,20 @@
}
func (c *RealGerritClient) Tag(ctx context.Context, project, tag, commit string) error {
- _, err := c.Client.CreateTag(ctx, project, tag, gerrit.TagInput{
+ info, err := c.Client.GetTag(ctx, project, tag)
+ if err != nil && err != gerrit.ErrTagNotExist {
+ return fmt.Errorf("checking if tag already exists: %v", err)
+ }
+ if err == nil {
+ if info.Revision != commit {
+ return fmt.Errorf("tag %q already exists on revision %q rather than our %q", tag, info.Revision, commit)
+ } else {
+ // Nothing to do.
+ return nil
+ }
+ }
+
+ _, err = c.Client.CreateTag(ctx, project, tag, gerrit.TagInput{
Revision: commit,
})
return err