File: merge_to_branch_gerrit.py

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (159 lines) | stat: -rwxr-xr-x 5,105 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/usr/bin/env vpython3
# Copyright 2024 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import argparse
import logging
import re
import sys

from pathlib import Path

# Add depot tools to the sys path, for gerrit_util
BASE_PATH = Path(__file__).resolve().parent.parent.parent
DEPOT_TOOLS_PATH = BASE_PATH / 'third_party' / 'depot_tools'
sys.path.append(str(DEPOT_TOOLS_PATH))

import gerrit_util

GERRIT_HOST = 'chromium-review.googlesource.com'

TAGS_TO_STRIP = [
    "Change-Id",
    "Reviewed-on",
    "Reviewed-by",
    "Commit-Queue",
    "Cr-Commit-Position",
    "Auto-Submit",
]


# TODO(leszeks): Upstream to gerrit_util
def CherryPickWithMessage(host,
                          change,
                          destination,
                          revision='current',
                          message=None):
  """Create a cherry-pick commit from the given change, onto the given
    destination.
    """
  path = 'changes/%s/revisions/%s/cherrypick' % (change, revision)
  body = {'destination': destination, 'allow_conflicts': True}
  if message is not None:
    body['message'] = message
  conn = gerrit_util.CreateHttpConn(host, path, reqtype='POST', body=body)
  return gerrit_util.ReadHttpJsonResponse(conn)


def main(sys_args=None):
  sys_args = sys_args or sys.argv[1:]
  parser = argparse.ArgumentParser(
      description="Use the gerrit API to cherry-pick a revision onto a branch")
  parser.add_argument(
      "-r",
      "--reviewer",
      help="The author email used for code review (defaults to reviewers of the"
      "original CL). Can specify multiple reviewers. Appends @chromium.org if "
      "not specified.",
      default=[],
      nargs="*")
  parser.add_argument(
      "-b",
      "--branch",
      help="The branch to merge to (e.g. 12.0).",
      required=True)
  # TODO(leszeks): Add support for more than one revision. This will need to
  # cherry pick one of them using the gerrit API, then somehow applying the rest
  # onto it as additional patches.
  parser.add_argument("revision", nargs=1, help="The revision to merge.")

  options = parser.parse_args(sys_args)

  revision = options.revision[0]

  branch = options.branch
  if not re.match(r"[0-9]+\.[0-9]+", branch):
    print("Branch is not of the form 1.2")
    return 1
  branch_rev = f'refs/branch-heads/{branch}'

  reviewers = []
  for reviewer in options.reviewer:
    if not re.match(r".+@.+\..+", reviewer):
      reviewer = f"{reviewer}@chromium.org"
    reviewers.append(reviewer)

  print(f"Cherry-picking {revision} onto {branch_rev}")

  # Create the commit message, using the new version and information about the
  # original commit.
  print("Creating commit message...")
  original_commit = gerrit_util.GetChangeCommit(GERRIT_HOST, revision)

  commit_msg = original_commit['message'].splitlines()
  if not reviewers:
    for line in commit_msg:
      m = re.match(r"^Reviewed-by:\s*(.+)$", line)
      if m:
        reviewers.append(m.group(1))
    print(f"Picking reviewers from original commit: {','.join(reviewers)}")

  commit_msg[0] = f'Merged: {commit_msg[0]}'
  commit_msg[1:] = [
      line for line in commit_msg[1:]
      if not re.match(r"^(" + "|".join(TAGS_TO_STRIP) + r")\s*:", line)
  ]
  commit_msg.append(f"(cherry picked from commit {original_commit['commit']})")

  print("New commit message:")
  for line in commit_msg:
    print("| " + line)

  # Create a cherry pick commit from the original commit.
  cherry_pick = CherryPickWithMessage(
      GERRIT_HOST, revision, branch_rev, message="\n".join(commit_msg))
  print(cherry_pick)
  # Use the cherry pick number to refer to it, rather than the 'id', because
  # cherry picks end up having the same Change-Id as the original CL.
  cherry_pick_id = cherry_pick['_number']
  print(
      f"Created cherry-pick: https://{GERRIT_HOST}/c/{cherry_pick['_number']}")

  has_conflicts = cherry_pick.get('contains_git_conflicts', False)
  if has_conflicts:
    print("==================================================")
    print("WARNING: Cherry-pick created with merge conflicts.")
    print("Resolve locally with:")
    print(
        f"  git cl patch --branch merge-{cherry_pick_id} --force {cherry_pick_id}"
    )
    print(
        f"  git fetch --refmap='' origin refs/branch-heads/{branch}:refs/remotes/branch-heads/{branch}"
    )
    print(f"  git branch --set-upstream-to branch-heads/{branch}")
    print("==================================================")

  # Set Auto-Submit +1
  print("Setting 'Auto-Submit +1'...")
  try:
    gerrit_util.SetReview(
        GERRIT_HOST,
        cherry_pick_id,
        labels={"Auto-Submit": 1},
        ready=not has_conflicts)
  except:
    logging.warning("Could not set Auto-Submit +1")

  print(f"Adding {','.join(reviewers)} as reviewer(s)...")
  gerrit_util.AddReviewers(
      GERRIT_HOST,
      cherry_pick_id,
      reviewers=reviewers,
      notify=not has_conflicts)

  print("Done.")


if __name__ == "__main__":  # pragma: no cover
  sys.exit(main())