diff --git a/gerrit_upload.py b/gerrit_upload.py index f8dccdc5..32451408 100755 --- a/gerrit_upload.py +++ b/gerrit_upload.py @@ -75,6 +75,7 @@ def UploadBundle(project, dest_branch, src_branch, bases, + replace_changes = None, save_cookies=True): srv = _GetRpcServer(email, server, save_cookies) @@ -113,6 +114,10 @@ def UploadBundle(project, req.dest_branch = str(dest_branch) for c in revlist: req.contained_object.append(c) + for change_id,commit_id in replace_changes.iteritems(): + r = req.replace.add() + r.change_id = change_id + r.object_id = commit_id else: req = UploadBundleContinue() req.bundle_id = bundle_id @@ -148,6 +153,10 @@ def UploadBundle(project, elif rsp.status_code == UploadBundleResponse.UNAUTHORIZED_USER: reason = ('Unauthorized user. Visit http://%s/hello to sign up.' % server) + elif rsp.status_code == UploadBundleResponse.UNKNOWN_CHANGE: + reason = 'invalid change id' + elif rsp.status_code == UploadBundleResponse.CHANGE_CLOSED: + reason = 'one or more changes are closed' else: reason = 'unknown error ' + str(rsp.status_code) raise UploadError(reason) diff --git a/project.py b/project.py index 1e25c2c9..39550335 100644 --- a/project.py +++ b/project.py @@ -104,6 +104,7 @@ class ReviewableBranch(object): self.project = project self.branch = branch self.base = base + self.replace_changes = None @property def name(self): @@ -123,6 +124,16 @@ class ReviewableBranch(object): '--') return self._commit_cache + @property + def unabbrev_commits(self): + r = dict() + for commit in self.project.bare_git.rev_list( + not_rev(self.base), + R_HEADS + self.name, + '--'): + r[commit[0:8]] = commit + return r + @property def date(self): return self.project.bare_git.log( @@ -132,7 +143,8 @@ class ReviewableBranch(object): '--') def UploadForReview(self): - self.project.UploadForReview(self.name) + self.project.UploadForReview(self.name, + self.replace_changes) @property def tip_url(self): @@ -444,7 +456,7 @@ class Project(object): return rb return None - def UploadForReview(self, branch=None): + def UploadForReview(self, branch=None, replace_changes=None): """Uploads the named branch for code review. """ if branch is None: @@ -482,7 +494,8 @@ class Project(object): dest_project = branch.remote.projectname, dest_branch = dest_branch, src_branch = R_HEADS + branch.name, - bases = base_list) + bases = base_list, + replace_changes = replace_changes) except proto_client.ClientLoginError: raise UploadError('Login failure') except urllib2.HTTPError, e: diff --git a/subcmds/upload.py b/subcmds/upload.py index 9018455f..11f035d7 100644 --- a/subcmds/upload.py +++ b/subcmds/upload.py @@ -29,7 +29,7 @@ class Upload(InteractiveCommand): common = True helpSummary = "Upload changes for code review" helpUsage=""" -%prog []... +%prog {[]... | --replace } """ helpDescription = """ The '%prog' command is used to send changes to the Gerrit code @@ -46,6 +46,11 @@ no projects are specified, '%prog' will search for uploadable changes in all projects listed in the manifest. """ + def _Options(self, p): + p.add_option('--replace', + dest='replace', action='store_true', + help='Upload replacement patchesets from this branch') + def _SingleBranch(self, branch): project = branch.project name = branch.name @@ -129,6 +134,50 @@ changes in all projects listed in the manifest. _die("nothing uncommented for upload") self._UploadAndReport(todo) + def _ReplaceBranch(self, project): + branch = project.CurrentBranch + if not branch: + print >>sys.stdout, "no branches ready for upload" + return + branch = project.GetUploadableBranch(branch) + if not branch: + print >>sys.stdout, "no branches ready for upload" + return + + script = [] + script.append('# Replacing from branch %s' % branch.name) + for commit in branch.commits: + script.append('[ ] %s' % commit) + script.append('') + script.append('# Insert change numbers in the brackets to add a new patch set.') + script.append('# To create a new change record, leave the brackets empty.') + + script = Editor.EditString("\n".join(script)).split("\n") + + change_re = re.compile(r'^\[\s*(\d{1,})\s*\]\s*([0-9a-f]{1,}) .*$') + to_replace = dict() + full_hashes = branch.unabbrev_commits + + for line in script: + m = change_re.match(line) + if m: + f = m.group(2) + try: + f = full_hashes[f] + except KeyError: + print 'fh = %s' % full_hashes + print >>sys.stderr, "error: commit %s not found" % f + sys.exit(1) + to_replace[m.group(1)] = f + + if not to_replace: + print >>sys.stderr, "error: no replacements specified" + print >>sys.stderr, " use 'repo upload' without --replace" + sys.exit(1) + + branch.replace_changes = to_replace + self._UploadAndReport([branch]) + def _UploadAndReport(self, todo): have_errors = False for branch in todo: @@ -168,6 +217,14 @@ changes in all projects listed in the manifest. project_list = self.GetProjects(args) pending = [] + if opt.replace: + if len(project_list) != 1: + print >>sys.stderr, \ + 'error: --replace requires exactly one project' + sys.exit(1) + self._ReplaceBranch(project_list[0]) + return + for project in project_list: avail = project.GetUploadableBranches() if avail: