diff options
| author | Shawn O. Pearce <sop@google.com> | 2010-12-07 10:31:19 -0800 |
|---|---|---|
| committer | Shawn O. Pearce <sop@google.com> | 2010-12-07 11:13:29 -0800 |
| commit | 13f3da50d40b89ee5b05f5f3de9542c20edac6d1 (patch) | |
| tree | d085b6f6b498bde85a1969fce884dd24e88d03d5 /subcmds/upload.py | |
| parent | 3218c13205694434edb2375ab8a8515554eed366 (diff) | |
| parent | 2b8db3ce3e7344b9f3b5216637c5af0d54be5656 (diff) | |
| download | git-repo-13f3da50d40b89ee5b05f5f3de9542c20edac6d1.tar.gz | |
Merge branch 'stable'
* stable: (33 commits)
Added feature to print a <notice> from manifest at the end of a sync.
sync: Use --force-broken to continue other projects
upload: Remove --replace option
sync --quiet: be more quiet
sync: Enable use of git clone --reference
Only delete corrupt pickle config files if they exist
Don't allow git fetch to start ControlMaster
Check for existing SSH ControlMaster
Fix for handling values of EDITOR which contain a space.
upload: Fix --replace flag
rebase: Pass through more options
upload: Allow review.HOST.username to override email
upload -t: Automatically include local branch name
Warn users before uploading if there are local changes
sync: Try fetching a tag as a last resort before giving up
rebase: Automatically rebase branch on upstrea
upload: Automatically --cc folks in review.URL.autocopy
Fix format string bugs in grep
Do not invoke ssh with -p argument when no port has been specified.
Allow files to be copied into new folders
...
Conflicts:
git_config.py
manifest_xml.py
subcmds/init.py
subcmds/sync.py
subcmds/upload.py
Change-Id: I4756a6908277e91505c35287a122a775b68f4df5
Diffstat (limited to 'subcmds/upload.py')
| -rw-r--r-- | subcmds/upload.py | 164 |
1 files changed, 83 insertions, 81 deletions
diff --git a/subcmds/upload.py b/subcmds/upload.py index 2ab6a484..20822096 100644 --- a/subcmds/upload.py +++ b/subcmds/upload.py | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | # See the License for the specific language governing permissions and | 13 | # See the License for the specific language governing permissions and |
| 14 | # limitations under the License. | 14 | # limitations under the License. |
| 15 | 15 | ||
| 16 | import copy | ||
| 16 | import re | 17 | import re |
| 17 | import sys | 18 | import sys |
| 18 | 19 | ||
| @@ -20,6 +21,17 @@ from command import InteractiveCommand | |||
| 20 | from editor import Editor | 21 | from editor import Editor |
| 21 | from error import UploadError | 22 | from error import UploadError |
| 22 | 23 | ||
| 24 | UNUSUAL_COMMIT_THRESHOLD = 5 | ||
| 25 | |||
| 26 | def _ConfirmManyUploads(multiple_branches=False): | ||
| 27 | if multiple_branches: | ||
| 28 | print "ATTENTION: One or more branches has an unusually high number of commits." | ||
| 29 | else: | ||
| 30 | print "ATTENTION: You are uploading an unusually high number of commits." | ||
| 31 | print "YOU PROBABLY DO NOT MEAN TO DO THIS. (Did you rebase across branches?)" | ||
| 32 | answer = raw_input("If you are sure you intend to do this, type 'yes': ").strip() | ||
| 33 | return answer == "yes" | ||
| 34 | |||
| 23 | def _die(fmt, *args): | 35 | def _die(fmt, *args): |
| 24 | msg = fmt % args | 36 | msg = fmt % args |
| 25 | print >>sys.stderr, 'error: %s' % msg | 37 | print >>sys.stderr, 'error: %s' % msg |
| @@ -35,7 +47,7 @@ class Upload(InteractiveCommand): | |||
| 35 | common = True | 47 | common = True |
| 36 | helpSummary = "Upload changes for code review" | 48 | helpSummary = "Upload changes for code review" |
| 37 | helpUsage=""" | 49 | helpUsage=""" |
| 38 | %prog [--re --cc] {[<project>]... | --replace <project>} | 50 | %prog [--re --cc] [<project>]... |
| 39 | """ | 51 | """ |
| 40 | helpDescription = """ | 52 | helpDescription = """ |
| 41 | The '%prog' command is used to send changes to the Gerrit Code | 53 | The '%prog' command is used to send changes to the Gerrit Code |
| @@ -55,12 +67,6 @@ added to the respective list of users, and emails are sent to any | |||
| 55 | new users. Users passed as --reviewers must already be registered | 67 | new users. Users passed as --reviewers must already be registered |
| 56 | with the code review system, or the upload will fail. | 68 | with the code review system, or the upload will fail. |
| 57 | 69 | ||
| 58 | If the --replace option (deprecated) is passed the user can designate | ||
| 59 | which existing change(s) in Gerrit match up to the commits in the | ||
| 60 | branch being uploaded. For each matched pair of change,commit the | ||
| 61 | commit will be added as a new patch set, completely replacing the | ||
| 62 | set of files and description associated with the change in Gerrit. | ||
| 63 | |||
| 64 | Configuration | 70 | Configuration |
| 65 | ------------- | 71 | ------------- |
| 66 | 72 | ||
| @@ -72,6 +78,19 @@ to "true" then repo will assume you always answer "y" at the prompt, | |||
| 72 | and will not prompt you further. If it is set to "false" then repo | 78 | and will not prompt you further. If it is set to "false" then repo |
| 73 | will assume you always answer "n", and will abort. | 79 | will assume you always answer "n", and will abort. |
| 74 | 80 | ||
| 81 | review.URL.autocopy: | ||
| 82 | |||
| 83 | To automatically copy a user or mailing list to all uploaded reviews, | ||
| 84 | you can set a per-project or global Git option to do so. Specifically, | ||
| 85 | review.URL.autocopy can be set to a comma separated list of reviewers | ||
| 86 | who you always want copied on all uploads with a non-empty --re | ||
| 87 | argument. | ||
| 88 | |||
| 89 | review.URL.username: | ||
| 90 | |||
| 91 | Override the username used to connect to Gerrit Code Review. | ||
| 92 | By default the local part of the email address is used. | ||
| 93 | |||
| 75 | The URL must match the review URL listed in the manifest XML file, | 94 | The URL must match the review URL listed in the manifest XML file, |
| 76 | or in the .git/config within the project. For example: | 95 | or in the .git/config within the project. For example: |
| 77 | 96 | ||
| @@ -81,6 +100,7 @@ or in the .git/config within the project. For example: | |||
| 81 | 100 | ||
| 82 | [review "http://review.example.com/"] | 101 | [review "http://review.example.com/"] |
| 83 | autoupload = true | 102 | autoupload = true |
| 103 | autocopy = johndoe@company.com,my-team-alias@company.com | ||
| 84 | 104 | ||
| 85 | References | 105 | References |
| 86 | ---------- | 106 | ---------- |
| @@ -90,9 +110,9 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 90 | """ | 110 | """ |
| 91 | 111 | ||
| 92 | def _Options(self, p): | 112 | def _Options(self, p): |
| 93 | p.add_option('--replace', | 113 | p.add_option('-t', |
| 94 | dest='replace', action='store_true', | 114 | dest='auto_topic', action='store_true', |
| 95 | help='Upload replacement patchsets from this branch (deprecated)') | 115 | help='Send local branch name to Gerrit Code Review') |
| 96 | p.add_option('--re', '--reviewers', | 116 | p.add_option('--re', '--reviewers', |
| 97 | type='string', action='append', dest='reviewers', | 117 | type='string', action='append', dest='reviewers', |
| 98 | help='Request reviews from these people.') | 118 | help='Request reviews from these people.') |
| @@ -100,7 +120,7 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 100 | type='string', action='append', dest='cc', | 120 | type='string', action='append', dest='cc', |
| 101 | help='Also send email to these email addresses.') | 121 | help='Also send email to these email addresses.') |
| 102 | 122 | ||
| 103 | def _SingleBranch(self, branch, people): | 123 | def _SingleBranch(self, opt, branch, people): |
| 104 | project = branch.project | 124 | project = branch.project |
| 105 | name = branch.name | 125 | name = branch.name |
| 106 | remote = project.GetBranch(name).remote | 126 | remote = project.GetBranch(name).remote |
| @@ -129,11 +149,15 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 129 | answer = answer in ('y', 'Y', 'yes', '1', 'true', 't') | 149 | answer = answer in ('y', 'Y', 'yes', '1', 'true', 't') |
| 130 | 150 | ||
| 131 | if answer: | 151 | if answer: |
| 132 | self._UploadAndReport([branch], people) | 152 | if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD: |
| 153 | answer = _ConfirmManyUploads() | ||
| 154 | |||
| 155 | if answer: | ||
| 156 | self._UploadAndReport(opt, [branch], people) | ||
| 133 | else: | 157 | else: |
| 134 | _die("upload aborted by user") | 158 | _die("upload aborted by user") |
| 135 | 159 | ||
| 136 | def _MultipleBranches(self, pending, people): | 160 | def _MultipleBranches(self, opt, pending, people): |
| 137 | projects = {} | 161 | projects = {} |
| 138 | branches = {} | 162 | branches = {} |
| 139 | 163 | ||
| @@ -192,7 +216,30 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 192 | todo.append(branch) | 216 | todo.append(branch) |
| 193 | if not todo: | 217 | if not todo: |
| 194 | _die("nothing uncommented for upload") | 218 | _die("nothing uncommented for upload") |
| 195 | self._UploadAndReport(todo, people) | 219 | |
| 220 | many_commits = False | ||
| 221 | for branch in todo: | ||
| 222 | if len(branch.commits) > UNUSUAL_COMMIT_THRESHOLD: | ||
| 223 | many_commits = True | ||
| 224 | break | ||
| 225 | if many_commits: | ||
| 226 | if not _ConfirmManyUploads(multiple_branches=True): | ||
| 227 | _die("upload aborted by user") | ||
| 228 | |||
| 229 | self._UploadAndReport(opt, todo, people) | ||
| 230 | |||
| 231 | def _AppendAutoCcList(self, branch, people): | ||
| 232 | """ | ||
| 233 | Appends the list of users in the CC list in the git project's config if a | ||
| 234 | non-empty reviewer list was found. | ||
| 235 | """ | ||
| 236 | |||
| 237 | name = branch.name | ||
| 238 | project = branch.project | ||
| 239 | key = 'review.%s.autocopy' % project.GetBranch(name).remote.review | ||
| 240 | raw_list = project.config.GetString(key) | ||
| 241 | if not raw_list is None and len(people[0]) > 0: | ||
| 242 | people[1].extend([entry.strip() for entry in raw_list.split(',')]) | ||
| 196 | 243 | ||
| 197 | def _FindGerritChange(self, branch): | 244 | def _FindGerritChange(self, branch): |
| 198 | last_pub = branch.project.WasPublished(branch.name) | 245 | last_pub = branch.project.WasPublished(branch.name) |
| @@ -206,66 +253,29 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 206 | except: | 253 | except: |
| 207 | return "" | 254 | return "" |
| 208 | 255 | ||
| 209 | def _ReplaceBranch(self, project, people): | 256 | def _UploadAndReport(self, opt, todo, original_people): |
| 210 | branch = project.CurrentBranch | ||
| 211 | if not branch: | ||
| 212 | print >>sys.stdout, "no branches ready for upload" | ||
| 213 | return | ||
| 214 | branch = project.GetUploadableBranch(branch) | ||
| 215 | if not branch: | ||
| 216 | print >>sys.stdout, "no branches ready for upload" | ||
| 217 | return | ||
| 218 | |||
| 219 | script = [] | ||
| 220 | script.append('# Replacing from branch %s' % branch.name) | ||
| 221 | |||
| 222 | if len(branch.commits) == 1: | ||
| 223 | change = self._FindGerritChange(branch) | ||
| 224 | script.append('[%-6s] %s' % (change, branch.commits[0])) | ||
| 225 | else: | ||
| 226 | for commit in branch.commits: | ||
| 227 | script.append('[ ] %s' % commit) | ||
| 228 | |||
| 229 | script.append('') | ||
| 230 | script.append('# Insert change numbers in the brackets to add a new patch set.') | ||
| 231 | script.append('# To create a new change record, leave the brackets empty.') | ||
| 232 | |||
| 233 | script = Editor.EditString("\n".join(script)).split("\n") | ||
| 234 | |||
| 235 | change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$') | ||
| 236 | to_replace = dict() | ||
| 237 | full_hashes = branch.unabbrev_commits | ||
| 238 | |||
| 239 | for line in script: | ||
| 240 | m = change_re.match(line) | ||
| 241 | if m: | ||
| 242 | c = m.group(1) | ||
| 243 | f = m.group(2) | ||
| 244 | try: | ||
| 245 | f = full_hashes[f] | ||
| 246 | except KeyError: | ||
| 247 | print 'fh = %s' % full_hashes | ||
| 248 | print >>sys.stderr, "error: commit %s not found" % f | ||
| 249 | sys.exit(1) | ||
| 250 | if c in to_replace: | ||
| 251 | print >>sys.stderr,\ | ||
| 252 | "error: change %s cannot accept multiple commits" % c | ||
| 253 | sys.exit(1) | ||
| 254 | to_replace[c] = f | ||
| 255 | |||
| 256 | if not to_replace: | ||
| 257 | print >>sys.stderr, "error: no replacements specified" | ||
| 258 | print >>sys.stderr, " use 'repo upload' without --replace" | ||
| 259 | sys.exit(1) | ||
| 260 | |||
| 261 | branch.replace_changes = to_replace | ||
| 262 | self._UploadAndReport([branch], people) | ||
| 263 | |||
| 264 | def _UploadAndReport(self, todo, people): | ||
| 265 | have_errors = False | 257 | have_errors = False |
| 266 | for branch in todo: | 258 | for branch in todo: |
| 267 | try: | 259 | try: |
| 268 | branch.UploadForReview(people) | 260 | people = copy.deepcopy(original_people) |
| 261 | self._AppendAutoCcList(branch, people) | ||
| 262 | |||
| 263 | # Check if there are local changes that may have been forgotten | ||
| 264 | if branch.project.HasChanges(): | ||
| 265 | key = 'review.%s.autoupload' % branch.project.remote.review | ||
| 266 | answer = branch.project.config.GetBoolean(key) | ||
| 267 | |||
| 268 | # if they want to auto upload, let's not ask because it could be automated | ||
| 269 | if answer is None: | ||
| 270 | sys.stdout.write('Uncommitted changes in ' + branch.project.name + ' (did you forget to amend?). Continue uploading? (y/n) ') | ||
| 271 | a = sys.stdin.readline().strip().lower() | ||
| 272 | if a not in ('y', 'yes', 't', 'true', 'on'): | ||
| 273 | print >>sys.stderr, "skipping upload" | ||
| 274 | branch.uploaded = False | ||
| 275 | branch.error = 'User aborted' | ||
| 276 | continue | ||
| 277 | |||
| 278 | branch.UploadForReview(people, auto_topic=opt.auto_topic) | ||
| 269 | branch.uploaded = True | 279 | branch.uploaded = True |
| 270 | except UploadError, e: | 280 | except UploadError, e: |
| 271 | branch.error = e | 281 | branch.error = e |
| @@ -309,14 +319,6 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 309 | cc = _SplitEmails(opt.cc) | 319 | cc = _SplitEmails(opt.cc) |
| 310 | people = (reviewers,cc) | 320 | people = (reviewers,cc) |
| 311 | 321 | ||
| 312 | if opt.replace: | ||
| 313 | if len(project_list) != 1: | ||
| 314 | print >>sys.stderr, \ | ||
| 315 | 'error: --replace requires exactly one project' | ||
| 316 | sys.exit(1) | ||
| 317 | self._ReplaceBranch(project_list[0], people) | ||
| 318 | return | ||
| 319 | |||
| 320 | for project in project_list: | 322 | for project in project_list: |
| 321 | avail = project.GetUploadableBranches() | 323 | avail = project.GetUploadableBranches() |
| 322 | if avail: | 324 | if avail: |
| @@ -325,6 +327,6 @@ Gerrit Code Review: http://code.google.com/p/gerrit/ | |||
| 325 | if not pending: | 327 | if not pending: |
| 326 | print >>sys.stdout, "no branches ready for upload" | 328 | print >>sys.stdout, "no branches ready for upload" |
| 327 | elif len(pending) == 1 and len(pending[0][1]) == 1: | 329 | elif len(pending) == 1 and len(pending[0][1]) == 1: |
| 328 | self._SingleBranch(pending[0][1][0], people) | 330 | self._SingleBranch(opt, pending[0][1][0], people) |
| 329 | else: | 331 | else: |
| 330 | self._MultipleBranches(pending, people) | 332 | self._MultipleBranches(opt, pending, people) |
