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]
252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	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
412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonfrom mercurial import cmdutil, commands, hg, util, error, match, discovery
422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonfrom mercurial.node import nullrev, hex, nullid, short
432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport os, re, time
442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport stat
452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport subprocess
462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport threading
472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonfrom HTMLParser import HTMLParser
482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# The standard 'json' package is new in Python 2.6.
502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Before that it was an external package named simplejson.
512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsontry:
522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Standard location in 2.6 and beyond.
532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	import json
542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonexcept Exception, e:
552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Conventional name for earlier package.
572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		import simplejson as json
582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# Was also bundled with django, which is commonly installed.
612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			from django.utils import simplejson as json
622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except:
632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# We give up.
642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise e
652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsontry:
672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	hgversion = util.version()
682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonexcept:
692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	from mercurial.version import version as v
702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	hgversion = v.get_version()
712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# in Mercurial 1.9 the cmdutil.match and cmdutil.revpair moved to scmutil
732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonif hgversion >= '1.9':
742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson    from mercurial import scmutil
752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonelse:
762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson    scmutil = cmdutil
772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonoldMessage = """
792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonThe code review extension requires Mercurial 1.3 or newer.
802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonTo install a new Mercurial,
822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sudo easy_install mercurial
842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonworks on most systems.
862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonlinuxMessage = """
892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonYou may need to clear your current Mercurial installation by running:
902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sudo apt-get remove mercurial mercurial-common
922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sudo rm -rf /etc/mercurial
932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonif hgversion < '1.3':
962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	msg = oldMessage
972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if os.access("/etc/mercurial", 0):
982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		msg += linuxMessage
992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	raise util.Abort(msg)
1002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef promptyesno(ui, msg):
1022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Arguments to ui.prompt changed between 1.3 and 1.3.1.
1032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Even so, some 1.3.1 distributions seem to have the old prompt!?!?
1042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# What a terrible way to maintain software.
1052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
1062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return ui.promptchoice(msg, ["&yes", "&no"], 0) == 0
1072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except AttributeError:
1082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return ui.prompt(msg, ["&yes", "&no"], "y") != "n"
1092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef incoming(repo, other):
1112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	fui = FakeMercurialUI()
1122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ret = commands.incoming(fui, repo, *[other.path], **{'bundle': '', 'force': False})
1132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if ret and ret != 1:
1142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise util.Abort(ret)
1152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	out = fui.output
1162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return out
1172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef outgoing(repo):
1192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	fui = FakeMercurialUI()
1202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ret = commands.outgoing(fui, repo, *[], **{})
1212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if ret and ret != 1:
1222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise util.Abort(ret)
1232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	out = fui.output
1242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return out
1252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# To experiment with Mercurial in the python interpreter:
1272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#    >>> repo = hg.repository(ui.ui(), path = ".")
1282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#######################################################################
1302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Normally I would split this into multiple files, but it simplifies
1312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# import path headaches to keep it all in one file.  Sorry.
1322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport sys
1342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonif __name__ == "__main__":
1352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	print >>sys.stderr, "This is a Mercurial extension and should not be invoked directly."
1362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sys.exit(2)
1372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonserver = "codereview.appspot.com"
1392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonserver_url_base = None
1402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondefaultcc = None
1412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsoncontributors = {}
1422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonmissing_codereview = None
1432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonreal_rollback = None
1442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonreleaseBranch = None
1452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#######################################################################
1472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# RE: UNICODE STRING HANDLING
1482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
1492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Python distinguishes between the str (string of bytes)
1502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# and unicode (string of code points) types.  Most operations
1512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# work on either one just fine, but some (like regexp matching)
1522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# require unicode, and others (like write) require str.
1532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
1542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# As befits the language, Python hides the distinction between
1552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# unicode and str by converting between them silently, but
1562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# *only* if all the bytes/code points involved are 7-bit ASCII.
1572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# This means that if you're not careful, your program works
1582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# fine on "hello, world" and fails on "hello, 世界".  And of course,
1592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# the obvious way to be careful - use static types - is unavailable.
1602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# So the only way is trial and error to find where to put explicit
1612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# conversions.
1622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
1632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Because more functions do implicit conversion to str (string of bytes)
1642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# than do implicit conversion to unicode (string of code points),
1652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# the convention in this module is to represent all text as str,
1662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# converting to unicode only when calling a unicode-only function
1672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# and then converting back to str as soon as possible.
1682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef typecheck(s, t):
1702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if type(s) != t:
1712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise util.Abort("type check failed: %s has type %s != %s" % (repr(s), type(s), t))
1722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# If we have to pass unicode instead of str, ustr does that conversion clearly.
1742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef ustr(s):
1752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(s, str)
1762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return s.decode("utf-8")
1772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Even with those, Mercurial still sometimes turns unicode into str
1792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# and then tries to use it as ascii.  Change Mercurial's default.
1802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef set_mercurial_encoding_to_utf8():
1812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	from mercurial import encoding
1822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	encoding.encoding = 'utf-8'
1832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonset_mercurial_encoding_to_utf8()
1852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
1862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Even with those we still run into problems.
1872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# I tried to do things by the book but could not convince
1882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Mercurial to let me check in a change with UTF-8 in the
1892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# CL description or author field, no matter how many conversions
1902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# between str and unicode I inserted and despite changing the
1912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# default encoding.  I'm tired of this game, so set the default
1922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# encoding for all of Python to 'utf-8', not 'ascii'.
1932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef default_to_utf8():
1942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	import sys
1952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	stdout, __stdout__ = sys.stdout, sys.__stdout__
1962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	reload(sys)  # site.py deleted setdefaultencoding; get it back
1972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sys.stdout, sys.__stdout__ = stdout, __stdout__
1982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sys.setdefaultencoding('utf-8')
1992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondefault_to_utf8()
2012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#######################################################################
2032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Change list parsing.
2042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
2052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Change lists are stored in .hg/codereview/cl.nnnnnn
2062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# where nnnnnn is the number assigned by the code review server.
2072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Most data about a change list is stored on the code review server
2082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# too: the description, reviewer, and cc list are all stored there.
2092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# The only thing in the cl.nnnnnn file is the list of relevant files.
2102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Also, the existence of the cl.nnnnnn file marks this repository
2112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# as the one where the change list lives.
2122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonemptydiff = """Index: ~rietveld~placeholder~
2142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson===================================================================
2152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondiff --git a/~rietveld~placeholder~ b/~rietveld~placeholder~
2162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonnew file mode 100644
2172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
2182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass CL(object):
2202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self, name):
2212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(name, str)
2222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.name = name
2232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.desc = ''
2242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.files = []
2252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.reviewer = []
2262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.cc = []
2272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.url = ''
2282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.local = False
2292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.web = False
2302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.copied_from = None	# None means current user
2312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.mailed = False
2322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.private = False
2332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.lgtm = []
2342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def DiskText(self):
2362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = self
2372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = ""
2382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.copied_from:
2392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "Author: " + cl.copied_from + "\n\n"
2402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.private:
2412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "Private: " + str(self.private) + "\n"
2422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "Mailed: " + str(self.mailed) + "\n"
2432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "Description:\n"
2442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += Indent(cl.desc, "\t")
2452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "Files:\n"
2462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for f in cl.files:
2472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "\t" + f + "\n"
2482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(s, str)
2492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return s
2502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def EditorText(self):
2522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = self
2532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = _change_prolog
2542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "\n"
2552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.copied_from:
2562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "Author: " + cl.copied_from + "\n"
2572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.url != '':
2582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += 'URL: ' + cl.url + '	# cannot edit\n\n'
2592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.private:
2602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "Private: True\n"
2612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "Reviewer: " + JoinComma(cl.reviewer) + "\n"
2622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "CC: " + JoinComma(cl.cc) + "\n"
2632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "\n"
2642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "Description:\n"
2652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.desc == '':
2662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "\t<enter description here>\n"
2672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
2682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += Indent(cl.desc, "\t")
2692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "\n"
2702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.local or cl.name == "new":
2712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "Files:\n"
2722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for f in cl.files:
2732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				s += "\t" + f + "\n"
2742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "\n"
2752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(s, str)
2762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return s
2772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def PendingText(self):
2792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = self
2802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = cl.name + ":" + "\n"
2812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += Indent(cl.desc, "\t")
2822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "\n"
2832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.copied_from:
2842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "\tAuthor: " + cl.copied_from + "\n"
2852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "\tReviewer: " + JoinComma(cl.reviewer) + "\n"
2862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for (who, line) in cl.lgtm:
2872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "\t\t" + who + ": " + line + "\n"
2882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "\tCC: " + JoinComma(cl.cc) + "\n"
2892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += "\tFiles:\n"
2902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for f in cl.files:
2912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "\t\t" + f + "\n"
2922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(s, str)
2932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return s
2942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
2952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def Flush(self, ui, repo):
2962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.name == "new":
2972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.Upload(ui, repo, gofmt_just_warn=True, creating=True)
2982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		dir = CodeReviewDir(ui, repo)
2992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		path = dir + '/cl.' + self.name
3002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		f = open(path+'!', "w")
3012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		f.write(self.DiskText())
3022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		f.close()
3032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if sys.platform == "win32" and os.path.isfile(path):
3042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			os.remove(path)
3052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		os.rename(path+'!', path)
3062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.web and not self.copied_from:
3072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			EditDesc(self.name, desc=self.desc,
3082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				reviewers=JoinComma(self.reviewer), cc=JoinComma(self.cc),
3092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				private=self.private)
3102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
3112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def Delete(self, ui, repo):
3122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		dir = CodeReviewDir(ui, repo)
3132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		os.unlink(dir + "/cl." + self.name)
3142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
3152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def Subject(self):
3162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = line1(self.desc)
3172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if len(s) > 60:
3182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s = s[0:55] + "..."
3192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.name != "new":
3202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s = "code review %s: %s" % (self.name, s)
3212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(s, str)
3222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return s
3232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
3242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def Upload(self, ui, repo, send_mail=False, gofmt=True, gofmt_just_warn=False, creating=False, quiet=False):
3252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not self.files and not creating:
3262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("no files in change list\n")
3272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if ui.configbool("codereview", "force_gofmt", True) and gofmt:
3282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			CheckFormat(ui, repo, self.files, just_warn=gofmt_just_warn)
3292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status("uploading CL metadata + diffs")
3302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		os.chdir(repo.root)
3312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields = [
3322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			("content_upload", "1"),
3332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			("reviewers", JoinComma(self.reviewer)),
3342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			("cc", JoinComma(self.cc)),
3352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			("description", self.desc),
3362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			("base_hashes", ""),
3372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		]
3382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
3392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.name != "new":
3402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			form_fields.append(("issue", self.name))
3412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		vcs = None
3422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# We do not include files when creating the issue,
3432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# because we want the patch sets to record the repository
3442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# and base revision they are diffs against.  We use the patch
3452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# set message for that purpose, but there is no message with
3462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# the first patch set.  Instead the message gets used as the
3472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# new CL's overall subject.  So omit the diffs when creating
3482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# and then we'll run an immediate upload.
3492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# This has the effect that every CL begins with an empty "Patch set 1".
3502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.files and not creating:
3512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			vcs = MercurialVCS(upload_options, ui, repo)
3522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			data = vcs.GenerateDiff(self.files)
3532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			files = vcs.GetBaseFiles(data)
3542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if len(data) > MAX_UPLOAD_SIZE:
3552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				uploaded_diff_file = []
3562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				form_fields.append(("separate_patches", "1"))
3572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
3582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				uploaded_diff_file = [("data", "data.diff", data)]
3592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
3602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			uploaded_diff_file = [("data", "data.diff", emptydiff)]
3612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
3622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if vcs and self.name != "new":
3632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			form_fields.append(("subject", "diff -r " + vcs.base_rev + " " + getremote(ui, repo, {}).path))
3642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
3652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# First upload sets the subject for the CL itself.
3662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			form_fields.append(("subject", self.Subject()))
3672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ctype, body = EncodeMultipartFormData(form_fields, uploaded_diff_file)
3682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		response_body = MySend("/upload", body, content_type=ctype)
3692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		patchset = None
3702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		msg = response_body
3712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines = msg.splitlines()
3722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if len(lines) >= 2:
3732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			msg = lines[0]
3742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			patchset = lines[1].strip()
3752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			patches = [x.split(" ", 1) for x in lines[2:]]
3762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if response_body.startswith("Issue updated.") and quiet:
3772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pass
3782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
3792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.status(msg + "\n")
3802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status("uploaded CL metadata + diffs")
3812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not response_body.startswith("Issue created.") and not response_body.startswith("Issue updated."):
3822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise util.Abort("failed to update issue: " + response_body)
3832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		issue = msg[msg.rfind("/")+1:]
3842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.name = issue
3852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not self.url:
3862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.url = server_url_base + self.name
3872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not uploaded_diff_file:
3882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			set_status("uploading patches")
3892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			patches = UploadSeparatePatches(issue, rpc, patchset, data, upload_options)
3902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if vcs:
3912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			set_status("uploading base files")
3922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			vcs.UploadBaseFiles(issue, rpc, patches, patchset, upload_options, files)
3932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if send_mail:
3942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			set_status("sending mail")
3952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			MySend("/" + issue + "/mail", payload="")
3962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.web = True
3972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status("flushing changes to disk")
3982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.Flush(ui, repo)
3992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
4002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def Mail(self, ui, repo):
4022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pmsg = "Hello " + JoinComma(self.reviewer)
4032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.cc:
4042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pmsg += " (cc: %s)" % (', '.join(self.cc),)
4052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pmsg += ",\n"
4062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pmsg += "\n"
4072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		repourl = getremote(ui, repo, {}).path
4082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not self.mailed:
4092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pmsg += "I'd like you to review this change to\n" + repourl + "\n"
4102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
4112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pmsg += "Please take another look.\n"
4122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(pmsg, str)
4132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		PostMessage(ui, self.name, pmsg, subject=self.Subject())
4142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.mailed = True
4152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.Flush(ui, repo)
4162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef GoodCLName(name):
4182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(name, str)
4192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return re.match("^[0-9]+$", name)
4202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef ParseCL(text, name):
4222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(text, str)
4232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(name, str)
4242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sname = None
4252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	lineno = 0
4262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sections = {
4272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'Author': '',
4282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'Description': '',
4292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'Files': '',
4302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'URL': '',
4312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'Reviewer': '',
4322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'CC': '',
4332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'Mailed': '',
4342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		'Private': '',
4352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	}
4362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in text.split('\n'):
4372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lineno += 1
4382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = line.rstrip()
4392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line != '' and line[0] == '#':
4402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
4412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line == '' or line[0] == ' ' or line[0] == '\t':
4422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if sname == None and line != '':
4432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return None, lineno, 'text outside section'
4442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if sname != None:
4452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				sections[sname] += line + '\n'
4462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
4472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		p = line.find(':')
4482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if p >= 0:
4492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s, val = line[:p].strip(), line[p+1:].strip()
4502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if s in sections:
4512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				sname = s
4522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if val != '':
4532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					sections[sname] += val + '\n'
4542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
4552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, lineno, 'malformed section header'
4562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for k in sections:
4582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		sections[k] = StripCommon(sections[k]).rstrip()
4592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl = CL(name)
4612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if sections['Author']:
4622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.copied_from = sections['Author']
4632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.desc = sections['Description']
4642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in sections['Files'].split('\n'):
4652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		i = line.find('#')
4662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if i >= 0:
4672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			line = line[0:i].rstrip()
4682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = line.strip()
4692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line == '':
4702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
4712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.files.append(line)
4722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.reviewer = SplitCommaSpace(sections['Reviewer'])
4732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.cc = SplitCommaSpace(sections['CC'])
4742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.url = sections['URL']
4752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if sections['Mailed'] != 'False':
4762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Odd default, but avoids spurious mailings when
4772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# reading old CLs that do not have a Mailed: line.
4782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# CLs created with this update will always have
4792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Mailed: False on disk.
4802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.mailed = True
4812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if sections['Private'] in ('True', 'true', 'Yes', 'yes'):
4822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.private = True
4832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.desc == '<enter description here>':
4842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.desc = ''
4852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return cl, 0, ''
4862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef SplitCommaSpace(s):
4882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(s, str)
4892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	s = s.strip()
4902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if s == "":
4912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return []
4922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return re.split(", *", s)
4932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
4942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef CutDomain(s):
4952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(s, str)
4962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	i = s.find('@')
4972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if i >= 0:
4982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = s[0:i]
4992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return s
5002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef JoinComma(l):
5022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for s in l:
5032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(s, str)
5042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return ", ".join(l)
5052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef ExceptionDetail():
5072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	s = str(sys.exc_info()[0])
5082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if s.startswith("<type '") and s.endswith("'>"):
5092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = s[7:-2]
5102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	elif s.startswith("<class '") and s.endswith("'>"):
5112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = s[8:-2]
5122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	arg = str(sys.exc_info()[1])
5132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(arg) > 0:
5142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s += ": " + arg
5152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return s
5162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef IsLocalCL(ui, repo, name):
5182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return GoodCLName(name) and os.access(CodeReviewDir(ui, repo) + "/cl." + name, 0)
5192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Load CL from disk and/or the web.
5212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef LoadCL(ui, repo, name, web=True):
5222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(name, str)
5232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("loading CL " + name)
5242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not GoodCLName(name):
5252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, "invalid CL name"
5262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dir = CodeReviewDir(ui, repo)
5272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	path = dir + "cl." + name
5282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if os.access(path, 0):
5292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ff = open(path)
5302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		text = ff.read()
5312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ff.close()
5322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl, lineno, err = ParseCL(text, name)
5332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
5342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "malformed CL data: "+err
5352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.local = True
5362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
5372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = CL(name)
5382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if web:
5392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status("getting issue metadata from web")
5402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		d = JSONGet(ui, "/api/" + name + "?messages=true")
5412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status(None)
5422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if d is None:
5432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "cannot load CL %s from server" % (name,)
5442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if 'owner_email' not in d or 'issue' not in d or str(d['issue']) != name:
5452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "malformed response loading CL data from code review server"
5462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.dict = d
5472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.reviewer = d.get('reviewers', [])
5482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.cc = d.get('cc', [])
5492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if cl.local and cl.copied_from and cl.desc:
5502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# local copy of CL written by someone else
5512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# and we saved a description.  use that one,
5522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# so that committers can edit the description
5532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# before doing hg submit.
5542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pass
5552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
5562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.desc = d.get('description', "")
5572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.url = server_url_base + name
5582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.web = True
5592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.private = d.get('private', False) != False
5602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.lgtm = []
5612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for m in d.get('messages', []):
5622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if m.get('approval', False) == True:
5632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				who = re.sub('@.*', '', m.get('sender', ''))
5642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				text = re.sub("\n(.|\n)*", '', m.get('text', ''))
5652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				cl.lgtm.append((who, text))
5662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("loaded CL " + name)
5682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return cl, ''
5692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonglobal_status = None
5712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef set_status(s):
5732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# print >>sys.stderr, "\t", time.asctime(), s
5742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	global global_status
5752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	global_status = s
5762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass StatusThread(threading.Thread):
5782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self):
5792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		threading.Thread.__init__(self)
5802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def run(self):
5812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# pause a reasonable amount of time before
5822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# starting to display status messages, so that
5832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# most hg commands won't ever see them.
5842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		time.sleep(30)
5852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# now show status every 15 seconds
5872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		while True:
5882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			time.sleep(15 - time.time() % 15)
5892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s = global_status
5902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if s is None:
5912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
5922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if s == "":
5932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				s = "(unknown status)"
5942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			print >>sys.stderr, time.asctime(), s
5952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
5962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef start_status_thread():
5972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	t = StatusThread()
5982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	t.setDaemon(True)  # allowed to exit if t is still running
5992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	t.start()
6002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass LoadCLThread(threading.Thread):
6022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self, ui, repo, dir, f, web):
6032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		threading.Thread.__init__(self)
6042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.ui = ui
6052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.repo = repo
6062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.dir = dir
6072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.f = f
6082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.web = web
6092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.cl = None
6102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def run(self):
6112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl, err = LoadCL(self.ui, self.repo, self.f[3:], web=self.web)
6122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != '':
6132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.ui.warn("loading "+self.dir+self.f+": " + err + "\n")
6142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return
6152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.cl = cl
6162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Load all the CLs from this repository.
6182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef LoadAllCL(ui, repo, web=True):
6192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dir = CodeReviewDir(ui, repo)
6202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	m = {}
6212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [f for f in os.listdir(dir) if f.startswith('cl.')]
6222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not files:
6232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return m
6242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	active = []
6252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	first = True
6262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for f in files:
6272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		t = LoadCLThread(ui, repo, dir, f, web)
6282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		t.start()
6292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if web and first:
6302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# first request: wait in case it needs to authenticate
6312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# otherwise we get lots of user/password prompts
6322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# running in parallel.
6332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			t.join()
6342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if t.cl:
6352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				m[t.cl.name] = t.cl
6362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			first = False
6372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
6382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			active.append(t)
6392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for t in active:
6402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		t.join()
6412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if t.cl:
6422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			m[t.cl.name] = t.cl
6432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return m
6442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Find repository root.  On error, ui.warn and return None
6462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef RepoDir(ui, repo):
6472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	url = repo.url();
6482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not url.startswith('file:'):
6492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.warn("repository %s is not in local file system\n" % (url,))
6502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None
6512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	url = url[5:]
6522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if url.endswith('/'):
6532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		url = url[:-1]
6542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(url, str)
6552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return url
6562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Find (or make) code review directory.  On error, ui.warn and return None
6582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef CodeReviewDir(ui, repo):
6592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dir = RepoDir(ui, repo)
6602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if dir == None:
6612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None
6622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dir += '/.hg/codereview/'
6632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not os.path.isdir(dir):
6642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
6652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			os.mkdir(dir, 0700)
6662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except:
6672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn('cannot mkdir %s: %s\n' % (dir, ExceptionDetail()))
6682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None
6692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(dir, str)
6702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return dir
6712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Turn leading tabs into spaces, so that the common white space
6732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# prefix doesn't get confused when people's editors write out
6742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# some lines with spaces, some with tabs.  Only a heuristic
6752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# (some editors don't use 8 spaces either) but a useful one.
6762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef TabsToSpaces(line):
6772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	i = 0
6782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	while i < len(line) and line[i] == '\t':
6792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		i += 1
6802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return ' '*(8*i) + line[i:]
6812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
6822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Strip maximal common leading white space prefix from text
6832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef StripCommon(text):
6842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(text, str)
6852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ws = None
6862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in text.split('\n'):
6872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = line.rstrip()
6882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line == '':
6892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
6902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = TabsToSpaces(line)
6912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		white = line[:len(line)-len(line.lstrip())]
6922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if ws == None:
6932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ws = white
6942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
6952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			common = ''
6962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for i in range(min(len(white), len(ws))+1):
6972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if white[0:i] == ws[0:i]:
6982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					common = white[0:i]
6992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ws = common
7002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if ws == '':
7012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			break
7022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if ws == None:
7032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return text
7042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	t = ''
7052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in text.split('\n'):
7062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = line.rstrip()
7072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = TabsToSpaces(line)
7082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line.startswith(ws):
7092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			line = line[len(ws):]
7102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line == '' and t == '':
7112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
7122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		t += line + '\n'
7132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	while len(t) >= 2 and t[-2:] == '\n\n':
7142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		t = t[:-1]
7152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(t, str)
7162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return t
7172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Indent text with indent.
7192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef Indent(text, indent):
7202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(text, str)
7212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(indent, str)
7222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	t = ''
7232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in text.split('\n'):
7242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		t += indent + line + '\n'
7252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(t, str)
7262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return t
7272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Return the first line of l
7292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef line1(text):
7302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(text, str)
7312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return text.split('\n')[0]
7322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson_change_prolog = """# Change list.
7342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Lines beginning with # are ignored.
7352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Multi-line values should be indented.
7362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
7372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#######################################################################
7392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Mercurial helper functions
7402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Get effective change nodes taking into account applied MQ patches
7422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef effective_revpair(repo):
7432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson    try:
7442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return scmutil.revpair(repo, ['qparent'])
7452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson    except:
7462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return scmutil.revpair(repo, None)
7472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Return list of changed files in repository that match pats.
7492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Warn about patterns that did not match.
7502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef matchpats(ui, repo, pats, opts):
7512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	matcher = scmutil.match(repo, pats, opts)
7522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	node1, node2 = effective_revpair(repo)
7532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	modified, added, removed, deleted, unknown, ignored, clean = repo.status(node1, node2, matcher, ignored=True, clean=True, unknown=True)
7542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return (modified, added, removed, deleted, unknown, ignored, clean)
7552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Return list of changed files in repository that match pats.
7572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# The patterns came from the command line, so we warn
7582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# if they have no effect or cannot be understood.
7592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef ChangedFiles(ui, repo, pats, opts, taken=None):
7602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	taken = taken or {}
7612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Run each pattern separately so that we can warn about
7622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# patterns that didn't do anything useful.
7632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for p in pats:
7642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		modified, added, removed, deleted, unknown, ignored, clean = matchpats(ui, repo, [p], opts)
7652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		redo = False
7662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for f in unknown:
7672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			promptadd(ui, repo, f)
7682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			redo = True
7692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for f in deleted:
7702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			promptremove(ui, repo, f)
7712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			redo = True
7722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if redo:
7732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			modified, added, removed, deleted, unknown, ignored, clean = matchpats(ui, repo, [p], opts)
7742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for f in modified + added + removed:
7752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if f in taken:
7762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("warning: %s already in CL %s\n" % (f, taken[f].name))
7772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not modified and not added and not removed:
7782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("warning: %s did not match any modified files\n" % (p,))
7792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Again, all at once (eliminates duplicates)
7812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	modified, added, removed = matchpats(ui, repo, pats, opts)[:3]
7822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	l = modified + added + removed
7832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	l.sort()
7842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if taken:
7852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		l = Sub(l, taken.keys())
7862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return l
7872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Return list of changed files in repository that match pats and still exist.
7892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef ChangedExistingFiles(ui, repo, pats, opts):
7902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	modified, added = matchpats(ui, repo, pats, opts)[:2]
7912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	l = modified + added
7922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	l.sort()
7932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return l
7942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
7952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Return list of files claimed by existing CLs
7962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef Taken(ui, repo):
7972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	all = LoadAllCL(ui, repo, web=False)
7982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	taken = {}
7992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for _, cl in all.items():
8002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for f in cl.files:
8012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			taken[f] = cl
8022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return taken
8032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Return list of changed files that are not claimed by other CLs
8052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef DefaultFiles(ui, repo, pats, opts):
8062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return ChangedFiles(ui, repo, pats, opts, taken=Taken(ui, repo))
8072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef Sub(l1, l2):
8092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return [l for l in l1 if l not in l2]
8102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef Add(l1, l2):
8122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	l = l1 + Sub(l2, l1)
8132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	l.sort()
8142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return l
8152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef Intersect(l1, l2):
8172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return [l for l in l1 if l in l2]
8182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef getremote(ui, repo, opts):
8202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# save $http_proxy; creating the HTTP repo object will
8212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# delete it in an attempt to "help"
8222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	proxy = os.environ.get('http_proxy')
8232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	source = hg.parseurl(ui.expandpath("default"), None)[0]
8242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
8252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		remoteui = hg.remoteui # hg 1.6
8262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
8272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		remoteui = cmdutil.remoteui
8282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	other = hg.repository(remoteui(repo, opts), source)
8292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if proxy is not None:
8302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		os.environ['http_proxy'] = proxy
8312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return other
8322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondesc_re = '^(.+: |(tag )?(release|weekly)\.|fix build|undo CL)'
8342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondesc_msg = '''Your CL description appears not to use the standard form.
8362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonThe first line of your change description is conventionally a
8382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonone-line summary of the change, prefixed by the primary affected package,
8392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonand is used as the subject for code review mail; the rest of the description
8402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonelaborates.
8412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonExamples:
8432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	encoding/rot13: new package
8452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	math: add IsInf, IsNaN
8472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	net: fix cname in LookupHost
8492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	unicode: update to Unicode 5.0.2
8512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson'''
8532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef promptremove(ui, repo, f):
8562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if promptyesno(ui, "hg remove %s (y/n)?" % (f,)):
8572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if commands.remove(ui, repo, 'path:'+f) != 0:
8582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("error removing %s" % (f,))
8592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef promptadd(ui, repo, f):
8612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if promptyesno(ui, "hg add %s (y/n)?" % (f,)):
8622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if commands.add(ui, repo, 'path:'+f) != 0:
8632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("error adding %s" % (f,))
8642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef EditCL(ui, repo, cl):
8662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status(None)	# do not show status
8672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	s = cl.EditorText()
8682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	while True:
8692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = ui.edit(s, ui.username())
8702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# We can't trust Mercurial + Python not to die before making the change,
8722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# so, by popular demand, just scribble the most recent CL edit into
8732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# $(hg root)/last-change so that if Mercurial does die, people
8742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# can look there for their work.
8752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
8762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			f = open(repo.root+"/last-change", "w")
8772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			f.write(s)
8782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			f.close()
8792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except:
8802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pass
8812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		clx, line, err = ParseCL(s, cl.name)
8832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != '':
8842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not promptyesno(ui, "error parsing change list: line %d: %s\nre-edit (y/n)?" % (line, err)):
8852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return "change list not modified"
8862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
8872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Check description.
8892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if clx.desc == '':
8902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if promptyesno(ui, "change list should have a description\nre-edit (y/n)?"):
8912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
8922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		elif re.search('<enter reason for undo>', clx.desc):
8932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if promptyesno(ui, "change list description omits reason for undo\nre-edit (y/n)?"):
8942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
8952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		elif not re.match(desc_re, clx.desc.split('\n')[0]):
8962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if promptyesno(ui, desc_msg + "re-edit (y/n)?"):
8972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
8982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
8992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Check file list for files that need to be hg added or hg removed
9002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# or simply aren't understood.
9012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pats = ['path:'+f for f in clx.files]
9022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		modified, added, removed, deleted, unknown, ignored, clean = matchpats(ui, repo, pats, {})
9032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		files = []
9042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for f in clx.files:
9052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if f in modified or f in added or f in removed:
9062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				files.append(f)
9072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
9082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if f in deleted:
9092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				promptremove(ui, repo, f)
9102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				files.append(f)
9112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
9122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if f in unknown:
9132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				promptadd(ui, repo, f)
9142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				files.append(f)
9152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
9162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if f in ignored:
9172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("error: %s is excluded by .hgignore; omitting\n" % (f,))
9182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
9192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if f in clean:
9202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("warning: %s is listed in the CL but unchanged\n" % (f,))
9212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				files.append(f)
9222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
9232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			p = repo.root + '/' + f
9242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if os.path.isfile(p):
9252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("warning: %s is a file but not known to hg\n" % (f,))
9262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				files.append(f)
9272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
9282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if os.path.isdir(p):
9292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("error: %s is a directory, not a file; omitting\n" % (f,))
9302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
9312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("error: %s does not exist; omitting\n" % (f,))
9322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		clx.files = files
9332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
9342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.desc = clx.desc
9352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.reviewer = clx.reviewer
9362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.cc = clx.cc
9372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.files = clx.files
9382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.private = clx.private
9392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		break
9402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return ""
9412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
9422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# For use by submit, etc. (NOT by change)
9432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Get change list number or list of files from command line.
9442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# If files are given, make a new change list.
9452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef CommandLineCL(ui, repo, pats, opts, defaultcc=None):
9462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(pats) > 0 and GoodCLName(pats[0]):
9472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if len(pats) != 1:
9482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "cannot specify change number and file names"
9492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if opts.get('message'):
9502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "cannot use -m with existing CL"
9512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl, err = LoadCL(ui, repo, pats[0], web=True)
9522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
9532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, err
9542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
9552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = CL("new")
9562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.local = True
9572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.files = ChangedFiles(ui, repo, pats, opts, taken=Taken(ui, repo))
9582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not cl.files:
9592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "no files changed"
9602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts.get('reviewer'):
9612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.reviewer = Add(cl.reviewer, SplitCommaSpace(opts.get('reviewer')))
9622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts.get('cc'):
9632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.cc = Add(cl.cc, SplitCommaSpace(opts.get('cc')))
9642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if defaultcc:
9652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.cc = Add(cl.cc, defaultcc)
9662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.name == "new":
9672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if opts.get('message'):
9682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.desc = opts.get('message')
9692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
9702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			err = EditCL(ui, repo, cl)
9712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if err != '':
9722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return None, err
9732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return cl, ""
9742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
9752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# reposetup replaces cmdutil.match with this wrapper,
9762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# which expands the syntax @clnumber to mean the files
9772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# in that CL.
9782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonoriginal_match = None
9792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonglobal_repo = None
9802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonglobal_ui = None
9812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef ReplacementForCmdutilMatch(ctx, pats=None, opts=None, globbed=False, default='relpath'):
9822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	taken = []
9832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = []
9842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	pats = pats or []
9852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	opts = opts or {}
9862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
9872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for p in pats:
9882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if p.startswith('@'):
9892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			taken.append(p)
9902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			clname = p[1:]
9912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not GoodCLName(clname):
9922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				raise util.Abort("invalid CL name " + clname)
9932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl, err = LoadCL(global_repo.ui, global_repo, clname, web=False)
9942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if err != '':
9952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				raise util.Abort("loading CL " + clname + ": " + err)
9962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not cl.files:
9972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				raise util.Abort("no files in CL " + clname)
9982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			files = Add(files, cl.files)
9992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	pats = Sub(pats, taken) + ['path:'+f for f in files]
10002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
10012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# work-around for http://selenic.com/hg/rev/785bbc8634f8
10022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if hgversion >= '1.9' and not hasattr(ctx, 'match'):
10032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ctx = ctx[None]
10042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return original_match(ctx, pats=pats, opts=opts, globbed=globbed, default=default)
10052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
10062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef RelativePath(path, cwd):
10072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	n = len(cwd)
10082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if path.startswith(cwd) and path[n] == '/':
10092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return path[n+1:]
10102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return path
10112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
10122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef CheckFormat(ui, repo, files, just_warn=False):
10132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("running gofmt")
10142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	CheckGofmt(ui, repo, files, just_warn)
10152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	CheckTabfmt(ui, repo, files, just_warn)
10162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
10172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Check that gofmt run on the list of files does not change them
10182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef CheckGofmt(ui, repo, files, just_warn):
10192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [f for f in files if (f.startswith('src/') or f.startswith('test/bench/')) and f.endswith('.go')]
10202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not files:
10212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
10222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cwd = os.getcwd()
10232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
10242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [f for f in files if os.access(f, 0)]
10252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not files:
10262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
10272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
10282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cmd = subprocess.Popen(["gofmt", "-l"] + files, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=sys.platform != "win32")
10292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cmd.stdin.close()
10302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
10312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise util.Abort("gofmt: " + ExceptionDetail())
10322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	data = cmd.stdout.read()
10332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	errors = cmd.stderr.read()
10342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cmd.wait()
10352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("done with gofmt")
10362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(errors) > 0:
10372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.warn("gofmt errors:\n" + errors.rstrip() + "\n")
10382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
10392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(data) > 0:
10402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		msg = "gofmt needs to format these files (run hg gofmt):\n" + Indent(data, "\t").rstrip()
10412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if just_warn:
10422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("warning: " + msg + "\n")
10432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
10442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise util.Abort(msg)
10452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
10462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
10472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Check that *.[chys] files indent using tabs.
10482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef CheckTabfmt(ui, repo, files, just_warn):
10492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [f for f in files if f.startswith('src/') and re.search(r"\.[chys]$", f)]
10502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not files:
10512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
10522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cwd = os.getcwd()
10532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
10542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [f for f in files if os.access(f, 0)]
10552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	badfiles = []
10562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for f in files:
10572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
10582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for line in open(f, 'r'):
10592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# Four leading spaces is enough to complain about,
10602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# except that some Plan 9 code uses four spaces as the label indent,
10612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# so allow that.
10622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if line.startswith('    ') and not re.match('    [A-Za-z0-9_]+:', line):
10632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					badfiles.append(f)
10642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
10652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except:
10662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# ignore cannot open file, etc.
10672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pass
10682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(badfiles) > 0:
10692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		msg = "these files use spaces for indentation (use tabs instead):\n\t" + "\n\t".join(badfiles)
10702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if just_warn:
10712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("warning: " + msg + "\n")
10722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
10732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise util.Abort(msg)
10742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
10752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
10762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#######################################################################
10772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Mercurial commands
10782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
10792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# every command must take a ui and and repo as arguments.
10802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# opts is a dict where you can find other command line flags
10812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
10822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Other parameters are taken in order from items on the command line that
10832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# don't start with a dash.  If no default value is given in the parameter list,
10842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# they are required.
10852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
10862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
10872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef change(ui, repo, *pats, **opts):
10882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""create, edit or delete a change list
10892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
10902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Create, edit or delete a change list.
10912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	A change list is a group of files to be reviewed and submitted together,
10922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	plus a textual description of the change.
10932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Change lists are referred to by simple alphanumeric names.
10942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
10952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Changes must be reviewed before they can be submitted.
10962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
10972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	In the absence of options, the change command opens the
10982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	change list for editing in the default editor.
10992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
11002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Deleting a change with the -d or -D flag does not affect
11012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	the contents of the files listed in that change.  To revert
11022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	the files listed in a change, use
11032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
11042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg revert @123456
11052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
11062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	before running hg change -d 123456.
11072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
11082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
11092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if missing_codereview:
11102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return missing_codereview
11112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
11122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dirty = {}
11132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(pats) > 0 and GoodCLName(pats[0]):
11142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		name = pats[0]
11152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if len(pats) != 1:
11162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "cannot specify CL name and file patterns"
11172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pats = pats[1:]
11182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl, err = LoadCL(ui, repo, name, web=True)
11192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != '':
11202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return err
11212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not cl.local and (opts["stdin"] or not opts["stdout"]):
11222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "cannot change non-local CL " + name
11232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
11242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if repo[None].branch() != "default":
11252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "cannot run hg change outside default branch"
11262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		name = "new"
11272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = CL("new")
11282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		dirty[cl] = True
11292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		files = ChangedFiles(ui, repo, pats, opts, taken=Taken(ui, repo))
11302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
11312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts["delete"] or opts["deletelocal"]:
11322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if opts["delete"] and opts["deletelocal"]:
11332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "cannot use -d and -D together"
11342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		flag = "-d"
11352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if opts["deletelocal"]:
11362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			flag = "-D"
11372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if name == "new":
11382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "cannot use "+flag+" with file patterns"
11392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if opts["stdin"] or opts["stdout"]:
11402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "cannot use "+flag+" with -i or -o"
11412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not cl.local:
11422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "cannot change non-local CL " + name
11432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if opts["delete"]:
11442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if cl.copied_from:
11452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return "original author must delete CL; hg change -D will remove locally"
11462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			PostMessage(ui, cl.name, "*** Abandoned ***", send_mail=cl.mailed)
11472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			EditDesc(cl.name, closed=True, private=cl.private)
11482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.Delete(ui, repo)
11492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
11502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
11512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts["stdin"]:
11522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = sys.stdin.read()
11532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		clx, line, err = ParseCL(s, name)
11542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != '':
11552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "error parsing change list: line %d: %s" % (line, err)
11562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if clx.desc is not None:
11572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.desc = clx.desc;
11582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			dirty[cl] = True
11592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if clx.reviewer is not None:
11602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.reviewer = clx.reviewer
11612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			dirty[cl] = True
11622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if clx.cc is not None:
11632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.cc = clx.cc
11642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			dirty[cl] = True
11652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if clx.files is not None:
11662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.files = clx.files
11672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			dirty[cl] = True
11682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if clx.private != cl.private:
11692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.private = clx.private
11702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			dirty[cl] = True
11712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
11722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not opts["stdin"] and not opts["stdout"]:
11732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if name == "new":
11742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.files = files
11752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		err = EditCL(ui, repo, cl)
11762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
11772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return err
11782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		dirty[cl] = True
11792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
11802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for d, _ in dirty.items():
11812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		name = d.name
11822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		d.Flush(ui, repo)
11832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if name == "new":
11842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			d.Upload(ui, repo, quiet=True)
11852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
11862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts["stdout"]:
11872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.write(cl.EditorText())
11882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	elif opts["pending"]:
11892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.write(cl.PendingText())
11902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	elif name == "new":
11912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if ui.quiet:
11922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.write(cl.name)
11932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
11942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.write("CL created: " + cl.url + "\n")
11952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
11962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
11972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef code_login(ui, repo, **opts):
11982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""log in to code review server
11992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Logs in to the code review server, saving a cookie in
12012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	a file in your home directory.
12022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
12032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if missing_codereview:
12042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return missing_codereview
12052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	MySend(None)
12072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef clpatch(ui, repo, clname, **opts):
12092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""import a patch from the code review server
12102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Imports a patch from the code review server into the local client.
12122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	If the local client has already modified any of the files that the
12132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	patch modifies, this command will refuse to apply the patch.
12142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Submitting an imported patch will keep the original author's
12162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	name as the Author: line but add your own name to a Committer: line.
12172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
12182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if repo[None].branch() != "default":
12192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "cannot run hg clpatch outside default branch"
12202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return clpatch_or_undo(ui, repo, clname, opts, mode="clpatch")
12212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef undo(ui, repo, clname, **opts):
12232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""undo the effect of a CL
12242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Creates a new CL that undoes an earlier CL.
12262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	After creating the CL, opens the CL text for editing so that
12272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	you can add the reason for the undo to the description.
12282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
12292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if repo[None].branch() != "default":
12302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "cannot run hg undo outside default branch"
12312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return clpatch_or_undo(ui, repo, clname, opts, mode="undo")
12322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef release_apply(ui, repo, clname, **opts):
12342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""apply a CL to the release branch
12352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Creates a new CL copying a previously committed change
12372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	from the main branch to the release branch.
12382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	The current client must either be clean or already be in
12392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	the release branch.
12402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	The release branch must be created by starting with a
12422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	clean client, disabling the code review plugin, and running:
12432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg update weekly.YYYY-MM-DD
12452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg branch release-branch.rNN
12462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg commit -m 'create release-branch.rNN'
12472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg push --new-branch
12482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Then re-enable the code review plugin.
12502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	People can test the release branch by running
12522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg update release-branch.rNN
12542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	in a clean client.  To return to the normal tree,
12562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg update default
12582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Move changes since the weekly into the release branch
12602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	using hg release-apply followed by the usual code review
12612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	process and hg submit.
12622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	When it comes time to tag the release, record the
12642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	final long-form tag of the release-branch.rNN
12652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	in the *default* branch's .hgtags file.  That is, run
12662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg update default
12682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	and then edit .hgtags as you would for a weekly.
12702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
12722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	c = repo[None]
12732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not releaseBranch:
12742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "no active release branches"
12752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if c.branch() != releaseBranch:
12762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if c.modified() or c.added() or c.removed():
12772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise util.Abort("uncommitted local changes - cannot switch branches")
12782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		err = hg.clean(repo, releaseBranch)
12792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err:
12802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return err
12812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
12822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		err = clpatch_or_undo(ui, repo, clname, opts, mode="backport")
12832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err:
12842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise util.Abort(err)
12852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except Exception, e:
12862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		hg.clean(repo, "default")
12872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise e
12882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return None
12892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef rev2clname(rev):
12912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Extract CL name from revision description.
12922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# The last line in the description that is a codereview URL is the real one.
12932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Earlier lines might be part of the user-written description.
12942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	all = re.findall('(?m)^http://codereview.appspot.com/([0-9]+)$', rev.description())
12952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(all) > 0:
12962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return all[-1]
12972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return ""
12982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
12992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonundoHeader = """undo CL %s / %s
13002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson<enter reason for undo>
13022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson««« original CL description
13042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
13052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonundoFooter = """
13072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson»»»
13082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
13092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonbackportHeader = """[%s] %s
13112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson««« CL %s / %s
13132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
13142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonbackportFooter = """
13162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson»»»
13172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
13182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Implementation of clpatch/undo.
13202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef clpatch_or_undo(ui, repo, clname, opts, mode):
13212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if missing_codereview:
13222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return missing_codereview
13232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if mode == "undo" or mode == "backport":
13252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if hgversion < '1.4':
13262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# Don't have cmdutil.match (see implementation of sync command).
13272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "hg is too old to run hg %s - update to 1.4 or newer" % mode
13282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Find revision in Mercurial repository.
13302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Assume CL number is 7+ decimal digits.
13312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Otherwise is either change log sequence number (fewer decimal digits),
13322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# hexadecimal hash, or tag name.
13332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Mercurial will fall over long before the change log
13342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# sequence numbers get to be 7 digits long.
13352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if re.match('^[0-9]{7,}$', clname):
13362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			found = False
13372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			matchfn = scmutil.match(repo, [], {'rev': None})
13382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			def prep(ctx, fns):
13392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				pass
13402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev': None}, prep):
13412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				rev = repo[ctx.rev()]
13422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# Last line with a code review URL is the actual review URL.
13432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# Earlier ones might be part of the CL description.
13442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				n = rev2clname(rev)
13452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if n == clname:
13462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					found = True
13472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
13482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not found:
13492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return "cannot find CL %s in local repository" % clname
13502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
13512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			rev = repo[clname]
13522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not rev:
13532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return "unknown revision %s" % clname
13542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			clname = rev2clname(rev)
13552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if clname == "":
13562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return "cannot find CL name in revision description"
13572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Create fresh CL and start with patch that would reverse the change.
13592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		vers = short(rev.node())
13602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = CL("new")
13612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		desc = str(rev.description())
13622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if mode == "undo":
13632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.desc = (undoHeader % (clname, vers)) + desc + undoFooter
13642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
13652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.desc = (backportHeader % (releaseBranch, line1(desc), clname, vers)) + desc + undoFooter
13662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		v1 = vers
13672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		v0 = short(rev.parents()[0].node())
13682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if mode == "undo":
13692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			arg = v1 + ":" + v0
13702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
13712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			vers = v0
13722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			arg = v0 + ":" + v1
13732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		patch = RunShell(["hg", "diff", "--git", "-r", arg])
13742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:  # clpatch
13762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl, vers, patch, err = DownloadCL(ui, repo, clname)
13772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
13782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return err
13792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if patch == emptydiff:
13802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "codereview issue %s has no diff" % clname
13812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# find current hg version (hg identify)
13832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ctx = repo[None]
13842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	parents = ctx.parents()
13852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	id = '+'.join([short(p.node()) for p in parents])
13862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
13872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# if version does not match the patch version,
13882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# try to update the patch line numbers.
13892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if vers != "" and id != vers:
13902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# "vers in repo" gives the wrong answer
13912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# on some versions of Mercurial.  Instead, do the actual
13922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# lookup and catch the exception.
13932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
13942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			repo[vers].description()
13952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except:
13962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "local repository is out of date; sync to get %s" % (vers)
13972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		patch1, err = portPatch(repo, patch, vers, id)
13982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
13992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not opts["ignore_hgpatch_failure"]:
14002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return "codereview issue %s is out of date: %s (%s->%s)" % (clname, err, vers, id)
14012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
14022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			patch = patch1
14032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	argv = ["hgpatch"]
14042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts["no_incoming"] or mode == "backport":
14052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		argv += ["--checksync=false"]
14062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
14072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cmd = subprocess.Popen(argv, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None, close_fds=sys.platform != "win32")
14082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
14092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "hgpatch: " + ExceptionDetail()
14102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	out, err = cmd.communicate(patch)
14122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cmd.returncode != 0 and not opts["ignore_hgpatch_failure"]:
14132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "hgpatch failed"
14142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.local = True
14152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.files = out.strip().split()
14162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.files and not opts["ignore_hgpatch_failure"]:
14172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "codereview issue %s has no changed files" % clname
14182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = ChangedFiles(ui, repo, [], opts)
14192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	extra = Sub(cl.files, files)
14202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if extra:
14212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.warn("warning: these files were listed in the patch but not changed:\n\t" + "\n\t".join(extra) + "\n")
14222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.Flush(ui, repo)
14232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if mode == "undo":
14242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		err = EditCL(ui, repo, cl)
14252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
14262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "CL created, but error editing: " + err
14272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.Flush(ui, repo)
14282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
14292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.write(cl.PendingText() + "\n")
14302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# portPatch rewrites patch from being a patch against
14322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# oldver to being a patch against newver.
14332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef portPatch(repo, patch, oldver, newver):
14342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	lines = patch.splitlines(True) # True = keep \n
14352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	delta = None
14362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for i in range(len(lines)):
14372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		line = lines[i]
14382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line.startswith('--- a/'):
14392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			file = line[6:-1]
14402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			delta = fileDeltas(repo, file, oldver, newver)
14412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not delta or not line.startswith('@@ '):
14422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
14432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# @@ -x,y +z,w @@ means the patch chunk replaces
14442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# the original file's line numbers x up to x+y with the
14452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# line numbers z up to z+w in the new file.
14462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Find the delta from x in the original to the same
14472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# line in the current version and add that delta to both
14482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# x and z.
14492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		m = re.match('@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@', line)
14502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not m:
14512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return None, "error parsing patch line numbers"
14522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		n1, len1, n2, len2 = int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
14532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		d, err = lineDelta(delta, n1, len1)
14542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err != "":
14552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "", err
14562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		n1 += d
14572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		n2 += d
14582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines[i] = "@@ -%d,%d +%d,%d @@\n" % (n1, len1, n2, len2)
14592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	newpatch = ''.join(lines)
14612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return newpatch, ""
14622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# fileDelta returns the line number deltas for the given file's
14642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# changes from oldver to newver.
14652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# The deltas are a list of (n, len, newdelta) triples that say
14662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# lines [n, n+len) were modified, and after that range the
14672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# line numbers are +newdelta from what they were before.
14682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef fileDeltas(repo, file, oldver, newver):
14692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cmd = ["hg", "diff", "--git", "-r", oldver + ":" + newver, "path:" + file]
14702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	data = RunShell(cmd, silent_ok=True)
14712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	deltas = []
14722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in data.splitlines():
14732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		m = re.match('@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@', line)
14742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not m:
14752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
14762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		n1, len1, n2, len2 = int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
14772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		deltas.append((n1, len1, n2+len2-(n1+len1)))
14782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return deltas
14792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# lineDelta finds the appropriate line number delta to apply to the lines [n, n+len).
14812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# It returns an error if those lines were rewritten by the patch.
14822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef lineDelta(deltas, n, len):
14832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	d = 0
14842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for (old, oldlen, newdelta) in deltas:
14852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if old >= n+len:
14862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			break
14872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if old+len > n:
14882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return 0, "patch and recent changes conflict"
14892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		d = newdelta
14902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return d, ""
14912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef download(ui, repo, clname, **opts):
14932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""download a change from the code review server
14942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
14952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Download prints a description of the given change list
14962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	followed by its diff, downloaded from the code review server.
14972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
14982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if missing_codereview:
14992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return missing_codereview
15002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl, vers, patch, err = DownloadCL(ui, repo, clname)
15022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if err != "":
15032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return err
15042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ui.write(cl.EditorText() + "\n")
15052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ui.write(patch + "\n")
15062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
15072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef file(ui, repo, clname, pat, *pats, **opts):
15092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""assign files to or remove files from a change list
15102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Assign files to or (with -d) remove files from a change list.
15122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	The -d option only removes files from the change list.
15142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	It does not edit them or remove them from the repository.
15152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
15162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if missing_codereview:
15172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return missing_codereview
15182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	pats = tuple([pat] + list(pats))
15202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not GoodCLName(clname):
15212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "invalid CL name " + clname
15222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dirty = {}
15242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl, err = LoadCL(ui, repo, clname, web=False)
15252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if err != '':
15262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return err
15272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.local:
15282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "cannot change non-local CL " + clname
15292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = ChangedFiles(ui, repo, pats, opts)
15312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts["delete"]:
15332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		oldfiles = Intersect(files, cl.files)
15342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if oldfiles:
15352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not ui.quiet:
15362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.status("# Removing files from CL.  To undo:\n")
15372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.status("#	cd %s\n" % (repo.root))
15382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				for f in oldfiles:
15392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					ui.status("#	hg file %s %s\n" % (cl.name, f))
15402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.files = Sub(cl.files, oldfiles)
15412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.Flush(ui, repo)
15422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
15432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.status("no such files in CL")
15442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
15452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not files:
15472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "no such modified files"
15482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = Sub(files, cl.files)
15502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	taken = Taken(ui, repo)
15512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	warned = False
15522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for f in files:
15532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if f in taken:
15542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not warned and not ui.quiet:
15552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.status("# Taking files from other CLs.  To undo:\n")
15562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.status("#	cd %s\n" % (repo.root))
15572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				warned = True
15582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ocl = taken[f]
15592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not ui.quiet:
15602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.status("#	hg file %s %s\n" % (ocl.name, f))
15612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if ocl not in dirty:
15622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ocl.files = Sub(ocl.files, files)
15632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				dirty[ocl] = True
15642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.files = Add(cl.files, files)
15652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dirty[cl] = True
15662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for d, _ in dirty.items():
15672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		d.Flush(ui, repo)
15682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
15692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef gofmt(ui, repo, *pats, **opts):
15712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""apply gofmt to modified files
15722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Applies gofmt to the modified files in the repository that match
15742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	the given patterns.
15752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
15762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if missing_codereview:
15772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return missing_codereview
15782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = ChangedExistingFiles(ui, repo, pats, opts)
15802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [f for f in files if f.endswith(".go")]
15812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not files:
15822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "no modified go files"
15832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cwd = os.getcwd()
15842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
15852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
15862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cmd = ["gofmt", "-l"]
15872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not opts["list"]:
15882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cmd += ["-w"]
15892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if os.spawnvp(os.P_WAIT, "gofmt", cmd + files) != 0:
15902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise util.Abort("gofmt did not exit cleanly")
15912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except error.Abort, e:
15922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise
15932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
15942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise util.Abort("gofmt: " + ExceptionDetail())
15952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
15962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
15972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef mail(ui, repo, *pats, **opts):
15982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""mail a change for review
15992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Uploads a patch to the code review server and then sends mail
16012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	to the reviewer and CC list asking for a review.
16022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
16032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if missing_codereview:
16042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return missing_codereview
16052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl, err = CommandLineCL(ui, repo, pats, opts, defaultcc=defaultcc)
16072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if err != "":
16082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return err
16092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.Upload(ui, repo, gofmt_just_warn=True)
16102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.reviewer:
16112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# If no reviewer is listed, assign the review to defaultcc.
16122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# This makes sure that it appears in the
16132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# codereview.appspot.com/user/defaultcc
16142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# page, so that it doesn't get dropped on the floor.
16152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not defaultcc:
16162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return "no reviewers listed in CL"
16172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.cc = Sub(cl.cc, defaultcc)
16182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.reviewer = defaultcc
16192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.Flush(ui, repo)
16202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.files == []:
16222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "no changed files, not sending mail"
16232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.Mail(ui, repo)
16252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef pending(ui, repo, *pats, **opts):
16272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""show pending changes
16282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Lists pending changes followed by a list of unassigned but modified files.
16302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
16312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if missing_codereview:
16322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return missing_codereview
16332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	m = LoadAllCL(ui, repo, web=True)
16352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	names = m.keys()
16362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	names.sort()
16372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for name in names:
16382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl = m[name]
16392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.write(cl.PendingText() + "\n")
16402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	files = DefaultFiles(ui, repo, [], opts)
16422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(files) > 0:
16432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		s = "Changed files not in any CL:\n"
16442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for f in files:
16452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			s += "\t" + f + "\n"
16462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.write(s)
16472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef reposetup(ui, repo):
16492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	global original_match
16502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if original_match is None:
16512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		global global_repo, global_ui
16522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		global_repo = repo
16532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		global_ui = ui
16542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		start_status_thread()
16552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		original_match = scmutil.match
16562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		scmutil.match = ReplacementForCmdutilMatch
16572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		RietveldSetup(ui, repo)
16582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef CheckContributor(ui, repo, user=None):
16602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("checking CONTRIBUTORS file")
16612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	user, userline = FindContributor(ui, repo, user, warn=False)
16622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not userline:
16632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise util.Abort("cannot find %s in CONTRIBUTORS" % (user,))
16642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return userline
16652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef FindContributor(ui, repo, user=None, warn=True):
16672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not user:
16682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		user = ui.config("ui", "username")
16692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not user:
16702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise util.Abort("[ui] username is not configured in .hgrc")
16712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	user = user.lower()
16722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	m = re.match(r".*<(.*)>", user)
16732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if m:
16742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		user = m.group(1)
16752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if user not in contributors:
16772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if warn:
16782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("warning: cannot find %s in CONTRIBUTORS\n" % (user,))
16792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return user, None
16802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	user, email = contributors[user]
16822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return email, "%s <%s>" % (user, email)
16832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef submit(ui, repo, *pats, **opts):
16852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""submit change to remote repository
16862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Submits change to remote repository.
16882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Bails out if the local repository is not in sync with the remote one.
16892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
16902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if missing_codereview:
16912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return missing_codereview
16922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# We already called this on startup but sometimes Mercurial forgets.
16942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_mercurial_encoding_to_utf8()
16952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
16962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	other = getremote(ui, repo, opts)
16972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	repo.ui.quiet = True
16982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not opts["no_incoming"] and incoming(repo, other):
16992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "local repository out of date; must sync before submit"
17002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl, err = CommandLineCL(ui, repo, pats, opts, defaultcc=defaultcc)
17022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if err != "":
17032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return err
17042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	user = None
17062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.copied_from:
17072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		user = cl.copied_from
17082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	userline = CheckContributor(ui, repo, user)
17092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(userline, str)
17102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	about = ""
17122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.reviewer:
17132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		about += "R=" + JoinComma([CutDomain(s) for s in cl.reviewer]) + "\n"
17142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts.get('tbr'):
17152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		tbr = SplitCommaSpace(opts.get('tbr'))
17162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.reviewer = Add(cl.reviewer, tbr)
17172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		about += "TBR=" + JoinComma([CutDomain(s) for s in tbr]) + "\n"
17182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.cc:
17192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		about += "CC=" + JoinComma([CutDomain(s) for s in cl.cc]) + "\n"
17202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.reviewer:
17222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "no reviewers listed in CL"
17232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.local:
17252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "cannot submit non-local CL"
17262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# upload, to sync current patch and also get change number if CL is new.
17282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.copied_from:
17292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.Upload(ui, repo, gofmt_just_warn=True)
17302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# check gofmt for real; allowed upload to warn in order to save CL.
17322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.Flush(ui, repo)
17332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	CheckFormat(ui, repo, cl.files)
17342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	about += "%s%s\n" % (server_url_base, cl.name)
17362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cl.copied_from:
17382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		about += "\nCommitter: " + CheckContributor(ui, repo, None) + "\n"
17392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(about, str)
17402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.mailed and not cl.copied_from:		# in case this is TBR
17422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.Mail(ui, repo)
17432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# submit changes locally
17452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	date = opts.get('date')
17462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if date:
17472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opts['date'] = util.parsedate(date)
17482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(opts['date'], str)
17492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	opts['message'] = cl.desc.rstrip() + "\n\n" + about
17502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	typecheck(opts['message'], str)
17512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if opts['dryrun']:
17532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print "NOT SUBMITTING:"
17542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print "User: ", userline
17552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print "Message:"
17562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print Indent(opts['message'], "\t")
17572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print "Files:"
17582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print Indent('\n'.join(cl.files), "\t")
17592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "dry run; not submitted"
17602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("pushing " + cl.name + " to remote server")
17622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	other = getremote(ui, repo, opts)
17642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if outgoing(repo):
17652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise util.Abort("local repository corrupt or out-of-phase with remote: found outgoing changes")
17662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	m = match.exact(repo.root, repo.getcwd(), cl.files)
17682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	node = repo.commit(ustr(opts['message']), ustr(userline), opts.get('date'), m)
17692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not node:
17702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "nothing changed"
17712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# push to remote; if it fails for any reason, roll back
17732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
17742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		log = repo.changelog
17752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		rev = log.rev(node)
17762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		parents = log.parentrevs(rev)
17772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if (rev-1 not in parents and
17782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				(parents == (nullrev, nullrev) or
17792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				len(log.heads(log.node(parents[0]))) > 1 and
17802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				(parents[1] == nullrev or len(log.heads(log.node(parents[1]))) > 1))):
17812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# created new head
17822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise util.Abort("local repository out of date; must sync before submit")
17832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# push changes to remote.
17852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# if it works, we're committed.
17862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# if not, roll back
17872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		r = repo.push(other, False, None)
17882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if r == 0:
17892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise util.Abort("local repository out of date; must sync before submit")
17902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
17912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		real_rollback()
17922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise
17932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
17942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# we're committed. upload final patch, close review, add commit message
17952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	changeURL = short(node)
17962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	url = other.url()
17972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	m = re.match("^https?://([^@/]+@)?([^.]+)\.googlecode\.com/hg/?", url)
17982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if m:
17992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		changeURL = "http://code.google.com/p/%s/source/detail?r=%s" % (m.group(2), changeURL)
18002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
18012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print >>sys.stderr, "URL: ", url
18022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	pmsg = "*** Submitted as " + changeURL + " ***\n\n" + opts['message']
18032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# When posting, move reviewers to CC line,
18052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# so that the issue stops showing up in their "My Issues" page.
18062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	PostMessage(ui, cl.name, pmsg, reviewers="", cc=JoinComma(cl.reviewer+cl.cc))
18072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.copied_from:
18092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		EditDesc(cl.name, closed=True, private=cl.private)
18102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.Delete(ui, repo)
18112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	c = repo[None]
18132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if c.branch() == releaseBranch and not c.modified() and not c.added() and not c.removed():
18142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.write("switching from %s to default branch.\n" % releaseBranch)
18152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		err = hg.clean(repo, "default")
18162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err:
18172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return err
18182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return None
18192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef sync(ui, repo, **opts):
18212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""synchronize with remote repository
18222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Incorporates recent changes from the remote repository
18242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	into the local repository.
18252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
18262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if missing_codereview:
18272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return missing_codereview
18282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not opts["local"]:
18302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.status = sync_note
18312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.note = sync_note
18322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		other = getremote(ui, repo, opts)
18332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		modheads = repo.pull(other)
18342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		err = commands.postincoming(ui, repo, modheads, True, "tip")
18352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if err:
18362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return err
18372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	commands.update(ui, repo, rev="default")
18382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sync_changes(ui, repo)
18392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef sync_note(msg):
18412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# we run sync (pull -u) in verbose mode to get the
18422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# list of files being updated, but that drags along
18432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# a bunch of messages we don't care about.
18442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# omit them.
18452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if msg == 'resolving manifests\n':
18462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
18472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if msg == 'searching for changes\n':
18482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
18492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if msg == "couldn't find merge tool hgmerge\n":
18502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
18512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sys.stdout.write(msg)
18522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef sync_changes(ui, repo):
18542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Look through recent change log descriptions to find
18552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# potential references to http://.*/our-CL-number.
18562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Double-check them by looking at the Rietveld log.
18572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def Rev(rev):
18582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		desc = repo[rev].description().strip()
18592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for clname in re.findall('(?m)^http://(?:[^\n]+)/([0-9]+)$', desc):
18602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if IsLocalCL(ui, repo, clname) and IsRietveldSubmitted(ui, clname, repo[rev].hex()):
18612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("CL %s submitted as %s; closing\n" % (clname, repo[rev]))
18622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				cl, err = LoadCL(ui, repo, clname, web=False)
18632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if err != "":
18642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					ui.warn("loading CL %s: %s\n" % (clname, err))
18652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					continue
18662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if not cl.copied_from:
18672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					EditDesc(cl.name, closed=True, private=cl.private)
18682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				cl.Delete(ui, repo)
18692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if hgversion < '1.4':
18712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		get = util.cachefunc(lambda r: repo[r].changeset())
18722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, [], get, {'rev': None})
18732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		n = 0
18742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for st, rev, fns in changeiter:
18752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if st != 'iter':
18762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				continue
18772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			n += 1
18782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if n > 100:
18792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				break
18802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			Rev(rev)
18812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
18822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		matchfn = scmutil.match(repo, [], {'rev': None})
18832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		def prep(ctx, fns):
18842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pass
18852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev': None}, prep):
18862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			Rev(ctx.rev())
18872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
18882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Remove files that are not modified from the CLs in which they appear.
18892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	all = LoadAllCL(ui, repo, web=False)
18902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	changed = ChangedFiles(ui, repo, [], {})
18912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for _, cl in all.items():
18922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		extra = Sub(cl.files, changed)
18932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if extra:
18942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ui.warn("Removing unmodified files from CL %s:\n" % (cl.name,))
18952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for f in extra:
18962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("\t%s\n" % (f,))
18972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.files = Sub(cl.files, extra)
18982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			cl.Flush(ui, repo)
18992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not cl.files:
19002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not cl.copied_from:
19012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("CL %s has no files; delete (abandon) with hg change -d %s\n" % (cl.name, cl.name))
19022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
19032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ui.warn("CL %s has no files; delete locally with hg change -D %s\n" % (cl.name, cl.name))
19042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
19052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef upload(ui, repo, name, **opts):
19072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""upload diffs to the code review server
19082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Uploads the current modifications for a given change to the server.
19102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
19112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if missing_codereview:
19122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return missing_codereview
19132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	repo.ui.quiet = True
19152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl, err = LoadCL(ui, repo, name, web=True)
19162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if err != "":
19172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return err
19182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not cl.local:
19192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "cannot upload non-local change"
19202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl.Upload(ui, repo)
19212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	print "%s%s\n" % (server_url_base, cl.name)
19222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return
19232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonreview_opts = [
19252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	('r', 'reviewer', '', 'add reviewer'),
19262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	('', 'cc', '', 'add cc'),
19272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	('', 'tbr', '', 'add future reviewer'),
19282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	('m', 'message', '', 'change description (for new change)'),
19292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson]
19302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
19312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsoncmdtable = {
19322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# The ^ means to show this command in the help text that
19332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# is printed when running hg with no arguments.
19342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^change": (
19352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		change,
19362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
19372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('d', 'delete', None, 'delete existing change list'),
19382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('D', 'deletelocal', None, 'delete locally, but do not change CL on server'),
19392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('i', 'stdin', None, 'read change list from standard input'),
19402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('o', 'stdout', None, 'print change list to standard output'),
19412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('p', 'pending', None, 'print pending summary to standard output'),
19422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
19432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"[-d | -D] [-i] [-o] change# or FILE ..."
19442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
19452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^clpatch": (
19462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		clpatch,
19472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
19482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
19492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'no_incoming', None, 'disable check for incoming changes'),
19502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
19512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"change#"
19522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
19532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Would prefer to call this codereview-login, but then
19542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# hg help codereview prints the help for this command
19552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# instead of the help for the extension.
19562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"code-login": (
19572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		code_login,
19582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[],
19592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"",
19602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
19612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^download": (
19622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		download,
19632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[],
19642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"change#"
19652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
19662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^file": (
19672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		file,
19682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
19692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('d', 'delete', None, 'delete files from change list (but not repository)'),
19702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
19712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"[-d] change# FILE ..."
19722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
19732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^gofmt": (
19742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		gofmt,
19752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
19762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('l', 'list', None, 'list files that would change, but do not edit them'),
19772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
19782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"FILE ..."
19792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
19802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^pending|p": (
19812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pending,
19822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[],
19832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"[FILE ...]"
19842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
19852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^mail": (
19862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		mail,
19872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		review_opts + [
19882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		] + commands.walkopts,
19892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"[-r reviewer] [--cc cc] [change# | file ...]"
19902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
19912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^release-apply": (
19922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		release_apply,
19932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
19942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
19952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'no_incoming', None, 'disable check for incoming changes'),
19962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
19972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"change#"
19982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
19992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# TODO: release-start, release-tag, weekly-tag
20002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^submit": (
20012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		submit,
20022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		review_opts + [
20032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'no_incoming', None, 'disable initial incoming check (for testing)'),
20042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('n', 'dryrun', None, 'make change only locally (for testing)'),
20052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		] + commands.walkopts + commands.commitopts + commands.commitopts2,
20062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"[-r reviewer] [--cc cc] [change# | file ...]"
20072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
20082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^sync": (
20092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		sync,
20102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
20112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'local', None, 'do not pull changes from remote repository')
20122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
20132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"[--local]",
20142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
20152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^undo": (
20162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		undo,
20172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[
20182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
20192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			('', 'no_incoming', None, 'disable check for incoming changes'),
20202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		],
20212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"change#"
20222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
20232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"^upload": (
20242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		upload,
20252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[],
20262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"change#"
20272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	),
20282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson}
20292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#######################################################################
20322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Wrappers around upload.py for interacting with Rietveld
20332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# HTML form parser
20352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass FormParser(HTMLParser):
20362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self):
20372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.map = {}
20382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.curtag = None
20392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.curdata = None
20402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		HTMLParser.__init__(self)
20412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def handle_starttag(self, tag, attrs):
20422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if tag == "input":
20432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			key = None
20442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			value = ''
20452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for a in attrs:
20462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if a[0] == 'name':
20472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					key = a[1]
20482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if a[0] == 'value':
20492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					value = a[1]
20502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if key is not None:
20512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				self.map[key] = value
20522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if tag == "textarea":
20532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			key = None
20542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for a in attrs:
20552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if a[0] == 'name':
20562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					key = a[1]
20572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if key is not None:
20582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				self.curtag = key
20592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				self.curdata = ''
20602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def handle_endtag(self, tag):
20612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if tag == "textarea" and self.curtag is not None:
20622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.map[self.curtag] = self.curdata
20632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.curtag = None
20642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.curdata = None
20652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def handle_charref(self, name):
20662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.handle_data(unichr(int(name)))
20672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def handle_entityref(self, name):
20682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		import htmlentitydefs
20692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if name in htmlentitydefs.entitydefs:
20702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.handle_data(htmlentitydefs.entitydefs[name])
20712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
20722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.handle_data("&" + name + ";")
20732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def handle_data(self, data):
20742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.curdata is not None:
20752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.curdata += data
20762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef JSONGet(ui, path):
20782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
20792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		data = MySend(path, force_auth=False)
20802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(data, str)
20812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		d = fix_json(json.loads(data))
20822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
20832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ui.warn("JSONGet %s: %s\n" % (path, ExceptionDetail()))
20842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None
20852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return d
20862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
20872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Clean up json parser output to match our expectations:
20882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#   * all strings are UTF-8-encoded str, not unicode.
20892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#   * missing fields are missing, not None,
20902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#     so that d.get("foo", defaultvalue) works.
20912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef fix_json(x):
20922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if type(x) in [str, int, float, bool, type(None)]:
20932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pass
20942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	elif type(x) is unicode:
20952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		x = x.encode("utf-8")
20962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	elif type(x) is list:
20972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for i in range(len(x)):
20982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			x[i] = fix_json(x[i])
20992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	elif type(x) is dict:
21002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		todel = []
21012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for k in x:
21022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if x[k] is None:
21032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				todel.append(k)
21042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
21052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				x[k] = fix_json(x[k])
21062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for k in todel:
21072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			del x[k]
21082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
21092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise util.Abort("unknown type " + str(type(x)) + " in fix_json")
21102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if type(x) is str:
21112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		x = x.replace('\r\n', '\n')
21122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return x
21132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
21142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef IsRietveldSubmitted(ui, clname, hex):
21152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	dict = JSONGet(ui, "/api/" + clname + "?messages=true")
21162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if dict is None:
21172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return False
21182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for msg in dict.get("messages", []):
21192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		text = msg.get("text", "")
21202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		m = re.match('\*\*\* Submitted as [^*]*?([0-9a-f]+) \*\*\*', text)
21212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if m is not None and len(m.group(1)) >= 8 and hex.startswith(m.group(1)):
21222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return True
21232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return False
21242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
21252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef IsRietveldMailed(cl):
21262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for msg in cl.dict.get("messages", []):
21272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if msg.get("text", "").find("I'd like you to review this change") >= 0:
21282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return True
21292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return False
21302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
21312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef DownloadCL(ui, repo, clname):
21322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("downloading CL " + clname)
21332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cl, err = LoadCL(ui, repo, clname, web=True)
21342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if err != "":
21352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, None, None, "error loading CL %s: %s" % (clname, err)
21362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
21372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Find most recent diff
21382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	diffs = cl.dict.get("patchsets", [])
21392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not diffs:
21402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, None, None, "CL has no patch sets"
21412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	patchid = diffs[-1]
21422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
21432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	patchset = JSONGet(ui, "/api/" + clname + "/" + str(patchid))
21442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if patchset is None:
21452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, None, None, "error loading CL patchset %s/%d" % (clname, patchid)
21462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if patchset.get("patchset", 0) != patchid:
21472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, None, None, "malformed patchset information"
21482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
21492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	vers = ""
21502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	msg = patchset.get("message", "").split()
21512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if len(msg) >= 3 and msg[0] == "diff" and msg[1] == "-r":
21522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		vers = msg[2]
21532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	diff = "/download/issue" + clname + "_" + str(patchid) + ".diff"
21542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
21552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	diffdata = MySend(diff, force_auth=False)
21562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
21572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Print warning if email is not in CONTRIBUTORS file.
21582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	email = cl.dict.get("owner_email", "")
21592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not email:
21602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return None, None, None, "cannot find owner for %s" % (clname)
21612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	him = FindContributor(ui, repo, email)
21622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	me = FindContributor(ui, repo, None)
21632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if him == me:
21642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.mailed = IsRietveldMailed(cl)
21652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
21662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cl.copied_from = email
21672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
21682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return cl, vers, diffdata, ""
21692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
21702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef MySend(request_path, payload=None,
21712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		content_type="application/octet-stream",
21722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		timeout=None, force_auth=True,
21732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		**kwargs):
21742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Run MySend1 maybe twice, because Rietveld is unreliable."""
21752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
21762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return MySend1(request_path, payload, content_type, timeout, force_auth, **kwargs)
21772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except Exception, e:
21782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if type(e) != urllib2.HTTPError or e.code != 500:	# only retry on HTTP 500 error
21792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise
21802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print >>sys.stderr, "Loading "+request_path+": "+ExceptionDetail()+"; trying again in 2 seconds."
21812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		time.sleep(2)
21822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return MySend1(request_path, payload, content_type, timeout, force_auth, **kwargs)
21832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
21842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Like upload.py Send but only authenticates when the
21852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# redirect is to www.google.com/accounts.  This keeps
21862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# unnecessary redirects from happening during testing.
21872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef MySend1(request_path, payload=None,
21882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				content_type="application/octet-stream",
21892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				timeout=None, force_auth=True,
21902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				**kwargs):
21912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Sends an RPC and returns the response.
21922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
21932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Args:
21942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		request_path: The path to send the request to, eg /api/appversion/create.
21952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		payload: The body of the request, or None to send an empty request.
21962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		content_type: The Content-Type header to use.
21972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		timeout: timeout in seconds; default None i.e. no timeout.
21982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			(Note: for large requests on OS X, the timeout doesn't work right.)
21992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		kwargs: Any keyword arguments are converted into query string parameters.
22002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
22012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Returns:
22022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		The response body, as a string.
22032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
22042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# TODO: Don't require authentication.  Let the server say
22052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# whether it is necessary.
22062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	global rpc
22072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if rpc == None:
22082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		rpc = GetRpcServer(upload_options)
22092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	self = rpc
22102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not self.authenticated and force_auth:
22112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self._Authenticate()
22122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if request_path is None:
22132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
22142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
22152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	old_timeout = socket.getdefaulttimeout()
22162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	socket.setdefaulttimeout(timeout)
22172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
22182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		tries = 0
22192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		while True:
22202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			tries += 1
22212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			args = dict(kwargs)
22222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			url = "http://%s%s" % (self.host, request_path)
22232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if args:
22242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				url += "?" + urllib.urlencode(args)
22252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			req = self._CreateRequest(url=url, data=payload)
22262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			req.add_header("Content-Type", content_type)
22272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			try:
22282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				f = self.opener.open(req)
22292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				response = f.read()
22302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				f.close()
22312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# Translate \r\n into \n, because Rietveld doesn't.
22322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				response = response.replace('\r\n', '\n')
22332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# who knows what urllib will give us
22342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if type(response) == unicode:
22352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					response = response.encode("utf-8")
22362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				typecheck(response, str)
22372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return response
22382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			except urllib2.HTTPError, e:
22392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if tries > 3:
22402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					raise
22412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				elif e.code == 401:
22422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					self._Authenticate()
22432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				elif e.code == 302:
22442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					loc = e.info()["location"]
22452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					if not loc.startswith('https://www.google.com/a') or loc.find('/ServiceLogin') < 0:
22462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						return ''
22472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					self._Authenticate()
22482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				else:
22492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					raise
22502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	finally:
22512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		socket.setdefaulttimeout(old_timeout)
22522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
22532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef GetForm(url):
22542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	f = FormParser()
22552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	f.feed(ustr(MySend(url)))	# f.feed wants unicode
22562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	f.close()
22572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# convert back to utf-8 to restore sanity
22582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	m = {}
22592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for k,v in f.map.items():
22602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		m[k.encode("utf-8")] = v.replace("\r\n", "\n").encode("utf-8")
22612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return m
22622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
22632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef EditDesc(issue, subject=None, desc=None, reviewers=None, cc=None, closed=False, private=False):
22642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("uploading change to description")
22652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	form_fields = GetForm("/" + issue + "/edit")
22662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if subject is not None:
22672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['subject'] = subject
22682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if desc is not None:
22692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['description'] = desc
22702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if reviewers is not None:
22712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['reviewers'] = reviewers
22722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cc is not None:
22732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['cc'] = cc
22742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if closed:
22752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['closed'] = "checked"
22762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if private:
22772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['private'] = "checked"
22782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ctype, body = EncodeMultipartFormData(form_fields.items(), [])
22792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	response = MySend("/" + issue + "/edit", body, content_type=ctype)
22802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if response != "":
22812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print >>sys.stderr, "Error editing description:\n" + "Sent form: \n", form_fields, "\n", response
22822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		sys.exit(2)
22832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
22842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef PostMessage(ui, issue, message, reviewers=None, cc=None, send_mail=True, subject=None):
22852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	set_status("uploading message")
22862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	form_fields = GetForm("/" + issue + "/publish")
22872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if reviewers is not None:
22882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['reviewers'] = reviewers
22892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if cc is not None:
22902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['cc'] = cc
22912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if send_mail:
22922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['send_mail'] = "checked"
22932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
22942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		del form_fields['send_mail']
22952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if subject is not None:
22962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['subject'] = subject
22972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	form_fields['message'] = message
22982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
22992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	form_fields['message_only'] = '1'	# Don't include draft comments
23002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if reviewers is not None or cc is not None:
23012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields['message_only'] = ''	# Must set '' in order to override cc/reviewer
23022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	ctype = "applications/x-www-form-urlencoded"
23032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	body = urllib.urlencode(form_fields)
23042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	response = MySend("/" + issue + "/publish", body, content_type=ctype)
23052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if response != "":
23062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print response
23072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		sys.exit(2)
23082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass opt(object):
23102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	pass
23112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef nocommit(*pats, **opts):
23132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""(disabled when using this extension)"""
23142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	raise util.Abort("codereview extension enabled; use mail, upload, or submit instead of commit")
23152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef nobackout(*pats, **opts):
23172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""(disabled when using this extension)"""
23182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	raise util.Abort("codereview extension enabled; use undo instead of backout")
23192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef norollback(*pats, **opts):
23212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""(disabled when using this extension)"""
23222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	raise util.Abort("codereview extension enabled; use undo instead of rollback")
23232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef RietveldSetup(ui, repo):
23252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	global defaultcc, upload_options, rpc, server, server_url_base, force_google_account, verbosity, contributors
23262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	global missing_codereview
23272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	repo_config_path = ''
23292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Read repository-specific options from lib/codereview/codereview.cfg
23302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
23312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		repo_config_path = repo.root + '/lib/codereview/codereview.cfg'
23322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		f = open(repo_config_path)
23332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for line in f:
23342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if line.startswith('defaultcc: '):
23352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				defaultcc = SplitCommaSpace(line[10:])
23362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
23372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# If there are no options, chances are good this is not
23382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# a code review repository; stop now before we foul
23392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# things up even worse.  Might also be that repo doesn't
23402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# even have a root.  See issue 959.
23412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if repo_config_path == '':
23422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			missing_codereview = 'codereview disabled: repository has no root'
23432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
23442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			missing_codereview = 'codereview disabled: cannot open ' + repo_config_path
23452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return
23462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Should only modify repository with hg submit.
23482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Disable the built-in Mercurial commands that might
23492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# trip things up.
23502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	cmdutil.commit = nocommit
23512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	global real_rollback
23522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	real_rollback = repo.rollback
23532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	repo.rollback = norollback
23542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# would install nobackout if we could; oh well
23552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	try:
23572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		f = open(repo.root + '/CONTRIBUTORS', 'r')
23582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	except:
23592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise util.Abort("cannot open %s: %s" % (repo.root+'/CONTRIBUTORS', ExceptionDetail()))
23602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in f:
23612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# CONTRIBUTORS is a list of lines like:
23622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		#	Person <email>
23632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		#	Person <email> <alt-email>
23642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# The first email address is the one used in commit logs.
23652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line.startswith('#'):
23662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
23672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		m = re.match(r"([^<>]+\S)\s+(<[^<>\s]+>)((\s+<[^<>\s]+>)*)\s*$", line)
23682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if m:
23692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			name = m.group(1)
23702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			email = m.group(2)[1:-1]
23712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			contributors[email.lower()] = (name, email)
23722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for extra in m.group(3).split():
23732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				contributors[extra[1:-1].lower()] = (name, email)
23742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not ui.verbose:
23762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		verbosity = 0
23772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# Config options.
23792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	x = ui.config("codereview", "server")
23802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if x is not None:
23812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		server = x
23822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# TODO(rsc): Take from ui.username?
23842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	email = None
23852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	x = ui.config("codereview", "email")
23862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if x is not None:
23872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		email = x
23882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	server_url_base = "http://" + server + "/"
23902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	testing = ui.config("codereview", "testing")
23922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	force_google_account = ui.configbool("codereview", "force_google_account", False)
23932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
23942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options = opt()
23952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.email = email
23962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.host = None
23972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.verbose = 0
23982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.description = None
23992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.description_file = None
24002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.reviewers = None
24012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.cc = None
24022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.message = None
24032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.issue = None
24042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.download_base = False
24052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.revision = None
24062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.send_mail = False
24072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.vcs = None
24082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.server = server
24092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	upload_options.save_cookies = True
24102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if testing:
24122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		upload_options.save_cookies = False
24132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		upload_options.email = "test@example.com"
24142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	rpc = None
24162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	global releaseBranch
24182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	tags = repo.branchtags().keys()
24192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if 'release-branch.r100' in tags:
24202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# NOTE(rsc): This tags.sort is going to get the wrong
24212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# answer when comparing release-branch.r99 with
24222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# release-branch.r100.  If we do ten releases a year
24232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# that gives us 4 years before we have to worry about this.
24242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise util.Abort('tags.sort needs to be fixed for release-branch.r100')
24252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	tags.sort()
24262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for t in tags:
24272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if t.startswith('release-branch.'):
24282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			releaseBranch = t
24292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#######################################################################
24312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# http://codereview.appspot.com/static/upload.py, heavily edited.
24322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#!/usr/bin/env python
24342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
24352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Copyright 2007 Google Inc.
24362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
24372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Licensed under the Apache License, Version 2.0 (the "License");
24382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# you may not use this file except in compliance with the License.
24392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# You may obtain a copy of the License at
24402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
24412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#	http://www.apache.org/licenses/LICENSE-2.0
24422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#
24432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Unless required by applicable law or agreed to in writing, software
24442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# distributed under the License is distributed on an "AS IS" BASIS,
24452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# See the License for the specific language governing permissions and
24472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# limitations under the License.
24482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""Tool for uploading diffs from a version control system to the codereview app.
24502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonUsage summary: upload.py [options] [-- diff_options]
24522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonDiff options are passed to the diff command of the underlying system.
24542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonSupported version control systems:
24562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Git
24572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Mercurial
24582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Subversion
24592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonIt is important for Git/Mercurial users to specify a tree/node/branch to diff
24612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonagainst by using the '--rev' option.
24622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson"""
24632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# This code is derived from appcfg.py in the App Engine SDK (open source),
24642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# and from ASPN recipe #146306.
24652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport cookielib
24672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport getpass
24682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport logging
24692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport mimetypes
24702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport optparse
24712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport os
24722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport re
24732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport socket
24742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport subprocess
24752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport sys
24762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport urllib
24772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport urllib2
24782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonimport urlparse
24792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# The md5 module was deprecated in Python 2.5.
24812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsontry:
24822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	from hashlib import md5
24832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonexcept ImportError:
24842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	from md5 import md5
24852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsontry:
24872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	import readline
24882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonexcept ImportError:
24892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	pass
24902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# The logging verbosity:
24922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#  0: Errors only.
24932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#  1: Status messages.
24942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#  2: Info logs.
24952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson#  3: Debug logs.
24962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonverbosity = 1
24972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
24982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Max size of patch or base file.
24992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonMAX_UPLOAD_SIZE = 900 * 1024
25002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# whitelist for non-binary filetypes which do not start with "text/"
25022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# .mm (Objective-C) shows up as application/x-freemind on my Linux box.
25032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian HodsonTEXT_MIMETYPES = [
25042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	'application/javascript',
25052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	'application/x-javascript',
25062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	'application/x-freemind'
25072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson]
25082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef GetEmail(prompt):
25102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Prompts the user for their email address and returns it.
25112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	The last used email address is saved to a file and offered up as a suggestion
25132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	to the user. If the user presses enter without typing in anything the last
25142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	used email address is used. If the user enters a new address, it is saved
25152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for next time we prompt.
25162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
25182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	last_email_file_name = os.path.expanduser("~/.last_codereview_email_address")
25192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	last_email = ""
25202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if os.path.exists(last_email_file_name):
25212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
25222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			last_email_file = open(last_email_file_name, "r")
25232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			last_email = last_email_file.readline().strip("\n")
25242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			last_email_file.close()
25252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			prompt += " [%s]" % last_email
25262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except IOError, e:
25272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pass
25282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	email = raw_input(prompt + ": ").strip()
25292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if email:
25302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
25312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			last_email_file = open(last_email_file_name, "w")
25322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			last_email_file.write(email)
25332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			last_email_file.close()
25342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except IOError, e:
25352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pass
25362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
25372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		email = last_email
25382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return email
25392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef StatusUpdate(msg):
25422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Print a status message to stdout.
25432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	If 'verbosity' is greater than 0, print the message.
25452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Args:
25472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		msg: The string to print.
25482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
25492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if verbosity > 0:
25502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print msg
25512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef ErrorExit(msg):
25542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Print an error message to stderr and exit."""
25552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	print >>sys.stderr, msg
25562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	sys.exit(1)
25572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass ClientLoginError(urllib2.HTTPError):
25602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Raised to indicate there was an error authenticating with ClientLogin."""
25612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self, url, code, msg, headers, args):
25632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		urllib2.HTTPError.__init__(self, url, code, msg, headers, None)
25642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.args = args
25652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.reason = args["Error"]
25662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass AbstractRpcServer(object):
25692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Provides a common interface for a simple RPC server."""
25702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self, host, auth_function, host_override=None, extra_headers={}, save_cookies=False):
25722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Creates a new HttpRpcServer.
25732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Args:
25752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			host: The host to send requests to.
25762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			auth_function: A function that takes no arguments and returns an
25772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				(email, password) tuple when called. Will be called if authentication
25782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				is required.
25792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			host_override: The host header to send to the server (defaults to host).
25802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			extra_headers: A dict of extra headers to append to every request.
25812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			save_cookies: If True, save the authentication cookies to local disk.
25822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				If False, use an in-memory cookiejar instead.  Subclasses must
25832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				implement this functionality.  Defaults to False.
25842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
25852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.host = host
25862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.host_override = host_override
25872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.auth_function = auth_function
25882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.authenticated = False
25892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.extra_headers = extra_headers
25902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.save_cookies = save_cookies
25912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.opener = self._GetOpener()
25922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.host_override:
25932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			logging.info("Server: %s; Host: %s", self.host, self.host_override)
25942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
25952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			logging.info("Server: %s", self.host)
25962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
25972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _GetOpener(self):
25982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Returns an OpenerDirector for making HTTP requests.
25992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Returns:
26012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			A urllib2.OpenerDirector object.
26022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
26032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise NotImplementedError()
26042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _CreateRequest(self, url, data=None):
26062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Creates a new urllib request."""
26072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		logging.debug("Creating request for: '%s' with payload:\n%s", url, data)
26082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		req = urllib2.Request(url, data=data)
26092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.host_override:
26102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			req.add_header("Host", self.host_override)
26112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for key, value in self.extra_headers.iteritems():
26122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			req.add_header(key, value)
26132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return req
26142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _GetAuthToken(self, email, password):
26162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Uses ClientLogin to authenticate the user, returning an auth token.
26172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Args:
26192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			email:    The user's email address
26202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			password: The user's password
26212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Raises:
26232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ClientLoginError: If there was an error authenticating with ClientLogin.
26242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			HTTPError: If there was some other form of HTTP error.
26252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Returns:
26272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			The authentication token returned by ClientLogin.
26282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
26292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		account_type = "GOOGLE"
26302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.host.endswith(".google.com") and not force_google_account:
26312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# Needed for use inside Google.
26322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			account_type = "HOSTED"
26332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		req = self._CreateRequest(
26342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				url="https://www.google.com/accounts/ClientLogin",
26352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				data=urllib.urlencode({
26362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"Email": email,
26372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"Passwd": password,
26382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"service": "ah",
26392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"source": "rietveld-codereview-upload",
26402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"accountType": account_type,
26412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				}),
26422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		)
26432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
26442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			response = self.opener.open(req)
26452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			response_body = response.read()
26462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			response_dict = dict(x.split("=") for x in response_body.split("\n") if x)
26472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return response_dict["Auth"]
26482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except urllib2.HTTPError, e:
26492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if e.code == 403:
26502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				body = e.read()
26512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				response_dict = dict(x.split("=", 1) for x in body.split("\n") if x)
26522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				raise ClientLoginError(req.get_full_url(), e.code, e.msg, e.headers, response_dict)
26532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
26542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				raise
26552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _GetAuthCookie(self, auth_token):
26572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Fetches authentication cookies for an authentication token.
26582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Args:
26602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			auth_token: The authentication token returned by ClientLogin.
26612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Raises:
26632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			HTTPError: If there was an error fetching the authentication cookies.
26642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
26652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# This is a dummy value to allow us to identify when we're successful.
26662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		continue_location = "http://localhost/"
26672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		args = {"continue": continue_location, "auth": auth_token}
26682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		req = self._CreateRequest("http://%s/_ah/login?%s" % (self.host, urllib.urlencode(args)))
26692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
26702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			response = self.opener.open(req)
26712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		except urllib2.HTTPError, e:
26722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			response = e
26732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if (response.code != 302 or
26742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				response.info()["location"] != continue_location):
26752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg, response.headers, response.fp)
26762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.authenticated = True
26772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _Authenticate(self):
26792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Authenticates the user.
26802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		The authentication process works as follows:
26822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		1) We get a username and password from the user
26832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		2) We use ClientLogin to obtain an AUTH token for the user
26842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				(see http://code.google.com/apis/accounts/AuthForInstalledApps.html).
26852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		3) We pass the auth token to /_ah/login on the server to obtain an
26862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				authentication cookie. If login was successful, it tries to redirect
26872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				us to the URL we provided.
26882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
26892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		If we attempt to access the upload API without first obtaining an
26902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		authentication cookie, it returns a 401 response (or a 302) and
26912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		directs us to authenticate ourselves with ClientLogin.
26922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
26932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for i in range(3):
26942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			credentials = self.auth_function()
26952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			try:
26962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				auth_token = self._GetAuthToken(credentials[0], credentials[1])
26972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			except ClientLoginError, e:
26982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "BadAuthentication":
26992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "Invalid username or password."
27002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					continue
27012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "CaptchaRequired":
27022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, (
27032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"Please go to\n"
27042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"https://www.google.com/accounts/DisplayUnlockCaptcha\n"
27052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						"and verify you are a human.  Then try again.")
27062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
27072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "NotVerified":
27082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "Account not verified."
27092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
27102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "TermsNotAgreed":
27112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "User has not agreed to TOS."
27122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
27132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "AccountDeleted":
27142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "The user account has been deleted."
27152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
27162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "AccountDisabled":
27172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "The user account has been disabled."
27182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
27192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "ServiceDisabled":
27202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "The user's access to the service has been disabled."
27212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
27222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if e.reason == "ServiceUnavailable":
27232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					print >>sys.stderr, "The service is not available; try again later."
27242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					break
27252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				raise
27262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self._GetAuthCookie(auth_token)
27272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return
27282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def Send(self, request_path, payload=None,
27302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					content_type="application/octet-stream",
27312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					timeout=None,
27322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					**kwargs):
27332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Sends an RPC and returns the response.
27342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Args:
27362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			request_path: The path to send the request to, eg /api/appversion/create.
27372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			payload: The body of the request, or None to send an empty request.
27382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			content_type: The Content-Type header to use.
27392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			timeout: timeout in seconds; default None i.e. no timeout.
27402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				(Note: for large requests on OS X, the timeout doesn't work right.)
27412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			kwargs: Any keyword arguments are converted into query string parameters.
27422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Returns:
27442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			The response body, as a string.
27452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
27462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# TODO: Don't require authentication.  Let the server say
27472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# whether it is necessary.
27482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not self.authenticated:
27492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self._Authenticate()
27502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		old_timeout = socket.getdefaulttimeout()
27522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		socket.setdefaulttimeout(timeout)
27532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		try:
27542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			tries = 0
27552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			while True:
27562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				tries += 1
27572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				args = dict(kwargs)
27582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				url = "http://%s%s" % (self.host, request_path)
27592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if args:
27602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					url += "?" + urllib.urlencode(args)
27612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				req = self._CreateRequest(url=url, data=payload)
27622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				req.add_header("Content-Type", content_type)
27632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				try:
27642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					f = self.opener.open(req)
27652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					response = f.read()
27662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					f.close()
27672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					return response
27682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				except urllib2.HTTPError, e:
27692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					if tries > 3:
27702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						raise
27712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					elif e.code == 401 or e.code == 302:
27722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						self._Authenticate()
27732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					else:
27742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson						raise
27752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		finally:
27762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			socket.setdefaulttimeout(old_timeout)
27772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass HttpRpcServer(AbstractRpcServer):
27802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Provides a simplified RPC-style interface for HTTP requests."""
27812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _Authenticate(self):
27832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Save the cookie jar after authentication."""
27842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		super(HttpRpcServer, self)._Authenticate()
27852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.save_cookies:
27862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			StatusUpdate("Saving authentication cookies to %s" % self.cookie_file)
27872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.cookie_jar.save()
27882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _GetOpener(self):
27902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Returns an OpenerDirector that supports cookies and ignores redirects.
27912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
27922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Returns:
27932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			A urllib2.OpenerDirector object.
27942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
27952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener = urllib2.OpenerDirector()
27962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.ProxyHandler())
27972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.UnknownHandler())
27982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.HTTPHandler())
27992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.HTTPDefaultErrorHandler())
28002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.HTTPSHandler())
28012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.HTTPErrorProcessor())
28022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.save_cookies:
28032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.cookie_file = os.path.expanduser("~/.codereview_upload_cookies_" + server)
28042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.cookie_jar = cookielib.MozillaCookieJar(self.cookie_file)
28052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if os.path.exists(self.cookie_file):
28062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				try:
28072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					self.cookie_jar.load()
28082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					self.authenticated = True
28092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					StatusUpdate("Loaded authentication cookies from %s" % self.cookie_file)
28102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				except (cookielib.LoadError, IOError):
28112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					# Failed to load cookies - just ignore them.
28122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					pass
28132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
28142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# Create an empty cookie file with mode 600
28152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				fd = os.open(self.cookie_file, os.O_CREAT, 0600)
28162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				os.close(fd)
28172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# Always chmod the cookie file
28182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			os.chmod(self.cookie_file, 0600)
28192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
28202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# Don't save cookies across runs of update.py.
28212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.cookie_jar = cookielib.CookieJar()
28222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar))
28232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return opener
28242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef GetRpcServer(options):
28272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Returns an instance of an AbstractRpcServer.
28282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Returns:
28302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		A new AbstractRpcServer, on which RPC calls can be made.
28312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
28322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	rpc_server_class = HttpRpcServer
28342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GetUserCredentials():
28362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Prompts the user for a username and password."""
28372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Disable status prints so they don't obscure the password prompt.
28382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		global global_status
28392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		st = global_status
28402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		global_status = None
28412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		email = options.email
28432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if email is None:
28442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			email = GetEmail("Email (login for uploading to %s)" % options.server)
28452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		password = getpass.getpass("Password for %s: " % email)
28462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Put status back.
28482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		global_status = st
28492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return (email, password)
28502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	# If this is the dev_appserver, use fake authentication.
28522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	host = (options.host or options.server).lower()
28532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if host == "localhost" or host.startswith("localhost:"):
28542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		email = options.email
28552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if email is None:
28562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			email = "test@example.com"
28572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			logging.info("Using debug user %s.  Override with --email" % email)
28582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		server = rpc_server_class(
28592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				options.server,
28602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				lambda: (email, "password"),
28612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				host_override=options.host,
28622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				extra_headers={"Cookie": 'dev_appserver_login="%s:False"' % email},
28632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				save_cookies=options.save_cookies)
28642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Don't try to talk to ClientLogin.
28652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		server.authenticated = True
28662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return server
28672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return rpc_server_class(options.server, GetUserCredentials,
28692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		host_override=options.host, save_cookies=options.save_cookies)
28702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef EncodeMultipartFormData(fields, files):
28732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Encode form fields for multipart/form-data.
28742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Args:
28762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		fields: A sequence of (name, value) elements for regular form fields.
28772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		files: A sequence of (name, filename, value) elements for data to be
28782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					uploaded as files.
28792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Returns:
28802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		(content_type, body) ready for httplib.HTTP instance.
28812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
28822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Source:
28832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306
28842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
28852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	BOUNDARY = '-M-A-G-I-C---B-O-U-N-D-A-R-Y-'
28862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	CRLF = '\r\n'
28872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	lines = []
28882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for (key, value) in fields:
28892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(key, str)
28902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(value, str)
28912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('--' + BOUNDARY)
28922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('Content-Disposition: form-data; name="%s"' % key)
28932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('')
28942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append(value)
28952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for (key, filename, value) in files:
28962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(key, str)
28972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(filename, str)
28982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		typecheck(value, str)
28992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('--' + BOUNDARY)
29002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
29012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('Content-Type: %s' % GetContentType(filename))
29022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append('')
29032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines.append(value)
29042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	lines.append('--' + BOUNDARY + '--')
29052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	lines.append('')
29062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	body = CRLF.join(lines)
29072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
29082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return content_type, body
29092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef GetContentType(filename):
29122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Helper to guess the content-type from the filename."""
29132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
29142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# Use a shell for subcommands on Windows to get a PATH search.
29172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonuse_shell = sys.platform.startswith("win")
29182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef RunShellWithReturnCode(command, print_output=False,
29202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		universal_newlines=True, env=os.environ):
29212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Executes a command and returns the output from stdout and the return code.
29222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Args:
29242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		command: Command to execute.
29252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print_output: If True, the output is printed to stdout.
29262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			If False, both stdout and stderr are ignored.
29272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		universal_newlines: Use universal_newlines flag (default: True).
29282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Returns:
29302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Tuple (output, return code)
29312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
29322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	logging.info("Running %s", command)
29332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
29342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		shell=use_shell, universal_newlines=universal_newlines, env=env)
29352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if print_output:
29362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		output_array = []
29372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		while True:
29382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			line = p.stdout.readline()
29392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not line:
29402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				break
29412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			print line.strip("\n")
29422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			output_array.append(line)
29432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		output = "".join(output_array)
29442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	else:
29452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		output = p.stdout.read()
29462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	p.wait()
29472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	errout = p.stderr.read()
29482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if print_output and errout:
29492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print >>sys.stderr, errout
29502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	p.stdout.close()
29512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	p.stderr.close()
29522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return output, p.returncode
29532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef RunShell(command, silent_ok=False, universal_newlines=True,
29562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print_output=False, env=os.environ):
29572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	data, retcode = RunShellWithReturnCode(command, print_output, universal_newlines, env)
29582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if retcode:
29592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ErrorExit("Got error status from %s:\n%s" % (command, data))
29602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if not silent_ok and not data:
29612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ErrorExit("No output from %s" % command)
29622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return data
29632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass VersionControlSystem(object):
29662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Abstract base class providing an interface to the VCS."""
29672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self, options):
29692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Constructor.
29702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Args:
29722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			options: Command line options.
29732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
29742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.options = options
29752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GenerateDiff(self, args):
29772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Return the current diff as a string.
29782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Args:
29802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			args: Extra arguments to pass to the diff command.
29812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
29822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise NotImplementedError(
29832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				"abstract method -- subclass %s must override" % self.__class__)
29842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GetUnknownFiles(self):
29862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Return a list of files unknown to the VCS."""
29872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise NotImplementedError(
29882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				"abstract method -- subclass %s must override" % self.__class__)
29892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
29902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def CheckForUnknownFiles(self):
29912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Show an "are you sure?" prompt if there are unknown files."""
29922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		unknown_files = self.GetUnknownFiles()
29932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if unknown_files:
29942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			print "The following files are not added to version control:"
29952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for line in unknown_files:
29962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				print line
29972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			prompt = "Are you sure to continue?(y/N) "
29982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			answer = raw_input(prompt).strip()
29992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if answer != "y":
30002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ErrorExit("User aborted")
30012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GetBaseFile(self, filename):
30032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Get the content of the upstream version of a file.
30042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Returns:
30062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			A tuple (base_content, new_content, is_binary, status)
30072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				base_content: The contents of the base file.
30082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				new_content: For text files, this is empty.  For binary files, this is
30092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					the contents of the new file, since the diff output won't contain
30102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					information to reconstruct the current file.
30112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				is_binary: True iff the file is binary.
30122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				status: The status of the file.
30132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
30142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise NotImplementedError(
30162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				"abstract method -- subclass %s must override" % self.__class__)
30172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GetBaseFiles(self, diff):
30202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Helper that calls GetBase file for each file in the patch.
30212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		Returns:
30232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			A dictionary that maps from filename to GetBaseFile's tuple.  Filenames
30242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			are retrieved based on lines that start with "Index:" or
30252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			"Property changes on:".
30262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""
30272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		files = {}
30282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for line in diff.splitlines(True):
30292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if line.startswith('Index:') or line.startswith('Property changes on:'):
30302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				unused, filename = line.split(':', 1)
30312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# On Windows if a file has property changes its filename uses '\'
30322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# instead of '/'.
30332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				filename = filename.strip().replace('\\', '/')
30342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				files[filename] = self.GetBaseFile(filename)
30352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return files
30362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def UploadBaseFiles(self, issue, rpc_server, patch_list, patchset, options,
30392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson											files):
30402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Uploads the base files (and if necessary, the current ones as well)."""
30412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		def UploadFile(filename, file_id, content, is_binary, status, is_base):
30432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			"""Uploads a file to the server."""
30442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			set_status("uploading " + filename)
30452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			file_too_large = False
30462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if is_base:
30472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				type = "base"
30482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
30492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				type = "current"
30502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if len(content) > MAX_UPLOAD_SIZE:
30512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				print ("Not uploading the %s file for %s because it's too large." %
30522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson							(type, filename))
30532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				file_too_large = True
30542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				content = ""
30552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			checksum = md5(content).hexdigest()
30562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if options.verbose > 0 and not file_too_large:
30572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				print "Uploading %s file for %s" % (type, filename)
30582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			url = "/%d/upload_content/%d/%d" % (int(issue), int(patchset), file_id)
30592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			form_fields = [
30602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				("filename", filename),
30612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				("status", status),
30622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				("checksum", checksum),
30632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				("is_binary", str(is_binary)),
30642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				("is_current", str(not is_base)),
30652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			]
30662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if file_too_large:
30672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				form_fields.append(("file_too_large", "1"))
30682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if options.email:
30692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				form_fields.append(("user", options.email))
30702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ctype, body = EncodeMultipartFormData(form_fields, [("data", filename, content)])
30712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			response_body = rpc_server.Send(url, body, content_type=ctype)
30722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not response_body.startswith("OK"):
30732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				StatusUpdate("  --> %s" % response_body)
30742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				sys.exit(1)
30752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Don't want to spawn too many threads, nor do we want to
30772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# hit Rietveld too hard, or it will start serving 500 errors.
30782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# When 8 works, it's no better than 4, and sometimes 8 is
30792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# too many for Rietveld to handle.
30802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		MAX_PARALLEL_UPLOADS = 4
30812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		sema = threading.BoundedSemaphore(MAX_PARALLEL_UPLOADS)
30832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		upload_threads = []
30842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		finished_upload_threads = []
30852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		class UploadFileThread(threading.Thread):
30872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			def __init__(self, args):
30882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				threading.Thread.__init__(self)
30892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				self.args = args
30902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			def run(self):
30912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				UploadFile(*self.args)
30922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				finished_upload_threads.append(self)
30932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				sema.release()
30942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
30952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		def StartUploadFile(*args):
30962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			sema.acquire()
30972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			while len(finished_upload_threads) > 0:
30982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				t = finished_upload_threads.pop()
30992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				upload_threads.remove(t)
31002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				t.join()
31012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			t = UploadFileThread(args)
31022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			upload_threads.append(t)
31032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			t.start()
31042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		def WaitForUploads():
31062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			for t in upload_threads:
31072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				t.join()
31082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		patches = dict()
31102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		[patches.setdefault(v, k) for k, v in patch_list]
31112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for filename in patches.keys():
31122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			base_content, new_content, is_binary, status = files[filename]
31132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			file_id_str = patches.get(filename)
31142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if file_id_str.find("nobase") != -1:
31152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				base_content = None
31162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				file_id_str = file_id_str[file_id_str.rfind("_") + 1:]
31172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			file_id = int(file_id_str)
31182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if base_content != None:
31192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				StartUploadFile(filename, file_id, base_content, is_binary, status, True)
31202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if new_content != None:
31212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				StartUploadFile(filename, file_id, new_content, is_binary, status, False)
31222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		WaitForUploads()
31232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def IsImage(self, filename):
31252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Returns true if the filename has an image extension."""
31262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		mimetype =  mimetypes.guess_type(filename)[0]
31272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not mimetype:
31282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return False
31292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return mimetype.startswith("image/")
31302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def IsBinary(self, filename):
31322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Returns true if the guessed mimetyped isnt't in text group."""
31332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		mimetype = mimetypes.guess_type(filename)[0]
31342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not mimetype:
31352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return False  # e.g. README, "real" binaries usually have an extension
31362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# special case for text files which don't start with text/
31372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if mimetype in TEXT_MIMETYPES:
31382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			return False
31392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return not mimetype.startswith("text/")
31402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass FakeMercurialUI(object):
31432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self):
31442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.quiet = True
31452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.output = ''
31462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def write(self, *args, **opts):
31482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.output += ' '.join(args)
31492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def copy(self):
31502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return self
31512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def status(self, *args, **opts):
31522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pass
31532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def readconfig(self, *args, **opts):
31552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		pass
31562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def expandpath(self, *args, **opts):
31572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return global_ui.expandpath(*args, **opts)
31582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def configitems(self, *args, **opts):
31592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return global_ui.configitems(*args, **opts)
31602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def config(self, *args, **opts):
31612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return global_ui.config(*args, **opts)
31622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonuse_hg_shell = False	# set to True to shell out to hg always; slower
31642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsonclass MercurialVCS(VersionControlSystem):
31662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Implementation of the VersionControlSystem interface for Mercurial."""
31672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def __init__(self, options, ui, repo):
31692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		super(MercurialVCS, self).__init__(options)
31702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.ui = ui
31712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.repo = repo
31722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.status = None
31732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Absolute path to repository (we can be in a subdir)
31742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.repo_dir = os.path.normpath(repo.root)
31752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Compute the subdir
31762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cwd = os.path.normpath(os.getcwd())
31772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		assert cwd.startswith(self.repo_dir)
31782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		self.subdir = cwd[len(self.repo_dir):].lstrip(r"\/")
31792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.options.revision:
31802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.base_rev = self.options.revision
31812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
31822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			mqparent, err = RunShellWithReturnCode(['hg', 'log', '--rev', 'qparent', '--template={node}'])
31832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if not err and mqparent != "":
31842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				self.base_rev = mqparent
31852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
31862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				self.base_rev = RunShell(["hg", "parents", "-q"]).split(':')[1].strip()
31872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def _GetRelPath(self, filename):
31882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Get relative path of a file according to the current directory,
31892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		given its logical path in the repo."""
31902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		assert filename.startswith(self.subdir), (filename, self.subdir)
31912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return filename[len(self.subdir):].lstrip(r"\/")
31922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
31932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GenerateDiff(self, extra_args):
31942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# If no file specified, restrict to the current subdir
31952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		extra_args = extra_args or ["."]
31962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		cmd = ["hg", "diff", "--git", "-r", self.base_rev] + extra_args
31972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		data = RunShell(cmd, silent_ok=True)
31982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		svndiff = []
31992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		filecount = 0
32002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for line in data.splitlines():
32012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			m = re.match("diff --git a/(\S+) b/(\S+)", line)
32022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if m:
32032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# Modify line to make it look like as it comes from svn diff.
32042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# With this modification no changes on the server side are required
32052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# to make upload.py work with Mercurial repos.
32062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# NOTE: for proper handling of moved/copied files, we have to use
32072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# the second filename.
32082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				filename = m.group(2)
32092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				svndiff.append("Index: %s" % filename)
32102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				svndiff.append("=" * 67)
32112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				filecount += 1
32122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				logging.info(line)
32132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
32142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				svndiff.append(line)
32152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not filecount:
32162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			ErrorExit("No valid patches found in output from hg diff")
32172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return "\n".join(svndiff) + "\n"
32182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GetUnknownFiles(self):
32202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		"""Return a list of files unknown to the VCS."""
32212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		args = []
32222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		status = RunShell(["hg", "status", "--rev", self.base_rev, "-u", "."],
32232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				silent_ok=True)
32242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		unknown_files = []
32252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for line in status.splitlines():
32262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			st, fn = line.split(" ", 1)
32272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if st == "?":
32282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				unknown_files.append(fn)
32292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return unknown_files
32302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def get_hg_status(self, rev, path):
32322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# We'd like to use 'hg status -C path', but that is buggy
32332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# (see http://mercurial.selenic.com/bts/issue3023).
32342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# Instead, run 'hg status -C' without a path
32352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# and skim the output for the path we want.
32362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if self.status is None:
32372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if use_hg_shell:
32382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				out = RunShell(["hg", "status", "-C", "--rev", rev])
32392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
32402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				fui = FakeMercurialUI()
32412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				ret = commands.status(fui, self.repo, *[], **{'rev': [rev], 'copies': True})
32422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if ret:
32432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					raise util.Abort(ret)
32442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				out = fui.output
32452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			self.status = out.splitlines()
32462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		for i in range(len(self.status)):
32472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# line is
32482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			#	A path
32492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			#	M path
32502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# etc
32512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			line = self.status[i].replace('\\', '/')
32522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if line[2:] == path:
32532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				if i+1 < len(self.status) and self.status[i+1][:2] == '  ':
32542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson					return self.status[i:i+2]
32552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				return self.status[i:i+1]
32562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		raise util.Abort("no status for " + path)
32572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	def GetBaseFile(self, filename):
32592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status("inspecting " + filename)
32602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# "hg status" and "hg cat" both take a path relative to the current subdir
32612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# rather than to the repo root, but "hg diff" has given us the full path
32622ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		# to the repo root.
32632ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		base_content = ""
32642ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		new_content = None
32652ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		is_binary = False
32662ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		oldrelpath = relpath = self._GetRelPath(filename)
32672ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		out = self.get_hg_status(self.base_rev, relpath)
32682ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		status, what = out[0].split(' ', 1)
32692ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if len(out) > 1 and status == "A" and what == relpath:
32702ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			oldrelpath = out[1].strip()
32712ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			status = "M"
32722ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if ":" in self.base_rev:
32732ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			base_rev = self.base_rev.split(":", 1)[0]
32742ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		else:
32752ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			base_rev = self.base_rev
32762ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if status != "A":
32772ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if use_hg_shell:
32782ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath], silent_ok=True)
32792ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			else:
32802ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				base_content = str(self.repo[base_rev][oldrelpath].data())
32812ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			is_binary = "\0" in base_content  # Mercurial's heuristic
32822ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if status != "R":
32832ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			new_content = open(relpath, "rb").read()
32842ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			is_binary = is_binary or "\0" in new_content
32852ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if is_binary and base_content and use_hg_shell:
32862ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# Fetch again without converting newlines
32872ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath],
32882ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				silent_ok=True, universal_newlines=False)
32892ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not is_binary or not self.IsImage(relpath):
32902ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			new_content = None
32912ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		return base_content, new_content, is_binary, status
32922ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32932ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32942ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson# NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync.
32952ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef SplitPatch(data):
32962ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Splits a patch into separate pieces for each file.
32972ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
32982ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Args:
32992ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		data: A string containing the output of svn diff.
33002ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33012ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Returns:
33022ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		A list of 2-tuple (filename, text) where text is the svn diff output
33032ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			pertaining to filename.
33042ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
33052ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	patches = []
33062ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	filename = None
33072ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	diff = []
33082ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for line in data.splitlines(True):
33092ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		new_filename = None
33102ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if line.startswith('Index:'):
33112ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			unused, new_filename = line.split(':', 1)
33122ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			new_filename = new_filename.strip()
33132ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		elif line.startswith('Property changes on:'):
33142ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			unused, temp_filename = line.split(':', 1)
33152ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# When a file is modified, paths use '/' between directories, however
33162ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# when a property is modified '\' is used on Windows.  Make them the same
33172ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			# otherwise the file shows up twice.
33182ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			temp_filename = temp_filename.strip().replace('\\', '/')
33192ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if temp_filename != filename:
33202ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				# File has property changes but no modifications, create a new diff.
33212ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				new_filename = temp_filename
33222ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if new_filename:
33232ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			if filename and diff:
33242ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				patches.append((filename, ''.join(diff)))
33252ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			filename = new_filename
33262ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			diff = [line]
33272ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
33282ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if diff is not None:
33292ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			diff.append(line)
33302ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	if filename and diff:
33312ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		patches.append((filename, ''.join(diff)))
33322ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return patches
33332ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33342ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33352ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodsondef UploadSeparatePatches(issue, rpc_server, patchset, data, options):
33362ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""Uploads a separate patch for each file in the diff output.
33372ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson
33382ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	Returns a list of [patch_key, filename] for each file.
33392ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	"""
33402ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	patches = SplitPatch(data)
33412ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	rv = []
33422ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	for patch in patches:
33432ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		set_status("uploading patch for " + patch[0])
33442ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if len(patch[1]) > MAX_UPLOAD_SIZE:
33452ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			print ("Not uploading the patch for " + patch[0] +
33462ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson				" because the file is too large.")
33472ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			continue
33482ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		form_fields = [("filename", patch[0])]
33492ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not options.download_base:
33502ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			form_fields.append(("content_upload", "1"))
33512ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		files = [("data", "data.diff", patch[1])]
33522ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		ctype, body = EncodeMultipartFormData(form_fields, files)
33532ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		url = "/%d/upload_patch/%d" % (int(issue), int(patchset))
33542ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		print "Uploading patch for " + patch[0]
33552ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		response_body = rpc_server.Send(url, body, content_type=ctype)
33562ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		lines = response_body.splitlines()
33572ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		if not lines or lines[0] != "OK":
33582ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			StatusUpdate("  --> %s" % response_body)
33592ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson			sys.exit(1)
33602ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson		rv.append([lines[1], patch[0]])
33612ee91b4af4353b9e6a9d591c32fedfc58fd4ef35Ian Hodson	return rv
3362