aboutsummaryrefslogtreecommitdiff
path: root/lib/codereview/codereview.py
diff options
context:
space:
mode:
authorShenghou Ma <minux.ma@gmail.com>2014-02-24 01:21:48 -0500
committerShenghou Ma <minux.ma@gmail.com>2014-02-24 01:21:48 -0500
commitdb800afb4e7b46df67feba70cda683f34110619b (patch)
tree3b344a7659cafa2c7afba09a005dbf10539a965d /lib/codereview/codereview.py
parent66ad987412438ff1fe01db3d3672dcbb99e8e3c6 (diff)
downloadplan9port-db800afb4e7b46df67feba70cda683f34110619b.tar.gz
plan9port-db800afb4e7b46df67feba70cda683f34110619b.tar.bz2
plan9port-db800afb4e7b46df67feba70cda683f34110619b.zip
codereview: sync from Go.
LGTM=rsc R=rsc https://codereview.appspot.com/67820044
Diffstat (limited to 'lib/codereview/codereview.py')
-rw-r--r--lib/codereview/codereview.py297
1 files changed, 185 insertions, 112 deletions
diff --git a/lib/codereview/codereview.py b/lib/codereview/codereview.py
index d26df2a5..12b000c1 100644
--- a/lib/codereview/codereview.py
+++ b/lib/codereview/codereview.py
@@ -61,6 +61,14 @@ import time
from mercurial import commands as hg_commands
from mercurial import util as hg_util
+# bind Plan 9 preferred dotfile location
+if os.sys.platform == 'plan9':
+ try:
+ import plan9
+ n = plan9.bind(os.path.expanduser("~/lib"), os.path.expanduser("~"), plan9.MBEFORE|plan9.MCREATE)
+ except ImportError:
+ pass
+
defaultcc = None
codereview_disabled = None
real_rollback = None
@@ -155,7 +163,8 @@ default_to_utf8()
global_status = None
def set_status(s):
- # print >>sys.stderr, "\t", time.asctime(), s
+ if verbosity > 0:
+ print >>sys.stderr, time.asctime(), s
global global_status
global_status = s
@@ -268,7 +277,7 @@ class CL(object):
s += "\tAuthor: " + cl.copied_from + "\n"
if not quick:
s += "\tReviewer: " + JoinComma(cl.reviewer) + "\n"
- for (who, line) in cl.lgtm:
+ for (who, line, _) in cl.lgtm:
s += "\t\t" + who + ": " + line + "\n"
s += "\tCC: " + JoinComma(cl.cc) + "\n"
s += "\tFiles:\n"
@@ -358,6 +367,8 @@ class CL(object):
msg = lines[0]
patchset = lines[1].strip()
patches = [x.split(" ", 1) for x in lines[2:]]
+ else:
+ print >>sys.stderr, "Server says there is nothing to upload (probably wrong):\n" + msg
if response_body.startswith("Issue updated.") and quiet:
pass
else:
@@ -484,9 +495,15 @@ def CutDomain(s):
return s
def JoinComma(l):
+ seen = {}
+ uniq = []
for s in l:
typecheck(s, str)
- return ", ".join(l)
+ if s not in seen:
+ seen[s] = True
+ uniq.append(s)
+
+ return ", ".join(uniq)
def ExceptionDetail():
s = str(sys.exc_info()[0])
@@ -544,10 +561,10 @@ def LoadCL(ui, repo, name, web=True):
cl.private = d.get('private', False) != False
cl.lgtm = []
for m in d.get('messages', []):
- if m.get('approval', False) == True:
+ if m.get('approval', False) == True or m.get('disapproval', False) == True:
who = re.sub('@.*', '', m.get('sender', ''))
text = re.sub("\n(.|\n)*", '', m.get('text', ''))
- cl.lgtm.append((who, text))
+ cl.lgtm.append((who, text, m.get('approval', False)))
set_status("loaded CL " + name)
return cl, ''
@@ -711,7 +728,10 @@ Examples:
'''
def promptyesno(ui, msg):
- return ui.promptchoice(msg, ["&yes", "&no"], 0) == 0
+ if hgversion >= "2.7":
+ return ui.promptchoice(msg + " $$ &yes $$ &no", 0) == 0
+ else:
+ return ui.promptchoice(msg, ["&yes", "&no"], 0) == 0
def promptremove(ui, repo, f):
if promptyesno(ui, "hg remove %s (y/n)?" % (f,)):
@@ -807,7 +827,7 @@ def EditCL(ui, repo, cl):
# For use by submit, etc. (NOT by change)
# Get change list number or list of files from command line.
# If files are given, make a new change list.
-def CommandLineCL(ui, repo, pats, opts, defaultcc=None):
+def CommandLineCL(ui, repo, pats, opts, op="verb", defaultcc=None):
if len(pats) > 0 and GoodCLName(pats[0]):
if len(pats) != 1:
return None, "cannot specify change number and file names"
@@ -821,7 +841,7 @@ def CommandLineCL(ui, repo, pats, opts, defaultcc=None):
cl.local = True
cl.files = ChangedFiles(ui, repo, pats, taken=Taken(ui, repo))
if not cl.files:
- return None, "no files changed"
+ return None, "no files changed (use hg %s <number> to use existing CL)" % op
if opts.get('reviewer'):
cl.reviewer = Add(cl.reviewer, SplitCommaSpace(opts.get('reviewer')))
if opts.get('cc'):
@@ -972,7 +992,7 @@ def ReadContributors(ui, repo):
f = open(repo.root + '/CONTRIBUTORS', 'r')
except:
ui.write("warning: cannot open %s: %s\n" % (opening, ExceptionDetail()))
- return
+ return {}
contributors = {}
for line in f:
@@ -1027,23 +1047,19 @@ def FindContributor(ui, repo, user=None, warn=True):
hgversion = hg_util.version()
-# We require Mercurial 1.9 and suggest Mercurial 2.0.
+# We require Mercurial 1.9 and suggest Mercurial 2.1.
# The details of the scmutil package changed then,
# so allowing earlier versions would require extra band-aids below.
# Ubuntu 11.10 ships with Mercurial 1.9.1 as the default version.
hg_required = "1.9"
-hg_suggested = "2.0"
+hg_suggested = "2.1"
old_message = """
The code review extension requires Mercurial """+hg_required+""" or newer.
You are using Mercurial """+hgversion+""".
-To install a new Mercurial, use
-
- sudo easy_install mercurial=="""+hg_suggested+"""
-
-or visit http://mercurial.selenic.com/downloads/.
+To install a new Mercurial, visit http://mercurial.selenic.com/downloads/.
"""
linux_message = """
@@ -1171,6 +1187,25 @@ def hg_pull(ui, repo, **opts):
ui.write(line + '\n')
return err
+def hg_update(ui, repo, **opts):
+ w = uiwrap(ui)
+ ui.quiet = False
+ ui.verbose = True # for file list
+ err = hg_commands.update(ui, repo, **opts)
+ for line in w.output().split('\n'):
+ if isNoise(line):
+ continue
+ if line.startswith('moving '):
+ line = 'mv ' + line[len('moving '):]
+ if line.startswith('getting ') and line.find(' to ') >= 0:
+ line = 'mv ' + line[len('getting '):]
+ if line.startswith('getting '):
+ line = '+ ' + line[len('getting '):]
+ if line.startswith('removing '):
+ line = '- ' + line[len('removing '):]
+ ui.write(line + '\n')
+ return err
+
def hg_push(ui, repo, **opts):
w = uiwrap(ui)
ui.quiet = False
@@ -1190,6 +1225,10 @@ def hg_commit(ui, repo, *pats, **opts):
commit_okay = False
def precommithook(ui, repo, **opts):
+ if hgversion >= "2.1":
+ from mercurial import phases
+ if repo.ui.config('phases', 'new-commit') >= phases.secret:
+ return False
if commit_okay:
return False # False means okay.
ui.write("\ncodereview extension enabled; use mail, upload, or submit instead of commit\n\n")
@@ -1247,24 +1286,8 @@ def MatchAt(ctx, pats=None, opts=None, globbed=False, default='relpath'):
#######################################################################
# Commands added by code review extension.
-# As of Mercurial 2.1 the commands are all required to return integer
-# exit codes, whereas earlier versions allowed returning arbitrary strings
-# to be printed as errors. We wrap the old functions to make sure we
-# always return integer exit codes now. Otherwise Mercurial dies
-# with a TypeError traceback (unsupported operand type(s) for &: 'str' and 'int').
-# Introduce a Python decorator to convert old functions to the new
-# stricter convention.
-
def hgcommand(f):
- def wrapped(ui, repo, *pats, **opts):
- err = f(ui, repo, *pats, **opts)
- if type(err) is int:
- return err
- if not err:
- return 0
- raise hg_util.Abort(err)
- wrapped.__doc__ = f.__doc__
- return wrapped
+ return f
#######################################################################
# hg change
@@ -1293,42 +1316,42 @@ def change(ui, repo, *pats, **opts):
"""
if codereview_disabled:
- return codereview_disabled
+ raise hg_util.Abort(codereview_disabled)
dirty = {}
if len(pats) > 0 and GoodCLName(pats[0]):
name = pats[0]
if len(pats) != 1:
- return "cannot specify CL name and file patterns"
+ raise hg_util.Abort("cannot specify CL name and file patterns")
pats = pats[1:]
cl, err = LoadCL(ui, repo, name, web=True)
if err != '':
- return err
+ raise hg_util.Abort(err)
if not cl.local and (opts["stdin"] or not opts["stdout"]):
- return "cannot change non-local CL " + name
+ raise hg_util.Abort("cannot change non-local CL " + name)
else:
name = "new"
cl = CL("new")
if repo[None].branch() != "default":
- return "cannot create CL outside default branch; switch with 'hg update default'"
+ raise hg_util.Abort("cannot create CL outside default branch; switch with 'hg update default'")
dirty[cl] = True
files = ChangedFiles(ui, repo, pats, taken=Taken(ui, repo))
if opts["delete"] or opts["deletelocal"]:
if opts["delete"] and opts["deletelocal"]:
- return "cannot use -d and -D together"
+ raise hg_util.Abort("cannot use -d and -D together")
flag = "-d"
if opts["deletelocal"]:
flag = "-D"
if name == "new":
- return "cannot use "+flag+" with file patterns"
+ raise hg_util.Abort("cannot use "+flag+" with file patterns")
if opts["stdin"] or opts["stdout"]:
- return "cannot use "+flag+" with -i or -o"
+ raise hg_util.Abort("cannot use "+flag+" with -i or -o")
if not cl.local:
- return "cannot change non-local CL " + name
+ raise hg_util.Abort("cannot change non-local CL " + name)
if opts["delete"]:
if cl.copied_from:
- return "original author must delete CL; hg change -D will remove locally"
+ raise hg_util.Abort("original author must delete CL; hg change -D will remove locally")
PostMessage(ui, cl.name, "*** Abandoned ***", send_mail=cl.mailed)
EditDesc(cl.name, closed=True, private=cl.private)
cl.Delete(ui, repo)
@@ -1338,7 +1361,7 @@ def change(ui, repo, *pats, **opts):
s = sys.stdin.read()
clx, line, err = ParseCL(s, name)
if err != '':
- return "error parsing change list: line %d: %s" % (line, err)
+ raise hg_util.Abort("error parsing change list: line %d: %s" % (line, err))
if clx.desc is not None:
cl.desc = clx.desc;
dirty[cl] = True
@@ -1360,7 +1383,7 @@ def change(ui, repo, *pats, **opts):
cl.files = files
err = EditCL(ui, repo, cl)
if err != "":
- return err
+ raise hg_util.Abort(err)
dirty[cl] = True
for d, _ in dirty.items():
@@ -1391,7 +1414,7 @@ def code_login(ui, repo, **opts):
a file in your home directory.
"""
if codereview_disabled:
- return codereview_disabled
+ raise hg_util.Abort(codereview_disabled)
MySend(None)
@@ -1411,8 +1434,10 @@ def clpatch(ui, repo, clname, **opts):
name as the Author: line but add your own name to a Committer: line.
"""
if repo[None].branch() != "default":
- return "cannot run hg clpatch outside default branch"
- return clpatch_or_undo(ui, repo, clname, opts, mode="clpatch")
+ raise hg_util.Abort("cannot run hg clpatch outside default branch")
+ err = clpatch_or_undo(ui, repo, clname, opts, mode="clpatch")
+ if err:
+ raise hg_util.Abort(err)
@hgcommand
def undo(ui, repo, clname, **opts):
@@ -1423,8 +1448,10 @@ def undo(ui, repo, clname, **opts):
you can add the reason for the undo to the description.
"""
if repo[None].branch() != "default":
- return "cannot run hg undo outside default branch"
- return clpatch_or_undo(ui, repo, clname, opts, mode="undo")
+ raise hg_util.Abort("cannot run hg undo outside default branch")
+ err = clpatch_or_undo(ui, repo, clname, opts, mode="undo")
+ if err:
+ raise hg_util.Abort(err)
@hgcommand
def release_apply(ui, repo, clname, **opts):
@@ -1468,13 +1495,13 @@ def release_apply(ui, repo, clname, **opts):
"""
c = repo[None]
if not releaseBranch:
- return "no active release branches"
+ raise hg_util.Abort("no active release branches")
if c.branch() != releaseBranch:
if c.modified() or c.added() or c.removed():
raise hg_util.Abort("uncommitted local changes - cannot switch branches")
err = hg_clean(repo, releaseBranch)
if err:
- return err
+ raise hg_util.Abort(err)
try:
err = clpatch_or_undo(ui, repo, clname, opts, mode="backport")
if err:
@@ -1482,13 +1509,12 @@ def release_apply(ui, repo, clname, **opts):
except Exception, e:
hg_clean(repo, "default")
raise e
- return None
def rev2clname(rev):
# Extract CL name from revision description.
# The last line in the description that is a codereview URL is the real one.
# Earlier lines might be part of the user-written description.
- all = re.findall('(?m)^http://codereview.appspot.com/([0-9]+)$', rev.description())
+ all = re.findall('(?m)^https?://codereview.appspot.com/([0-9]+)$', rev.description())
if len(all) > 0:
return all[-1]
return ""
@@ -1586,24 +1612,24 @@ def clpatch_or_undo(ui, repo, clname, opts, mode):
return "local repository is out of date; sync to get %s" % (vers)
patch1, err = portPatch(repo, patch, vers, id)
if err != "":
- if not opts["ignore_hgpatch_failure"]:
+ if not opts["ignore_hgapplydiff_failure"]:
return "codereview issue %s is out of date: %s (%s->%s)" % (clname, err, vers, id)
else:
patch = patch1
- argv = ["hgpatch"]
+ argv = ["hgapplydiff"]
if opts["no_incoming"] or mode == "backport":
argv += ["--checksync=false"]
try:
cmd = subprocess.Popen(argv, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None, close_fds=sys.platform != "win32")
except:
- return "hgpatch: " + ExceptionDetail() + "\nInstall hgpatch with:\n$ go get code.google.com/p/go.codereview/cmd/hgpatch\n"
+ return "hgapplydiff: " + ExceptionDetail() + "\nInstall hgapplydiff with:\n$ go get code.google.com/p/go.codereview/cmd/hgapplydiff\n"
out, err = cmd.communicate(patch)
- if cmd.returncode != 0 and not opts["ignore_hgpatch_failure"]:
- return "hgpatch failed"
+ if cmd.returncode != 0 and not opts["ignore_hgapplydiff_failure"]:
+ return "hgapplydiff failed"
cl.local = True
cl.files = out.strip().split()
- if not cl.files and not opts["ignore_hgpatch_failure"]:
+ if not cl.files and not opts["ignore_hgapplydiff_failure"]:
return "codereview issue %s has no changed files" % clname
files = ChangedFiles(ui, repo, [])
extra = Sub(cl.files, files)
@@ -1618,6 +1644,17 @@ def clpatch_or_undo(ui, repo, clname, opts, mode):
else:
ui.write(cl.PendingText() + "\n")
+ # warn if clpatch will modify file already in another CL (it's unsafe to submit them)
+ if mode == "clpatch":
+ msgs = []
+ cls = LoadAllCL(ui, repo, web=False)
+ for k, v in cls.iteritems():
+ isec = Intersect(v.files, cl.files)
+ if isec and k != clname:
+ msgs.append("CL " + k + ", because it also modifies " + ", ".join(isec) + ".")
+ if msgs:
+ ui.warn("warning: please double check before submitting this CL and:\n\t" + "\n\t".join(msgs) + "\n")
+
# portPatch rewrites patch from being a patch against
# oldver to being a patch against newver.
def portPatch(repo, patch, oldver, newver):
@@ -1687,7 +1724,7 @@ def download(ui, repo, clname, **opts):
followed by its diff, downloaded from the code review server.
"""
if codereview_disabled:
- return codereview_disabled
+ raise hg_util.Abort(codereview_disabled)
cl, vers, patch, err = DownloadCL(ui, repo, clname)
if err != "":
@@ -1709,7 +1746,7 @@ def file(ui, repo, clname, pat, *pats, **opts):
It does not edit them or remove them from the repository.
"""
if codereview_disabled:
- return codereview_disabled
+ raise hg_util.Abort(codereview_disabled)
pats = tuple([pat] + list(pats))
if not GoodCLName(clname):
@@ -1773,19 +1810,20 @@ def gofmt(ui, repo, *pats, **opts):
the given patterns.
"""
if codereview_disabled:
- return codereview_disabled
+ raise hg_util.Abort(codereview_disabled)
files = ChangedExistingFiles(ui, repo, pats, opts)
files = gofmt_required(files)
if not files:
- return "no modified go files"
+ ui.status("no modified go files\n")
+ return
cwd = os.getcwd()
files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
try:
cmd = ["gofmt", "-l"]
if not opts["list"]:
cmd += ["-w"]
- if os.spawnvp(os.P_WAIT, "gofmt", cmd + files) != 0:
+ if subprocess.call(cmd + files) != 0:
raise hg_util.Abort("gofmt did not exit cleanly")
except hg_error.Abort, e:
raise
@@ -1807,11 +1845,11 @@ def mail(ui, repo, *pats, **opts):
to the reviewer and CC list asking for a review.
"""
if codereview_disabled:
- return codereview_disabled
+ raise hg_util.Abort(codereview_disabled)
- cl, err = CommandLineCL(ui, repo, pats, opts, defaultcc=defaultcc)
+ cl, err = CommandLineCL(ui, repo, pats, opts, op="mail", defaultcc=defaultcc)
if err != "":
- return err
+ raise hg_util.Abort(err)
cl.Upload(ui, repo, gofmt_just_warn=True)
if not cl.reviewer:
# If no reviewer is listed, assign the review to defaultcc.
@@ -1819,15 +1857,15 @@ def mail(ui, repo, *pats, **opts):
# codereview.appspot.com/user/defaultcc
# page, so that it doesn't get dropped on the floor.
if not defaultcc:
- return "no reviewers listed in CL"
+ raise hg_util.Abort("no reviewers listed in CL")
cl.cc = Sub(cl.cc, defaultcc)
cl.reviewer = defaultcc
cl.Flush(ui, repo)
if cl.files == []:
- return "no changed files, not sending mail"
+ raise hg_util.Abort("no changed files, not sending mail")
- cl.Mail(ui, repo)
+ cl.Mail(ui, repo)
#######################################################################
# hg p / hg pq / hg ps / hg pending
@@ -1853,7 +1891,7 @@ def pending(ui, repo, *pats, **opts):
Lists pending changes followed by a list of unassigned but modified files.
"""
if codereview_disabled:
- return codereview_disabled
+ raise hg_util.Abort(codereview_disabled)
quick = opts.get('quick', False)
short = opts.get('short', False)
@@ -1868,7 +1906,7 @@ def pending(ui, repo, *pats, **opts):
ui.write(cl.PendingText(quick=quick) + "\n")
if short:
- return
+ return 0
files = DefaultFiles(ui, repo, [])
if len(files) > 0:
s = "Changed files not in any CL:\n"
@@ -1890,7 +1928,7 @@ def submit(ui, repo, *pats, **opts):
Bails out if the local repository is not in sync with the remote one.
"""
if codereview_disabled:
- return codereview_disabled
+ raise hg_util.Abort(codereview_disabled)
# We already called this on startup but sometimes Mercurial forgets.
set_mercurial_encoding_to_utf8()
@@ -1898,9 +1936,9 @@ def submit(ui, repo, *pats, **opts):
if not opts["no_incoming"] and hg_incoming(ui, repo):
need_sync()
- cl, err = CommandLineCL(ui, repo, pats, opts, defaultcc=defaultcc)
+ cl, err = CommandLineCL(ui, repo, pats, opts, op="submit", defaultcc=defaultcc)
if err != "":
- return err
+ raise hg_util.Abort(err)
user = None
if cl.copied_from:
@@ -1909,20 +1947,29 @@ def submit(ui, repo, *pats, **opts):
typecheck(userline, str)
about = ""
- if cl.reviewer:
- about += "R=" + JoinComma([CutDomain(s) for s in cl.reviewer]) + "\n"
+
+ if not cl.lgtm and not opts.get('tbr') and not isAddca(cl):
+ raise hg_util.Abort("this CL has not been LGTM'ed")
+ if cl.lgtm:
+ about += "LGTM=" + JoinComma([CutDomain(who) for (who, line, approval) in cl.lgtm if approval]) + "\n"
+ reviewer = cl.reviewer
if opts.get('tbr'):
tbr = SplitCommaSpace(opts.get('tbr'))
+ for name in tbr:
+ if name.startswith('golang-'):
+ raise hg_util.Abort("--tbr requires a person, not a mailing list")
cl.reviewer = Add(cl.reviewer, tbr)
about += "TBR=" + JoinComma([CutDomain(s) for s in tbr]) + "\n"
+ if reviewer:
+ about += "R=" + JoinComma([CutDomain(s) for s in reviewer]) + "\n"
if cl.cc:
about += "CC=" + JoinComma([CutDomain(s) for s in cl.cc]) + "\n"
if not cl.reviewer:
- return "no reviewers listed in CL"
+ raise hg_util.Abort("no reviewers listed in CL")
if not cl.local:
- return "cannot submit non-local CL"
+ raise hg_util.Abort("cannot submit non-local CL")
# upload, to sync current patch and also get change number if CL is new.
if not cl.copied_from:
@@ -1957,7 +2004,7 @@ def submit(ui, repo, *pats, **opts):
ret = hg_commit(ui, repo, *['path:'+f for f in cl.files], message=message, user=userline)
commit_okay = False
if ret:
- return "nothing changed"
+ raise hg_util.Abort("nothing changed")
node = repo["-1"].node()
# push to remote; if it fails for any reason, roll back
try:
@@ -1968,12 +2015,16 @@ def submit(ui, repo, *pats, **opts):
# Push changes to remote. If it works, we're committed. If not, roll back.
try:
- hg_push(ui, repo)
+ if hg_push(ui, repo):
+ raise hg_util.Abort("push error")
except hg_error.Abort, e:
if e.message.find("push creates new heads") >= 0:
# Remote repository had changes we missed.
need_sync()
raise
+ except urllib2.HTTPError, e:
+ print >>sys.stderr, "pushing to remote server failed; do you have commit permissions?"
+ raise
except:
real_rollback()
raise
@@ -1985,11 +2036,11 @@ def submit(ui, repo, *pats, **opts):
"(^https?://([^@/]+@)?code\.google\.com/p/([^/.]+)(\.[^./]+)?/?)", url)
if m:
if m.group(1): # prj.googlecode.com/hg/ case
- changeURL = "http://code.google.com/p/%s/source/detail?r=%s" % (m.group(3), changeURL)
+ changeURL = "https://code.google.com/p/%s/source/detail?r=%s" % (m.group(3), changeURL)
elif m.group(4) and m.group(7): # code.google.com/p/prj.subrepo/ case
- changeURL = "http://code.google.com/p/%s/source/detail?r=%s&repo=%s" % (m.group(6), changeURL, m.group(7)[1:])
+ changeURL = "https://code.google.com/p/%s/source/detail?r=%s&repo=%s" % (m.group(6), changeURL, m.group(7)[1:])
elif m.group(4): # code.google.com/p/prj/ case
- changeURL = "http://code.google.com/p/%s/source/detail?r=%s" % (m.group(6), changeURL)
+ changeURL = "https://code.google.com/p/%s/source/detail?r=%s" % (m.group(6), changeURL)
else:
print >>sys.stderr, "URL: ", url
else:
@@ -2010,7 +2061,12 @@ def submit(ui, repo, *pats, **opts):
err = hg_clean(repo, "default")
if err:
return err
- return None
+ return 0
+
+def isAddca(cl):
+ rev = cl.reviewer
+ isGobot = 'gobot' in rev or 'gobot@swtch.com' in rev or 'gobot@golang.org' in rev
+ return cl.desc.startswith('A+C:') and 'Generated by addca.' in cl.desc and isGobot
#######################################################################
# hg sync
@@ -2023,10 +2079,22 @@ def sync(ui, repo, **opts):
into the local repository.
"""
if codereview_disabled:
- return codereview_disabled
+ raise hg_util.Abort(codereview_disabled)
if not opts["local"]:
- err = hg_pull(ui, repo, update=True)
+ # If there are incoming CLs, pull -u will do the update.
+ # If there are no incoming CLs, do hg update to make sure
+ # that an update always happens regardless. This is less
+ # surprising than update depending on incoming CLs.
+ # It is important not to do both hg pull -u and hg update
+ # in the same command, because the hg update will end
+ # up marking resolve conflicts from the hg pull -u as resolved,
+ # causing files with <<< >>> markers to not show up in
+ # hg resolve -l. Yay Mercurial.
+ if hg_incoming(ui, repo):
+ err = hg_pull(ui, repo, update=True)
+ else:
+ err = hg_update(ui, repo)
if err:
return err
sync_changes(ui, repo)
@@ -2037,7 +2105,7 @@ def sync_changes(ui, repo):
# Double-check them by looking at the Rietveld log.
for rev in hg_log(ui, repo, limit=100, template="{node}\n").split():
desc = repo[rev].description().strip()
- for clname in re.findall('(?m)^http://(?:[^\n]+)/([0-9]+)$', desc):
+ for clname in re.findall('(?m)^https?://(?:[^\n]+)/([0-9]+)$', desc):
if IsLocalCL(ui, repo, clname) and IsRietveldSubmitted(ui, clname, repo[rev].hex()):
ui.warn("CL %s submitted as %s; closing\n" % (clname, repo[rev]))
cl, err = LoadCL(ui, repo, clname, web=False)
@@ -2064,7 +2132,7 @@ def sync_changes(ui, repo):
ui.warn("CL %s has no files; delete (abandon) with hg change -d %s\n" % (cl.name, cl.name))
else:
ui.warn("CL %s has no files; delete locally with hg change -D %s\n" % (cl.name, cl.name))
- return
+ return 0
#######################################################################
# hg upload
@@ -2076,17 +2144,17 @@ def upload(ui, repo, name, **opts):
Uploads the current modifications for a given change to the server.
"""
if codereview_disabled:
- return codereview_disabled
+ raise hg_util.Abort(codereview_disabled)
repo.ui.quiet = True
cl, err = LoadCL(ui, repo, name, web=True)
if err != "":
- return err
+ raise hg_util.Abort(err)
if not cl.local:
- return "cannot upload non-local change"
+ raise hg_util.Abort("cannot upload non-local change")
cl.Upload(ui, repo)
print "%s%s\n" % (server_url_base, cl.name)
- return
+ return 0
#######################################################################
# Table of commands, supplied to Mercurial for installation.
@@ -2115,7 +2183,7 @@ cmdtable = {
"^clpatch": (
clpatch,
[
- ('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
+ ('', 'ignore_hgapplydiff_failure', None, 'create CL metadata even if hgapplydiff fails'),
('', 'no_incoming', None, 'disable check for incoming changes'),
],
"change#"
@@ -2174,7 +2242,7 @@ cmdtable = {
"^release-apply": (
release_apply,
[
- ('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
+ ('', 'ignore_hgapplydiff_failure', None, 'create CL metadata even if hgapplydiff fails'),
('', 'no_incoming', None, 'disable check for incoming changes'),
],
"change#"
@@ -2197,7 +2265,7 @@ cmdtable = {
"^undo": (
undo,
[
- ('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
+ ('', 'ignore_hgapplydiff_failure', None, 'create CL metadata even if hgapplydiff fails'),
('', 'no_incoming', None, 'disable check for incoming changes'),
],
"change#"
@@ -2229,6 +2297,7 @@ def reposetup(ui, repo):
if codereview_init:
return
codereview_init = True
+ start_status_thread()
# Read repository-specific options from lib/codereview/codereview.cfg or codereview.cfg.
root = ''
@@ -2366,7 +2435,7 @@ def IsRietveldSubmitted(ui, clname, hex):
return False
for msg in dict.get("messages", []):
text = msg.get("text", "")
- m = re.match('\*\*\* Submitted as [^*]*?([0-9a-f]+) \*\*\*', text)
+ m = re.match('\*\*\* Submitted as [^*]*?r=([0-9a-f]+)[^ ]* \*\*\*', text)
if m is not None and len(m.group(1)) >= 8 and hex.startswith(m.group(1)):
return True
return False
@@ -2460,6 +2529,8 @@ def MySend1(request_path, payload=None,
self._Authenticate()
if request_path is None:
return
+ if timeout is None:
+ timeout = 30 # seconds
old_timeout = socket.getdefaulttimeout()
socket.setdefaulttimeout(timeout)
@@ -2468,7 +2539,7 @@ def MySend1(request_path, payload=None,
while True:
tries += 1
args = dict(kwargs)
- url = "http://%s%s" % (self.host, request_path)
+ url = "https://%s%s" % (self.host, request_path)
if args:
url += "?" + urllib.urlencode(args)
req = self._CreateRequest(url=url, data=payload)
@@ -2580,7 +2651,7 @@ def RietveldSetup(ui, repo):
if x is not None:
email = x
- server_url_base = "http://" + server + "/"
+ server_url_base = "https://" + server + "/"
testing = ui.config("codereview", "testing")
force_google_account = ui.configbool("codereview", "force_google_account", False)
@@ -2609,7 +2680,7 @@ def RietveldSetup(ui, repo):
rpc = None
global releaseBranch
- tags = repo.branchtags().keys()
+ tags = repo.branchmap().keys()
if 'release-branch.go10' in tags:
# NOTE(rsc): This tags.sort is going to get the wrong
# answer when comparing release-branch.go9 with
@@ -2755,7 +2826,9 @@ class ClientLoginError(urllib2.HTTPError):
def __init__(self, url, code, msg, headers, args):
urllib2.HTTPError.__init__(self, url, code, msg, headers, None)
self.args = args
- self.reason = args["Error"]
+ # .reason is now a read-only property based on .msg
+ # this means we ignore 'msg', but that seems to work fine.
+ self.msg = args["Error"]
class AbstractRpcServer(object):
@@ -2858,7 +2931,7 @@ class AbstractRpcServer(object):
# This is a dummy value to allow us to identify when we're successful.
continue_location = "http://localhost/"
args = {"continue": continue_location, "auth": auth_token}
- req = self._CreateRequest("http://%s/_ah/login?%s" % (self.host, urllib.urlencode(args)))
+ req = self._CreateRequest("https://%s/_ah/login?%s" % (self.host, urllib.urlencode(args)))
try:
response = self.opener.open(req)
except urllib2.HTTPError, e:
@@ -2888,31 +2961,31 @@ class AbstractRpcServer(object):
try:
auth_token = self._GetAuthToken(credentials[0], credentials[1])
except ClientLoginError, e:
- if e.reason == "BadAuthentication":
+ if e.msg == "BadAuthentication":
print >>sys.stderr, "Invalid username or password."
continue
- if e.reason == "CaptchaRequired":
+ if e.msg == "CaptchaRequired":
print >>sys.stderr, (
"Please go to\n"
"https://www.google.com/accounts/DisplayUnlockCaptcha\n"
"and verify you are a human. Then try again.")
break
- if e.reason == "NotVerified":
+ if e.msg == "NotVerified":
print >>sys.stderr, "Account not verified."
break
- if e.reason == "TermsNotAgreed":
+ if e.msg == "TermsNotAgreed":
print >>sys.stderr, "User has not agreed to TOS."
break
- if e.reason == "AccountDeleted":
+ if e.msg == "AccountDeleted":
print >>sys.stderr, "The user account has been deleted."
break
- if e.reason == "AccountDisabled":
+ if e.msg == "AccountDisabled":
print >>sys.stderr, "The user account has been disabled."
break
- if e.reason == "ServiceDisabled":
+ if e.msg == "ServiceDisabled":
print >>sys.stderr, "The user's access to the service has been disabled."
break
- if e.reason == "ServiceUnavailable":
+ if e.msg == "ServiceUnavailable":
print >>sys.stderr, "The service is not available; try again later."
break
raise
@@ -2948,7 +3021,7 @@ class AbstractRpcServer(object):
while True:
tries += 1
args = dict(kwargs)
- url = "http://%s%s" % (self.host, request_path)
+ url = "https://%s%s" % (self.host, request_path)
if args:
url += "?" + urllib.urlencode(args)
req = self._CreateRequest(url=url, data=payload)