codereview: sync from Go.

LGTM=rsc
R=rsc
https://codereview.appspot.com/67820044
diff --git a/lib/codereview/codereview.py b/lib/codereview/codereview.py
index d26df2a..12b000c 100644
--- a/lib/codereview/codereview.py
+++ b/lib/codereview/codereview.py
@@ -61,6 +61,14 @@
 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 @@
 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 @@
 			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 @@
 			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 @@
 	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 @@
 		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 @@
 '''
 
 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 @@
 # 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 @@
 		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 @@
 			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 @@
 
 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 @@
 		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 @@
 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 @@
 #######################################################################
 # 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 @@
 	"""
 
 	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 @@
 		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 @@
 			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 @@
 	a file in your home directory.
 	"""
 	if codereview_disabled:
-		return codereview_disabled
+		raise hg_util.Abort(codereview_disabled)
 
 	MySend(None)
 
@@ -1411,8 +1434,10 @@
 	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 @@
 	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 @@
 	"""
 	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 @@
 	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 @@
 			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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 		# 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 @@
 	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 @@
 			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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 
 		# 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 @@
 		"(^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 @@
 		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 @@
 	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 @@
 	# 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 @@
 				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 @@
 	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 @@
 	"^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 @@
 	"^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 @@
 	"^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 @@
 	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 @@
 		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 @@
 		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 @@
 		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 @@
 	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 @@
 	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 @@
 	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 @@
 		# 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 @@
 			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 @@
 			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)