12ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# coding=utf-8
22ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# (The line above is necessary so that I can use 世界 in the
32ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# *comment* below without Python getting all bent out of shape.)
42ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
52ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Copyright 2007-2009 Google Inc.
62ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
72ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Licensed under the Apache License, Version 2.0 (the "License");
82ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# you may not use this file except in compliance with the License.
92ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# You may obtain a copy of the License at
102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#	http://www.apache.org/licenses/LICENSE-2.0
122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Unless required by applicable law or agreed to in writing, software
142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# distributed under the License is distributed on an "AS IS" BASIS,
152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# See the License for the specific language governing permissions and
172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# limitations under the License.
182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson'''Mercurial interface to codereview.appspot.com.
202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonTo configure, set the following options in
222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonyour repository's .hg/hgrc file.
232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	[extensions]
250d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	codereview = /path/to/codereview.py
262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	[codereview]
282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	server = codereview.appspot.com
292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonThe server should be running Rietveld; see http://code.google.com/p/rietveld/.
312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonIn addition to the new commands, this extension introduces
332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonthe file pattern syntax @nnnnnn, where nnnnnn is a change list
342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonnumber, to mean the files included in that change list, which
352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonmust be associated with the current client.
362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonFor example, if change 123456 contains the files x.go and y.go,
382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"hg diff @123456" is equivalent to"hg diff x.go y.go".
392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson'''
402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
410d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinimport sys
422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
430d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinif __name__ == "__main__":
440d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	print >>sys.stderr, "This is a Mercurial extension and should not be invoked directly."
450d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	sys.exit(2)
462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
470d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# We require Python 2.6 for the json package.
480d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinif sys.version < '2.6':
490d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	print >>sys.stderr, "The codereview extension requires Python 2.6 or newer."
500d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	print >>sys.stderr, "You are running Python " + sys.version
510d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	sys.exit(2)
522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
530d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinimport json
540d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinimport os
550d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinimport re
560d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinimport stat
570d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinimport subprocess
580d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinimport threading
590d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinimport time
602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
610d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinfrom mercurial import commands as hg_commands
620d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinfrom mercurial import util as hg_util
632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
640d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindefaultcc = None
650d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkincodereview_disabled = None
660d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinreal_rollback = None
670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander GutkinreleaseBranch = None
680d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinserver = "codereview.appspot.com"
690d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinserver_url_base = None
702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#######################################################################
722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Normally I would split this into multiple files, but it simplifies
732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# import path headaches to keep it all in one file.  Sorry.
740d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# The different parts of the file are separated by banners like this one.
752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
760d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
770d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Helpers
782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
790d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef RelativePath(path, cwd):
800d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	n = len(cwd)
810d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if path.startswith(cwd) and path[n] == '/':
820d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return path[n+1:]
830d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return path
840d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
850d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef Sub(l1, l2):
860d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return [l for l in l1 if l not in l2]
870d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
880d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef Add(l1, l2):
890d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	l = l1 + Sub(l2, l1)
900d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	l.sort()
910d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return l
920d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
930d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef Intersect(l1, l2):
940d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return [l for l in l1 if l in l2]
952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#######################################################################
972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# RE: UNICODE STRING HANDLING
982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Python distinguishes between the str (string of bytes)
1002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# and unicode (string of code points) types.  Most operations
1012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# work on either one just fine, but some (like regexp matching)
1022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# require unicode, and others (like write) require str.
1032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
1042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# As befits the language, Python hides the distinction between
1052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# unicode and str by converting between them silently, but
1062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# *only* if all the bytes/code points involved are 7-bit ASCII.
1072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# This means that if you're not careful, your program works
1082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# fine on "hello, world" and fails on "hello, 世界".  And of course,
1092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# the obvious way to be careful - use static types - is unavailable.
1102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# So the only way is trial and error to find where to put explicit
1112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# conversions.
1122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
1132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Because more functions do implicit conversion to str (string of bytes)
1142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# than do implicit conversion to unicode (string of code points),
1152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# the convention in this module is to represent all text as str,
1162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# converting to unicode only when calling a unicode-only function
1172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# and then converting back to str as soon as possible.
1182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef typecheck(s, t):
1202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if type(s) != t:
1210d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		raise hg_util.Abort("type check failed: %s has type %s != %s" % (repr(s), type(s), t))
1222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# If we have to pass unicode instead of str, ustr does that conversion clearly.
1242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef ustr(s):
1252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(s, str)
1262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return s.decode("utf-8")
1272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Even with those, Mercurial still sometimes turns unicode into str
1292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# and then tries to use it as ascii.  Change Mercurial's default.
1302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef set_mercurial_encoding_to_utf8():
1312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	from mercurial import encoding
1322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	encoding.encoding = 'utf-8'
1332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonset_mercurial_encoding_to_utf8()
1352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Even with those we still run into problems.
1372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# I tried to do things by the book but could not convince
1382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Mercurial to let me check in a change with UTF-8 in the
1392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# CL description or author field, no matter how many conversions
1402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# between str and unicode I inserted and despite changing the
1412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# default encoding.  I'm tired of this game, so set the default
1422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# encoding for all of Python to 'utf-8', not 'ascii'.
1432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef default_to_utf8():
1442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	import sys
1452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	stdout, __stdout__ = sys.stdout, sys.__stdout__
1462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	reload(sys)  # site.py deleted setdefaultencoding; get it back
1472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sys.stdout, sys.__stdout__ = stdout, __stdout__
1482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sys.setdefaultencoding('utf-8')
1492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondefault_to_utf8()
1512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#######################################################################
1530d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Status printer for long-running commands
1540d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
1550d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinglobal_status = None
1560d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
1570d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef set_status(s):
1580d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	# print >>sys.stderr, "\t", time.asctime(), s
1590d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global global_status
1600d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global_status = s
1610d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
1620d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinclass StatusThread(threading.Thread):
1630d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	def __init__(self):
1640d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		threading.Thread.__init__(self)
1650d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	def run(self):
1660d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		# pause a reasonable amount of time before
1670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		# starting to display status messages, so that
1680d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		# most hg commands won't ever see them.
1690d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		time.sleep(30)
1700d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
1710d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		# now show status every 15 seconds
1720d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		while True:
1730d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			time.sleep(15 - time.time() % 15)
1740d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			s = global_status
1750d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			if s is None:
1760d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				continue
1770d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			if s == "":
1780d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				s = "(unknown status)"
1790d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			print >>sys.stderr, time.asctime(), s
1800d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
1810d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef start_status_thread():
1820d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	t = StatusThread()
1830d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	t.setDaemon(True)  # allowed to exit if t is still running
1840d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	t.start()
1850d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
1860d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
1872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Change list parsing.
1882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
1892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Change lists are stored in .hg/codereview/cl.nnnnnn
1902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# where nnnnnn is the number assigned by the code review server.
1912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Most data about a change list is stored on the code review server
1922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# too: the description, reviewer, and cc list are all stored there.
1932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# The only thing in the cl.nnnnnn file is the list of relevant files.
1942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Also, the existence of the cl.nnnnnn file marks this repository
1952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# as the one where the change list lives.
1962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonemptydiff = """Index: ~rietveld~placeholder~
1982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson===================================================================
1992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondiff --git a/~rietveld~placeholder~ b/~rietveld~placeholder~
2002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonnew file mode 100644
2012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
2022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass CL(object):
2042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self, name):
2052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(name, str)
2062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.name = name
2072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.desc = ''
2082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.files = []
2092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.reviewer = []
2102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.cc = []
2112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.url = ''
2122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.local = False
2132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.web = False
2142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.copied_from = None	# None means current user
2152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.mailed = False
2162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.private = False
2172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.lgtm = []
2182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def DiskText(self):
2202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = self
2212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = ""
2222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.copied_from:
2232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "Author: " + cl.copied_from + "\n\n"
2242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.private:
2252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "Private: " + str(self.private) + "\n"
2262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "Mailed: " + str(self.mailed) + "\n"
2272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "Description:\n"
2282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += Indent(cl.desc, "\t")
2292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "Files:\n"
2302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for f in cl.files:
2312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "\t" + f + "\n"
2322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(s, str)
2332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return s
2342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def EditorText(self):
2362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = self
2372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = _change_prolog
2382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "\n"
2392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.copied_from:
2402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "Author: " + cl.copied_from + "\n"
2412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.url != '':
2422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += 'URL: ' + cl.url + '	# cannot edit\n\n'
2432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.private:
2442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "Private: True\n"
2452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "Reviewer: " + JoinComma(cl.reviewer) + "\n"
2462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "CC: " + JoinComma(cl.cc) + "\n"
2472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "\n"
2482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "Description:\n"
2492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.desc == '':
2502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "\t<enter description here>\n"
2512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
2522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += Indent(cl.desc, "\t")
2532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "\n"
2542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.local or cl.name == "new":
2552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "Files:\n"
2562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for f in cl.files:
2572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				s += "\t" + f + "\n"
2582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "\n"
2592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(s, str)
2602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return s
2612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2620d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	def PendingText(self, quick=False):
2632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = self
2642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = cl.name + ":" + "\n"
2652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += Indent(cl.desc, "\t")
2662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "\n"
2672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.copied_from:
2682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "\tAuthor: " + cl.copied_from + "\n"
2690d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if not quick:
2700d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			s += "\tReviewer: " + JoinComma(cl.reviewer) + "\n"
2710d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			for (who, line) in cl.lgtm:
2720d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				s += "\t\t" + who + ": " + line + "\n"
2730d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			s += "\tCC: " + JoinComma(cl.cc) + "\n"
2742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "\tFiles:\n"
2752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for f in cl.files:
2762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "\t\t" + f + "\n"
2772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(s, str)
2782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return s
2792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def Flush(self, ui, repo):
2812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.name == "new":
2822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.Upload(ui, repo, gofmt_just_warn=True, creating=True)
2832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		dir = CodeReviewDir(ui, repo)
2842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		path = dir + '/cl.' + self.name
2852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		f = open(path+'!', "w")
2862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		f.write(self.DiskText())
2872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		f.close()
2882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if sys.platform == "win32" and os.path.isfile(path):
2892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			os.remove(path)
2902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		os.rename(path+'!', path)
2912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.web and not self.copied_from:
2922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			EditDesc(self.name, desc=self.desc,
2932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				reviewers=JoinComma(self.reviewer), cc=JoinComma(self.cc),
2942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				private=self.private)
2952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def Delete(self, ui, repo):
2972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		dir = CodeReviewDir(ui, repo)
2982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		os.unlink(dir + "/cl." + self.name)
2992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
3002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def Subject(self):
3012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = line1(self.desc)
3022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if len(s) > 60:
3032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s = s[0:55] + "..."
3042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.name != "new":
3052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s = "code review %s: %s" % (self.name, s)
3062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(s, str)
3072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return s
3082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
3092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def Upload(self, ui, repo, send_mail=False, gofmt=True, gofmt_just_warn=False, creating=False, quiet=False):
3102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not self.files and not creating:
3112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("no files in change list\n")
3122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if ui.configbool("codereview", "force_gofmt", True) and gofmt:
3132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			CheckFormat(ui, repo, self.files, just_warn=gofmt_just_warn)
3142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status("uploading CL metadata + diffs")
3152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		os.chdir(repo.root)
3162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields = [
3172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			("content_upload", "1"),
3182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			("reviewers", JoinComma(self.reviewer)),
3192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			("cc", JoinComma(self.cc)),
3202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			("description", self.desc),
3212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			("base_hashes", ""),
3222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		]
3232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
3242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.name != "new":
3252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			form_fields.append(("issue", self.name))
3262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		vcs = None
3272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# We do not include files when creating the issue,
3282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# because we want the patch sets to record the repository
3292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# and base revision they are diffs against.  We use the patch
3302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# set message for that purpose, but there is no message with
3312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# the first patch set.  Instead the message gets used as the
3322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# new CL's overall subject.  So omit the diffs when creating
3332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# and then we'll run an immediate upload.
3342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# This has the effect that every CL begins with an empty "Patch set 1".
3352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.files and not creating:
3362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			vcs = MercurialVCS(upload_options, ui, repo)
3372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			data = vcs.GenerateDiff(self.files)
3382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			files = vcs.GetBaseFiles(data)
3392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if len(data) > MAX_UPLOAD_SIZE:
3402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				uploaded_diff_file = []
3412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				form_fields.append(("separate_patches", "1"))
3422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
3432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				uploaded_diff_file = [("data", "data.diff", data)]
3442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
3452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			uploaded_diff_file = [("data", "data.diff", emptydiff)]
3462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
3472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if vcs and self.name != "new":
3480d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			form_fields.append(("subject", "diff -r " + vcs.base_rev + " " + ui.expandpath("default")))
3492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
3502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# First upload sets the subject for the CL itself.
3512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			form_fields.append(("subject", self.Subject()))
3522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ctype, body = EncodeMultipartFormData(form_fields, uploaded_diff_file)
3532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		response_body = MySend("/upload", body, content_type=ctype)
3542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		patchset = None
3552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		msg = response_body
3562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines = msg.splitlines()
3572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if len(lines) >= 2:
3582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			msg = lines[0]
3592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			patchset = lines[1].strip()
3602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			patches = [x.split(" ", 1) for x in lines[2:]]
3612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if response_body.startswith("Issue updated.") and quiet:
3622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pass
3632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
3642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.status(msg + "\n")
3652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status("uploaded CL metadata + diffs")
3662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not response_body.startswith("Issue created.") and not response_body.startswith("Issue updated."):
3670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			raise hg_util.Abort("failed to update issue: " + response_body)
3682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		issue = msg[msg.rfind("/")+1:]
3692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.name = issue
3702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not self.url:
3712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.url = server_url_base + self.name
3722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not uploaded_diff_file:
3732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			set_status("uploading patches")
3742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			patches = UploadSeparatePatches(issue, rpc, patchset, data, upload_options)
3752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if vcs:
3762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			set_status("uploading base files")
3772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			vcs.UploadBaseFiles(issue, rpc, patches, patchset, upload_options, files)
3782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if send_mail:
3792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			set_status("sending mail")
3802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			MySend("/" + issue + "/mail", payload="")
3812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.web = True
3822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status("flushing changes to disk")
3832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.Flush(ui, repo)
3842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
3852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
3862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def Mail(self, ui, repo):
3872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pmsg = "Hello " + JoinComma(self.reviewer)
3882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.cc:
3892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pmsg += " (cc: %s)" % (', '.join(self.cc),)
3902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pmsg += ",\n"
3912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pmsg += "\n"
3920d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		repourl = ui.expandpath("default")
3932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not self.mailed:
3942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pmsg += "I'd like you to review this change to\n" + repourl + "\n"
3952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
3962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pmsg += "Please take another look.\n"
3972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(pmsg, str)
3982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		PostMessage(ui, self.name, pmsg, subject=self.Subject())
3992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.mailed = True
4002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.Flush(ui, repo)
4012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef GoodCLName(name):
4032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(name, str)
4042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return re.match("^[0-9]+$", name)
4052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef ParseCL(text, name):
4072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(text, str)
4082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(name, str)
4092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sname = None
4102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	lineno = 0
4112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sections = {
4122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'Author': '',
4132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'Description': '',
4142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'Files': '',
4152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'URL': '',
4162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'Reviewer': '',
4172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'CC': '',
4182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'Mailed': '',
4192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'Private': '',
4202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	}
4212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in text.split('\n'):
4222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lineno += 1
4232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = line.rstrip()
4242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line != '' and line[0] == '#':
4252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
4262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line == '' or line[0] == ' ' or line[0] == '\t':
4272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if sname == None and line != '':
4282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return None, lineno, 'text outside section'
4292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if sname != None:
4302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				sections[sname] += line + '\n'
4312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
4322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		p = line.find(':')
4332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if p >= 0:
4342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s, val = line[:p].strip(), line[p+1:].strip()
4352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if s in sections:
4362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				sname = s
4372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if val != '':
4382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					sections[sname] += val + '\n'
4392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
4402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, lineno, 'malformed section header'
4412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for k in sections:
4432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		sections[k] = StripCommon(sections[k]).rstrip()
4442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl = CL(name)
4462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if sections['Author']:
4472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.copied_from = sections['Author']
4482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.desc = sections['Description']
4492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in sections['Files'].split('\n'):
4502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		i = line.find('#')
4512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if i >= 0:
4522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			line = line[0:i].rstrip()
4532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = line.strip()
4542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line == '':
4552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
4562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.files.append(line)
4572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.reviewer = SplitCommaSpace(sections['Reviewer'])
4582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.cc = SplitCommaSpace(sections['CC'])
4592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.url = sections['URL']
4602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if sections['Mailed'] != 'False':
4612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Odd default, but avoids spurious mailings when
4622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# reading old CLs that do not have a Mailed: line.
4632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# CLs created with this update will always have
4642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Mailed: False on disk.
4652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.mailed = True
4662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if sections['Private'] in ('True', 'true', 'Yes', 'yes'):
4672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.private = True
4682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.desc == '<enter description here>':
4692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.desc = ''
4702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return cl, 0, ''
4712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef SplitCommaSpace(s):
4732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(s, str)
4742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	s = s.strip()
4752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if s == "":
4762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return []
4772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return re.split(", *", s)
4782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef CutDomain(s):
4802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(s, str)
4812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	i = s.find('@')
4822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if i >= 0:
4832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = s[0:i]
4842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return s
4852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef JoinComma(l):
4872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for s in l:
4882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(s, str)
4892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return ", ".join(l)
4902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef ExceptionDetail():
4922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	s = str(sys.exc_info()[0])
4932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if s.startswith("<type '") and s.endswith("'>"):
4942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = s[7:-2]
4952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	elif s.startswith("<class '") and s.endswith("'>"):
4962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = s[8:-2]
4972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	arg = str(sys.exc_info()[1])
4982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(arg) > 0:
4992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += ": " + arg
5002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return s
5012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef IsLocalCL(ui, repo, name):
5032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return GoodCLName(name) and os.access(CodeReviewDir(ui, repo) + "/cl." + name, 0)
5042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Load CL from disk and/or the web.
5062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef LoadCL(ui, repo, name, web=True):
5072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(name, str)
5082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("loading CL " + name)
5092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not GoodCLName(name):
5102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, "invalid CL name"
5112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dir = CodeReviewDir(ui, repo)
5122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	path = dir + "cl." + name
5132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if os.access(path, 0):
5142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ff = open(path)
5152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		text = ff.read()
5162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ff.close()
5172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl, lineno, err = ParseCL(text, name)
5182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
5192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "malformed CL data: "+err
5202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.local = True
5212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
5222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = CL(name)
5232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if web:
5242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status("getting issue metadata from web")
5252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		d = JSONGet(ui, "/api/" + name + "?messages=true")
5262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status(None)
5272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if d is None:
5282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "cannot load CL %s from server" % (name,)
5292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if 'owner_email' not in d or 'issue' not in d or str(d['issue']) != name:
5302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "malformed response loading CL data from code review server"
5312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.dict = d
5322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.reviewer = d.get('reviewers', [])
5332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.cc = d.get('cc', [])
5342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.local and cl.copied_from and cl.desc:
5352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# local copy of CL written by someone else
5362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# and we saved a description.  use that one,
5372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# so that committers can edit the description
5382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# before doing hg submit.
5392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pass
5402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
5412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.desc = d.get('description', "")
5422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.url = server_url_base + name
5432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.web = True
5442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.private = d.get('private', False) != False
5452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.lgtm = []
5462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for m in d.get('messages', []):
5472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if m.get('approval', False) == True:
5482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				who = re.sub('@.*', '', m.get('sender', ''))
5492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				text = re.sub("\n(.|\n)*", '', m.get('text', ''))
5502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				cl.lgtm.append((who, text))
5512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("loaded CL " + name)
5532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return cl, ''
5542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass LoadCLThread(threading.Thread):
5562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self, ui, repo, dir, f, web):
5572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		threading.Thread.__init__(self)
5582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.ui = ui
5592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.repo = repo
5602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.dir = dir
5612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.f = f
5622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.web = web
5632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.cl = None
5642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def run(self):
5652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl, err = LoadCL(self.ui, self.repo, self.f[3:], web=self.web)
5662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != '':
5672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.ui.warn("loading "+self.dir+self.f+": " + err + "\n")
5682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return
5692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.cl = cl
5702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Load all the CLs from this repository.
5722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef LoadAllCL(ui, repo, web=True):
5732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dir = CodeReviewDir(ui, repo)
5742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	m = {}
5752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [f for f in os.listdir(dir) if f.startswith('cl.')]
5762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not files:
5772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return m
5782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	active = []
5792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	first = True
5802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for f in files:
5812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		t = LoadCLThread(ui, repo, dir, f, web)
5822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		t.start()
5832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if web and first:
5842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# first request: wait in case it needs to authenticate
5852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# otherwise we get lots of user/password prompts
5862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# running in parallel.
5872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			t.join()
5882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if t.cl:
5892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				m[t.cl.name] = t.cl
5902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			first = False
5912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
5922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			active.append(t)
5932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for t in active:
5942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		t.join()
5952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if t.cl:
5962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			m[t.cl.name] = t.cl
5972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return m
5982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Find repository root.  On error, ui.warn and return None
6002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef RepoDir(ui, repo):
6012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	url = repo.url();
6022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not url.startswith('file:'):
6032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.warn("repository %s is not in local file system\n" % (url,))
6042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None
6052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	url = url[5:]
6062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if url.endswith('/'):
6072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		url = url[:-1]
6082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(url, str)
6092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return url
6102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Find (or make) code review directory.  On error, ui.warn and return None
6122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef CodeReviewDir(ui, repo):
6132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dir = RepoDir(ui, repo)
6142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if dir == None:
6152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None
6162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dir += '/.hg/codereview/'
6172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not os.path.isdir(dir):
6182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
6192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			os.mkdir(dir, 0700)
6202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except:
6212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn('cannot mkdir %s: %s\n' % (dir, ExceptionDetail()))
6222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None
6232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(dir, str)
6242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return dir
6252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Turn leading tabs into spaces, so that the common white space
6272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# prefix doesn't get confused when people's editors write out
6282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# some lines with spaces, some with tabs.  Only a heuristic
6292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# (some editors don't use 8 spaces either) but a useful one.
6302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef TabsToSpaces(line):
6312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	i = 0
6322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	while i < len(line) and line[i] == '\t':
6332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		i += 1
6342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return ' '*(8*i) + line[i:]
6352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Strip maximal common leading white space prefix from text
6372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef StripCommon(text):
6382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(text, str)
6392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ws = None
6402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in text.split('\n'):
6412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = line.rstrip()
6422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line == '':
6432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
6442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = TabsToSpaces(line)
6452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		white = line[:len(line)-len(line.lstrip())]
6462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if ws == None:
6472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ws = white
6482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
6492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			common = ''
6502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for i in range(min(len(white), len(ws))+1):
6512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if white[0:i] == ws[0:i]:
6522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					common = white[0:i]
6532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ws = common
6542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if ws == '':
6552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			break
6562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if ws == None:
6572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return text
6582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	t = ''
6592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in text.split('\n'):
6602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = line.rstrip()
6612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = TabsToSpaces(line)
6622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line.startswith(ws):
6632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			line = line[len(ws):]
6642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line == '' and t == '':
6652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
6662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		t += line + '\n'
6672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	while len(t) >= 2 and t[-2:] == '\n\n':
6682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		t = t[:-1]
6692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(t, str)
6702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return t
6712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Indent text with indent.
6732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef Indent(text, indent):
6742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(text, str)
6752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(indent, str)
6762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	t = ''
6772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in text.split('\n'):
6782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		t += indent + line + '\n'
6792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(t, str)
6802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return t
6812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Return the first line of l
6832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef line1(text):
6842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(text, str)
6852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return text.split('\n')[0]
6862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson_change_prolog = """# Change list.
6882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Lines beginning with # are ignored.
6892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Multi-line values should be indented.
6902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
6912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondesc_re = '^(.+: |(tag )?(release|weekly)\.|fix build|undo CL)'
6932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondesc_msg = '''Your CL description appears not to use the standard form.
6952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonThe first line of your change description is conventionally a
6972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonone-line summary of the change, prefixed by the primary affected package,
6982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonand is used as the subject for code review mail; the rest of the description
6992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonelaborates.
7002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonExamples:
7022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	encoding/rot13: new package
7042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	math: add IsInf, IsNaN
7062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	net: fix cname in LookupHost
7082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	unicode: update to Unicode 5.0.2
7102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson'''
7122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7130d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef promptyesno(ui, msg):
7140d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return ui.promptchoice(msg, ["&yes", "&no"], 0) == 0
7152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef promptremove(ui, repo, f):
7172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if promptyesno(ui, "hg remove %s (y/n)?" % (f,)):
7180d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if hg_commands.remove(ui, repo, 'path:'+f) != 0:
7192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("error removing %s" % (f,))
7202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef promptadd(ui, repo, f):
7222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if promptyesno(ui, "hg add %s (y/n)?" % (f,)):
7230d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if hg_commands.add(ui, repo, 'path:'+f) != 0:
7242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("error adding %s" % (f,))
7252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef EditCL(ui, repo, cl):
7272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status(None)	# do not show status
7282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	s = cl.EditorText()
7292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	while True:
7302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = ui.edit(s, ui.username())
7312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# We can't trust Mercurial + Python not to die before making the change,
7332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# so, by popular demand, just scribble the most recent CL edit into
7342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# $(hg root)/last-change so that if Mercurial does die, people
7352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# can look there for their work.
7362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
7372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			f = open(repo.root+"/last-change", "w")
7382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			f.write(s)
7392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			f.close()
7402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except:
7412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pass
7422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		clx, line, err = ParseCL(s, cl.name)
7442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != '':
7452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not promptyesno(ui, "error parsing change list: line %d: %s\nre-edit (y/n)?" % (line, err)):
7462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return "change list not modified"
7472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
7482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Check description.
7502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if clx.desc == '':
7512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if promptyesno(ui, "change list should have a description\nre-edit (y/n)?"):
7522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
7532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		elif re.search('<enter reason for undo>', clx.desc):
7542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if promptyesno(ui, "change list description omits reason for undo\nre-edit (y/n)?"):
7552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
7562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		elif not re.match(desc_re, clx.desc.split('\n')[0]):
7572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if promptyesno(ui, desc_msg + "re-edit (y/n)?"):
7582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
7592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Check file list for files that need to be hg added or hg removed
7612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# or simply aren't understood.
7622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pats = ['path:'+f for f in clx.files]
7630d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		changed = hg_matchPattern(ui, repo, *pats, modified=True, added=True, removed=True)
7640d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		deleted = hg_matchPattern(ui, repo, *pats, deleted=True)
7650d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		unknown = hg_matchPattern(ui, repo, *pats, unknown=True)
7660d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		ignored = hg_matchPattern(ui, repo, *pats, ignored=True)
7670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		clean = hg_matchPattern(ui, repo, *pats, clean=True)
7682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		files = []
7692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for f in clx.files:
7700d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			if f in changed:
7712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				files.append(f)
7722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
7732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if f in deleted:
7742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				promptremove(ui, repo, f)
7752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				files.append(f)
7762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
7772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if f in unknown:
7782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				promptadd(ui, repo, f)
7792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				files.append(f)
7802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
7812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if f in ignored:
7822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("error: %s is excluded by .hgignore; omitting\n" % (f,))
7832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
7842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if f in clean:
7852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("warning: %s is listed in the CL but unchanged\n" % (f,))
7862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				files.append(f)
7872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
7882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			p = repo.root + '/' + f
7892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if os.path.isfile(p):
7902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("warning: %s is a file but not known to hg\n" % (f,))
7912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				files.append(f)
7922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
7932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if os.path.isdir(p):
7942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("error: %s is a directory, not a file; omitting\n" % (f,))
7952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
7962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("error: %s does not exist; omitting\n" % (f,))
7972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		clx.files = files
7982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.desc = clx.desc
8002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.reviewer = clx.reviewer
8012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.cc = clx.cc
8022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.files = clx.files
8032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.private = clx.private
8042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		break
8052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return ""
8062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# For use by submit, etc. (NOT by change)
8082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Get change list number or list of files from command line.
8092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# If files are given, make a new change list.
8102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef CommandLineCL(ui, repo, pats, opts, defaultcc=None):
8112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(pats) > 0 and GoodCLName(pats[0]):
8122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if len(pats) != 1:
8132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "cannot specify change number and file names"
8142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if opts.get('message'):
8152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "cannot use -m with existing CL"
8162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl, err = LoadCL(ui, repo, pats[0], web=True)
8172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
8182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, err
8192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
8202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = CL("new")
8212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.local = True
8220d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		cl.files = ChangedFiles(ui, repo, pats, taken=Taken(ui, repo))
8232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not cl.files:
8242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "no files changed"
8252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts.get('reviewer'):
8262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.reviewer = Add(cl.reviewer, SplitCommaSpace(opts.get('reviewer')))
8272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts.get('cc'):
8282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.cc = Add(cl.cc, SplitCommaSpace(opts.get('cc')))
8292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if defaultcc:
8302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.cc = Add(cl.cc, defaultcc)
8312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.name == "new":
8322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if opts.get('message'):
8332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.desc = opts.get('message')
8342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
8352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			err = EditCL(ui, repo, cl)
8362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if err != '':
8372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return None, err
8382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return cl, ""
8392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8400d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
8410d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Change list file management
8420d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
8430d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Return list of changed files in repository that match pats.
8440d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# The patterns came from the command line, so we warn
8450d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# if they have no effect or cannot be understood.
8460d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef ChangedFiles(ui, repo, pats, taken=None):
8470d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	taken = taken or {}
8480d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	# Run each pattern separately so that we can warn about
8490d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	# patterns that didn't do anything useful.
8502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for p in pats:
8510d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		for f in hg_matchPattern(ui, repo, p, unknown=True):
8520d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			promptadd(ui, repo, f)
8530d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		for f in hg_matchPattern(ui, repo, p, removed=True):
8540d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			promptremove(ui, repo, f)
8550d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		files = hg_matchPattern(ui, repo, p, modified=True, added=True, removed=True)
8560d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		for f in files:
8570d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			if f in taken:
8580d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				ui.warn("warning: %s already in CL %s\n" % (f, taken[f].name))
8590d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if not files:
8600d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			ui.warn("warning: %s did not match any modified files\n" % (p,))
8612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8620d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	# Again, all at once (eliminates duplicates)
8630d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	l = hg_matchPattern(ui, repo, *pats, modified=True, added=True, removed=True)
8640d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	l.sort()
8650d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if taken:
8660d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		l = Sub(l, taken.keys())
8670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return l
8682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8690d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Return list of changed files in repository that match pats and still exist.
8700d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef ChangedExistingFiles(ui, repo, pats, opts):
8710d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	l = hg_matchPattern(ui, repo, *pats, modified=True, added=True)
8720d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	l.sort()
8730d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return l
8740d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
8750d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Return list of files claimed by existing CLs
8760d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef Taken(ui, repo):
8770d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	all = LoadAllCL(ui, repo, web=False)
8780d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	taken = {}
8790d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	for _, cl in all.items():
8800d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		for f in cl.files:
8810d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			taken[f] = cl
8820d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return taken
8830d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
8840d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Return list of changed files that are not claimed by other CLs
8850d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef DefaultFiles(ui, repo, pats):
8860d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return ChangedFiles(ui, repo, pats, taken=Taken(ui, repo))
8870d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
8880d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
8890d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# File format checking.
8902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef CheckFormat(ui, repo, files, just_warn=False):
8922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("running gofmt")
8932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	CheckGofmt(ui, repo, files, just_warn)
8942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	CheckTabfmt(ui, repo, files, just_warn)
8952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Check that gofmt run on the list of files does not change them
8972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef CheckGofmt(ui, repo, files, just_warn):
8980d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	files = gofmt_required(files)
8992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not files:
9002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
9012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cwd = os.getcwd()
9022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
9032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [f for f in files if os.access(f, 0)]
9042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not files:
9052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
9062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
9072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cmd = subprocess.Popen(["gofmt", "-l"] + files, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=sys.platform != "win32")
9082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cmd.stdin.close()
9092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
9100d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		raise hg_util.Abort("gofmt: " + ExceptionDetail())
9112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	data = cmd.stdout.read()
9122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	errors = cmd.stderr.read()
9132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cmd.wait()
9142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("done with gofmt")
9152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(errors) > 0:
9162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.warn("gofmt errors:\n" + errors.rstrip() + "\n")
9172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
9182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(data) > 0:
9192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		msg = "gofmt needs to format these files (run hg gofmt):\n" + Indent(data, "\t").rstrip()
9202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if just_warn:
9212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("warning: " + msg + "\n")
9222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
9230d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			raise hg_util.Abort(msg)
9242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
9252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
9262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Check that *.[chys] files indent using tabs.
9272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef CheckTabfmt(ui, repo, files, just_warn):
9280d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	files = [f for f in files if f.startswith('src/') and re.search(r"\.[chys]$", f) and not re.search(r"\.tab\.[ch]$", f)]
9292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not files:
9302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
9312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cwd = os.getcwd()
9322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
9332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [f for f in files if os.access(f, 0)]
9342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	badfiles = []
9352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for f in files:
9362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
9372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for line in open(f, 'r'):
9382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# Four leading spaces is enough to complain about,
9392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# except that some Plan 9 code uses four spaces as the label indent,
9402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# so allow that.
9412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if line.startswith('    ') and not re.match('    [A-Za-z0-9_]+:', line):
9422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					badfiles.append(f)
9432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
9442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except:
9452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# ignore cannot open file, etc.
9462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pass
9472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(badfiles) > 0:
9482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		msg = "these files use spaces for indentation (use tabs instead):\n\t" + "\n\t".join(badfiles)
9492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if just_warn:
9502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("warning: " + msg + "\n")
9512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
9520d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			raise hg_util.Abort(msg)
9532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
9542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
9552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#######################################################################
9560d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# CONTRIBUTORS file parsing
9572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
9580d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander GutkincontributorsCache = None
9590d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander GutkincontributorsURL = None
9600d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
9610d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef ReadContributors(ui, repo):
9620d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global contributorsCache
9630d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if contributorsCache is not None:
9640d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return contributorsCache
9650d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
9660d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	try:
9670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if contributorsURL is not None:
9680d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			opening = contributorsURL
9690d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			f = urllib2.urlopen(contributorsURL)
9700d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		else:
9710d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			opening = repo.root + '/CONTRIBUTORS'
9720d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			f = open(repo.root + '/CONTRIBUTORS', 'r')
9730d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	except:
9740d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		ui.write("warning: cannot open %s: %s\n" % (opening, ExceptionDetail()))
9750d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return
9760d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
9770d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	contributors = {}
9780d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	for line in f:
9790d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		# CONTRIBUTORS is a list of lines like:
9800d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		#	Person <email>
9810d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		#	Person <email> <alt-email>
9820d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		# The first email address is the one used in commit logs.
9830d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if line.startswith('#'):
9840d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			continue
9850d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		m = re.match(r"([^<>]+\S)\s+(<[^<>\s]+>)((\s+<[^<>\s]+>)*)\s*$", line)
9860d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if m:
9870d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			name = m.group(1)
9880d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			email = m.group(2)[1:-1]
9890d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			contributors[email.lower()] = (name, email)
9900d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			for extra in m.group(3).split():
9910d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				contributors[extra[1:-1].lower()] = (name, email)
9920d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
9930d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	contributorsCache = contributors
9940d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return contributors
9950d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
9960d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef CheckContributor(ui, repo, user=None):
9970d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	set_status("checking CONTRIBUTORS file")
9980d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	user, userline = FindContributor(ui, repo, user, warn=False)
9990d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if not userline:
10000d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		raise hg_util.Abort("cannot find %s in CONTRIBUTORS" % (user,))
10010d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return userline
10020d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10030d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef FindContributor(ui, repo, user=None, warn=True):
10040d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if not user:
10050d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		user = ui.config("ui", "username")
10060d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if not user:
10070d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			raise hg_util.Abort("[ui] username is not configured in .hgrc")
10080d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	user = user.lower()
10090d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	m = re.match(r".*<(.*)>", user)
10100d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if m:
10110d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		user = m.group(1)
10120d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10130d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	contributors = ReadContributors(ui, repo)
10140d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if user not in contributors:
10150d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if warn:
10160d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			ui.warn("warning: cannot find %s in CONTRIBUTORS\n" % (user,))
10170d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return user, None
10180d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10190d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	user, email = contributors[user]
10200d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return email, "%s <%s>" % (user, email)
10210d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10220d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
10230d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Mercurial helper functions.
10240d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Read http://mercurial.selenic.com/wiki/MercurialApi before writing any of these.
10250d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# We use the ui.pushbuffer/ui.popbuffer + hg_commands.xxx tricks for all interaction
10260d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# with Mercurial.  It has proved the most stable as they make changes.
10270d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10280d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinhgversion = hg_util.version()
10290d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10300d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# We require Mercurial 1.9 and suggest Mercurial 2.0.
10310d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# The details of the scmutil package changed then,
10320d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# so allowing earlier versions would require extra band-aids below.
10330d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Ubuntu 11.10 ships with Mercurial 1.9.1 as the default version.
10340d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinhg_required = "1.9"
10350d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinhg_suggested = "2.0"
10360d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10370d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinold_message = """
10380d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10390d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander GutkinThe code review extension requires Mercurial """+hg_required+""" or newer.
10400d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander GutkinYou are using Mercurial """+hgversion+""".
10410d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10420d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander GutkinTo install a new Mercurial, use
10430d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10440d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	sudo easy_install mercurial=="""+hg_suggested+"""
10450d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10460d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinor visit http://mercurial.selenic.com/downloads/.
10470d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin"""
10480d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10490d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinlinux_message = """
10500d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander GutkinYou may need to clear your current Mercurial installation by running:
10510d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10520d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	sudo apt-get remove mercurial mercurial-common
10530d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	sudo rm -rf /etc/mercurial
10540d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin"""
10550d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10560d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinif hgversion < hg_required:
10570d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	msg = old_message
10580d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if os.access("/etc/mercurial", 0):
10590d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		msg += linux_message
10600d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	raise hg_util.Abort(msg)
10610d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10620d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinfrom mercurial.hg import clean as hg_clean
10630d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinfrom mercurial import cmdutil as hg_cmdutil
10640d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinfrom mercurial import error as hg_error
10650d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinfrom mercurial import match as hg_match
10660d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinfrom mercurial import node as hg_node
10670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10680d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinclass uiwrap(object):
10690d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	def __init__(self, ui):
10700d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		self.ui = ui
10710d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		ui.pushbuffer()
10720d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		self.oldQuiet = ui.quiet
10730d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		ui.quiet = True
10740d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		self.oldVerbose = ui.verbose
10750d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		ui.verbose = False
10760d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	def output(self):
10770d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		ui = self.ui
10780d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		ui.quiet = self.oldQuiet
10790d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		ui.verbose = self.oldVerbose
10800d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return ui.popbuffer()
10810d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10820d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef to_slash(path):
10830d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if sys.platform == "win32":
10840d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return path.replace('\\', '/')
10850d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return path
10860d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
10870d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef hg_matchPattern(ui, repo, *pats, **opts):
10880d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	w = uiwrap(ui)
10890d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	hg_commands.status(ui, repo, *pats, **opts)
10900d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	text = w.output()
10910d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	ret = []
10920d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	prefix = to_slash(os.path.realpath(repo.root))+'/'
10930d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	for line in text.split('\n'):
10940d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		f = line.split()
10950d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if len(f) > 1:
10960d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			if len(pats) > 0:
10970d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				# Given patterns, Mercurial shows relative to cwd
10980d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				p = to_slash(os.path.realpath(f[1]))
10990d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				if not p.startswith(prefix):
11000d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin					print >>sys.stderr, "File %s not in repo root %s.\n" % (p, prefix)
11010d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				else:
11020d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin					ret.append(p[len(prefix):])
11030d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			else:
11040d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				# Without patterns, Mercurial shows relative to root (what we want)
11050d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				ret.append(to_slash(f[1]))
11060d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return ret
11070d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
11080d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef hg_heads(ui, repo):
11090d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	w = uiwrap(ui)
11100d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	hg_commands.heads(ui, repo)
11110d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return w.output()
11120d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
11130d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinnoise = [
11140d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"",
11150d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"resolving manifests",
11160d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"searching for changes",
11170d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"couldn't find merge tool hgmerge",
11180d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"adding changesets",
11190d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"adding manifests",
11200d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"adding file changes",
11210d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"all local heads known remotely",
11220d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin]
11230d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
11240d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef isNoise(line):
11250d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	line = str(line)
11260d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	for x in noise:
11270d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if line == x:
11280d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			return True
11290d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return False
11300d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
11310d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef hg_incoming(ui, repo):
11320d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	w = uiwrap(ui)
11330d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	ret = hg_commands.incoming(ui, repo, force=False, bundle="")
11340d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if ret and ret != 1:
11350d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		raise hg_util.Abort(ret)
11360d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return w.output()
11370d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
11380d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef hg_log(ui, repo, **opts):
11390d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	for k in ['date', 'keyword', 'rev', 'user']:
11400d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if not opts.has_key(k):
11410d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			opts[k] = ""
11420d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	w = uiwrap(ui)
11430d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	ret = hg_commands.log(ui, repo, **opts)
11440d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if ret:
11450d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		raise hg_util.Abort(ret)
11460d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return w.output()
11470d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
11480d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef hg_outgoing(ui, repo, **opts):
11490d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	w = uiwrap(ui)
11500d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	ret = hg_commands.outgoing(ui, repo, **opts)
11510d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if ret and ret != 1:
11520d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		raise hg_util.Abort(ret)
11530d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return w.output()
11540d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
11550d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef hg_pull(ui, repo, **opts):
11560d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	w = uiwrap(ui)
11570d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	ui.quiet = False
11580d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	ui.verbose = True  # for file list
11590d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	err = hg_commands.pull(ui, repo, **opts)
11600d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	for line in w.output().split('\n'):
11610d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if isNoise(line):
11620d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			continue
11630d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if line.startswith('moving '):
11640d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			line = 'mv ' + line[len('moving '):]
11650d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if line.startswith('getting ') and line.find(' to ') >= 0:
11660d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			line = 'mv ' + line[len('getting '):]
11670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if line.startswith('getting '):
11680d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			line = '+ ' + line[len('getting '):]
11690d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if line.startswith('removing '):
11700d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			line = '- ' + line[len('removing '):]
11710d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		ui.write(line + '\n')
11720d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return err
11730d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
11740d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef hg_push(ui, repo, **opts):
11750d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	w = uiwrap(ui)
11760d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	ui.quiet = False
11770d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	ui.verbose = True
11780d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	err = hg_commands.push(ui, repo, **opts)
11790d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	for line in w.output().split('\n'):
11800d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if not isNoise(line):
11810d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			ui.write(line + '\n')
11820d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return err
11830d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
11840d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef hg_commit(ui, repo, *pats, **opts):
11850d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return hg_commands.commit(ui, repo, *pats, **opts)
11860d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
11870d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
11880d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Mercurial precommit hook to disable commit except through this interface.
11890d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
11900d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkincommit_okay = False
11910d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
11920d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef precommithook(ui, repo, **opts):
11930d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if commit_okay:
11940d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return False  # False means okay.
11950d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	ui.write("\ncodereview extension enabled; use mail, upload, or submit instead of commit\n\n")
11960d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return True
11970d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
11980d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
11990d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# @clnumber file pattern support
12000d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
12010d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# We replace scmutil.match with the MatchAt wrapper to add the @clnumber pattern.
12020d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
12030d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinmatch_repo = None
12040d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinmatch_ui = None
12050d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinmatch_orig = None
12060d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
12070d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef InstallMatch(ui, repo):
12080d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global match_repo
12090d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global match_ui
12100d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global match_orig
12110d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
12120d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	match_ui = ui
12130d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	match_repo = repo
12140d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
12150d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	from mercurial import scmutil
12160d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	match_orig = scmutil.match
12170d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	scmutil.match = MatchAt
12180d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
12190d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef MatchAt(ctx, pats=None, opts=None, globbed=False, default='relpath'):
12200d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	taken = []
12210d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	files = []
12220d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	pats = pats or []
12230d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	opts = opts or {}
12240d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
12250d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	for p in pats:
12260d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if p.startswith('@'):
12270d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			taken.append(p)
12280d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			clname = p[1:]
12290d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			if clname == "default":
12300d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				files = DefaultFiles(match_ui, match_repo, [])
12310d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			else:
12320d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				if not GoodCLName(clname):
12330d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin					raise hg_util.Abort("invalid CL name " + clname)
12340d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				cl, err = LoadCL(match_repo.ui, match_repo, clname, web=False)
12350d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				if err != '':
12360d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin					raise hg_util.Abort("loading CL " + clname + ": " + err)
12370d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				if not cl.files:
12380d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin					raise hg_util.Abort("no files in CL " + clname)
12390d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				files = Add(files, cl.files)
12400d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	pats = Sub(pats, taken) + ['path:'+f for f in files]
12412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12420d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	# work-around for http://selenic.com/hg/rev/785bbc8634f8
12430d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if not hasattr(ctx, 'match'):
12440d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		ctx = ctx[None]
12450d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return match_orig(ctx, pats=pats, opts=opts, globbed=globbed, default=default)
12460d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
12470d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
12480d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Commands added by code review extension.
12490d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
12500d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# As of Mercurial 2.1 the commands are all required to return integer
12510d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# exit codes, whereas earlier versions allowed returning arbitrary strings
12520d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# to be printed as errors.  We wrap the old functions to make sure we
12530d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# always return integer exit codes now.  Otherwise Mercurial dies
12540d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# with a TypeError traceback (unsupported operand type(s) for &: 'str' and 'int').
12550d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Introduce a Python decorator to convert old functions to the new
12560d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# stricter convention.
12570d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
12580d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef hgcommand(f):
12590d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	def wrapped(ui, repo, *pats, **opts):
12600d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		err = f(ui, repo, *pats, **opts)
12610d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if type(err) is int:
12620d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			return err
12630d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if not err:
12640d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			return 0
12650d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		raise hg_util.Abort(err)
12660d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	wrapped.__doc__ = f.__doc__
12670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return wrapped
12680d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
12690d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
12700d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# hg change
12710d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
12720d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
12732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef change(ui, repo, *pats, **opts):
12742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""create, edit or delete a change list
12752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Create, edit or delete a change list.
12772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	A change list is a group of files to be reviewed and submitted together,
12782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	plus a textual description of the change.
12792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Change lists are referred to by simple alphanumeric names.
12802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Changes must be reviewed before they can be submitted.
12822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	In the absence of options, the change command opens the
12842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	change list for editing in the default editor.
12852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Deleting a change with the -d or -D flag does not affect
12872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	the contents of the files listed in that change.  To revert
12882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	the files listed in a change, use
12892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg revert @123456
12912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	before running hg change -d 123456.
12932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
12942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12950d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if codereview_disabled:
12960d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return codereview_disabled
12972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dirty = {}
12992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(pats) > 0 and GoodCLName(pats[0]):
13002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		name = pats[0]
13012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if len(pats) != 1:
13022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "cannot specify CL name and file patterns"
13032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pats = pats[1:]
13042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl, err = LoadCL(ui, repo, name, web=True)
13052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != '':
13062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return err
13072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not cl.local and (opts["stdin"] or not opts["stdout"]):
13082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "cannot change non-local CL " + name
13092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
13102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		name = "new"
13112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = CL("new")
13120d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if repo[None].branch() != "default":
13130d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			return "cannot create CL outside default branch; switch with 'hg update default'"
13142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		dirty[cl] = True
13150d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		files = ChangedFiles(ui, repo, pats, taken=Taken(ui, repo))
13162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts["delete"] or opts["deletelocal"]:
13182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if opts["delete"] and opts["deletelocal"]:
13192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "cannot use -d and -D together"
13202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		flag = "-d"
13212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if opts["deletelocal"]:
13222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			flag = "-D"
13232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if name == "new":
13242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "cannot use "+flag+" with file patterns"
13252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if opts["stdin"] or opts["stdout"]:
13262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "cannot use "+flag+" with -i or -o"
13272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not cl.local:
13282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "cannot change non-local CL " + name
13292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if opts["delete"]:
13302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if cl.copied_from:
13312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return "original author must delete CL; hg change -D will remove locally"
13322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			PostMessage(ui, cl.name, "*** Abandoned ***", send_mail=cl.mailed)
13332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			EditDesc(cl.name, closed=True, private=cl.private)
13342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.Delete(ui, repo)
13352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
13362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts["stdin"]:
13382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = sys.stdin.read()
13392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		clx, line, err = ParseCL(s, name)
13402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != '':
13412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "error parsing change list: line %d: %s" % (line, err)
13422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if clx.desc is not None:
13432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.desc = clx.desc;
13442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			dirty[cl] = True
13452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if clx.reviewer is not None:
13462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.reviewer = clx.reviewer
13472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			dirty[cl] = True
13482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if clx.cc is not None:
13492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.cc = clx.cc
13502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			dirty[cl] = True
13512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if clx.files is not None:
13522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.files = clx.files
13532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			dirty[cl] = True
13542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if clx.private != cl.private:
13552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.private = clx.private
13562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			dirty[cl] = True
13572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not opts["stdin"] and not opts["stdout"]:
13592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if name == "new":
13602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.files = files
13612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		err = EditCL(ui, repo, cl)
13622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
13632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return err
13642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		dirty[cl] = True
13652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for d, _ in dirty.items():
13672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		name = d.name
13682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		d.Flush(ui, repo)
13692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if name == "new":
13702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			d.Upload(ui, repo, quiet=True)
13712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts["stdout"]:
13732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.write(cl.EditorText())
13742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	elif opts["pending"]:
13752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.write(cl.PendingText())
13762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	elif name == "new":
13772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if ui.quiet:
13782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.write(cl.name)
13792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
13802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.write("CL created: " + cl.url + "\n")
13812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
13822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13830d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
13840d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# hg code-login (broken?)
13850d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
13860d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
13872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef code_login(ui, repo, **opts):
13882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""log in to code review server
13892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Logs in to the code review server, saving a cookie in
13912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	a file in your home directory.
13922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
13930d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if codereview_disabled:
13940d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return codereview_disabled
13952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	MySend(None)
13972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13980d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
13990d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# hg clpatch / undo / release-apply / download
14000d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# All concerned with applying or unapplying patches to the repository.
14010d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
14020d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
14032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef clpatch(ui, repo, clname, **opts):
14042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""import a patch from the code review server
14052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Imports a patch from the code review server into the local client.
14072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	If the local client has already modified any of the files that the
14082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	patch modifies, this command will refuse to apply the patch.
14092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Submitting an imported patch will keep the original author's
14112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	name as the Author: line but add your own name to a Committer: line.
14122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
14132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if repo[None].branch() != "default":
14142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "cannot run hg clpatch outside default branch"
14152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return clpatch_or_undo(ui, repo, clname, opts, mode="clpatch")
14162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14170d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
14182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef undo(ui, repo, clname, **opts):
14192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""undo the effect of a CL
14202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Creates a new CL that undoes an earlier CL.
14222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	After creating the CL, opens the CL text for editing so that
14232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	you can add the reason for the undo to the description.
14242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
14252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if repo[None].branch() != "default":
14262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "cannot run hg undo outside default branch"
14272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return clpatch_or_undo(ui, repo, clname, opts, mode="undo")
14282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14290d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
14302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef release_apply(ui, repo, clname, **opts):
14312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""apply a CL to the release branch
14322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Creates a new CL copying a previously committed change
14342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	from the main branch to the release branch.
14352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	The current client must either be clean or already be in
14362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	the release branch.
14372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	The release branch must be created by starting with a
14392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	clean client, disabling the code review plugin, and running:
14402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg update weekly.YYYY-MM-DD
14422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg branch release-branch.rNN
14432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg commit -m 'create release-branch.rNN'
14442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg push --new-branch
14452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Then re-enable the code review plugin.
14472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	People can test the release branch by running
14492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg update release-branch.rNN
14512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	in a clean client.  To return to the normal tree,
14532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg update default
14552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Move changes since the weekly into the release branch
14572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	using hg release-apply followed by the usual code review
14582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	process and hg submit.
14592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	When it comes time to tag the release, record the
14612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	final long-form tag of the release-branch.rNN
14622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	in the *default* branch's .hgtags file.  That is, run
14632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg update default
14652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	and then edit .hgtags as you would for a weekly.
14672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
14692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	c = repo[None]
14702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not releaseBranch:
14712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "no active release branches"
14722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if c.branch() != releaseBranch:
14732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if c.modified() or c.added() or c.removed():
14740d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			raise hg_util.Abort("uncommitted local changes - cannot switch branches")
14750d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		err = hg_clean(repo, releaseBranch)
14762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err:
14772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return err
14782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
14792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		err = clpatch_or_undo(ui, repo, clname, opts, mode="backport")
14802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err:
14810d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			raise hg_util.Abort(err)
14822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except Exception, e:
14830d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		hg_clean(repo, "default")
14842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise e
14852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return None
14862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef rev2clname(rev):
14882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Extract CL name from revision description.
14892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# The last line in the description that is a codereview URL is the real one.
14902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Earlier lines might be part of the user-written description.
14912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	all = re.findall('(?m)^http://codereview.appspot.com/([0-9]+)$', rev.description())
14922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(all) > 0:
14932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return all[-1]
14942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return ""
14952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonundoHeader = """undo CL %s / %s
14972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson<enter reason for undo>
14992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson««« original CL description
15012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
15022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonundoFooter = """
15042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson»»»
15052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
15062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonbackportHeader = """[%s] %s
15082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson««« CL %s / %s
15102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
15112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonbackportFooter = """
15132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson»»»
15142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
15152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Implementation of clpatch/undo.
15172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef clpatch_or_undo(ui, repo, clname, opts, mode):
15180d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if codereview_disabled:
15190d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return codereview_disabled
15202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if mode == "undo" or mode == "backport":
15222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Find revision in Mercurial repository.
15232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Assume CL number is 7+ decimal digits.
15242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Otherwise is either change log sequence number (fewer decimal digits),
15252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# hexadecimal hash, or tag name.
15262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Mercurial will fall over long before the change log
15272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# sequence numbers get to be 7 digits long.
15282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if re.match('^[0-9]{7,}$', clname):
15292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			found = False
15300d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			for r in hg_log(ui, repo, keyword="codereview.appspot.com/"+clname, limit=100, template="{node}\n").split():
15310d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				rev = repo[r]
15322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# Last line with a code review URL is the actual review URL.
15332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# Earlier ones might be part of the CL description.
15342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				n = rev2clname(rev)
15352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if n == clname:
15362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					found = True
15372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
15382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not found:
15392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return "cannot find CL %s in local repository" % clname
15402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
15412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			rev = repo[clname]
15422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not rev:
15432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return "unknown revision %s" % clname
15442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			clname = rev2clname(rev)
15452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if clname == "":
15462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return "cannot find CL name in revision description"
15472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Create fresh CL and start with patch that would reverse the change.
15490d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		vers = hg_node.short(rev.node())
15502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = CL("new")
15512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		desc = str(rev.description())
15522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if mode == "undo":
15532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.desc = (undoHeader % (clname, vers)) + desc + undoFooter
15542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
15552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.desc = (backportHeader % (releaseBranch, line1(desc), clname, vers)) + desc + undoFooter
15562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		v1 = vers
15570d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		v0 = hg_node.short(rev.parents()[0].node())
15582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if mode == "undo":
15592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			arg = v1 + ":" + v0
15602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
15612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			vers = v0
15622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			arg = v0 + ":" + v1
15632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		patch = RunShell(["hg", "diff", "--git", "-r", arg])
15642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:  # clpatch
15662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl, vers, patch, err = DownloadCL(ui, repo, clname)
15672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
15682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return err
15692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if patch == emptydiff:
15702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "codereview issue %s has no diff" % clname
15712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# find current hg version (hg identify)
15732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ctx = repo[None]
15742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	parents = ctx.parents()
15750d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	id = '+'.join([hg_node.short(p.node()) for p in parents])
15762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# if version does not match the patch version,
15782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# try to update the patch line numbers.
15792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if vers != "" and id != vers:
15802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# "vers in repo" gives the wrong answer
15812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# on some versions of Mercurial.  Instead, do the actual
15822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# lookup and catch the exception.
15832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
15842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			repo[vers].description()
15852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except:
15862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "local repository is out of date; sync to get %s" % (vers)
15872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		patch1, err = portPatch(repo, patch, vers, id)
15882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
15892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not opts["ignore_hgpatch_failure"]:
15902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return "codereview issue %s is out of date: %s (%s->%s)" % (clname, err, vers, id)
15912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
15922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			patch = patch1
15932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	argv = ["hgpatch"]
15942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts["no_incoming"] or mode == "backport":
15952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		argv += ["--checksync=false"]
15962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
15972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cmd = subprocess.Popen(argv, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None, close_fds=sys.platform != "win32")
15982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
15990d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return "hgpatch: " + ExceptionDetail() + "\nInstall hgpatch with:\n$ go get code.google.com/p/go.codereview/cmd/hgpatch\n"
16002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	out, err = cmd.communicate(patch)
16022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cmd.returncode != 0 and not opts["ignore_hgpatch_failure"]:
16032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "hgpatch failed"
16042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.local = True
16052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.files = out.strip().split()
16062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.files and not opts["ignore_hgpatch_failure"]:
16072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "codereview issue %s has no changed files" % clname
16080d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	files = ChangedFiles(ui, repo, [])
16092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	extra = Sub(cl.files, files)
16102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if extra:
16112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.warn("warning: these files were listed in the patch but not changed:\n\t" + "\n\t".join(extra) + "\n")
16122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.Flush(ui, repo)
16132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if mode == "undo":
16142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		err = EditCL(ui, repo, cl)
16152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
16162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "CL created, but error editing: " + err
16172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.Flush(ui, repo)
16182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
16192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.write(cl.PendingText() + "\n")
16202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# portPatch rewrites patch from being a patch against
16222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# oldver to being a patch against newver.
16232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef portPatch(repo, patch, oldver, newver):
16242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	lines = patch.splitlines(True) # True = keep \n
16252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	delta = None
16262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for i in range(len(lines)):
16272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = lines[i]
16282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line.startswith('--- a/'):
16292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			file = line[6:-1]
16302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			delta = fileDeltas(repo, file, oldver, newver)
16312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not delta or not line.startswith('@@ '):
16322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
16332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# @@ -x,y +z,w @@ means the patch chunk replaces
16342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# the original file's line numbers x up to x+y with the
16352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# line numbers z up to z+w in the new file.
16362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Find the delta from x in the original to the same
16372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# line in the current version and add that delta to both
16382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# x and z.
16392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		m = re.match('@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@', line)
16402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not m:
16412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "error parsing patch line numbers"
16422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		n1, len1, n2, len2 = int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
16432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		d, err = lineDelta(delta, n1, len1)
16442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
16452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "", err
16462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		n1 += d
16472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		n2 += d
16482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines[i] = "@@ -%d,%d +%d,%d @@\n" % (n1, len1, n2, len2)
16492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	newpatch = ''.join(lines)
16512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return newpatch, ""
16522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# fileDelta returns the line number deltas for the given file's
16542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# changes from oldver to newver.
16552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# The deltas are a list of (n, len, newdelta) triples that say
16562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# lines [n, n+len) were modified, and after that range the
16572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# line numbers are +newdelta from what they were before.
16582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef fileDeltas(repo, file, oldver, newver):
16592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cmd = ["hg", "diff", "--git", "-r", oldver + ":" + newver, "path:" + file]
16602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	data = RunShell(cmd, silent_ok=True)
16612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	deltas = []
16622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in data.splitlines():
16632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		m = re.match('@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@', line)
16642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not m:
16652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
16662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		n1, len1, n2, len2 = int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
16672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		deltas.append((n1, len1, n2+len2-(n1+len1)))
16682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return deltas
16692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# lineDelta finds the appropriate line number delta to apply to the lines [n, n+len).
16712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# It returns an error if those lines were rewritten by the patch.
16722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef lineDelta(deltas, n, len):
16732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	d = 0
16742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for (old, oldlen, newdelta) in deltas:
16752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if old >= n+len:
16762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			break
16772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if old+len > n:
16782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return 0, "patch and recent changes conflict"
16792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		d = newdelta
16802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return d, ""
16812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16820d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
16832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef download(ui, repo, clname, **opts):
16842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""download a change from the code review server
16852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Download prints a description of the given change list
16872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	followed by its diff, downloaded from the code review server.
16882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
16890d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if codereview_disabled:
16900d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return codereview_disabled
16912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl, vers, patch, err = DownloadCL(ui, repo, clname)
16932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if err != "":
16942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return err
16952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ui.write(cl.EditorText() + "\n")
16962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ui.write(patch + "\n")
16972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
16982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16990d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
17000d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# hg file
17010d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
17020d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
17032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef file(ui, repo, clname, pat, *pats, **opts):
17042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""assign files to or remove files from a change list
17052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Assign files to or (with -d) remove files from a change list.
17072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	The -d option only removes files from the change list.
17092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	It does not edit them or remove them from the repository.
17102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
17110d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if codereview_disabled:
17120d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return codereview_disabled
17132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	pats = tuple([pat] + list(pats))
17152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not GoodCLName(clname):
17162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "invalid CL name " + clname
17172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dirty = {}
17192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl, err = LoadCL(ui, repo, clname, web=False)
17202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if err != '':
17212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return err
17222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.local:
17232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "cannot change non-local CL " + clname
17242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17250d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	files = ChangedFiles(ui, repo, pats)
17262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts["delete"]:
17282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		oldfiles = Intersect(files, cl.files)
17292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if oldfiles:
17302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not ui.quiet:
17312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.status("# Removing files from CL.  To undo:\n")
17322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.status("#	cd %s\n" % (repo.root))
17332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				for f in oldfiles:
17342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					ui.status("#	hg file %s %s\n" % (cl.name, f))
17352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.files = Sub(cl.files, oldfiles)
17362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.Flush(ui, repo)
17372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
17382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.status("no such files in CL")
17392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
17402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not files:
17422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "no such modified files"
17432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = Sub(files, cl.files)
17452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	taken = Taken(ui, repo)
17462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	warned = False
17472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for f in files:
17482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if f in taken:
17492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not warned and not ui.quiet:
17502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.status("# Taking files from other CLs.  To undo:\n")
17512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.status("#	cd %s\n" % (repo.root))
17522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				warned = True
17532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ocl = taken[f]
17542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not ui.quiet:
17552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.status("#	hg file %s %s\n" % (ocl.name, f))
17562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if ocl not in dirty:
17572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ocl.files = Sub(ocl.files, files)
17582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				dirty[ocl] = True
17592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.files = Add(cl.files, files)
17602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dirty[cl] = True
17612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for d, _ in dirty.items():
17622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		d.Flush(ui, repo)
17632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
17642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17650d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
17660d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# hg gofmt
17670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
17680d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
17692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef gofmt(ui, repo, *pats, **opts):
17702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""apply gofmt to modified files
17712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Applies gofmt to the modified files in the repository that match
17732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	the given patterns.
17742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
17750d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if codereview_disabled:
17760d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return codereview_disabled
17772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = ChangedExistingFiles(ui, repo, pats, opts)
17790d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	files = gofmt_required(files)
17802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not files:
17812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "no modified go files"
17822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cwd = os.getcwd()
17832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
17842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
17852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cmd = ["gofmt", "-l"]
17862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not opts["list"]:
17872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cmd += ["-w"]
17882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if os.spawnvp(os.P_WAIT, "gofmt", cmd + files) != 0:
17890d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			raise hg_util.Abort("gofmt did not exit cleanly")
17900d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	except hg_error.Abort, e:
17912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise
17922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
17930d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		raise hg_util.Abort("gofmt: " + ExceptionDetail())
17942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
17952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17960d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef gofmt_required(files):
17970d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return [f for f in files if (not f.startswith('test/') or f.startswith('test/bench/')) and f.endswith('.go')]
17980d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
17990d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
18000d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# hg mail
18010d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
18020d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
18032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef mail(ui, repo, *pats, **opts):
18042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""mail a change for review
18052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Uploads a patch to the code review server and then sends mail
18072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	to the reviewer and CC list asking for a review.
18082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
18090d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if codereview_disabled:
18100d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return codereview_disabled
18112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl, err = CommandLineCL(ui, repo, pats, opts, defaultcc=defaultcc)
18132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if err != "":
18142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return err
18152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.Upload(ui, repo, gofmt_just_warn=True)
18162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.reviewer:
18172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# If no reviewer is listed, assign the review to defaultcc.
18182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# This makes sure that it appears in the
18192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# codereview.appspot.com/user/defaultcc
18202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# page, so that it doesn't get dropped on the floor.
18212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not defaultcc:
18222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "no reviewers listed in CL"
18232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.cc = Sub(cl.cc, defaultcc)
18242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.reviewer = defaultcc
18252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.Flush(ui, repo)
18262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.files == []:
18282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "no changed files, not sending mail"
18292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.Mail(ui, repo)
18312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18320d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
18330d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# hg p / hg pq / hg ps / hg pending
18340d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
18350d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
18360d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef ps(ui, repo, *pats, **opts):
18370d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"""alias for hg p --short
18380d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"""
18390d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	opts['short'] = True
18400d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return pending(ui, repo, *pats, **opts)
18410d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
18420d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
18430d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef pq(ui, repo, *pats, **opts):
18440d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"""alias for hg p --quick
18450d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"""
18460d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	opts['quick'] = True
18470d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	return pending(ui, repo, *pats, **opts)
18480d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
18490d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
18502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef pending(ui, repo, *pats, **opts):
18512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""show pending changes
18522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Lists pending changes followed by a list of unassigned but modified files.
18542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
18550d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if codereview_disabled:
18560d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return codereview_disabled
18572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18580d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	quick = opts.get('quick', False)
18590d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	short = opts.get('short', False)
18600d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	m = LoadAllCL(ui, repo, web=not quick and not short)
18612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	names = m.keys()
18622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	names.sort()
18632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for name in names:
18642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = m[name]
18650d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if short:
18660d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			ui.write(name + "\t" + line1(cl.desc) + "\n")
18670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		else:
18680d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			ui.write(cl.PendingText(quick=quick) + "\n")
18692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18700d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if short:
18710d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return
18720d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	files = DefaultFiles(ui, repo, [])
18732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(files) > 0:
18742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = "Changed files not in any CL:\n"
18752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for f in files:
18762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "\t" + f + "\n"
18772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.write(s)
18782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18790d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
18800d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# hg submit
18812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18820d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef need_sync():
18830d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	raise hg_util.Abort("local repository out of date; must sync before submit")
18842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18850d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
18862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef submit(ui, repo, *pats, **opts):
18872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""submit change to remote repository
18882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Submits change to remote repository.
18902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Bails out if the local repository is not in sync with the remote one.
18912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
18920d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if codereview_disabled:
18930d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return codereview_disabled
18942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# We already called this on startup but sometimes Mercurial forgets.
18962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_mercurial_encoding_to_utf8()
18972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18980d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if not opts["no_incoming"] and hg_incoming(ui, repo):
18990d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		need_sync()
19002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl, err = CommandLineCL(ui, repo, pats, opts, defaultcc=defaultcc)
19022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if err != "":
19032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return err
19042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	user = None
19062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.copied_from:
19072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		user = cl.copied_from
19082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	userline = CheckContributor(ui, repo, user)
19092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(userline, str)
19102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	about = ""
19122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.reviewer:
19132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		about += "R=" + JoinComma([CutDomain(s) for s in cl.reviewer]) + "\n"
19142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts.get('tbr'):
19152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		tbr = SplitCommaSpace(opts.get('tbr'))
19162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.reviewer = Add(cl.reviewer, tbr)
19172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		about += "TBR=" + JoinComma([CutDomain(s) for s in tbr]) + "\n"
19182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.cc:
19192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		about += "CC=" + JoinComma([CutDomain(s) for s in cl.cc]) + "\n"
19202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.reviewer:
19222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "no reviewers listed in CL"
19232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.local:
19252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "cannot submit non-local CL"
19262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# upload, to sync current patch and also get change number if CL is new.
19282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.copied_from:
19292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.Upload(ui, repo, gofmt_just_warn=True)
19302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# check gofmt for real; allowed upload to warn in order to save CL.
19322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.Flush(ui, repo)
19332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	CheckFormat(ui, repo, cl.files)
19342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	about += "%s%s\n" % (server_url_base, cl.name)
19362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.copied_from:
19382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		about += "\nCommitter: " + CheckContributor(ui, repo, None) + "\n"
19392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(about, str)
19402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.mailed and not cl.copied_from:		# in case this is TBR
19422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.Mail(ui, repo)
19432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# submit changes locally
19450d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	message = cl.desc.rstrip() + "\n\n" + about
19460d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	typecheck(message, str)
19472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("pushing " + cl.name + " to remote server")
19492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19500d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if hg_outgoing(ui, repo):
19510d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		raise hg_util.Abort("local repository corrupt or out-of-phase with remote: found outgoing changes")
19520d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
19530d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	old_heads = len(hg_heads(ui, repo).split())
19542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19550d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global commit_okay
19560d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	commit_okay = True
19570d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	ret = hg_commit(ui, repo, *['path:'+f for f in cl.files], message=message, user=userline)
19580d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	commit_okay = False
19590d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if ret:
19602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "nothing changed"
19610d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	node = repo["-1"].node()
19622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# push to remote; if it fails for any reason, roll back
19632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
19640d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		new_heads = len(hg_heads(ui, repo).split())
19650d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if old_heads != new_heads and not (old_heads == 0 and new_heads == 1):
19660d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			# Created new head, so we weren't up to date.
19670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			need_sync()
19680d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
19690d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		# Push changes to remote.  If it works, we're committed.  If not, roll back.
19700d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		try:
19710d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			hg_push(ui, repo)
19720d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		except hg_error.Abort, e:
19730d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			if e.message.find("push creates new heads") >= 0:
19740d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				# Remote repository had changes we missed.
19750d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				need_sync()
19760d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			raise
19772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
19782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		real_rollback()
19792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise
19802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19810d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	# We're committed. Upload final patch, close review, add commit message.
19820d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	changeURL = hg_node.short(node)
19830d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	url = ui.expandpath("default")
19840d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	m = re.match("(^https?://([^@/]+@)?([^.]+)\.googlecode\.com/hg/?)" + "|" +
19850d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		"(^https?://([^@/]+@)?code\.google\.com/p/([^/.]+)(\.[^./]+)?/?)", url)
19862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if m:
19870d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if m.group(1): # prj.googlecode.com/hg/ case
19880d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			changeURL = "http://code.google.com/p/%s/source/detail?r=%s" % (m.group(3), changeURL)
19890d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		elif m.group(4) and m.group(7): # code.google.com/p/prj.subrepo/ case
19900d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			changeURL = "http://code.google.com/p/%s/source/detail?r=%s&repo=%s" % (m.group(6), changeURL, m.group(7)[1:])
19910d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		elif m.group(4): # code.google.com/p/prj/ case
19920d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			changeURL = "http://code.google.com/p/%s/source/detail?r=%s" % (m.group(6), changeURL)
19930d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		else:
19940d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			print >>sys.stderr, "URL: ", url
19952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
19962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print >>sys.stderr, "URL: ", url
19970d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	pmsg = "*** Submitted as " + changeURL + " ***\n\n" + message
19982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# When posting, move reviewers to CC line,
20002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# so that the issue stops showing up in their "My Issues" page.
20012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	PostMessage(ui, cl.name, pmsg, reviewers="", cc=JoinComma(cl.reviewer+cl.cc))
20022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.copied_from:
20042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		EditDesc(cl.name, closed=True, private=cl.private)
20052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.Delete(ui, repo)
20060d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
20072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	c = repo[None]
20082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if c.branch() == releaseBranch and not c.modified() and not c.added() and not c.removed():
20092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.write("switching from %s to default branch.\n" % releaseBranch)
20100d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		err = hg_clean(repo, "default")
20112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err:
20122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return err
20132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return None
20142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20150d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
20160d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# hg sync
20170d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
20180d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
20192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef sync(ui, repo, **opts):
20202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""synchronize with remote repository
20212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Incorporates recent changes from the remote repository
20232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	into the local repository.
20242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
20250d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if codereview_disabled:
20260d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return codereview_disabled
20272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not opts["local"]:
20290d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		err = hg_pull(ui, repo, update=True)
20302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err:
20312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return err
20322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sync_changes(ui, repo)
20332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef sync_changes(ui, repo):
20352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Look through recent change log descriptions to find
20362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# potential references to http://.*/our-CL-number.
20372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Double-check them by looking at the Rietveld log.
20380d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	for rev in hg_log(ui, repo, limit=100, template="{node}\n").split():
20392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		desc = repo[rev].description().strip()
20402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for clname in re.findall('(?m)^http://(?:[^\n]+)/([0-9]+)$', desc):
20412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if IsLocalCL(ui, repo, clname) and IsRietveldSubmitted(ui, clname, repo[rev].hex()):
20422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("CL %s submitted as %s; closing\n" % (clname, repo[rev]))
20432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				cl, err = LoadCL(ui, repo, clname, web=False)
20442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if err != "":
20452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					ui.warn("loading CL %s: %s\n" % (clname, err))
20462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					continue
20472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if not cl.copied_from:
20482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					EditDesc(cl.name, closed=True, private=cl.private)
20492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				cl.Delete(ui, repo)
20502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Remove files that are not modified from the CLs in which they appear.
20522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	all = LoadAllCL(ui, repo, web=False)
20530d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	changed = ChangedFiles(ui, repo, [])
20540d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	for cl in all.values():
20552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		extra = Sub(cl.files, changed)
20562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if extra:
20572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("Removing unmodified files from CL %s:\n" % (cl.name,))
20582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for f in extra:
20592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("\t%s\n" % (f,))
20602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.files = Sub(cl.files, extra)
20612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.Flush(ui, repo)
20622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not cl.files:
20632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not cl.copied_from:
20642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("CL %s has no files; delete (abandon) with hg change -d %s\n" % (cl.name, cl.name))
20652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
20662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("CL %s has no files; delete locally with hg change -D %s\n" % (cl.name, cl.name))
20672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
20682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20690d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
20700d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# hg upload
20710d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
20720d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin@hgcommand
20732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef upload(ui, repo, name, **opts):
20742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""upload diffs to the code review server
20752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Uploads the current modifications for a given change to the server.
20772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
20780d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if codereview_disabled:
20790d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return codereview_disabled
20802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	repo.ui.quiet = True
20822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl, err = LoadCL(ui, repo, name, web=True)
20832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if err != "":
20842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return err
20852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.local:
20862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "cannot upload non-local change"
20872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.Upload(ui, repo)
20882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	print "%s%s\n" % (server_url_base, cl.name)
20892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
20902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20910d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
20920d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Table of commands, supplied to Mercurial for installation.
20930d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
20942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonreview_opts = [
20952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	('r', 'reviewer', '', 'add reviewer'),
20962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	('', 'cc', '', 'add cc'),
20972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	('', 'tbr', '', 'add future reviewer'),
20982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	('m', 'message', '', 'change description (for new change)'),
20992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson]
21002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
21012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsoncmdtable = {
21022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# The ^ means to show this command in the help text that
21032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# is printed when running hg with no arguments.
21042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^change": (
21052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		change,
21062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
21072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('d', 'delete', None, 'delete existing change list'),
21082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('D', 'deletelocal', None, 'delete locally, but do not change CL on server'),
21092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('i', 'stdin', None, 'read change list from standard input'),
21102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('o', 'stdout', None, 'print change list to standard output'),
21112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('p', 'pending', None, 'print pending summary to standard output'),
21122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
21132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"[-d | -D] [-i] [-o] change# or FILE ..."
21142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
21152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^clpatch": (
21162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		clpatch,
21172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
21182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
21192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'no_incoming', None, 'disable check for incoming changes'),
21202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
21212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"change#"
21222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
21232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Would prefer to call this codereview-login, but then
21242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# hg help codereview prints the help for this command
21252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# instead of the help for the extension.
21262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"code-login": (
21272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		code_login,
21282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[],
21292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"",
21302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
21312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^download": (
21322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		download,
21332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[],
21342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"change#"
21352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
21362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^file": (
21372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		file,
21382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
21392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('d', 'delete', None, 'delete files from change list (but not repository)'),
21402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
21412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"[-d] change# FILE ..."
21422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
21432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^gofmt": (
21442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		gofmt,
21452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
21462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('l', 'list', None, 'list files that would change, but do not edit them'),
21472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
21482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"FILE ..."
21492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
21502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^pending|p": (
21512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pending,
21520d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		[
21530d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			('s', 'short', False, 'show short result form'),
21540d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			('', 'quick', False, 'do not consult codereview server'),
21550d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		],
21560d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		"[FILE ...]"
21570d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	),
21580d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"^ps": (
21590d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		ps,
21600d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		[],
21610d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		"[FILE ...]"
21620d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	),
21630d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"^pq": (
21640d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		pq,
21652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[],
21662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"[FILE ...]"
21672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
21682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^mail": (
21692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		mail,
21702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		review_opts + [
21710d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		] + hg_commands.walkopts,
21722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"[-r reviewer] [--cc cc] [change# | file ...]"
21732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
21742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^release-apply": (
21752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		release_apply,
21762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
21772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
21782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'no_incoming', None, 'disable check for incoming changes'),
21792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
21802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"change#"
21812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
21822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# TODO: release-start, release-tag, weekly-tag
21832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^submit": (
21842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		submit,
21852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		review_opts + [
21862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'no_incoming', None, 'disable initial incoming check (for testing)'),
21870d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		] + hg_commands.walkopts + hg_commands.commitopts + hg_commands.commitopts2,
21882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"[-r reviewer] [--cc cc] [change# | file ...]"
21892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
21902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^sync": (
21912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		sync,
21922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
21932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'local', None, 'do not pull changes from remote repository')
21942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
21952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"[--local]",
21962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
21972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^undo": (
21982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		undo,
21992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
22002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
22012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'no_incoming', None, 'disable check for incoming changes'),
22022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
22032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"change#"
22042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
22052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^upload": (
22062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		upload,
22072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[],
22082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"change#"
22092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
22102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson}
22112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
22120d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin#######################################################################
22130d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin# Mercurial extension initialization
22140d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
22150d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef norollback(*pats, **opts):
22160d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	"""(disabled when using this extension)"""
22170d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	raise hg_util.Abort("codereview extension enabled; use undo instead of rollback")
22180d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
22190d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkincodereview_init = False
22200d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
22210d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkindef reposetup(ui, repo):
22220d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global codereview_disabled
22230d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global defaultcc
22240d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
22250d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	# reposetup gets called both for the local repository
22260d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	# and also for any repository we are pulling or pushing to.
22270d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	# Only initialize the first time.
22280d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global codereview_init
22290d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if codereview_init:
22300d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return
22310d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	codereview_init = True
22320d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
22330d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	# Read repository-specific options from lib/codereview/codereview.cfg or codereview.cfg.
22340d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	root = ''
22350d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	try:
22360d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		root = repo.root
22370d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	except:
22380d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		# Yes, repo might not have root; see issue 959.
22390d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		codereview_disabled = 'codereview disabled: repository has no root'
22400d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return
22410d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
22420d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	repo_config_path = ''
22430d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	p1 = root + '/lib/codereview/codereview.cfg'
22440d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	p2 = root + '/codereview.cfg'
22450d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if os.access(p1, os.F_OK):
22460d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		repo_config_path = p1
22470d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	else:
22480d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		repo_config_path = p2
22490d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	try:
22500d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		f = open(repo_config_path)
22510d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		for line in f:
22520d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			if line.startswith('defaultcc:'):
22530d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				defaultcc = SplitCommaSpace(line[len('defaultcc:'):])
22540d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			if line.startswith('contributors:'):
22550d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				global contributorsURL
22560d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				contributorsURL = line[len('contributors:'):].strip()
22570d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	except:
22580d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		codereview_disabled = 'codereview disabled: cannot open ' + repo_config_path
22590d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return
22600d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
22610d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	remote = ui.config("paths", "default", "")
22620d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if remote.find("://") < 0:
22630d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		raise hg_util.Abort("codereview: default path '%s' is not a URL" % (remote,))
22640d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
22650d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	InstallMatch(ui, repo)
22660d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	RietveldSetup(ui, repo)
22670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
22680d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	# Disable the Mercurial commands that might change the repository.
22690d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	# Only commands in this extension are supposed to do that.
22700d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	ui.setconfig("hooks", "precommit.codereview", precommithook)
22710d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
22720d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	# Rollback removes an existing commit.  Don't do that either.
22730d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global real_rollback
22740d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	real_rollback = repo.rollback
22750d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	repo.rollback = norollback
22760d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
22772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
22782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#######################################################################
22792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Wrappers around upload.py for interacting with Rietveld
22802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
22810d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkinfrom HTMLParser import HTMLParser
22820d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
22832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# HTML form parser
22842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass FormParser(HTMLParser):
22852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self):
22862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.map = {}
22872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.curtag = None
22882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.curdata = None
22892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		HTMLParser.__init__(self)
22902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def handle_starttag(self, tag, attrs):
22912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if tag == "input":
22922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			key = None
22932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			value = ''
22942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for a in attrs:
22952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if a[0] == 'name':
22962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					key = a[1]
22972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if a[0] == 'value':
22982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					value = a[1]
22992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if key is not None:
23002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				self.map[key] = value
23012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if tag == "textarea":
23022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			key = None
23032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for a in attrs:
23042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if a[0] == 'name':
23052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					key = a[1]
23062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if key is not None:
23072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				self.curtag = key
23082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				self.curdata = ''
23092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def handle_endtag(self, tag):
23102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if tag == "textarea" and self.curtag is not None:
23112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.map[self.curtag] = self.curdata
23122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.curtag = None
23132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.curdata = None
23142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def handle_charref(self, name):
23152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.handle_data(unichr(int(name)))
23162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def handle_entityref(self, name):
23172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		import htmlentitydefs
23182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if name in htmlentitydefs.entitydefs:
23192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.handle_data(htmlentitydefs.entitydefs[name])
23202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
23212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.handle_data("&" + name + ";")
23222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def handle_data(self, data):
23232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.curdata is not None:
23242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.curdata += data
23252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef JSONGet(ui, path):
23272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
23282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		data = MySend(path, force_auth=False)
23292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(data, str)
23302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		d = fix_json(json.loads(data))
23312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
23322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.warn("JSONGet %s: %s\n" % (path, ExceptionDetail()))
23332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None
23342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return d
23352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Clean up json parser output to match our expectations:
23372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#   * all strings are UTF-8-encoded str, not unicode.
23382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#   * missing fields are missing, not None,
23392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#     so that d.get("foo", defaultvalue) works.
23402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef fix_json(x):
23412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if type(x) in [str, int, float, bool, type(None)]:
23422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pass
23432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	elif type(x) is unicode:
23442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		x = x.encode("utf-8")
23452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	elif type(x) is list:
23462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for i in range(len(x)):
23472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			x[i] = fix_json(x[i])
23482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	elif type(x) is dict:
23492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		todel = []
23502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for k in x:
23512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if x[k] is None:
23522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				todel.append(k)
23532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
23542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				x[k] = fix_json(x[k])
23552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for k in todel:
23562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			del x[k]
23572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
23580d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		raise hg_util.Abort("unknown type " + str(type(x)) + " in fix_json")
23592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if type(x) is str:
23602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		x = x.replace('\r\n', '\n')
23612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return x
23622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef IsRietveldSubmitted(ui, clname, hex):
23642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dict = JSONGet(ui, "/api/" + clname + "?messages=true")
23652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if dict is None:
23662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return False
23672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for msg in dict.get("messages", []):
23682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		text = msg.get("text", "")
23692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		m = re.match('\*\*\* Submitted as [^*]*?([0-9a-f]+) \*\*\*', text)
23702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if m is not None and len(m.group(1)) >= 8 and hex.startswith(m.group(1)):
23712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return True
23722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return False
23732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef IsRietveldMailed(cl):
23752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for msg in cl.dict.get("messages", []):
23762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if msg.get("text", "").find("I'd like you to review this change") >= 0:
23772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return True
23782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return False
23792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef DownloadCL(ui, repo, clname):
23812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("downloading CL " + clname)
23822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl, err = LoadCL(ui, repo, clname, web=True)
23832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if err != "":
23842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, None, None, "error loading CL %s: %s" % (clname, err)
23852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Find most recent diff
23872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	diffs = cl.dict.get("patchsets", [])
23882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not diffs:
23892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, None, None, "CL has no patch sets"
23902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	patchid = diffs[-1]
23912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	patchset = JSONGet(ui, "/api/" + clname + "/" + str(patchid))
23932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if patchset is None:
23942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, None, None, "error loading CL patchset %s/%d" % (clname, patchid)
23952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if patchset.get("patchset", 0) != patchid:
23962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, None, None, "malformed patchset information"
23972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	vers = ""
23992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	msg = patchset.get("message", "").split()
24002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(msg) >= 3 and msg[0] == "diff" and msg[1] == "-r":
24012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		vers = msg[2]
24022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	diff = "/download/issue" + clname + "_" + str(patchid) + ".diff"
24032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	diffdata = MySend(diff, force_auth=False)
24052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Print warning if email is not in CONTRIBUTORS file.
24072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	email = cl.dict.get("owner_email", "")
24082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not email:
24092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, None, None, "cannot find owner for %s" % (clname)
24102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	him = FindContributor(ui, repo, email)
24112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	me = FindContributor(ui, repo, None)
24122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if him == me:
24132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.mailed = IsRietveldMailed(cl)
24142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
24152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.copied_from = email
24162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return cl, vers, diffdata, ""
24182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef MySend(request_path, payload=None,
24202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		content_type="application/octet-stream",
24212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		timeout=None, force_auth=True,
24222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		**kwargs):
24232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Run MySend1 maybe twice, because Rietveld is unreliable."""
24242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
24252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return MySend1(request_path, payload, content_type, timeout, force_auth, **kwargs)
24262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except Exception, e:
24272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if type(e) != urllib2.HTTPError or e.code != 500:	# only retry on HTTP 500 error
24282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise
24292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print >>sys.stderr, "Loading "+request_path+": "+ExceptionDetail()+"; trying again in 2 seconds."
24302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		time.sleep(2)
24312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return MySend1(request_path, payload, content_type, timeout, force_auth, **kwargs)
24322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Like upload.py Send but only authenticates when the
24342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# redirect is to www.google.com/accounts.  This keeps
24352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# unnecessary redirects from happening during testing.
24362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef MySend1(request_path, payload=None,
24372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				content_type="application/octet-stream",
24382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				timeout=None, force_auth=True,
24392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				**kwargs):
24402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Sends an RPC and returns the response.
24412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Args:
24432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		request_path: The path to send the request to, eg /api/appversion/create.
24442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		payload: The body of the request, or None to send an empty request.
24452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		content_type: The Content-Type header to use.
24462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		timeout: timeout in seconds; default None i.e. no timeout.
24472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			(Note: for large requests on OS X, the timeout doesn't work right.)
24482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		kwargs: Any keyword arguments are converted into query string parameters.
24492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Returns:
24512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		The response body, as a string.
24522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
24532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# TODO: Don't require authentication.  Let the server say
24542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# whether it is necessary.
24552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	global rpc
24562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if rpc == None:
24572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		rpc = GetRpcServer(upload_options)
24582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	self = rpc
24592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not self.authenticated and force_auth:
24602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self._Authenticate()
24612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if request_path is None:
24622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
24632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	old_timeout = socket.getdefaulttimeout()
24652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	socket.setdefaulttimeout(timeout)
24662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
24672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		tries = 0
24682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		while True:
24692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			tries += 1
24702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			args = dict(kwargs)
24712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			url = "http://%s%s" % (self.host, request_path)
24722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if args:
24732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				url += "?" + urllib.urlencode(args)
24742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			req = self._CreateRequest(url=url, data=payload)
24752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			req.add_header("Content-Type", content_type)
24762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			try:
24772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				f = self.opener.open(req)
24782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				response = f.read()
24792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				f.close()
24802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# Translate \r\n into \n, because Rietveld doesn't.
24812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				response = response.replace('\r\n', '\n')
24822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# who knows what urllib will give us
24832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if type(response) == unicode:
24842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					response = response.encode("utf-8")
24852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				typecheck(response, str)
24862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return response
24872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			except urllib2.HTTPError, e:
24882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if tries > 3:
24892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					raise
24902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				elif e.code == 401:
24912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					self._Authenticate()
24922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				elif e.code == 302:
24932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					loc = e.info()["location"]
24942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					if not loc.startswith('https://www.google.com/a') or loc.find('/ServiceLogin') < 0:
24952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						return ''
24962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					self._Authenticate()
24972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				else:
24982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					raise
24992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	finally:
25002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		socket.setdefaulttimeout(old_timeout)
25012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef GetForm(url):
25032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	f = FormParser()
25042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	f.feed(ustr(MySend(url)))	# f.feed wants unicode
25052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	f.close()
25062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# convert back to utf-8 to restore sanity
25072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	m = {}
25082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for k,v in f.map.items():
25092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		m[k.encode("utf-8")] = v.replace("\r\n", "\n").encode("utf-8")
25102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return m
25112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef EditDesc(issue, subject=None, desc=None, reviewers=None, cc=None, closed=False, private=False):
25132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("uploading change to description")
25142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	form_fields = GetForm("/" + issue + "/edit")
25152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if subject is not None:
25162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['subject'] = subject
25172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if desc is not None:
25182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['description'] = desc
25192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if reviewers is not None:
25202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['reviewers'] = reviewers
25212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cc is not None:
25222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['cc'] = cc
25232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if closed:
25242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['closed'] = "checked"
25252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if private:
25262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['private'] = "checked"
25272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ctype, body = EncodeMultipartFormData(form_fields.items(), [])
25282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	response = MySend("/" + issue + "/edit", body, content_type=ctype)
25292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if response != "":
25302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print >>sys.stderr, "Error editing description:\n" + "Sent form: \n", form_fields, "\n", response
25312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		sys.exit(2)
25322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef PostMessage(ui, issue, message, reviewers=None, cc=None, send_mail=True, subject=None):
25342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("uploading message")
25352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	form_fields = GetForm("/" + issue + "/publish")
25362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if reviewers is not None:
25372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['reviewers'] = reviewers
25382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cc is not None:
25392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['cc'] = cc
25402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if send_mail:
25412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['send_mail'] = "checked"
25422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
25432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		del form_fields['send_mail']
25442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if subject is not None:
25452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['subject'] = subject
25462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	form_fields['message'] = message
25472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	form_fields['message_only'] = '1'	# Don't include draft comments
25492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if reviewers is not None or cc is not None:
25502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['message_only'] = ''	# Must set '' in order to override cc/reviewer
25512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ctype = "applications/x-www-form-urlencoded"
25522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	body = urllib.urlencode(form_fields)
25532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	response = MySend("/" + issue + "/publish", body, content_type=ctype)
25542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if response != "":
25552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print response
25562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		sys.exit(2)
25572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass opt(object):
25592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	pass
25602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef RietveldSetup(ui, repo):
25620d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global force_google_account
25630d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global rpc
25640d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global server
25650d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global server_url_base
25660d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global upload_options
25670d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	global verbosity
25682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not ui.verbose:
25702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		verbosity = 0
25712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Config options.
25732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	x = ui.config("codereview", "server")
25742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if x is not None:
25752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		server = x
25762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# TODO(rsc): Take from ui.username?
25782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	email = None
25792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	x = ui.config("codereview", "email")
25802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if x is not None:
25812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		email = x
25822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	server_url_base = "http://" + server + "/"
25842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	testing = ui.config("codereview", "testing")
25862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	force_google_account = ui.configbool("codereview", "force_google_account", False)
25872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options = opt()
25892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.email = email
25902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.host = None
25912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.verbose = 0
25922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.description = None
25932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.description_file = None
25942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.reviewers = None
25952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.cc = None
25962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.message = None
25972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.issue = None
25982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.download_base = False
25992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.revision = None
26002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.send_mail = False
26012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.vcs = None
26022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.server = server
26032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.save_cookies = True
26042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if testing:
26062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		upload_options.save_cookies = False
26072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		upload_options.email = "test@example.com"
26082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	rpc = None
26102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	global releaseBranch
26122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	tags = repo.branchtags().keys()
26130d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	if 'release-branch.go10' in tags:
26142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# NOTE(rsc): This tags.sort is going to get the wrong
26150d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		# answer when comparing release-branch.go9 with
26160d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		# release-branch.go10.  It will be a while before we care.
26170d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		raise hg_util.Abort('tags.sort needs to be fixed for release-branch.go10')
26182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	tags.sort()
26192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for t in tags:
26200d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		if t.startswith('release-branch.go'):
26212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			releaseBranch = t
26222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#######################################################################
26242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# http://codereview.appspot.com/static/upload.py, heavily edited.
26252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#!/usr/bin/env python
26272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
26282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Copyright 2007 Google Inc.
26292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
26302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Licensed under the Apache License, Version 2.0 (the "License");
26312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# you may not use this file except in compliance with the License.
26322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# You may obtain a copy of the License at
26332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
26342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#	http://www.apache.org/licenses/LICENSE-2.0
26352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
26362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Unless required by applicable law or agreed to in writing, software
26372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# distributed under the License is distributed on an "AS IS" BASIS,
26382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# See the License for the specific language governing permissions and
26402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# limitations under the License.
26412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""Tool for uploading diffs from a version control system to the codereview app.
26432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonUsage summary: upload.py [options] [-- diff_options]
26452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonDiff options are passed to the diff command of the underlying system.
26472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonSupported version control systems:
26492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Git
26502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Mercurial
26512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Subversion
26522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonIt is important for Git/Mercurial users to specify a tree/node/branch to diff
26542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonagainst by using the '--rev' option.
26552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
26562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# This code is derived from appcfg.py in the App Engine SDK (open source),
26572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# and from ASPN recipe #146306.
26582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport cookielib
26602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport getpass
26612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport logging
26622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport mimetypes
26632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport optparse
26642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport os
26652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport re
26662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport socket
26672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport subprocess
26682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport sys
26692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport urllib
26702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport urllib2
26712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport urlparse
26722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# The md5 module was deprecated in Python 2.5.
26742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsontry:
26752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	from hashlib import md5
26762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonexcept ImportError:
26772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	from md5 import md5
26782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsontry:
26802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	import readline
26812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonexcept ImportError:
26822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	pass
26832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# The logging verbosity:
26852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#  0: Errors only.
26862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#  1: Status messages.
26872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#  2: Info logs.
26882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#  3: Debug logs.
26892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonverbosity = 1
26902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Max size of patch or base file.
26922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonMAX_UPLOAD_SIZE = 900 * 1024
26932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# whitelist for non-binary filetypes which do not start with "text/"
26952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# .mm (Objective-C) shows up as application/x-freemind on my Linux box.
26962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonTEXT_MIMETYPES = [
26972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	'application/javascript',
26982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	'application/x-javascript',
26992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	'application/x-freemind'
27002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson]
27012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef GetEmail(prompt):
27032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Prompts the user for their email address and returns it.
27042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	The last used email address is saved to a file and offered up as a suggestion
27062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	to the user. If the user presses enter without typing in anything the last
27072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	used email address is used. If the user enters a new address, it is saved
27082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for next time we prompt.
27092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
27112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	last_email_file_name = os.path.expanduser("~/.last_codereview_email_address")
27122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	last_email = ""
27132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if os.path.exists(last_email_file_name):
27142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
27152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			last_email_file = open(last_email_file_name, "r")
27162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			last_email = last_email_file.readline().strip("\n")
27172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			last_email_file.close()
27182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			prompt += " [%s]" % last_email
27192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except IOError, e:
27202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pass
27212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	email = raw_input(prompt + ": ").strip()
27222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if email:
27232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
27242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			last_email_file = open(last_email_file_name, "w")
27252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			last_email_file.write(email)
27262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			last_email_file.close()
27272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except IOError, e:
27282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pass
27292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
27302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		email = last_email
27312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return email
27322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef StatusUpdate(msg):
27352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Print a status message to stdout.
27362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	If 'verbosity' is greater than 0, print the message.
27382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Args:
27402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		msg: The string to print.
27412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
27422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if verbosity > 0:
27432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print msg
27442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef ErrorExit(msg):
27472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Print an error message to stderr and exit."""
27482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	print >>sys.stderr, msg
27492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sys.exit(1)
27502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass ClientLoginError(urllib2.HTTPError):
27532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Raised to indicate there was an error authenticating with ClientLogin."""
27542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self, url, code, msg, headers, args):
27562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		urllib2.HTTPError.__init__(self, url, code, msg, headers, None)
27572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.args = args
27582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.reason = args["Error"]
27592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass AbstractRpcServer(object):
27622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Provides a common interface for a simple RPC server."""
27632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self, host, auth_function, host_override=None, extra_headers={}, save_cookies=False):
27652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Creates a new HttpRpcServer.
27662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Args:
27682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			host: The host to send requests to.
27692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			auth_function: A function that takes no arguments and returns an
27702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				(email, password) tuple when called. Will be called if authentication
27712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				is required.
27722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			host_override: The host header to send to the server (defaults to host).
27732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			extra_headers: A dict of extra headers to append to every request.
27742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			save_cookies: If True, save the authentication cookies to local disk.
27752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				If False, use an in-memory cookiejar instead.  Subclasses must
27762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				implement this functionality.  Defaults to False.
27772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
27782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.host = host
27792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.host_override = host_override
27802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.auth_function = auth_function
27812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.authenticated = False
27822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.extra_headers = extra_headers
27832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.save_cookies = save_cookies
27842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.opener = self._GetOpener()
27852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.host_override:
27862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			logging.info("Server: %s; Host: %s", self.host, self.host_override)
27872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
27882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			logging.info("Server: %s", self.host)
27892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _GetOpener(self):
27912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Returns an OpenerDirector for making HTTP requests.
27922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Returns:
27942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			A urllib2.OpenerDirector object.
27952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
27962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise NotImplementedError()
27972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _CreateRequest(self, url, data=None):
27992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Creates a new urllib request."""
28002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		logging.debug("Creating request for: '%s' with payload:\n%s", url, data)
28012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		req = urllib2.Request(url, data=data)
28022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.host_override:
28032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			req.add_header("Host", self.host_override)
28042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for key, value in self.extra_headers.iteritems():
28052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			req.add_header(key, value)
28062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return req
28072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _GetAuthToken(self, email, password):
28092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Uses ClientLogin to authenticate the user, returning an auth token.
28102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Args:
28122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			email:    The user's email address
28132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			password: The user's password
28142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Raises:
28162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ClientLoginError: If there was an error authenticating with ClientLogin.
28172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			HTTPError: If there was some other form of HTTP error.
28182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Returns:
28202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			The authentication token returned by ClientLogin.
28212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
28222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		account_type = "GOOGLE"
28232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.host.endswith(".google.com") and not force_google_account:
28242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# Needed for use inside Google.
28252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			account_type = "HOSTED"
28262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		req = self._CreateRequest(
28272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				url="https://www.google.com/accounts/ClientLogin",
28282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				data=urllib.urlencode({
28292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"Email": email,
28302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"Passwd": password,
28312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"service": "ah",
28322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"source": "rietveld-codereview-upload",
28332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"accountType": account_type,
28342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				}),
28352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		)
28362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
28372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			response = self.opener.open(req)
28382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			response_body = response.read()
28392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			response_dict = dict(x.split("=") for x in response_body.split("\n") if x)
28402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return response_dict["Auth"]
28412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except urllib2.HTTPError, e:
28422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if e.code == 403:
28432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				body = e.read()
28442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				response_dict = dict(x.split("=", 1) for x in body.split("\n") if x)
28452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				raise ClientLoginError(req.get_full_url(), e.code, e.msg, e.headers, response_dict)
28462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
28472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				raise
28482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _GetAuthCookie(self, auth_token):
28502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Fetches authentication cookies for an authentication token.
28512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Args:
28532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			auth_token: The authentication token returned by ClientLogin.
28542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Raises:
28562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			HTTPError: If there was an error fetching the authentication cookies.
28572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
28582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# This is a dummy value to allow us to identify when we're successful.
28592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		continue_location = "http://localhost/"
28602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		args = {"continue": continue_location, "auth": auth_token}
28612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		req = self._CreateRequest("http://%s/_ah/login?%s" % (self.host, urllib.urlencode(args)))
28622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
28632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			response = self.opener.open(req)
28642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except urllib2.HTTPError, e:
28652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			response = e
28662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if (response.code != 302 or
28672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				response.info()["location"] != continue_location):
28682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg, response.headers, response.fp)
28692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.authenticated = True
28702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _Authenticate(self):
28722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Authenticates the user.
28732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		The authentication process works as follows:
28752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		1) We get a username and password from the user
28762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		2) We use ClientLogin to obtain an AUTH token for the user
28772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				(see http://code.google.com/apis/accounts/AuthForInstalledApps.html).
28782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		3) We pass the auth token to /_ah/login on the server to obtain an
28792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				authentication cookie. If login was successful, it tries to redirect
28802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				us to the URL we provided.
28812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		If we attempt to access the upload API without first obtaining an
28832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		authentication cookie, it returns a 401 response (or a 302) and
28842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		directs us to authenticate ourselves with ClientLogin.
28852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
28862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for i in range(3):
28872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			credentials = self.auth_function()
28882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			try:
28892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				auth_token = self._GetAuthToken(credentials[0], credentials[1])
28902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			except ClientLoginError, e:
28912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "BadAuthentication":
28922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "Invalid username or password."
28932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					continue
28942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "CaptchaRequired":
28952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, (
28962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"Please go to\n"
28972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"https://www.google.com/accounts/DisplayUnlockCaptcha\n"
28982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"and verify you are a human.  Then try again.")
28992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
29002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "NotVerified":
29012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "Account not verified."
29022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
29032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "TermsNotAgreed":
29042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "User has not agreed to TOS."
29052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
29062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "AccountDeleted":
29072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "The user account has been deleted."
29082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
29092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "AccountDisabled":
29102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "The user account has been disabled."
29112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
29122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "ServiceDisabled":
29132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "The user's access to the service has been disabled."
29142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
29152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "ServiceUnavailable":
29162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "The service is not available; try again later."
29172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
29182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				raise
29192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self._GetAuthCookie(auth_token)
29202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return
29212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def Send(self, request_path, payload=None,
29232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					content_type="application/octet-stream",
29242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					timeout=None,
29252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					**kwargs):
29262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Sends an RPC and returns the response.
29272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Args:
29292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			request_path: The path to send the request to, eg /api/appversion/create.
29302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			payload: The body of the request, or None to send an empty request.
29312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			content_type: The Content-Type header to use.
29322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			timeout: timeout in seconds; default None i.e. no timeout.
29332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				(Note: for large requests on OS X, the timeout doesn't work right.)
29342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			kwargs: Any keyword arguments are converted into query string parameters.
29352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Returns:
29372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			The response body, as a string.
29382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
29392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# TODO: Don't require authentication.  Let the server say
29402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# whether it is necessary.
29412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not self.authenticated:
29422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self._Authenticate()
29432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		old_timeout = socket.getdefaulttimeout()
29452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		socket.setdefaulttimeout(timeout)
29462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
29472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			tries = 0
29482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			while True:
29492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				tries += 1
29502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				args = dict(kwargs)
29512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				url = "http://%s%s" % (self.host, request_path)
29522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if args:
29532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					url += "?" + urllib.urlencode(args)
29542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				req = self._CreateRequest(url=url, data=payload)
29552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				req.add_header("Content-Type", content_type)
29562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				try:
29572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					f = self.opener.open(req)
29582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					response = f.read()
29592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					f.close()
29602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					return response
29612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				except urllib2.HTTPError, e:
29622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					if tries > 3:
29632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						raise
29642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					elif e.code == 401 or e.code == 302:
29652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						self._Authenticate()
29662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					else:
29672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						raise
29682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		finally:
29692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			socket.setdefaulttimeout(old_timeout)
29702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass HttpRpcServer(AbstractRpcServer):
29732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Provides a simplified RPC-style interface for HTTP requests."""
29742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _Authenticate(self):
29762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Save the cookie jar after authentication."""
29772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		super(HttpRpcServer, self)._Authenticate()
29782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.save_cookies:
29792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			StatusUpdate("Saving authentication cookies to %s" % self.cookie_file)
29802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.cookie_jar.save()
29812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _GetOpener(self):
29832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Returns an OpenerDirector that supports cookies and ignores redirects.
29842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Returns:
29862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			A urllib2.OpenerDirector object.
29872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
29882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener = urllib2.OpenerDirector()
29892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.ProxyHandler())
29902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.UnknownHandler())
29912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.HTTPHandler())
29922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.HTTPDefaultErrorHandler())
29932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.HTTPSHandler())
29942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.HTTPErrorProcessor())
29952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.save_cookies:
29962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.cookie_file = os.path.expanduser("~/.codereview_upload_cookies_" + server)
29972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.cookie_jar = cookielib.MozillaCookieJar(self.cookie_file)
29982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if os.path.exists(self.cookie_file):
29992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				try:
30002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					self.cookie_jar.load()
30012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					self.authenticated = True
30022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					StatusUpdate("Loaded authentication cookies from %s" % self.cookie_file)
30032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				except (cookielib.LoadError, IOError):
30042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					# Failed to load cookies - just ignore them.
30052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					pass
30062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
30072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# Create an empty cookie file with mode 600
30082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				fd = os.open(self.cookie_file, os.O_CREAT, 0600)
30092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				os.close(fd)
30102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# Always chmod the cookie file
30112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			os.chmod(self.cookie_file, 0600)
30122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
30132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# Don't save cookies across runs of update.py.
30142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.cookie_jar = cookielib.CookieJar()
30152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar))
30162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return opener
30172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef GetRpcServer(options):
30202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Returns an instance of an AbstractRpcServer.
30212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Returns:
30232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		A new AbstractRpcServer, on which RPC calls can be made.
30242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
30252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	rpc_server_class = HttpRpcServer
30272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GetUserCredentials():
30292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Prompts the user for a username and password."""
30302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Disable status prints so they don't obscure the password prompt.
30312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		global global_status
30322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		st = global_status
30332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		global_status = None
30342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		email = options.email
30362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if email is None:
30372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			email = GetEmail("Email (login for uploading to %s)" % options.server)
30382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		password = getpass.getpass("Password for %s: " % email)
30392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Put status back.
30412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		global_status = st
30422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return (email, password)
30432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# If this is the dev_appserver, use fake authentication.
30452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	host = (options.host or options.server).lower()
30462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if host == "localhost" or host.startswith("localhost:"):
30472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		email = options.email
30482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if email is None:
30492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			email = "test@example.com"
30502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			logging.info("Using debug user %s.  Override with --email" % email)
30512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		server = rpc_server_class(
30522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				options.server,
30532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				lambda: (email, "password"),
30542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				host_override=options.host,
30552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				extra_headers={"Cookie": 'dev_appserver_login="%s:False"' % email},
30562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				save_cookies=options.save_cookies)
30572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Don't try to talk to ClientLogin.
30582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		server.authenticated = True
30592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return server
30602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return rpc_server_class(options.server, GetUserCredentials,
30622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		host_override=options.host, save_cookies=options.save_cookies)
30632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef EncodeMultipartFormData(fields, files):
30662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Encode form fields for multipart/form-data.
30672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Args:
30692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		fields: A sequence of (name, value) elements for regular form fields.
30702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		files: A sequence of (name, filename, value) elements for data to be
30712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					uploaded as files.
30722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Returns:
30732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		(content_type, body) ready for httplib.HTTP instance.
30742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Source:
30762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306
30772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
30782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	BOUNDARY = '-M-A-G-I-C---B-O-U-N-D-A-R-Y-'
30792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	CRLF = '\r\n'
30802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	lines = []
30812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for (key, value) in fields:
30822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(key, str)
30832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(value, str)
30842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('--' + BOUNDARY)
30852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('Content-Disposition: form-data; name="%s"' % key)
30862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('')
30872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append(value)
30882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for (key, filename, value) in files:
30892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(key, str)
30902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(filename, str)
30912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(value, str)
30922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('--' + BOUNDARY)
30932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
30942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('Content-Type: %s' % GetContentType(filename))
30952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('')
30962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append(value)
30972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	lines.append('--' + BOUNDARY + '--')
30982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	lines.append('')
30992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	body = CRLF.join(lines)
31002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
31012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return content_type, body
31022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef GetContentType(filename):
31052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Helper to guess the content-type from the filename."""
31062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
31072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Use a shell for subcommands on Windows to get a PATH search.
31102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonuse_shell = sys.platform.startswith("win")
31112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef RunShellWithReturnCode(command, print_output=False,
31132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		universal_newlines=True, env=os.environ):
31142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Executes a command and returns the output from stdout and the return code.
31152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Args:
31172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		command: Command to execute.
31182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print_output: If True, the output is printed to stdout.
31192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			If False, both stdout and stderr are ignored.
31202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		universal_newlines: Use universal_newlines flag (default: True).
31212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Returns:
31232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Tuple (output, return code)
31242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
31252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	logging.info("Running %s", command)
31262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
31272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		shell=use_shell, universal_newlines=universal_newlines, env=env)
31282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if print_output:
31292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		output_array = []
31302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		while True:
31312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			line = p.stdout.readline()
31322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not line:
31332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				break
31342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			print line.strip("\n")
31352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			output_array.append(line)
31362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		output = "".join(output_array)
31372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
31382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		output = p.stdout.read()
31392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	p.wait()
31402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	errout = p.stderr.read()
31412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if print_output and errout:
31422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print >>sys.stderr, errout
31432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	p.stdout.close()
31442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	p.stderr.close()
31452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return output, p.returncode
31462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef RunShell(command, silent_ok=False, universal_newlines=True,
31492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print_output=False, env=os.environ):
31502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	data, retcode = RunShellWithReturnCode(command, print_output, universal_newlines, env)
31512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if retcode:
31522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ErrorExit("Got error status from %s:\n%s" % (command, data))
31532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not silent_ok and not data:
31542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ErrorExit("No output from %s" % command)
31552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return data
31562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass VersionControlSystem(object):
31592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Abstract base class providing an interface to the VCS."""
31602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self, options):
31622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Constructor.
31632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Args:
31652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			options: Command line options.
31662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
31672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.options = options
31682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GenerateDiff(self, args):
31702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Return the current diff as a string.
31712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Args:
31732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			args: Extra arguments to pass to the diff command.
31742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
31752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise NotImplementedError(
31762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				"abstract method -- subclass %s must override" % self.__class__)
31772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GetUnknownFiles(self):
31792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Return a list of files unknown to the VCS."""
31802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise NotImplementedError(
31812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				"abstract method -- subclass %s must override" % self.__class__)
31822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def CheckForUnknownFiles(self):
31842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Show an "are you sure?" prompt if there are unknown files."""
31852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		unknown_files = self.GetUnknownFiles()
31862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if unknown_files:
31872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			print "The following files are not added to version control:"
31882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for line in unknown_files:
31892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				print line
31902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			prompt = "Are you sure to continue?(y/N) "
31912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			answer = raw_input(prompt).strip()
31922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if answer != "y":
31932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ErrorExit("User aborted")
31942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GetBaseFile(self, filename):
31962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Get the content of the upstream version of a file.
31972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Returns:
31992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			A tuple (base_content, new_content, is_binary, status)
32002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				base_content: The contents of the base file.
32012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				new_content: For text files, this is empty.  For binary files, this is
32022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					the contents of the new file, since the diff output won't contain
32032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					information to reconstruct the current file.
32042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				is_binary: True iff the file is binary.
32052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				status: The status of the file.
32062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
32072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise NotImplementedError(
32092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				"abstract method -- subclass %s must override" % self.__class__)
32102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GetBaseFiles(self, diff):
32132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Helper that calls GetBase file for each file in the patch.
32142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Returns:
32162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			A dictionary that maps from filename to GetBaseFile's tuple.  Filenames
32172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			are retrieved based on lines that start with "Index:" or
32182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			"Property changes on:".
32192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
32202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		files = {}
32212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for line in diff.splitlines(True):
32222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if line.startswith('Index:') or line.startswith('Property changes on:'):
32232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				unused, filename = line.split(':', 1)
32242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# On Windows if a file has property changes its filename uses '\'
32252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# instead of '/'.
32260d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				filename = to_slash(filename.strip())
32272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				files[filename] = self.GetBaseFile(filename)
32282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return files
32292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def UploadBaseFiles(self, issue, rpc_server, patch_list, patchset, options,
32322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson											files):
32332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Uploads the base files (and if necessary, the current ones as well)."""
32342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		def UploadFile(filename, file_id, content, is_binary, status, is_base):
32362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			"""Uploads a file to the server."""
32372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			set_status("uploading " + filename)
32382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			file_too_large = False
32392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if is_base:
32402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				type = "base"
32412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
32422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				type = "current"
32432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if len(content) > MAX_UPLOAD_SIZE:
32442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				print ("Not uploading the %s file for %s because it's too large." %
32452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson							(type, filename))
32462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				file_too_large = True
32472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				content = ""
32482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			checksum = md5(content).hexdigest()
32492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if options.verbose > 0 and not file_too_large:
32502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				print "Uploading %s file for %s" % (type, filename)
32512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			url = "/%d/upload_content/%d/%d" % (int(issue), int(patchset), file_id)
32522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			form_fields = [
32532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				("filename", filename),
32542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				("status", status),
32552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				("checksum", checksum),
32562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				("is_binary", str(is_binary)),
32572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				("is_current", str(not is_base)),
32582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			]
32592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if file_too_large:
32602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				form_fields.append(("file_too_large", "1"))
32612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if options.email:
32622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				form_fields.append(("user", options.email))
32632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ctype, body = EncodeMultipartFormData(form_fields, [("data", filename, content)])
32642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			response_body = rpc_server.Send(url, body, content_type=ctype)
32652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not response_body.startswith("OK"):
32662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				StatusUpdate("  --> %s" % response_body)
32672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				sys.exit(1)
32682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Don't want to spawn too many threads, nor do we want to
32702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# hit Rietveld too hard, or it will start serving 500 errors.
32712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# When 8 works, it's no better than 4, and sometimes 8 is
32722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# too many for Rietveld to handle.
32732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		MAX_PARALLEL_UPLOADS = 4
32742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		sema = threading.BoundedSemaphore(MAX_PARALLEL_UPLOADS)
32762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		upload_threads = []
32772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		finished_upload_threads = []
32782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		class UploadFileThread(threading.Thread):
32802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			def __init__(self, args):
32812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				threading.Thread.__init__(self)
32822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				self.args = args
32832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			def run(self):
32842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				UploadFile(*self.args)
32852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				finished_upload_threads.append(self)
32862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				sema.release()
32872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		def StartUploadFile(*args):
32892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			sema.acquire()
32902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			while len(finished_upload_threads) > 0:
32912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				t = finished_upload_threads.pop()
32922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				upload_threads.remove(t)
32932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				t.join()
32942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			t = UploadFileThread(args)
32952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			upload_threads.append(t)
32962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			t.start()
32972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		def WaitForUploads():
32992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for t in upload_threads:
33002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				t.join()
33012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		patches = dict()
33032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[patches.setdefault(v, k) for k, v in patch_list]
33042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for filename in patches.keys():
33052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			base_content, new_content, is_binary, status = files[filename]
33062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			file_id_str = patches.get(filename)
33072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if file_id_str.find("nobase") != -1:
33082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				base_content = None
33092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				file_id_str = file_id_str[file_id_str.rfind("_") + 1:]
33102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			file_id = int(file_id_str)
33112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if base_content != None:
33122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				StartUploadFile(filename, file_id, base_content, is_binary, status, True)
33132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if new_content != None:
33142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				StartUploadFile(filename, file_id, new_content, is_binary, status, False)
33152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		WaitForUploads()
33162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def IsImage(self, filename):
33182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Returns true if the filename has an image extension."""
33192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		mimetype =  mimetypes.guess_type(filename)[0]
33202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not mimetype:
33212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return False
33222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return mimetype.startswith("image/")
33232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def IsBinary(self, filename):
33252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Returns true if the guessed mimetyped isnt't in text group."""
33262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		mimetype = mimetypes.guess_type(filename)[0]
33272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not mimetype:
33282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return False  # e.g. README, "real" binaries usually have an extension
33292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# special case for text files which don't start with text/
33302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if mimetype in TEXT_MIMETYPES:
33312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return False
33322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return not mimetype.startswith("text/")
33332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass FakeMercurialUI(object):
33362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self):
33372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.quiet = True
33382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.output = ''
33392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def write(self, *args, **opts):
33412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.output += ' '.join(args)
33422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def copy(self):
33432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return self
33442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def status(self, *args, **opts):
33452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pass
33460d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin
33470d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin	def formatter(self, topic, opts):
33480d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		from mercurial.formatter import plainformatter
33490d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		return plainformatter(self, topic, opts)
33502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def readconfig(self, *args, **opts):
33522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pass
33532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def expandpath(self, *args, **opts):
33542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return global_ui.expandpath(*args, **opts)
33552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def configitems(self, *args, **opts):
33562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return global_ui.configitems(*args, **opts)
33572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def config(self, *args, **opts):
33582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return global_ui.config(*args, **opts)
33592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonuse_hg_shell = False	# set to True to shell out to hg always; slower
33612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass MercurialVCS(VersionControlSystem):
33632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Implementation of the VersionControlSystem interface for Mercurial."""
33642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self, options, ui, repo):
33662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		super(MercurialVCS, self).__init__(options)
33672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.ui = ui
33682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.repo = repo
33692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.status = None
33702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Absolute path to repository (we can be in a subdir)
33712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.repo_dir = os.path.normpath(repo.root)
33722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Compute the subdir
33732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cwd = os.path.normpath(os.getcwd())
33742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		assert cwd.startswith(self.repo_dir)
33752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.subdir = cwd[len(self.repo_dir):].lstrip(r"\/")
33762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.options.revision:
33772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.base_rev = self.options.revision
33782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
33792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			mqparent, err = RunShellWithReturnCode(['hg', 'log', '--rev', 'qparent', '--template={node}'])
33802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not err and mqparent != "":
33812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				self.base_rev = mqparent
33822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
33830d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				out = RunShell(["hg", "parents", "-q"], silent_ok=True).strip()
33840d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				if not out:
33850d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin					# No revisions; use 0 to mean a repository with nothing.
33860d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin					out = "0:0"
33870d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				self.base_rev = out.split(':')[1].strip()
33882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _GetRelPath(self, filename):
33892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Get relative path of a file according to the current directory,
33902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		given its logical path in the repo."""
33912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		assert filename.startswith(self.subdir), (filename, self.subdir)
33922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return filename[len(self.subdir):].lstrip(r"\/")
33932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GenerateDiff(self, extra_args):
33952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# If no file specified, restrict to the current subdir
33962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		extra_args = extra_args or ["."]
33972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cmd = ["hg", "diff", "--git", "-r", self.base_rev] + extra_args
33982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		data = RunShell(cmd, silent_ok=True)
33992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		svndiff = []
34002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		filecount = 0
34012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for line in data.splitlines():
34022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			m = re.match("diff --git a/(\S+) b/(\S+)", line)
34032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if m:
34042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# Modify line to make it look like as it comes from svn diff.
34052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# With this modification no changes on the server side are required
34062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# to make upload.py work with Mercurial repos.
34072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# NOTE: for proper handling of moved/copied files, we have to use
34082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# the second filename.
34092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				filename = m.group(2)
34102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				svndiff.append("Index: %s" % filename)
34112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				svndiff.append("=" * 67)
34122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				filecount += 1
34132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				logging.info(line)
34142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
34152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				svndiff.append(line)
34162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not filecount:
34172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ErrorExit("No valid patches found in output from hg diff")
34182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "\n".join(svndiff) + "\n"
34192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
34202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GetUnknownFiles(self):
34212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Return a list of files unknown to the VCS."""
34222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		args = []
34232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		status = RunShell(["hg", "status", "--rev", self.base_rev, "-u", "."],
34242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				silent_ok=True)
34252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		unknown_files = []
34262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for line in status.splitlines():
34272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			st, fn = line.split(" ", 1)
34282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if st == "?":
34292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				unknown_files.append(fn)
34302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return unknown_files
34312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
34322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def get_hg_status(self, rev, path):
34332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# We'd like to use 'hg status -C path', but that is buggy
34342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# (see http://mercurial.selenic.com/bts/issue3023).
34352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Instead, run 'hg status -C' without a path
34362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# and skim the output for the path we want.
34372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.status is None:
34382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if use_hg_shell:
34392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				out = RunShell(["hg", "status", "-C", "--rev", rev])
34402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
34412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				fui = FakeMercurialUI()
34420d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin				ret = hg_commands.status(fui, self.repo, *[], **{'rev': [rev], 'copies': True})
34432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if ret:
34440d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin					raise hg_util.Abort(ret)
34452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				out = fui.output
34462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.status = out.splitlines()
34472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for i in range(len(self.status)):
34482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# line is
34492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			#	A path
34502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			#	M path
34512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# etc
34520d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			line = to_slash(self.status[i])
34532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if line[2:] == path:
34542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if i+1 < len(self.status) and self.status[i+1][:2] == '  ':
34552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					return self.status[i:i+2]
34562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return self.status[i:i+1]
34570d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin		raise hg_util.Abort("no status for " + path)
34582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
34592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GetBaseFile(self, filename):
34602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status("inspecting " + filename)
34612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# "hg status" and "hg cat" both take a path relative to the current subdir
34622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# rather than to the repo root, but "hg diff" has given us the full path
34632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# to the repo root.
34642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		base_content = ""
34652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		new_content = None
34662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		is_binary = False
34672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		oldrelpath = relpath = self._GetRelPath(filename)
34682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		out = self.get_hg_status(self.base_rev, relpath)
34692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		status, what = out[0].split(' ', 1)
34702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if len(out) > 1 and status == "A" and what == relpath:
34712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			oldrelpath = out[1].strip()
34722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			status = "M"
34732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if ":" in self.base_rev:
34742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			base_rev = self.base_rev.split(":", 1)[0]
34752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
34762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			base_rev = self.base_rev
34772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if status != "A":
34782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if use_hg_shell:
34792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath], silent_ok=True)
34802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
34812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				base_content = str(self.repo[base_rev][oldrelpath].data())
34822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			is_binary = "\0" in base_content  # Mercurial's heuristic
34832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if status != "R":
34842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			new_content = open(relpath, "rb").read()
34852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			is_binary = is_binary or "\0" in new_content
34862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if is_binary and base_content and use_hg_shell:
34872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# Fetch again without converting newlines
34882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath],
34892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				silent_ok=True, universal_newlines=False)
34902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not is_binary or not self.IsImage(relpath):
34912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			new_content = None
34922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return base_content, new_content, is_binary, status
34932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
34942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
34952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync.
34962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef SplitPatch(data):
34972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Splits a patch into separate pieces for each file.
34982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
34992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Args:
35002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		data: A string containing the output of svn diff.
35012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
35022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Returns:
35032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		A list of 2-tuple (filename, text) where text is the svn diff output
35042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pertaining to filename.
35052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
35062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	patches = []
35072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	filename = None
35082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	diff = []
35092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in data.splitlines(True):
35102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		new_filename = None
35112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line.startswith('Index:'):
35122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			unused, new_filename = line.split(':', 1)
35132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			new_filename = new_filename.strip()
35142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		elif line.startswith('Property changes on:'):
35152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			unused, temp_filename = line.split(':', 1)
35162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# When a file is modified, paths use '/' between directories, however
35172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# when a property is modified '\' is used on Windows.  Make them the same
35182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# otherwise the file shows up twice.
35190d4c52358a1af421705c54bd8a9fdd8a30558a2eAlexander Gutkin			temp_filename = to_slash(temp_filename.strip())
35202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if temp_filename != filename:
35212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# File has property changes but no modifications, create a new diff.
35222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				new_filename = temp_filename
35232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if new_filename:
35242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if filename and diff:
35252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				patches.append((filename, ''.join(diff)))
35262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			filename = new_filename
35272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			diff = [line]
35282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
35292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if diff is not None:
35302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			diff.append(line)
35312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if filename and diff:
35322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		patches.append((filename, ''.join(diff)))
35332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return patches
35342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
35352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
35362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef UploadSeparatePatches(issue, rpc_server, patchset, data, options):
35372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Uploads a separate patch for each file in the diff output.
35382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
35392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Returns a list of [patch_key, filename] for each file.
35402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
35412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	patches = SplitPatch(data)
35422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	rv = []
35432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for patch in patches:
35442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status("uploading patch for " + patch[0])
35452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if len(patch[1]) > MAX_UPLOAD_SIZE:
35462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			print ("Not uploading the patch for " + patch[0] +
35472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				" because the file is too large.")
35482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
35492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields = [("filename", patch[0])]
35502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not options.download_base:
35512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			form_fields.append(("content_upload", "1"))
35522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		files = [("data", "data.diff", patch[1])]
35532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ctype, body = EncodeMultipartFormData(form_fields, files)
35542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		url = "/%d/upload_patch/%d" % (int(issue), int(patchset))
35552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print "Uploading patch for " + patch[0]
35562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		response_body = rpc_server.Send(url, body, content_type=ctype)
35572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines = response_body.splitlines()
35582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not lines or lines[0] != "OK":
35592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			StatusUpdate("  --> %s" % response_body)
35602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			sys.exit(1)
35612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		rv.append([lines[1], patch[0]])
35622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return rv
3563