mirror of
https://github.com/9fans/plan9port.git
synced 2025-01-12 11:10:07 +00:00
codereview: sync from Go.
LGTM=rsc R=rsc https://codereview.appspot.com/67820044
This commit is contained in:
parent
66ad987412
commit
db800afb4e
1 changed files with 185 additions and 112 deletions
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue