15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# coding=utf-8
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# (The line above is necessary so that I can use 世界 in the
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# *comment* below without Python getting all bent out of shape.)
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright 2007-2009 Google Inc.
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Licensed under the Apache License, Version 2.0 (the "License");
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# you may not use this file except in compliance with the License.
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# You may obtain a copy of the License at
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#	http://www.apache.org/licenses/LICENSE-2.0
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Unless required by applicable law or agreed to in writing, software
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# distributed under the License is distributed on an "AS IS" BASIS,
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# See the License for the specific language governing permissions and
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# limitations under the License.
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)'''Mercurial interface to codereview.appspot.com.
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)To configure, set the following options in
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)your repository's .hg/hgrc file.
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	[extensions]
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	codereview = /path/to/codereview.py
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	[codereview]
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	server = codereview.appspot.com
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The server should be running Rietveld; see http://code.google.com/p/rietveld/.
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)In addition to the new commands, this extension introduces
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)the file pattern syntax @nnnnnn, where nnnnnn is a change list
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)number, to mean the files included in that change list, which
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)must be associated with the current client.
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)For example, if change 123456 contains the files x.go and y.go,
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"hg diff @123456" is equivalent to"hg diff x.go y.go".
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)'''
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == "__main__":
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	print >>sys.stderr, "This is a Mercurial extension and should not be invoked directly."
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	sys.exit(2)
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# We require Python 2.6 for the json package.
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if sys.version < '2.6':
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	print >>sys.stderr, "The codereview extension requires Python 2.6 or newer."
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	print >>sys.stderr, "You are running Python " + sys.version
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	sys.exit(2)
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import json
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import re
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import stat
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import subprocess
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import threading
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from mercurial import commands as hg_commands
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from mercurial import util as hg_util
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)defaultcc = None
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)codereview_disabled = None
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)real_rollback = None
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)releaseBranch = None
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)server = "codereview.appspot.com"
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)server_url_base = None
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Normally I would split this into multiple files, but it simplifies
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# import path headaches to keep it all in one file.  Sorry.
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The different parts of the file are separated by banners like this one.
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Helpers
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def RelativePath(path, cwd):
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	n = len(cwd)
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if path.startswith(cwd) and path[n] == '/':
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return path[n+1:]
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return path
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def Sub(l1, l2):
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return [l for l in l1 if l not in l2]
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def Add(l1, l2):
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	l = l1 + Sub(l2, l1)
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	l.sort()
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return l
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def Intersect(l1, l2):
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return [l for l in l1 if l in l2]
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# RE: UNICODE STRING HANDLING
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Python distinguishes between the str (string of bytes)
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# and unicode (string of code points) types.  Most operations
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# work on either one just fine, but some (like regexp matching)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# require unicode, and others (like write) require str.
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# As befits the language, Python hides the distinction between
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# unicode and str by converting between them silently, but
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# *only* if all the bytes/code points involved are 7-bit ASCII.
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# This means that if you're not careful, your program works
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# fine on "hello, world" and fails on "hello, 世界".  And of course,
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# the obvious way to be careful - use static types - is unavailable.
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# So the only way is trial and error to find where to put explicit
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# conversions.
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Because more functions do implicit conversion to str (string of bytes)
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# than do implicit conversion to unicode (string of code points),
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# the convention in this module is to represent all text as str,
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# converting to unicode only when calling a unicode-only function
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# and then converting back to str as soon as possible.
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def typecheck(s, t):
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if type(s) != t:
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise hg_util.Abort("type check failed: %s has type %s != %s" % (repr(s), type(s), t))
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# If we have to pass unicode instead of str, ustr does that conversion clearly.
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def ustr(s):
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(s, str)
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return s.decode("utf-8")
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Even with those, Mercurial still sometimes turns unicode into str
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# and then tries to use it as ascii.  Change Mercurial's default.
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def set_mercurial_encoding_to_utf8():
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	from mercurial import encoding
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	encoding.encoding = 'utf-8'
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)set_mercurial_encoding_to_utf8()
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Even with those we still run into problems.
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# I tried to do things by the book but could not convince
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Mercurial to let me check in a change with UTF-8 in the
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# CL description or author field, no matter how many conversions
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# between str and unicode I inserted and despite changing the
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# default encoding.  I'm tired of this game, so set the default
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# encoding for all of Python to 'utf-8', not 'ascii'.
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def default_to_utf8():
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	import sys
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	stdout, __stdout__ = sys.stdout, sys.__stdout__
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	reload(sys)  # site.py deleted setdefaultencoding; get it back
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	sys.stdout, sys.__stdout__ = stdout, __stdout__
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	sys.setdefaultencoding('utf-8')
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)default_to_utf8()
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Status printer for long-running commands
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)global_status = None
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def set_status(s):
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# print >>sys.stderr, "\t", time.asctime(), s
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global global_status
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global_status = s
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class StatusThread(threading.Thread):
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def __init__(self):
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		threading.Thread.__init__(self)
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def run(self):
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# pause a reasonable amount of time before
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# starting to display status messages, so that
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# most hg commands won't ever see them.
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		time.sleep(30)
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# now show status every 15 seconds
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		while True:
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			time.sleep(15 - time.time() % 15)
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s = global_status
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if s is None:
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				continue
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if s == "":
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				s = "(unknown status)"
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			print >>sys.stderr, time.asctime(), s
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def start_status_thread():
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	t = StatusThread()
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	t.setDaemon(True)  # allowed to exit if t is still running
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	t.start()
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Change list parsing.
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Change lists are stored in .hg/codereview/cl.nnnnnn
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# where nnnnnn is the number assigned by the code review server.
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Most data about a change list is stored on the code review server
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# too: the description, reviewer, and cc list are all stored there.
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The only thing in the cl.nnnnnn file is the list of relevant files.
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Also, the existence of the cl.nnnnnn file marks this repository
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# as the one where the change list lives.
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)emptydiff = """Index: ~rietveld~placeholder~
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)===================================================================
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)diff --git a/~rietveld~placeholder~ b/~rietveld~placeholder~
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)new file mode 100644
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class CL(object):
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def __init__(self, name):
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		typecheck(name, str)
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.name = name
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.desc = ''
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.files = []
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.reviewer = []
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.cc = []
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.url = ''
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.local = False
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.web = False
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.copied_from = None	# None means current user
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.mailed = False
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.private = False
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.lgtm = []
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def DiskText(self):
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl = self
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s = ""
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if cl.copied_from:
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += "Author: " + cl.copied_from + "\n\n"
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if cl.private:
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += "Private: " + str(self.private) + "\n"
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += "Mailed: " + str(self.mailed) + "\n"
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += "Description:\n"
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += Indent(cl.desc, "\t")
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += "Files:\n"
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for f in cl.files:
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += "\t" + f + "\n"
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		typecheck(s, str)
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return s
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def EditorText(self):
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl = self
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s = _change_prolog
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += "\n"
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if cl.copied_from:
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += "Author: " + cl.copied_from + "\n"
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if cl.url != '':
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += 'URL: ' + cl.url + '	# cannot edit\n\n'
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if cl.private:
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += "Private: True\n"
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += "Reviewer: " + JoinComma(cl.reviewer) + "\n"
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += "CC: " + JoinComma(cl.cc) + "\n"
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += "\n"
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += "Description:\n"
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if cl.desc == '':
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += "\t<enter description here>\n"
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += Indent(cl.desc, "\t")
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += "\n"
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if cl.local or cl.name == "new":
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += "Files:\n"
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for f in cl.files:
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				s += "\t" + f + "\n"
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += "\n"
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		typecheck(s, str)
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return s
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def PendingText(self, quick=False):
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl = self
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s = cl.name + ":" + "\n"
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += Indent(cl.desc, "\t")
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += "\n"
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if cl.copied_from:
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += "\tAuthor: " + cl.copied_from + "\n"
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not quick:
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += "\tReviewer: " + JoinComma(cl.reviewer) + "\n"
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for (who, line) in cl.lgtm:
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				s += "\t\t" + who + ": " + line + "\n"
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += "\tCC: " + JoinComma(cl.cc) + "\n"
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += "\tFiles:\n"
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for f in cl.files:
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += "\t\t" + f + "\n"
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		typecheck(s, str)
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return s
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def Flush(self, ui, repo):
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.name == "new":
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.Upload(ui, repo, gofmt_just_warn=True, creating=True)
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		dir = CodeReviewDir(ui, repo)
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		path = dir + '/cl.' + self.name
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		f = open(path+'!', "w")
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		f.write(self.DiskText())
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		f.close()
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if sys.platform == "win32" and os.path.isfile(path):
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			os.remove(path)
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		os.rename(path+'!', path)
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.web and not self.copied_from:
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			EditDesc(self.name, desc=self.desc,
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				reviewers=JoinComma(self.reviewer), cc=JoinComma(self.cc),
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				private=self.private)
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def Delete(self, ui, repo):
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		dir = CodeReviewDir(ui, repo)
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		os.unlink(dir + "/cl." + self.name)
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def Subject(self):
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s = line1(self.desc)
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if len(s) > 60:
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s = s[0:55] + "..."
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.name != "new":
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s = "code review %s: %s" % (self.name, s)
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		typecheck(s, str)
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return s
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def Upload(self, ui, repo, send_mail=False, gofmt=True, gofmt_just_warn=False, creating=False, quiet=False):
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not self.files and not creating:
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.warn("no files in change list\n")
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if ui.configbool("codereview", "force_gofmt", True) and gofmt:
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			CheckFormat(ui, repo, self.files, just_warn=gofmt_just_warn)
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		set_status("uploading CL metadata + diffs")
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		os.chdir(repo.root)
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		form_fields = [
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			("content_upload", "1"),
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			("reviewers", JoinComma(self.reviewer)),
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			("cc", JoinComma(self.cc)),
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			("description", self.desc),
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			("base_hashes", ""),
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		]
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.name != "new":
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			form_fields.append(("issue", self.name))
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		vcs = None
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# We do not include files when creating the issue,
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# because we want the patch sets to record the repository
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# and base revision they are diffs against.  We use the patch
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# set message for that purpose, but there is no message with
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# the first patch set.  Instead the message gets used as the
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# new CL's overall subject.  So omit the diffs when creating
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# and then we'll run an immediate upload.
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# This has the effect that every CL begins with an empty "Patch set 1".
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.files and not creating:
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			vcs = MercurialVCS(upload_options, ui, repo)
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			data = vcs.GenerateDiff(self.files)
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			files = vcs.GetBaseFiles(data)
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if len(data) > MAX_UPLOAD_SIZE:
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				uploaded_diff_file = []
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				form_fields.append(("separate_patches", "1"))
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			else:
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				uploaded_diff_file = [("data", "data.diff", data)]
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			uploaded_diff_file = [("data", "data.diff", emptydiff)]
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if vcs and self.name != "new":
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			form_fields.append(("subject", "diff -r " + vcs.base_rev + " " + ui.expandpath("default")))
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# First upload sets the subject for the CL itself.
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			form_fields.append(("subject", self.Subject()))
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ctype, body = EncodeMultipartFormData(form_fields, uploaded_diff_file)
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		response_body = MySend("/upload", body, content_type=ctype)
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		patchset = None
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		msg = response_body
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		lines = msg.splitlines()
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if len(lines) >= 2:
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			msg = lines[0]
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			patchset = lines[1].strip()
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			patches = [x.split(" ", 1) for x in lines[2:]]
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if response_body.startswith("Issue updated.") and quiet:
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			pass
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.status(msg + "\n")
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		set_status("uploaded CL metadata + diffs")
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not response_body.startswith("Issue created.") and not response_body.startswith("Issue updated."):
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			raise hg_util.Abort("failed to update issue: " + response_body)
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		issue = msg[msg.rfind("/")+1:]
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.name = issue
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not self.url:
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.url = server_url_base + self.name
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not uploaded_diff_file:
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			set_status("uploading patches")
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			patches = UploadSeparatePatches(issue, rpc, patchset, data, upload_options)
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if vcs:
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			set_status("uploading base files")
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			vcs.UploadBaseFiles(issue, rpc, patches, patchset, upload_options, files)
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if send_mail:
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			set_status("sending mail")
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			MySend("/" + issue + "/mail", payload="")
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.web = True
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		set_status("flushing changes to disk")
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.Flush(ui, repo)
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def Mail(self, ui, repo):
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		pmsg = "Hello " + JoinComma(self.reviewer)
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.cc:
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			pmsg += " (cc: %s)" % (', '.join(self.cc),)
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		pmsg += ",\n"
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		pmsg += "\n"
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		repourl = ui.expandpath("default")
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not self.mailed:
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			pmsg += "I'd like you to review this change to\n" + repourl + "\n"
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			pmsg += "Please take another look.\n"
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		typecheck(pmsg, str)
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		PostMessage(ui, self.name, pmsg, subject=self.Subject())
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.mailed = True
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.Flush(ui, repo)
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def GoodCLName(name):
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(name, str)
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return re.match("^[0-9]+$", name)
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def ParseCL(text, name):
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(text, str)
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(name, str)
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	sname = None
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	lineno = 0
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	sections = {
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		'Author': '',
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		'Description': '',
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		'Files': '',
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		'URL': '',
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		'Reviewer': '',
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		'CC': '',
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		'Mailed': '',
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		'Private': '',
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	}
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for line in text.split('\n'):
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		lineno += 1
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		line = line.rstrip()
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line != '' and line[0] == '#':
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			continue
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line == '' or line[0] == ' ' or line[0] == '\t':
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if sname == None and line != '':
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				return None, lineno, 'text outside section'
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if sname != None:
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				sections[sname] += line + '\n'
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			continue
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		p = line.find(':')
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if p >= 0:
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s, val = line[:p].strip(), line[p+1:].strip()
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if s in sections:
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				sname = s
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if val != '':
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					sections[sname] += val + '\n'
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				continue
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return None, lineno, 'malformed section header'
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for k in sections:
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		sections[k] = StripCommon(sections[k]).rstrip()
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl = CL(name)
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if sections['Author']:
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.copied_from = sections['Author']
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl.desc = sections['Description']
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for line in sections['Files'].split('\n'):
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		i = line.find('#')
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if i >= 0:
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			line = line[0:i].rstrip()
4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		line = line.strip()
4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line == '':
4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			continue
4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.files.append(line)
4575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl.reviewer = SplitCommaSpace(sections['Reviewer'])
4585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl.cc = SplitCommaSpace(sections['CC'])
4595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl.url = sections['URL']
4605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if sections['Mailed'] != 'False':
4615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Odd default, but avoids spurious mailings when
4625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# reading old CLs that do not have a Mailed: line.
4635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# CLs created with this update will always have
4645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Mailed: False on disk.
4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.mailed = True
4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if sections['Private'] in ('True', 'true', 'Yes', 'yes'):
4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.private = True
4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if cl.desc == '<enter description here>':
4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.desc = ''
4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return cl, 0, ''
4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def SplitCommaSpace(s):
4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(s, str)
4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	s = s.strip()
4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if s == "":
4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return []
4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return re.split(", *", s)
4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def CutDomain(s):
4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(s, str)
4815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	i = s.find('@')
4825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if i >= 0:
4835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s = s[0:i]
4845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return s
4855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def JoinComma(l):
4875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for s in l:
4885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		typecheck(s, str)
4895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return ", ".join(l)
4905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def ExceptionDetail():
4925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	s = str(sys.exc_info()[0])
4935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if s.startswith("<type '") and s.endswith("'>"):
4945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s = s[7:-2]
4955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	elif s.startswith("<class '") and s.endswith("'>"):
4965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s = s[8:-2]
4975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	arg = str(sys.exc_info()[1])
4985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if len(arg) > 0:
4995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s += ": " + arg
5005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return s
5015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def IsLocalCL(ui, repo, name):
5035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return GoodCLName(name) and os.access(CodeReviewDir(ui, repo) + "/cl." + name, 0)
5045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Load CL from disk and/or the web.
5065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def LoadCL(ui, repo, name, web=True):
5075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(name, str)
5085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	set_status("loading CL " + name)
5095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not GoodCLName(name):
5105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return None, "invalid CL name"
5115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	dir = CodeReviewDir(ui, repo)
5125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	path = dir + "cl." + name
5135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if os.access(path, 0):
5145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ff = open(path)
5155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		text = ff.read()
5165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ff.close()
5175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl, lineno, err = ParseCL(text, name)
5185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err != "":
5195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return None, "malformed CL data: "+err
5205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.local = True
5215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	else:
5225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl = CL(name)
5235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if web:
5245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		set_status("getting issue metadata from web")
5255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		d = JSONGet(ui, "/api/" + name + "?messages=true")
5265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		set_status(None)
5275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if d is None:
5285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return None, "cannot load CL %s from server" % (name,)
5295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if 'owner_email' not in d or 'issue' not in d or str(d['issue']) != name:
5305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return None, "malformed response loading CL data from code review server"
5315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.dict = d
5325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.reviewer = d.get('reviewers', [])
5335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.cc = d.get('cc', [])
5345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if cl.local and cl.copied_from and cl.desc:
5355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# local copy of CL written by someone else
5365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# and we saved a description.  use that one,
5375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# so that committers can edit the description
5385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# before doing hg submit.
5395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			pass
5405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
5415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.desc = d.get('description', "")
5425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.url = server_url_base + name
5435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.web = True
5445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.private = d.get('private', False) != False
5455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.lgtm = []
5465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for m in d.get('messages', []):
5475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if m.get('approval', False) == True:
5485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				who = re.sub('@.*', '', m.get('sender', ''))
5495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				text = re.sub("\n(.|\n)*", '', m.get('text', ''))
5505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				cl.lgtm.append((who, text))
5515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	set_status("loaded CL " + name)
5535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return cl, ''
5545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class LoadCLThread(threading.Thread):
5565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def __init__(self, ui, repo, dir, f, web):
5575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		threading.Thread.__init__(self)
5585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.ui = ui
5595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.repo = repo
5605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.dir = dir
5615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.f = f
5625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.web = web
5635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.cl = None
5645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def run(self):
5655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl, err = LoadCL(self.ui, self.repo, self.f[3:], web=self.web)
5665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err != '':
5675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.ui.warn("loading "+self.dir+self.f+": " + err + "\n")
5685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return
5695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.cl = cl
5705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Load all the CLs from this repository.
5725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def LoadAllCL(ui, repo, web=True):
5735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	dir = CodeReviewDir(ui, repo)
5745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	m = {}
5755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = [f for f in os.listdir(dir) if f.startswith('cl.')]
5765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not files:
5775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return m
5785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	active = []
5795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	first = True
5805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for f in files:
5815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		t = LoadCLThread(ui, repo, dir, f, web)
5825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		t.start()
5835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if web and first:
5845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# first request: wait in case it needs to authenticate
5855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# otherwise we get lots of user/password prompts
5865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# running in parallel.
5875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			t.join()
5885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if t.cl:
5895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				m[t.cl.name] = t.cl
5905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			first = False
5915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
5925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			active.append(t)
5935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for t in active:
5945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		t.join()
5955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if t.cl:
5965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			m[t.cl.name] = t.cl
5975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return m
5985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Find repository root.  On error, ui.warn and return None
6005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def RepoDir(ui, repo):
6015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	url = repo.url();
6025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not url.startswith('file:'):
6035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.warn("repository %s is not in local file system\n" % (url,))
6045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return None
6055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	url = url[5:]
6065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if url.endswith('/'):
6075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		url = url[:-1]
6085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(url, str)
6095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return url
6105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Find (or make) code review directory.  On error, ui.warn and return None
6125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def CodeReviewDir(ui, repo):
6135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	dir = RepoDir(ui, repo)
6145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if dir == None:
6155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return None
6165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	dir += '/.hg/codereview/'
6175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not os.path.isdir(dir):
6185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		try:
6195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			os.mkdir(dir, 0700)
6205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		except:
6215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.warn('cannot mkdir %s: %s\n' % (dir, ExceptionDetail()))
6225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return None
6235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(dir, str)
6245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return dir
6255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Turn leading tabs into spaces, so that the common white space
6275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# prefix doesn't get confused when people's editors write out
6285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# some lines with spaces, some with tabs.  Only a heuristic
6295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# (some editors don't use 8 spaces either) but a useful one.
6305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def TabsToSpaces(line):
6315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	i = 0
6325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	while i < len(line) and line[i] == '\t':
6335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		i += 1
6345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return ' '*(8*i) + line[i:]
6355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Strip maximal common leading white space prefix from text
6375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def StripCommon(text):
6385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(text, str)
6395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ws = None
6405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for line in text.split('\n'):
6415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		line = line.rstrip()
6425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line == '':
6435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			continue
6445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		line = TabsToSpaces(line)
6455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		white = line[:len(line)-len(line.lstrip())]
6465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if ws == None:
6475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ws = white
6485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
6495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			common = ''
6505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for i in range(min(len(white), len(ws))+1):
6515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if white[0:i] == ws[0:i]:
6525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					common = white[0:i]
6535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ws = common
6545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if ws == '':
6555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			break
6565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if ws == None:
6575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return text
6585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	t = ''
6595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for line in text.split('\n'):
6605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		line = line.rstrip()
6615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		line = TabsToSpaces(line)
6625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line.startswith(ws):
6635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			line = line[len(ws):]
6645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line == '' and t == '':
6655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			continue
6665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		t += line + '\n'
6675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	while len(t) >= 2 and t[-2:] == '\n\n':
6685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		t = t[:-1]
6695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(t, str)
6705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return t
6715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Indent text with indent.
6735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def Indent(text, indent):
6745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(text, str)
6755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(indent, str)
6765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	t = ''
6775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for line in text.split('\n'):
6785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		t += indent + line + '\n'
6795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(t, str)
6805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return t
6815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Return the first line of l
6835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def line1(text):
6845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(text, str)
6855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return text.split('\n')[0]
6865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)_change_prolog = """# Change list.
6885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Lines beginning with # are ignored.
6895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Multi-line values should be indented.
6905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
6915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)desc_re = '^(.+: |(tag )?(release|weekly)\.|fix build|undo CL)'
6935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)desc_msg = '''Your CL description appears not to use the standard form.
6955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The first line of your change description is conventionally a
6975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)one-line summary of the change, prefixed by the primary affected package,
6985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)and is used as the subject for code review mail; the rest of the description
6995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)elaborates.
7005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Examples:
7025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	encoding/rot13: new package
7045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	math: add IsInf, IsNaN
7065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	net: fix cname in LookupHost
7085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	unicode: update to Unicode 5.0.2
7105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)'''
7125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def promptyesno(ui, msg):
7141320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci	if hgversion >= "2.7":
7151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		return ui.promptchoice(msg + " $$ &yes $$ &no", 0) == 0
7161320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci	else:
7171320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci		return ui.promptchoice(msg, ["&yes", "&no"], 0) == 0
7185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def promptremove(ui, repo, f):
7205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if promptyesno(ui, "hg remove %s (y/n)?" % (f,)):
7215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if hg_commands.remove(ui, repo, 'path:'+f) != 0:
7225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.warn("error removing %s" % (f,))
7235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def promptadd(ui, repo, f):
7255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if promptyesno(ui, "hg add %s (y/n)?" % (f,)):
7265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if hg_commands.add(ui, repo, 'path:'+f) != 0:
7275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.warn("error adding %s" % (f,))
7285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def EditCL(ui, repo, cl):
7305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	set_status(None)	# do not show status
7315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	s = cl.EditorText()
7325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	while True:
7335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s = ui.edit(s, ui.username())
7345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# We can't trust Mercurial + Python not to die before making the change,
7365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# so, by popular demand, just scribble the most recent CL edit into
7375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# $(hg root)/last-change so that if Mercurial does die, people
7385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# can look there for their work.
7395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		try:
7405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			f = open(repo.root+"/last-change", "w")
7415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			f.write(s)
7425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			f.close()
7435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		except:
7445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			pass
7455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		clx, line, err = ParseCL(s, cl.name)
7475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err != '':
7485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if not promptyesno(ui, "error parsing change list: line %d: %s\nre-edit (y/n)?" % (line, err)):
7495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				return "change list not modified"
7505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			continue
7515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Check description.
7535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if clx.desc == '':
7545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if promptyesno(ui, "change list should have a description\nre-edit (y/n)?"):
7555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				continue
7565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		elif re.search('<enter reason for undo>', clx.desc):
7575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if promptyesno(ui, "change list description omits reason for undo\nre-edit (y/n)?"):
7585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				continue
7595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		elif not re.match(desc_re, clx.desc.split('\n')[0]):
7605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if promptyesno(ui, desc_msg + "re-edit (y/n)?"):
7615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				continue
7625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Check file list for files that need to be hg added or hg removed
7645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# or simply aren't understood.
7655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		pats = ['path:'+f for f in clx.files]
7665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		changed = hg_matchPattern(ui, repo, *pats, modified=True, added=True, removed=True)
7675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		deleted = hg_matchPattern(ui, repo, *pats, deleted=True)
7685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		unknown = hg_matchPattern(ui, repo, *pats, unknown=True)
7695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ignored = hg_matchPattern(ui, repo, *pats, ignored=True)
7705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		clean = hg_matchPattern(ui, repo, *pats, clean=True)
7715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		files = []
7725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for f in clx.files:
7735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if f in changed:
7745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				files.append(f)
7755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				continue
7765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if f in deleted:
7775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				promptremove(ui, repo, f)
7785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				files.append(f)
7795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				continue
7805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if f in unknown:
7815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				promptadd(ui, repo, f)
7825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				files.append(f)
7835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				continue
7845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if f in ignored:
7855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.warn("error: %s is excluded by .hgignore; omitting\n" % (f,))
7865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				continue
7875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if f in clean:
7885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.warn("warning: %s is listed in the CL but unchanged\n" % (f,))
7895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				files.append(f)
7905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				continue
7915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			p = repo.root + '/' + f
7925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if os.path.isfile(p):
7935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.warn("warning: %s is a file but not known to hg\n" % (f,))
7945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				files.append(f)
7955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				continue
7965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if os.path.isdir(p):
7975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.warn("error: %s is a directory, not a file; omitting\n" % (f,))
7985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				continue
7995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.warn("error: %s does not exist; omitting\n" % (f,))
8005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		clx.files = files
8015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.desc = clx.desc
8035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.reviewer = clx.reviewer
8045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.cc = clx.cc
8055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.files = clx.files
8065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.private = clx.private
8075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		break
8085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return ""
8095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# For use by submit, etc. (NOT by change)
8115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Get change list number or list of files from command line.
8125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# If files are given, make a new change list.
8135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def CommandLineCL(ui, repo, pats, opts, defaultcc=None):
8145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if len(pats) > 0 and GoodCLName(pats[0]):
8155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if len(pats) != 1:
8165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return None, "cannot specify change number and file names"
8175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if opts.get('message'):
8185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return None, "cannot use -m with existing CL"
8195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl, err = LoadCL(ui, repo, pats[0], web=True)
8205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err != "":
8215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return None, err
8225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	else:
8235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl = CL("new")
8245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.local = True
8255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.files = ChangedFiles(ui, repo, pats, taken=Taken(ui, repo))
8265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not cl.files:
8275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return None, "no files changed"
8285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if opts.get('reviewer'):
8295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.reviewer = Add(cl.reviewer, SplitCommaSpace(opts.get('reviewer')))
8305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if opts.get('cc'):
8315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.cc = Add(cl.cc, SplitCommaSpace(opts.get('cc')))
8325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if defaultcc:
8335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.cc = Add(cl.cc, defaultcc)
8345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if cl.name == "new":
8355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if opts.get('message'):
8365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.desc = opts.get('message')
8375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
8385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			err = EditCL(ui, repo, cl)
8395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if err != '':
8405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				return None, err
8415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return cl, ""
8425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
8445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Change list file management
8455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Return list of changed files in repository that match pats.
8475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The patterns came from the command line, so we warn
8485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# if they have no effect or cannot be understood.
8495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def ChangedFiles(ui, repo, pats, taken=None):
8505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	taken = taken or {}
8515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Run each pattern separately so that we can warn about
8525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# patterns that didn't do anything useful.
8535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for p in pats:
8545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for f in hg_matchPattern(ui, repo, p, unknown=True):
8555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			promptadd(ui, repo, f)
8565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for f in hg_matchPattern(ui, repo, p, removed=True):
8575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			promptremove(ui, repo, f)
8585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		files = hg_matchPattern(ui, repo, p, modified=True, added=True, removed=True)
8595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for f in files:
8605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if f in taken:
8615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.warn("warning: %s already in CL %s\n" % (f, taken[f].name))
8625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not files:
8635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.warn("warning: %s did not match any modified files\n" % (p,))
8645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Again, all at once (eliminates duplicates)
8665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	l = hg_matchPattern(ui, repo, *pats, modified=True, added=True, removed=True)
8675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	l.sort()
8685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if taken:
8695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		l = Sub(l, taken.keys())
8705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return l
8715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Return list of changed files in repository that match pats and still exist.
8735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def ChangedExistingFiles(ui, repo, pats, opts):
8745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	l = hg_matchPattern(ui, repo, *pats, modified=True, added=True)
8755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	l.sort()
8765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return l
8775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Return list of files claimed by existing CLs
8795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def Taken(ui, repo):
8805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	all = LoadAllCL(ui, repo, web=False)
8815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	taken = {}
8825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for _, cl in all.items():
8835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for f in cl.files:
8845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			taken[f] = cl
8855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return taken
8865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Return list of changed files that are not claimed by other CLs
8885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def DefaultFiles(ui, repo, pats):
8895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return ChangedFiles(ui, repo, pats, taken=Taken(ui, repo))
8905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
8925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# File format checking.
8935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def CheckFormat(ui, repo, files, just_warn=False):
8955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	set_status("running gofmt")
8965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	CheckGofmt(ui, repo, files, just_warn)
8975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	CheckTabfmt(ui, repo, files, just_warn)
8985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Check that gofmt run on the list of files does not change them
9005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def CheckGofmt(ui, repo, files, just_warn):
9015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = gofmt_required(files)
9025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not files:
9035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return
9045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cwd = os.getcwd()
9055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
9065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = [f for f in files if os.access(f, 0)]
9075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not files:
9085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return
9095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	try:
9105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cmd = subprocess.Popen(["gofmt", "-l"] + files, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=sys.platform != "win32")
9115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cmd.stdin.close()
9125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	except:
9135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise hg_util.Abort("gofmt: " + ExceptionDetail())
9145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	data = cmd.stdout.read()
9155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	errors = cmd.stderr.read()
9165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cmd.wait()
9175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	set_status("done with gofmt")
9185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if len(errors) > 0:
9195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.warn("gofmt errors:\n" + errors.rstrip() + "\n")
9205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return
9215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if len(data) > 0:
9225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		msg = "gofmt needs to format these files (run hg gofmt):\n" + Indent(data, "\t").rstrip()
9235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if just_warn:
9245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.warn("warning: " + msg + "\n")
9255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
9265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			raise hg_util.Abort(msg)
9275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return
9285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Check that *.[chys] files indent using tabs.
9305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def CheckTabfmt(ui, repo, files, just_warn):
9315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = [f for f in files if f.startswith('src/') and re.search(r"\.[chys]$", f) and not re.search(r"\.tab\.[ch]$", f)]
9325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not files:
9335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return
9345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cwd = os.getcwd()
9355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
9365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = [f for f in files if os.access(f, 0)]
9375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	badfiles = []
9385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for f in files:
9395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		try:
9405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for line in open(f, 'r'):
9415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# Four leading spaces is enough to complain about,
9425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# except that some Plan 9 code uses four spaces as the label indent,
9435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# so allow that.
9445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if line.startswith('    ') and not re.match('    [A-Za-z0-9_]+:', line):
9455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					badfiles.append(f)
9465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					break
9475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		except:
9485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# ignore cannot open file, etc.
9495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			pass
9505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if len(badfiles) > 0:
9515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		msg = "these files use spaces for indentation (use tabs instead):\n\t" + "\n\t".join(badfiles)
9525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if just_warn:
9535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.warn("warning: " + msg + "\n")
9545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
9555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			raise hg_util.Abort(msg)
9565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return
9575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
9595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# CONTRIBUTORS file parsing
9605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)contributorsCache = None
9625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)contributorsURL = None
9635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def ReadContributors(ui, repo):
9655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global contributorsCache
9665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if contributorsCache is not None:
9675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return contributorsCache
9685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	try:
9705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if contributorsURL is not None:
9715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			opening = contributorsURL
9725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			f = urllib2.urlopen(contributorsURL)
9735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
9745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			opening = repo.root + '/CONTRIBUTORS'
9755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			f = open(repo.root + '/CONTRIBUTORS', 'r')
9765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	except:
9775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.write("warning: cannot open %s: %s\n" % (opening, ExceptionDetail()))
9785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return
9795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	contributors = {}
9815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for line in f:
9825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# CONTRIBUTORS is a list of lines like:
9835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		#	Person <email>
9845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		#	Person <email> <alt-email>
9855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# The first email address is the one used in commit logs.
9865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line.startswith('#'):
9875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			continue
9885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		m = re.match(r"([^<>]+\S)\s+(<[^<>\s]+>)((\s+<[^<>\s]+>)*)\s*$", line)
9895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if m:
9905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			name = m.group(1)
9915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			email = m.group(2)[1:-1]
9925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			contributors[email.lower()] = (name, email)
9935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for extra in m.group(3).split():
9945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				contributors[extra[1:-1].lower()] = (name, email)
9955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	contributorsCache = contributors
9975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return contributors
9985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def CheckContributor(ui, repo, user=None):
10005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	set_status("checking CONTRIBUTORS file")
10015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	user, userline = FindContributor(ui, repo, user, warn=False)
10025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not userline:
10035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise hg_util.Abort("cannot find %s in CONTRIBUTORS" % (user,))
10045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return userline
10055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def FindContributor(ui, repo, user=None, warn=True):
10075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not user:
10085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		user = ui.config("ui", "username")
10095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not user:
10105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			raise hg_util.Abort("[ui] username is not configured in .hgrc")
10115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	user = user.lower()
10125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	m = re.match(r".*<(.*)>", user)
10135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if m:
10145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		user = m.group(1)
10155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	contributors = ReadContributors(ui, repo)
10175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if user not in contributors:
10185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if warn:
10195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.warn("warning: cannot find %s in CONTRIBUTORS\n" % (user,))
10205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return user, None
10215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	user, email = contributors[user]
10235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return email, "%s <%s>" % (user, email)
10245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
10265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Mercurial helper functions.
10275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Read http://mercurial.selenic.com/wiki/MercurialApi before writing any of these.
10285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# We use the ui.pushbuffer/ui.popbuffer + hg_commands.xxx tricks for all interaction
10295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# with Mercurial.  It has proved the most stable as they make changes.
10305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)hgversion = hg_util.version()
10325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# We require Mercurial 1.9 and suggest Mercurial 2.0.
10345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The details of the scmutil package changed then,
10355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# so allowing earlier versions would require extra band-aids below.
10365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Ubuntu 11.10 ships with Mercurial 1.9.1 as the default version.
10375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)hg_required = "1.9"
10385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)hg_suggested = "2.0"
10395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)old_message = """
10415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The code review extension requires Mercurial """+hg_required+""" or newer.
10435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)You are using Mercurial """+hgversion+""".
10445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)To install a new Mercurial, use
10465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	sudo easy_install mercurial=="""+hg_suggested+"""
10485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)or visit http://mercurial.selenic.com/downloads/.
10505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
10515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)linux_message = """
10535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)You may need to clear your current Mercurial installation by running:
10545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	sudo apt-get remove mercurial mercurial-common
10565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	sudo rm -rf /etc/mercurial
10575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
10585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if hgversion < hg_required:
10605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	msg = old_message
10615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if os.access("/etc/mercurial", 0):
10625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		msg += linux_message
10635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	raise hg_util.Abort(msg)
10645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from mercurial.hg import clean as hg_clean
10665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from mercurial import cmdutil as hg_cmdutil
10675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from mercurial import error as hg_error
10685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from mercurial import match as hg_match
10695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from mercurial import node as hg_node
10705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class uiwrap(object):
10725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def __init__(self, ui):
10735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.ui = ui
10745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.pushbuffer()
10755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.oldQuiet = ui.quiet
10765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.quiet = True
10775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.oldVerbose = ui.verbose
10785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.verbose = False
10795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def output(self):
10805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui = self.ui
10815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.quiet = self.oldQuiet
10825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.verbose = self.oldVerbose
10835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return ui.popbuffer()
10845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def to_slash(path):
10865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if sys.platform == "win32":
10875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return path.replace('\\', '/')
10885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return path
10895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def hg_matchPattern(ui, repo, *pats, **opts):
10915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	w = uiwrap(ui)
10925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	hg_commands.status(ui, repo, *pats, **opts)
10935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	text = w.output()
10945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ret = []
10955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	prefix = to_slash(os.path.realpath(repo.root))+'/'
10965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for line in text.split('\n'):
10975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		f = line.split()
10985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if len(f) > 1:
10995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if len(pats) > 0:
11005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# Given patterns, Mercurial shows relative to cwd
11015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				p = to_slash(os.path.realpath(f[1]))
11025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if not p.startswith(prefix):
11035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					print >>sys.stderr, "File %s not in repo root %s.\n" % (p, prefix)
11045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				else:
11055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					ret.append(p[len(prefix):])
11065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			else:
11075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# Without patterns, Mercurial shows relative to root (what we want)
11085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ret.append(to_slash(f[1]))
11095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return ret
11105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def hg_heads(ui, repo):
11125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	w = uiwrap(ui)
11135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	hg_commands.heads(ui, repo)
11145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return w.output()
11155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)noise = [
11175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"",
11185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"resolving manifests",
11195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"searching for changes",
11205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"couldn't find merge tool hgmerge",
11215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"adding changesets",
11225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"adding manifests",
11235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"adding file changes",
11245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"all local heads known remotely",
11255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)]
11265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def isNoise(line):
11285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	line = str(line)
11295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for x in noise:
11305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line == x:
11315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return True
11325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return False
11335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def hg_incoming(ui, repo):
11355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	w = uiwrap(ui)
11365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ret = hg_commands.incoming(ui, repo, force=False, bundle="")
11375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if ret and ret != 1:
11385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise hg_util.Abort(ret)
11395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return w.output()
11405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def hg_log(ui, repo, **opts):
11425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for k in ['date', 'keyword', 'rev', 'user']:
11435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not opts.has_key(k):
11445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			opts[k] = ""
11455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	w = uiwrap(ui)
11465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ret = hg_commands.log(ui, repo, **opts)
11475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if ret:
11485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise hg_util.Abort(ret)
11495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return w.output()
11505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def hg_outgoing(ui, repo, **opts):
11525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	w = uiwrap(ui)
11535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ret = hg_commands.outgoing(ui, repo, **opts)
11545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if ret and ret != 1:
11555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise hg_util.Abort(ret)
11565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return w.output()
11575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def hg_pull(ui, repo, **opts):
11595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	w = uiwrap(ui)
11605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ui.quiet = False
11615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ui.verbose = True  # for file list
11625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	err = hg_commands.pull(ui, repo, **opts)
11635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for line in w.output().split('\n'):
11645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if isNoise(line):
11655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			continue
11665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line.startswith('moving '):
11675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			line = 'mv ' + line[len('moving '):]
11685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line.startswith('getting ') and line.find(' to ') >= 0:
11695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			line = 'mv ' + line[len('getting '):]
11705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line.startswith('getting '):
11715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			line = '+ ' + line[len('getting '):]
11725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line.startswith('removing '):
11735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			line = '- ' + line[len('removing '):]
11745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.write(line + '\n')
11755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return err
11765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def hg_push(ui, repo, **opts):
11785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	w = uiwrap(ui)
11795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ui.quiet = False
11805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ui.verbose = True
11815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	err = hg_commands.push(ui, repo, **opts)
11825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for line in w.output().split('\n'):
11835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not isNoise(line):
11845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.write(line + '\n')
11855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return err
11865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def hg_commit(ui, repo, *pats, **opts):
11885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return hg_commands.commit(ui, repo, *pats, **opts)
11895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
11915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Mercurial precommit hook to disable commit except through this interface.
11925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)commit_okay = False
11945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def precommithook(ui, repo, **opts):
11965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if commit_okay:
11975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return False  # False means okay.
11985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ui.write("\ncodereview extension enabled; use mail, upload, or submit instead of commit\n\n")
11995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return True
12005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
12025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# @clnumber file pattern support
12035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# We replace scmutil.match with the MatchAt wrapper to add the @clnumber pattern.
12055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)match_repo = None
12075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)match_ui = None
12085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)match_orig = None
12095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def InstallMatch(ui, repo):
12115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global match_repo
12125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global match_ui
12135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global match_orig
12145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	match_ui = ui
12165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	match_repo = repo
12175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	from mercurial import scmutil
12195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	match_orig = scmutil.match
12205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	scmutil.match = MatchAt
12215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def MatchAt(ctx, pats=None, opts=None, globbed=False, default='relpath'):
12235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	taken = []
12245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = []
12255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	pats = pats or []
12265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	opts = opts or {}
12275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for p in pats:
12295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if p.startswith('@'):
12305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			taken.append(p)
12315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			clname = p[1:]
12325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if clname == "default":
12335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				files = DefaultFiles(match_ui, match_repo, [])
12345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			else:
12355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if not GoodCLName(clname):
12365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					raise hg_util.Abort("invalid CL name " + clname)
12375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				cl, err = LoadCL(match_repo.ui, match_repo, clname, web=False)
12385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if err != '':
12395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					raise hg_util.Abort("loading CL " + clname + ": " + err)
12405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if not cl.files:
12415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					raise hg_util.Abort("no files in CL " + clname)
12425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				files = Add(files, cl.files)
12435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	pats = Sub(pats, taken) + ['path:'+f for f in files]
12445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# work-around for http://selenic.com/hg/rev/785bbc8634f8
12465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not hasattr(ctx, 'match'):
12475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ctx = ctx[None]
12485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return match_orig(ctx, pats=pats, opts=opts, globbed=globbed, default=default)
12495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
12515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Commands added by code review extension.
12525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# As of Mercurial 2.1 the commands are all required to return integer
12545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# exit codes, whereas earlier versions allowed returning arbitrary strings
12555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# to be printed as errors.  We wrap the old functions to make sure we
12565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# always return integer exit codes now.  Otherwise Mercurial dies
12575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# with a TypeError traceback (unsupported operand type(s) for &: 'str' and 'int').
12585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Introduce a Python decorator to convert old functions to the new
12595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# stricter convention.
12605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def hgcommand(f):
12625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def wrapped(ui, repo, *pats, **opts):
12635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		err = f(ui, repo, *pats, **opts)
12645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if type(err) is int:
12655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return err
12665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not err:
12675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return 0
12685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise hg_util.Abort(err)
12695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	wrapped.__doc__ = f.__doc__
12705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return wrapped
12715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
12735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# hg change
12745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
12765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def change(ui, repo, *pats, **opts):
12775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""create, edit or delete a change list
12785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Create, edit or delete a change list.
12805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	A change list is a group of files to be reviewed and submitted together,
12815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	plus a textual description of the change.
12825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Change lists are referred to by simple alphanumeric names.
12835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Changes must be reviewed before they can be submitted.
12855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	In the absence of options, the change command opens the
12875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	change list for editing in the default editor.
12885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Deleting a change with the -d or -D flag does not affect
12905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	the contents of the files listed in that change.  To revert
12915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	the files listed in a change, use
12925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		hg revert @123456
12945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	before running hg change -d 123456.
12965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
12975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if codereview_disabled:
12995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return codereview_disabled
13005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	dirty = {}
13025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if len(pats) > 0 and GoodCLName(pats[0]):
13035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		name = pats[0]
13045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if len(pats) != 1:
13055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return "cannot specify CL name and file patterns"
13065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		pats = pats[1:]
13075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl, err = LoadCL(ui, repo, name, web=True)
13085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err != '':
13095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return err
13105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not cl.local and (opts["stdin"] or not opts["stdout"]):
13115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return "cannot change non-local CL " + name
13125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	else:
13135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		name = "new"
13145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl = CL("new")
13155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if repo[None].branch() != "default":
13165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return "cannot create CL outside default branch; switch with 'hg update default'"
13175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		dirty[cl] = True
13185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		files = ChangedFiles(ui, repo, pats, taken=Taken(ui, repo))
13195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if opts["delete"] or opts["deletelocal"]:
13215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if opts["delete"] and opts["deletelocal"]:
13225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return "cannot use -d and -D together"
13235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		flag = "-d"
13245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if opts["deletelocal"]:
13255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			flag = "-D"
13265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if name == "new":
13275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return "cannot use "+flag+" with file patterns"
13285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if opts["stdin"] or opts["stdout"]:
13295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return "cannot use "+flag+" with -i or -o"
13305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not cl.local:
13315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return "cannot change non-local CL " + name
13325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if opts["delete"]:
13335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if cl.copied_from:
13345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				return "original author must delete CL; hg change -D will remove locally"
13355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			PostMessage(ui, cl.name, "*** Abandoned ***", send_mail=cl.mailed)
13365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			EditDesc(cl.name, closed=True, private=cl.private)
13375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.Delete(ui, repo)
13385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return
13395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if opts["stdin"]:
13415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s = sys.stdin.read()
13425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		clx, line, err = ParseCL(s, name)
13435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err != '':
13445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return "error parsing change list: line %d: %s" % (line, err)
13455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if clx.desc is not None:
13465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.desc = clx.desc;
13475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			dirty[cl] = True
13485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if clx.reviewer is not None:
13495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.reviewer = clx.reviewer
13505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			dirty[cl] = True
13515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if clx.cc is not None:
13525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.cc = clx.cc
13535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			dirty[cl] = True
13545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if clx.files is not None:
13555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.files = clx.files
13565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			dirty[cl] = True
13575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if clx.private != cl.private:
13585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.private = clx.private
13595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			dirty[cl] = True
13605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not opts["stdin"] and not opts["stdout"]:
13625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if name == "new":
13635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.files = files
13645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		err = EditCL(ui, repo, cl)
13655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err != "":
13665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return err
13675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		dirty[cl] = True
13685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for d, _ in dirty.items():
13705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		name = d.name
13715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		d.Flush(ui, repo)
13725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if name == "new":
13735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			d.Upload(ui, repo, quiet=True)
13745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if opts["stdout"]:
13765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.write(cl.EditorText())
13775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	elif opts["pending"]:
13785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.write(cl.PendingText())
13795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	elif name == "new":
13805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if ui.quiet:
13815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.write(cl.name)
13825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
13835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.write("CL created: " + cl.url + "\n")
13845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return
13855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
13875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# hg code-login (broken?)
13885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
13905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def code_login(ui, repo, **opts):
13915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""log in to code review server
13925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Logs in to the code review server, saving a cookie in
13945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	a file in your home directory.
13955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
13965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if codereview_disabled:
13975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return codereview_disabled
13985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	MySend(None)
14005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
14025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# hg clpatch / undo / release-apply / download
14035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# All concerned with applying or unapplying patches to the repository.
14045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
14065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def clpatch(ui, repo, clname, **opts):
14075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""import a patch from the code review server
14085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Imports a patch from the code review server into the local client.
14105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	If the local client has already modified any of the files that the
14115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	patch modifies, this command will refuse to apply the patch.
14125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Submitting an imported patch will keep the original author's
14145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	name as the Author: line but add your own name to a Committer: line.
14155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
14165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if repo[None].branch() != "default":
14175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "cannot run hg clpatch outside default branch"
14185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return clpatch_or_undo(ui, repo, clname, opts, mode="clpatch")
14195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
14215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def undo(ui, repo, clname, **opts):
14225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""undo the effect of a CL
14235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Creates a new CL that undoes an earlier CL.
14255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	After creating the CL, opens the CL text for editing so that
14265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	you can add the reason for the undo to the description.
14275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
14285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if repo[None].branch() != "default":
14295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "cannot run hg undo outside default branch"
14305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return clpatch_or_undo(ui, repo, clname, opts, mode="undo")
14315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
14335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def release_apply(ui, repo, clname, **opts):
14345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""apply a CL to the release branch
14355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Creates a new CL copying a previously committed change
14375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	from the main branch to the release branch.
14385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	The current client must either be clean or already be in
14395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	the release branch.
14405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	The release branch must be created by starting with a
14425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	clean client, disabling the code review plugin, and running:
14435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		hg update weekly.YYYY-MM-DD
14455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		hg branch release-branch.rNN
14465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		hg commit -m 'create release-branch.rNN'
14475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		hg push --new-branch
14485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Then re-enable the code review plugin.
14505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	People can test the release branch by running
14525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		hg update release-branch.rNN
14545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	in a clean client.  To return to the normal tree,
14565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		hg update default
14585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Move changes since the weekly into the release branch
14605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	using hg release-apply followed by the usual code review
14615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	process and hg submit.
14625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	When it comes time to tag the release, record the
14645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	final long-form tag of the release-branch.rNN
14655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	in the *default* branch's .hgtags file.  That is, run
14665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		hg update default
14685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	and then edit .hgtags as you would for a weekly.
14705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
14725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	c = repo[None]
14735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not releaseBranch:
14745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "no active release branches"
14755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if c.branch() != releaseBranch:
14765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if c.modified() or c.added() or c.removed():
14775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			raise hg_util.Abort("uncommitted local changes - cannot switch branches")
14785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		err = hg_clean(repo, releaseBranch)
14795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err:
14805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return err
14815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	try:
14825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		err = clpatch_or_undo(ui, repo, clname, opts, mode="backport")
14835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err:
14845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			raise hg_util.Abort(err)
14855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	except Exception, e:
14865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		hg_clean(repo, "default")
14875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise e
14885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return None
14895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def rev2clname(rev):
14915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Extract CL name from revision description.
14925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# The last line in the description that is a codereview URL is the real one.
14935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Earlier lines might be part of the user-written description.
14945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	all = re.findall('(?m)^http://codereview.appspot.com/([0-9]+)$', rev.description())
14955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if len(all) > 0:
14965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return all[-1]
14975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return ""
14985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)undoHeader = """undo CL %s / %s
15005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)<enter reason for undo>
15025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)««« original CL description
15045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
15055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)undoFooter = """
15075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)»»»
15085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
15095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)backportHeader = """[%s] %s
15115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)««« CL %s / %s
15135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
15145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)backportFooter = """
15165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)»»»
15175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
15185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Implementation of clpatch/undo.
15205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def clpatch_or_undo(ui, repo, clname, opts, mode):
15215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if codereview_disabled:
15225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return codereview_disabled
15235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if mode == "undo" or mode == "backport":
15255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Find revision in Mercurial repository.
15265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Assume CL number is 7+ decimal digits.
15275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Otherwise is either change log sequence number (fewer decimal digits),
15285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# hexadecimal hash, or tag name.
15295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Mercurial will fall over long before the change log
15305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# sequence numbers get to be 7 digits long.
15315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if re.match('^[0-9]{7,}$', clname):
15325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			found = False
15335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for r in hg_log(ui, repo, keyword="codereview.appspot.com/"+clname, limit=100, template="{node}\n").split():
15345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				rev = repo[r]
15355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# Last line with a code review URL is the actual review URL.
15365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# Earlier ones might be part of the CL description.
15375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				n = rev2clname(rev)
15385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if n == clname:
15395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					found = True
15405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					break
15415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if not found:
15425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				return "cannot find CL %s in local repository" % clname
15435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
15445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			rev = repo[clname]
15455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if not rev:
15465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				return "unknown revision %s" % clname
15475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			clname = rev2clname(rev)
15485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if clname == "":
15495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				return "cannot find CL name in revision description"
15505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Create fresh CL and start with patch that would reverse the change.
15525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		vers = hg_node.short(rev.node())
15535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl = CL("new")
15545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		desc = str(rev.description())
15555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if mode == "undo":
15565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.desc = (undoHeader % (clname, vers)) + desc + undoFooter
15575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
15585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.desc = (backportHeader % (releaseBranch, line1(desc), clname, vers)) + desc + undoFooter
15595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		v1 = vers
15605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		v0 = hg_node.short(rev.parents()[0].node())
15615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if mode == "undo":
15625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			arg = v1 + ":" + v0
15635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
15645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			vers = v0
15655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			arg = v0 + ":" + v1
15665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		patch = RunShell(["hg", "diff", "--git", "-r", arg])
15675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	else:  # clpatch
15695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl, vers, patch, err = DownloadCL(ui, repo, clname)
15705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err != "":
15715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return err
15725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if patch == emptydiff:
15735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return "codereview issue %s has no diff" % clname
15745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# find current hg version (hg identify)
15765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ctx = repo[None]
15775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	parents = ctx.parents()
15785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	id = '+'.join([hg_node.short(p.node()) for p in parents])
15795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# if version does not match the patch version,
15815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# try to update the patch line numbers.
15825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if vers != "" and id != vers:
15835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# "vers in repo" gives the wrong answer
15845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# on some versions of Mercurial.  Instead, do the actual
15855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# lookup and catch the exception.
15865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		try:
15875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			repo[vers].description()
15885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		except:
15895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return "local repository is out of date; sync to get %s" % (vers)
15905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		patch1, err = portPatch(repo, patch, vers, id)
15915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err != "":
15925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if not opts["ignore_hgpatch_failure"]:
15935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				return "codereview issue %s is out of date: %s (%s->%s)" % (clname, err, vers, id)
15945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
15955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			patch = patch1
15965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	argv = ["hgpatch"]
15975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if opts["no_incoming"] or mode == "backport":
15985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		argv += ["--checksync=false"]
15995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	try:
16005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cmd = subprocess.Popen(argv, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None, close_fds=sys.platform != "win32")
16015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	except:
16025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "hgpatch: " + ExceptionDetail() + "\nInstall hgpatch with:\n$ go get code.google.com/p/go.codereview/cmd/hgpatch\n"
16035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	out, err = cmd.communicate(patch)
16055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if cmd.returncode != 0 and not opts["ignore_hgpatch_failure"]:
16065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "hgpatch failed"
16075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl.local = True
16085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl.files = out.strip().split()
16095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not cl.files and not opts["ignore_hgpatch_failure"]:
16105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "codereview issue %s has no changed files" % clname
16115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = ChangedFiles(ui, repo, [])
16125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	extra = Sub(cl.files, files)
16135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if extra:
16145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.warn("warning: these files were listed in the patch but not changed:\n\t" + "\n\t".join(extra) + "\n")
16155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl.Flush(ui, repo)
16165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if mode == "undo":
16175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		err = EditCL(ui, repo, cl)
16185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err != "":
16195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return "CL created, but error editing: " + err
16205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.Flush(ui, repo)
16215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	else:
16225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.write(cl.PendingText() + "\n")
16235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# portPatch rewrites patch from being a patch against
16255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# oldver to being a patch against newver.
16265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def portPatch(repo, patch, oldver, newver):
16275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	lines = patch.splitlines(True) # True = keep \n
16285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	delta = None
16295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for i in range(len(lines)):
16305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		line = lines[i]
16315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line.startswith('--- a/'):
16325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			file = line[6:-1]
16335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			delta = fileDeltas(repo, file, oldver, newver)
16345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not delta or not line.startswith('@@ '):
16355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			continue
16365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# @@ -x,y +z,w @@ means the patch chunk replaces
16375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# the original file's line numbers x up to x+y with the
16385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# line numbers z up to z+w in the new file.
16395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Find the delta from x in the original to the same
16405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# line in the current version and add that delta to both
16415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# x and z.
16425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		m = re.match('@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@', line)
16435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not m:
16445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return None, "error parsing patch line numbers"
16455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		n1, len1, n2, len2 = int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
16465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		d, err = lineDelta(delta, n1, len1)
16475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err != "":
16485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return "", err
16495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		n1 += d
16505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		n2 += d
16515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		lines[i] = "@@ -%d,%d +%d,%d @@\n" % (n1, len1, n2, len2)
16525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	newpatch = ''.join(lines)
16545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return newpatch, ""
16555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# fileDelta returns the line number deltas for the given file's
16575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# changes from oldver to newver.
16585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The deltas are a list of (n, len, newdelta) triples that say
16595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# lines [n, n+len) were modified, and after that range the
16605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# line numbers are +newdelta from what they were before.
16615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def fileDeltas(repo, file, oldver, newver):
16625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cmd = ["hg", "diff", "--git", "-r", oldver + ":" + newver, "path:" + file]
16635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	data = RunShell(cmd, silent_ok=True)
16645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	deltas = []
16655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for line in data.splitlines():
16665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		m = re.match('@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@', line)
16675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not m:
16685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			continue
16695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		n1, len1, n2, len2 = int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
16705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		deltas.append((n1, len1, n2+len2-(n1+len1)))
16715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return deltas
16725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# lineDelta finds the appropriate line number delta to apply to the lines [n, n+len).
16745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# It returns an error if those lines were rewritten by the patch.
16755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def lineDelta(deltas, n, len):
16765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	d = 0
16775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for (old, oldlen, newdelta) in deltas:
16785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if old >= n+len:
16795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			break
16805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if old+len > n:
16815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return 0, "patch and recent changes conflict"
16825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		d = newdelta
16835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return d, ""
16845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
16865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def download(ui, repo, clname, **opts):
16875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""download a change from the code review server
16885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Download prints a description of the given change list
16905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	followed by its diff, downloaded from the code review server.
16915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
16925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if codereview_disabled:
16935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return codereview_disabled
16945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
16955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl, vers, patch, err = DownloadCL(ui, repo, clname)
16965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if err != "":
16975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return err
16985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ui.write(cl.EditorText() + "\n")
16995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ui.write(patch + "\n")
17005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return
17015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
17035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# hg file
17045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
17065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def file(ui, repo, clname, pat, *pats, **opts):
17075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""assign files to or remove files from a change list
17085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Assign files to or (with -d) remove files from a change list.
17105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	The -d option only removes files from the change list.
17125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	It does not edit them or remove them from the repository.
17135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
17145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if codereview_disabled:
17155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return codereview_disabled
17165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	pats = tuple([pat] + list(pats))
17185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not GoodCLName(clname):
17195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "invalid CL name " + clname
17205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	dirty = {}
17225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl, err = LoadCL(ui, repo, clname, web=False)
17235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if err != '':
17245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return err
17255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not cl.local:
17265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "cannot change non-local CL " + clname
17275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = ChangedFiles(ui, repo, pats)
17295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if opts["delete"]:
17315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		oldfiles = Intersect(files, cl.files)
17325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if oldfiles:
17335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if not ui.quiet:
17345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.status("# Removing files from CL.  To undo:\n")
17355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.status("#	cd %s\n" % (repo.root))
17365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				for f in oldfiles:
17375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					ui.status("#	hg file %s %s\n" % (cl.name, f))
17385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.files = Sub(cl.files, oldfiles)
17395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.Flush(ui, repo)
17405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
17415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.status("no such files in CL")
17425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return
17435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not files:
17455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "no such modified files"
17465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = Sub(files, cl.files)
17485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	taken = Taken(ui, repo)
17495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	warned = False
17505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for f in files:
17515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if f in taken:
17525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if not warned and not ui.quiet:
17535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.status("# Taking files from other CLs.  To undo:\n")
17545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.status("#	cd %s\n" % (repo.root))
17555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				warned = True
17565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ocl = taken[f]
17575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if not ui.quiet:
17585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.status("#	hg file %s %s\n" % (ocl.name, f))
17595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if ocl not in dirty:
17605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ocl.files = Sub(ocl.files, files)
17615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				dirty[ocl] = True
17625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl.files = Add(cl.files, files)
17635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	dirty[cl] = True
17645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for d, _ in dirty.items():
17655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		d.Flush(ui, repo)
17665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return
17675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
17695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# hg gofmt
17705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
17725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def gofmt(ui, repo, *pats, **opts):
17735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""apply gofmt to modified files
17745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Applies gofmt to the modified files in the repository that match
17765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	the given patterns.
17775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
17785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if codereview_disabled:
17795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return codereview_disabled
17805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = ChangedExistingFiles(ui, repo, pats, opts)
17825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = gofmt_required(files)
17835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not files:
17845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "no modified go files"
17855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cwd = os.getcwd()
17865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
17875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	try:
17885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cmd = ["gofmt", "-l"]
17895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not opts["list"]:
17905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cmd += ["-w"]
17915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if os.spawnvp(os.P_WAIT, "gofmt", cmd + files) != 0:
17925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			raise hg_util.Abort("gofmt did not exit cleanly")
17935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	except hg_error.Abort, e:
17945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise
17955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	except:
17965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise hg_util.Abort("gofmt: " + ExceptionDetail())
17975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return
17985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
17995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def gofmt_required(files):
18005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return [f for f in files if (not f.startswith('test/') or f.startswith('test/bench/')) and f.endswith('.go')]
18015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
18035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# hg mail
18045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
18065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def mail(ui, repo, *pats, **opts):
18075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""mail a change for review
18085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Uploads a patch to the code review server and then sends mail
18105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	to the reviewer and CC list asking for a review.
18115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
18125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if codereview_disabled:
18135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return codereview_disabled
18145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl, err = CommandLineCL(ui, repo, pats, opts, defaultcc=defaultcc)
18165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if err != "":
18175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return err
18185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl.Upload(ui, repo, gofmt_just_warn=True)
18195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not cl.reviewer:
18205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# If no reviewer is listed, assign the review to defaultcc.
18215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# This makes sure that it appears in the
18225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# codereview.appspot.com/user/defaultcc
18235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# page, so that it doesn't get dropped on the floor.
18245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not defaultcc:
18255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return "no reviewers listed in CL"
18265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.cc = Sub(cl.cc, defaultcc)
18275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.reviewer = defaultcc
18285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.Flush(ui, repo)
18295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if cl.files == []:
18315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "no changed files, not sending mail"
18325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl.Mail(ui, repo)
18345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
18365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# hg p / hg pq / hg ps / hg pending
18375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
18395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def ps(ui, repo, *pats, **opts):
18405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""alias for hg p --short
18415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
18425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	opts['short'] = True
18435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return pending(ui, repo, *pats, **opts)
18445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
18465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def pq(ui, repo, *pats, **opts):
18475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""alias for hg p --quick
18485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
18495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	opts['quick'] = True
18505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return pending(ui, repo, *pats, **opts)
18515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
18535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def pending(ui, repo, *pats, **opts):
18545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""show pending changes
18555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Lists pending changes followed by a list of unassigned but modified files.
18575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
18585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if codereview_disabled:
18595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return codereview_disabled
18605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	quick = opts.get('quick', False)
18625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	short = opts.get('short', False)
18635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	m = LoadAllCL(ui, repo, web=not quick and not short)
18645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	names = m.keys()
18655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	names.sort()
18665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for name in names:
18675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl = m[name]
18685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if short:
18695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.write(name + "\t" + line1(cl.desc) + "\n")
18705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
18715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.write(cl.PendingText(quick=quick) + "\n")
18725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if short:
18745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return
18755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	files = DefaultFiles(ui, repo, [])
18765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if len(files) > 0:
18775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		s = "Changed files not in any CL:\n"
18785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for f in files:
18795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			s += "\t" + f + "\n"
18805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.write(s)
18815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
18835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# hg submit
18845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def need_sync():
18865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	raise hg_util.Abort("local repository out of date; must sync before submit")
18875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
18895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def submit(ui, repo, *pats, **opts):
18905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""submit change to remote repository
18915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Submits change to remote repository.
18935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Bails out if the local repository is not in sync with the remote one.
18945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
18955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if codereview_disabled:
18965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return codereview_disabled
18975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
18985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# We already called this on startup but sometimes Mercurial forgets.
18995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	set_mercurial_encoding_to_utf8()
19005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not opts["no_incoming"] and hg_incoming(ui, repo):
19025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		need_sync()
19035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl, err = CommandLineCL(ui, repo, pats, opts, defaultcc=defaultcc)
19055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if err != "":
19065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return err
19075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	user = None
19095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if cl.copied_from:
19105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		user = cl.copied_from
19115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	userline = CheckContributor(ui, repo, user)
19125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(userline, str)
19135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	about = ""
19155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if cl.reviewer:
19165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		about += "R=" + JoinComma([CutDomain(s) for s in cl.reviewer]) + "\n"
19175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if opts.get('tbr'):
19185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		tbr = SplitCommaSpace(opts.get('tbr'))
19195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.reviewer = Add(cl.reviewer, tbr)
19205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		about += "TBR=" + JoinComma([CutDomain(s) for s in tbr]) + "\n"
19215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if cl.cc:
19225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		about += "CC=" + JoinComma([CutDomain(s) for s in cl.cc]) + "\n"
19235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not cl.reviewer:
19255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "no reviewers listed in CL"
19265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not cl.local:
19285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "cannot submit non-local CL"
19295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# upload, to sync current patch and also get change number if CL is new.
19315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not cl.copied_from:
19325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.Upload(ui, repo, gofmt_just_warn=True)
19335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# check gofmt for real; allowed upload to warn in order to save CL.
19355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl.Flush(ui, repo)
19365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	CheckFormat(ui, repo, cl.files)
19375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	about += "%s%s\n" % (server_url_base, cl.name)
19395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if cl.copied_from:
19415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		about += "\nCommitter: " + CheckContributor(ui, repo, None) + "\n"
19425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(about, str)
19435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not cl.mailed and not cl.copied_from:		# in case this is TBR
19455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.Mail(ui, repo)
19465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# submit changes locally
19485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	message = cl.desc.rstrip() + "\n\n" + about
19495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	typecheck(message, str)
19505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	set_status("pushing " + cl.name + " to remote server")
19525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if hg_outgoing(ui, repo):
19545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise hg_util.Abort("local repository corrupt or out-of-phase with remote: found outgoing changes")
19555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	old_heads = len(hg_heads(ui, repo).split())
19575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global commit_okay
19595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	commit_okay = True
19605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ret = hg_commit(ui, repo, *['path:'+f for f in cl.files], message=message, user=userline)
19615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	commit_okay = False
19625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if ret:
19635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "nothing changed"
19645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	node = repo["-1"].node()
19655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# push to remote; if it fails for any reason, roll back
19665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	try:
19675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		new_heads = len(hg_heads(ui, repo).split())
19685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if old_heads != new_heads and not (old_heads == 0 and new_heads == 1):
19695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# Created new head, so we weren't up to date.
19705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			need_sync()
19715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Push changes to remote.  If it works, we're committed.  If not, roll back.
19735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		try:
19745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			hg_push(ui, repo)
19755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		except hg_error.Abort, e:
19765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if e.message.find("push creates new heads") >= 0:
19775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# Remote repository had changes we missed.
19785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				need_sync()
19795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			raise
19805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	except:
19815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		real_rollback()
19825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise
19835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
19845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# We're committed. Upload final patch, close review, add commit message.
19855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	changeURL = hg_node.short(node)
19865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	url = ui.expandpath("default")
19875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	m = re.match("(^https?://([^@/]+@)?([^.]+)\.googlecode\.com/hg/?)" + "|" +
19885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"(^https?://([^@/]+@)?code\.google\.com/p/([^/.]+)(\.[^./]+)?/?)", url)
19895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if m:
19905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if m.group(1): # prj.googlecode.com/hg/ case
19915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			changeURL = "http://code.google.com/p/%s/source/detail?r=%s" % (m.group(3), changeURL)
19925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		elif m.group(4) and m.group(7): # code.google.com/p/prj.subrepo/ case
19935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			changeURL = "http://code.google.com/p/%s/source/detail?r=%s&repo=%s" % (m.group(6), changeURL, m.group(7)[1:])
19945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		elif m.group(4): # code.google.com/p/prj/ case
19955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			changeURL = "http://code.google.com/p/%s/source/detail?r=%s" % (m.group(6), changeURL)
19965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
19975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			print >>sys.stderr, "URL: ", url
19985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	else:
19995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		print >>sys.stderr, "URL: ", url
20005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	pmsg = "*** Submitted as " + changeURL + " ***\n\n" + message
20015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# When posting, move reviewers to CC line,
20035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# so that the issue stops showing up in their "My Issues" page.
20045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	PostMessage(ui, cl.name, pmsg, reviewers="", cc=JoinComma(cl.reviewer+cl.cc))
20055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not cl.copied_from:
20075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		EditDesc(cl.name, closed=True, private=cl.private)
20085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl.Delete(ui, repo)
20095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	c = repo[None]
20115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if c.branch() == releaseBranch and not c.modified() and not c.added() and not c.removed():
20125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.write("switching from %s to default branch.\n" % releaseBranch)
20135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		err = hg_clean(repo, "default")
20145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err:
20155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return err
20165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return None
20175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
20195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# hg sync
20205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
20225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def sync(ui, repo, **opts):
20235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""synchronize with remote repository
20245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Incorporates recent changes from the remote repository
20265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	into the local repository.
20275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
20285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if codereview_disabled:
20295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return codereview_disabled
20305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not opts["local"]:
20325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		err = hg_pull(ui, repo, update=True)
20335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if err:
20345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return err
20355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	sync_changes(ui, repo)
20365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def sync_changes(ui, repo):
20385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Look through recent change log descriptions to find
20395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# potential references to http://.*/our-CL-number.
20405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Double-check them by looking at the Rietveld log.
20415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for rev in hg_log(ui, repo, limit=100, template="{node}\n").split():
20425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		desc = repo[rev].description().strip()
20435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for clname in re.findall('(?m)^http://(?:[^\n]+)/([0-9]+)$', desc):
20445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if IsLocalCL(ui, repo, clname) and IsRietveldSubmitted(ui, clname, repo[rev].hex()):
20455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.warn("CL %s submitted as %s; closing\n" % (clname, repo[rev]))
20465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				cl, err = LoadCL(ui, repo, clname, web=False)
20475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if err != "":
20485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					ui.warn("loading CL %s: %s\n" % (clname, err))
20495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					continue
20505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if not cl.copied_from:
20515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					EditDesc(cl.name, closed=True, private=cl.private)
20525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				cl.Delete(ui, repo)
20535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Remove files that are not modified from the CLs in which they appear.
20555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	all = LoadAllCL(ui, repo, web=False)
20565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	changed = ChangedFiles(ui, repo, [])
20575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for cl in all.values():
20585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		extra = Sub(cl.files, changed)
20595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if extra:
20605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ui.warn("Removing unmodified files from CL %s:\n" % (cl.name,))
20615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for f in extra:
20625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.warn("\t%s\n" % (f,))
20635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.files = Sub(cl.files, extra)
20645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			cl.Flush(ui, repo)
20655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not cl.files:
20665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if not cl.copied_from:
20675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.warn("CL %s has no files; delete (abandon) with hg change -d %s\n" % (cl.name, cl.name))
20685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			else:
20695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ui.warn("CL %s has no files; delete locally with hg change -D %s\n" % (cl.name, cl.name))
20705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return
20715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
20735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# hg upload
20745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@hgcommand
20765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def upload(ui, repo, name, **opts):
20775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""upload diffs to the code review server
20785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Uploads the current modifications for a given change to the server.
20805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
20815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if codereview_disabled:
20825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return codereview_disabled
20835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	repo.ui.quiet = True
20855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl, err = LoadCL(ui, repo, name, web=True)
20865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if err != "":
20875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return err
20885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not cl.local:
20895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "cannot upload non-local change"
20905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl.Upload(ui, repo)
20915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	print "%s%s\n" % (server_url_base, cl.name)
20925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return
20935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
20955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Table of commands, supplied to Mercurial for installation.
20965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
20975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)review_opts = [
20985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	('r', 'reviewer', '', 'add reviewer'),
20995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	('', 'cc', '', 'add cc'),
21005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	('', 'tbr', '', 'add future reviewer'),
21015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	('m', 'message', '', 'change description (for new change)'),
21025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)]
21035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
21045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)cmdtable = {
21055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# The ^ means to show this command in the help text that
21065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# is printed when running hg with no arguments.
21075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^change": (
21085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		change,
21095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[
21105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('d', 'delete', None, 'delete existing change list'),
21115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('D', 'deletelocal', None, 'delete locally, but do not change CL on server'),
21125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('i', 'stdin', None, 'read change list from standard input'),
21135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('o', 'stdout', None, 'print change list to standard output'),
21145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('p', 'pending', None, 'print pending summary to standard output'),
21155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		],
21165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"[-d | -D] [-i] [-o] change# or FILE ..."
21175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
21185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^clpatch": (
21195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		clpatch,
21205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[
21215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
21225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('', 'no_incoming', None, 'disable check for incoming changes'),
21235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		],
21245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"change#"
21255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
21265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Would prefer to call this codereview-login, but then
21275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# hg help codereview prints the help for this command
21285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# instead of the help for the extension.
21295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"code-login": (
21305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		code_login,
21315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[],
21325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"",
21335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
21345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^download": (
21355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		download,
21365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[],
21375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"change#"
21385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
21395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^file": (
21405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		file,
21415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[
21425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('d', 'delete', None, 'delete files from change list (but not repository)'),
21435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		],
21445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"[-d] change# FILE ..."
21455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
21465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^gofmt": (
21475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		gofmt,
21485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[
21495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('l', 'list', None, 'list files that would change, but do not edit them'),
21505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		],
21515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"FILE ..."
21525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
21535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^pending|p": (
21545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		pending,
21555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[
21565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('s', 'short', False, 'show short result form'),
21575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('', 'quick', False, 'do not consult codereview server'),
21585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		],
21595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"[FILE ...]"
21605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
21615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^ps": (
21625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ps,
21635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[],
21645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"[FILE ...]"
21655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
21665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^pq": (
21675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		pq,
21685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[],
21695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"[FILE ...]"
21705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
21715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^mail": (
21725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		mail,
21735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		review_opts + [
21745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		] + hg_commands.walkopts,
21755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"[-r reviewer] [--cc cc] [change# | file ...]"
21765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
21775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^release-apply": (
21785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		release_apply,
21795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[
21805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
21815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('', 'no_incoming', None, 'disable check for incoming changes'),
21825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		],
21835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"change#"
21845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
21855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# TODO: release-start, release-tag, weekly-tag
21865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^submit": (
21875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		submit,
21885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		review_opts + [
21895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('', 'no_incoming', None, 'disable initial incoming check (for testing)'),
21905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		] + hg_commands.walkopts + hg_commands.commitopts + hg_commands.commitopts2,
21915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"[-r reviewer] [--cc cc] [change# | file ...]"
21925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
21935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^sync": (
21945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		sync,
21955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[
21965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('', 'local', None, 'do not pull changes from remote repository')
21975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		],
21985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"[--local]",
21995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
22005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^undo": (
22015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		undo,
22025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[
22035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
22045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			('', 'no_incoming', None, 'disable check for incoming changes'),
22055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		],
22065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"change#"
22075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
22085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"^upload": (
22095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		upload,
22105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[],
22115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"change#"
22125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	),
22135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
22145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
22165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Mercurial extension initialization
22175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def norollback(*pats, **opts):
22195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""(disabled when using this extension)"""
22205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	raise hg_util.Abort("codereview extension enabled; use undo instead of rollback")
22215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)codereview_init = False
22235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def reposetup(ui, repo):
22255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global codereview_disabled
22265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global defaultcc
22275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# reposetup gets called both for the local repository
22295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# and also for any repository we are pulling or pushing to.
22305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Only initialize the first time.
22315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global codereview_init
22325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if codereview_init:
22335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return
22345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	codereview_init = True
22355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Read repository-specific options from lib/codereview/codereview.cfg or codereview.cfg.
22375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	root = ''
22385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	try:
22395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		root = repo.root
22405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	except:
22415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Yes, repo might not have root; see issue 959.
22425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		codereview_disabled = 'codereview disabled: repository has no root'
22435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return
22445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	repo_config_path = ''
22465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	p1 = root + '/lib/codereview/codereview.cfg'
22475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	p2 = root + '/codereview.cfg'
22485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if os.access(p1, os.F_OK):
22495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		repo_config_path = p1
22505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	else:
22515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		repo_config_path = p2
22525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	try:
22535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		f = open(repo_config_path)
22545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for line in f:
22555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if line.startswith('defaultcc:'):
22565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				defaultcc = SplitCommaSpace(line[len('defaultcc:'):])
22575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if line.startswith('contributors:'):
22585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				global contributorsURL
22595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				contributorsURL = line[len('contributors:'):].strip()
22605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	except:
22615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		codereview_disabled = 'codereview disabled: cannot open ' + repo_config_path
22625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return
22635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	remote = ui.config("paths", "default", "")
22655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if remote.find("://") < 0:
22665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise hg_util.Abort("codereview: default path '%s' is not a URL" % (remote,))
22675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	InstallMatch(ui, repo)
22695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	RietveldSetup(ui, repo)
22705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Disable the Mercurial commands that might change the repository.
22725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Only commands in this extension are supposed to do that.
22735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ui.setconfig("hooks", "precommit.codereview", precommithook)
22745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Rollback removes an existing commit.  Don't do that either.
22765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global real_rollback
22775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	real_rollback = repo.rollback
22785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	repo.rollback = norollback
22795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
22825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Wrappers around upload.py for interacting with Rietveld
22835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from HTMLParser import HTMLParser
22855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
22865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# HTML form parser
22875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class FormParser(HTMLParser):
22885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def __init__(self):
22895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.map = {}
22905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.curtag = None
22915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.curdata = None
22925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		HTMLParser.__init__(self)
22935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def handle_starttag(self, tag, attrs):
22945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if tag == "input":
22955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			key = None
22965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			value = ''
22975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for a in attrs:
22985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if a[0] == 'name':
22995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					key = a[1]
23005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if a[0] == 'value':
23015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					value = a[1]
23025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if key is not None:
23035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				self.map[key] = value
23045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if tag == "textarea":
23055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			key = None
23065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for a in attrs:
23075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if a[0] == 'name':
23085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					key = a[1]
23095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if key is not None:
23105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				self.curtag = key
23115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				self.curdata = ''
23125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def handle_endtag(self, tag):
23135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if tag == "textarea" and self.curtag is not None:
23145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.map[self.curtag] = self.curdata
23155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.curtag = None
23165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.curdata = None
23175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def handle_charref(self, name):
23185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.handle_data(unichr(int(name)))
23195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def handle_entityref(self, name):
23205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		import htmlentitydefs
23215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if name in htmlentitydefs.entitydefs:
23225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.handle_data(htmlentitydefs.entitydefs[name])
23235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
23245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.handle_data("&" + name + ";")
23255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def handle_data(self, data):
23265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.curdata is not None:
23275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.curdata += data
23285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
23295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def JSONGet(ui, path):
23305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	try:
23315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		data = MySend(path, force_auth=False)
23325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		typecheck(data, str)
23335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		d = fix_json(json.loads(data))
23345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	except:
23355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ui.warn("JSONGet %s: %s\n" % (path, ExceptionDetail()))
23365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return None
23375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return d
23385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
23395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Clean up json parser output to match our expectations:
23405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   * all strings are UTF-8-encoded str, not unicode.
23415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   * missing fields are missing, not None,
23425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#     so that d.get("foo", defaultvalue) works.
23435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def fix_json(x):
23445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if type(x) in [str, int, float, bool, type(None)]:
23455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		pass
23465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	elif type(x) is unicode:
23475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		x = x.encode("utf-8")
23485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	elif type(x) is list:
23495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for i in range(len(x)):
23505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			x[i] = fix_json(x[i])
23515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	elif type(x) is dict:
23525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		todel = []
23535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for k in x:
23545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if x[k] is None:
23555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				todel.append(k)
23565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			else:
23575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				x[k] = fix_json(x[k])
23585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for k in todel:
23595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			del x[k]
23605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	else:
23615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise hg_util.Abort("unknown type " + str(type(x)) + " in fix_json")
23625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if type(x) is str:
23635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		x = x.replace('\r\n', '\n')
23645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return x
23655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
23665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def IsRietveldSubmitted(ui, clname, hex):
23675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	dict = JSONGet(ui, "/api/" + clname + "?messages=true")
23685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if dict is None:
23695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return False
23705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for msg in dict.get("messages", []):
23715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		text = msg.get("text", "")
23725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		m = re.match('\*\*\* Submitted as [^*]*?([0-9a-f]+) \*\*\*', text)
23735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if m is not None and len(m.group(1)) >= 8 and hex.startswith(m.group(1)):
23745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return True
23755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return False
23765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
23775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def IsRietveldMailed(cl):
23785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for msg in cl.dict.get("messages", []):
23795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if msg.get("text", "").find("I'd like you to review this change") >= 0:
23805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return True
23815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return False
23825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
23835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def DownloadCL(ui, repo, clname):
23845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	set_status("downloading CL " + clname)
23855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	cl, err = LoadCL(ui, repo, clname, web=True)
23865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if err != "":
23875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return None, None, None, "error loading CL %s: %s" % (clname, err)
23885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
23895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Find most recent diff
23905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	diffs = cl.dict.get("patchsets", [])
23915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not diffs:
23925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return None, None, None, "CL has no patch sets"
23935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	patchid = diffs[-1]
23945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
23955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	patchset = JSONGet(ui, "/api/" + clname + "/" + str(patchid))
23965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if patchset is None:
23975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return None, None, None, "error loading CL patchset %s/%d" % (clname, patchid)
23985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if patchset.get("patchset", 0) != patchid:
23995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return None, None, None, "malformed patchset information"
24005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
24015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	vers = ""
24025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	msg = patchset.get("message", "").split()
24035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if len(msg) >= 3 and msg[0] == "diff" and msg[1] == "-r":
24045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		vers = msg[2]
24055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	diff = "/download/issue" + clname + "_" + str(patchid) + ".diff"
24065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
24075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	diffdata = MySend(diff, force_auth=False)
24085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
24095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Print warning if email is not in CONTRIBUTORS file.
24105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	email = cl.dict.get("owner_email", "")
24115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not email:
24125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return None, None, None, "cannot find owner for %s" % (clname)
24135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	him = FindContributor(ui, repo, email)
24145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	me = FindContributor(ui, repo, None)
24155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if him == me:
24165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.mailed = IsRietveldMailed(cl)
24175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	else:
24185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cl.copied_from = email
24195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
24205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return cl, vers, diffdata, ""
24215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
24225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def MySend(request_path, payload=None,
24235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		content_type="application/octet-stream",
24245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		timeout=None, force_auth=True,
24255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		**kwargs):
24265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Run MySend1 maybe twice, because Rietveld is unreliable."""
24275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	try:
24285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return MySend1(request_path, payload, content_type, timeout, force_auth, **kwargs)
24295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	except Exception, e:
24305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if type(e) != urllib2.HTTPError or e.code != 500:	# only retry on HTTP 500 error
24315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			raise
24325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		print >>sys.stderr, "Loading "+request_path+": "+ExceptionDetail()+"; trying again in 2 seconds."
24335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		time.sleep(2)
24345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return MySend1(request_path, payload, content_type, timeout, force_auth, **kwargs)
24355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
24365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Like upload.py Send but only authenticates when the
24375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# redirect is to www.google.com/accounts.  This keeps
24385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# unnecessary redirects from happening during testing.
24395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def MySend1(request_path, payload=None,
24405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				content_type="application/octet-stream",
24415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				timeout=None, force_auth=True,
24425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				**kwargs):
24435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Sends an RPC and returns the response.
24445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
24455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Args:
24465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		request_path: The path to send the request to, eg /api/appversion/create.
24475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		payload: The body of the request, or None to send an empty request.
24485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		content_type: The Content-Type header to use.
24495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		timeout: timeout in seconds; default None i.e. no timeout.
24505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			(Note: for large requests on OS X, the timeout doesn't work right.)
24515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		kwargs: Any keyword arguments are converted into query string parameters.
24525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
24535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Returns:
24545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		The response body, as a string.
24555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
24565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# TODO: Don't require authentication.  Let the server say
24575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# whether it is necessary.
24585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global rpc
24595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if rpc == None:
24605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		rpc = GetRpcServer(upload_options)
24615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	self = rpc
24625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not self.authenticated and force_auth:
24635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self._Authenticate()
24645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if request_path is None:
24655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return
24665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
24675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	old_timeout = socket.getdefaulttimeout()
24685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	socket.setdefaulttimeout(timeout)
24695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	try:
24705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		tries = 0
24715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		while True:
24725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			tries += 1
24735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			args = dict(kwargs)
24745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			url = "http://%s%s" % (self.host, request_path)
24755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if args:
24765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				url += "?" + urllib.urlencode(args)
24775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			req = self._CreateRequest(url=url, data=payload)
24785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			req.add_header("Content-Type", content_type)
24795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			try:
24805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				f = self.opener.open(req)
24815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				response = f.read()
24825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				f.close()
24835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# Translate \r\n into \n, because Rietveld doesn't.
24845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				response = response.replace('\r\n', '\n')
24855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# who knows what urllib will give us
24865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if type(response) == unicode:
24875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					response = response.encode("utf-8")
24885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				typecheck(response, str)
24895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				return response
24905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			except urllib2.HTTPError, e:
24915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if tries > 3:
24925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					raise
24935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				elif e.code == 401:
24945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					self._Authenticate()
24955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				elif e.code == 302:
24965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					loc = e.info()["location"]
24975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					if not loc.startswith('https://www.google.com/a') or loc.find('/ServiceLogin') < 0:
24985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)						return ''
24995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					self._Authenticate()
25005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				else:
25015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					raise
25025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	finally:
25035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		socket.setdefaulttimeout(old_timeout)
25045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def GetForm(url):
25065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	f = FormParser()
25075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	f.feed(ustr(MySend(url)))	# f.feed wants unicode
25085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	f.close()
25095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# convert back to utf-8 to restore sanity
25105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	m = {}
25115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for k,v in f.map.items():
25125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		m[k.encode("utf-8")] = v.replace("\r\n", "\n").encode("utf-8")
25135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return m
25145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def EditDesc(issue, subject=None, desc=None, reviewers=None, cc=None, closed=False, private=False):
25165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	set_status("uploading change to description")
25175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	form_fields = GetForm("/" + issue + "/edit")
25185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if subject is not None:
25195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		form_fields['subject'] = subject
25205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if desc is not None:
25215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		form_fields['description'] = desc
25225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if reviewers is not None:
25235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		form_fields['reviewers'] = reviewers
25245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if cc is not None:
25255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		form_fields['cc'] = cc
25265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if closed:
25275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		form_fields['closed'] = "checked"
25285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if private:
25295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		form_fields['private'] = "checked"
25305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ctype, body = EncodeMultipartFormData(form_fields.items(), [])
25315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	response = MySend("/" + issue + "/edit", body, content_type=ctype)
25325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if response != "":
25335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		print >>sys.stderr, "Error editing description:\n" + "Sent form: \n", form_fields, "\n", response
25345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		sys.exit(2)
25355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def PostMessage(ui, issue, message, reviewers=None, cc=None, send_mail=True, subject=None):
25375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	set_status("uploading message")
25385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	form_fields = GetForm("/" + issue + "/publish")
25395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if reviewers is not None:
25405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		form_fields['reviewers'] = reviewers
25415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if cc is not None:
25425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		form_fields['cc'] = cc
25435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if send_mail:
25445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		form_fields['send_mail'] = "checked"
25455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	else:
25465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		del form_fields['send_mail']
25475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if subject is not None:
25485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		form_fields['subject'] = subject
25495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	form_fields['message'] = message
25505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	form_fields['message_only'] = '1'	# Don't include draft comments
25525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if reviewers is not None or cc is not None:
25535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		form_fields['message_only'] = ''	# Must set '' in order to override cc/reviewer
25545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	ctype = "applications/x-www-form-urlencoded"
25555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	body = urllib.urlencode(form_fields)
25565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	response = MySend("/" + issue + "/publish", body, content_type=ctype)
25575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if response != "":
25585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		print response
25595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		sys.exit(2)
25605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class opt(object):
25625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	pass
25635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def RietveldSetup(ui, repo):
25655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global force_google_account
25665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global rpc
25675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global server
25685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global server_url_base
25695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global upload_options
25705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global verbosity
25715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not ui.verbose:
25735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		verbosity = 0
25745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# Config options.
25765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	x = ui.config("codereview", "server")
25775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if x is not None:
25785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		server = x
25795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# TODO(rsc): Take from ui.username?
25815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	email = None
25825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	x = ui.config("codereview", "email")
25835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if x is not None:
25845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		email = x
25855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	server_url_base = "http://" + server + "/"
25875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	testing = ui.config("codereview", "testing")
25895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	force_google_account = ui.configbool("codereview", "force_google_account", False)
25905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
25915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options = opt()
25925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.email = email
25935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.host = None
25945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.verbose = 0
25955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.description = None
25965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.description_file = None
25975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.reviewers = None
25985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.cc = None
25995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.message = None
26005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.issue = None
26015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.download_base = False
26025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.revision = None
26035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.send_mail = False
26045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.vcs = None
26055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.server = server
26065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	upload_options.save_cookies = True
26075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if testing:
26095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		upload_options.save_cookies = False
26105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		upload_options.email = "test@example.com"
26115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	rpc = None
26135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	global releaseBranch
26151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci	tags = repo.branchmap().keys()
26165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if 'release-branch.go10' in tags:
26175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# NOTE(rsc): This tags.sort is going to get the wrong
26185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# answer when comparing release-branch.go9 with
26195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# release-branch.go10.  It will be a while before we care.
26205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise hg_util.Abort('tags.sort needs to be fixed for release-branch.go10')
26215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	tags.sort()
26225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for t in tags:
26235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if t.startswith('release-branch.go'):
26245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			releaseBranch = t
26255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#######################################################################
26275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# http://codereview.appspot.com/static/upload.py, heavily edited.
26285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python
26305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
26315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright 2007 Google Inc.
26325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
26335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Licensed under the Apache License, Version 2.0 (the "License");
26345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# you may not use this file except in compliance with the License.
26355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# You may obtain a copy of the License at
26365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
26375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#	http://www.apache.org/licenses/LICENSE-2.0
26385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
26395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Unless required by applicable law or agreed to in writing, software
26405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# distributed under the License is distributed on an "AS IS" BASIS,
26415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# See the License for the specific language governing permissions and
26435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# limitations under the License.
26445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""Tool for uploading diffs from a version control system to the codereview app.
26465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Usage summary: upload.py [options] [-- diff_options]
26485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Diff options are passed to the diff command of the underlying system.
26505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Supported version control systems:
26525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Git
26535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Mercurial
26545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Subversion
26555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)It is important for Git/Mercurial users to specify a tree/node/branch to diff
26575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)against by using the '--rev' option.
26585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
26595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# This code is derived from appcfg.py in the App Engine SDK (open source),
26605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# and from ASPN recipe #146306.
26615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import cookielib
26635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import getpass
26645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging
26655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import mimetypes
26665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import optparse
26675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
26685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import re
26695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import socket
26705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import subprocess
26715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
26725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urllib
26735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urllib2
26745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urlparse
26755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The md5 module was deprecated in Python 2.5.
26775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)try:
26785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	from hashlib import md5
26795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)except ImportError:
26805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	from md5 import md5
26815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)try:
26835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	import readline
26845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)except ImportError:
26855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	pass
26865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The logging verbosity:
26885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  0: Errors only.
26895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  1: Status messages.
26905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  2: Info logs.
26915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  3: Debug logs.
26925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)verbosity = 1
26935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Max size of patch or base file.
26955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)MAX_UPLOAD_SIZE = 900 * 1024
26965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
26975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# whitelist for non-binary filetypes which do not start with "text/"
26985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# .mm (Objective-C) shows up as application/x-freemind on my Linux box.
26995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)TEXT_MIMETYPES = [
27005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	'application/javascript',
27015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	'application/x-javascript',
27025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	'application/x-freemind'
27035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)]
27045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def GetEmail(prompt):
27065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Prompts the user for their email address and returns it.
27075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	The last used email address is saved to a file and offered up as a suggestion
27095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	to the user. If the user presses enter without typing in anything the last
27105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	used email address is used. If the user enters a new address, it is saved
27115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for next time we prompt.
27125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
27145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	last_email_file_name = os.path.expanduser("~/.last_codereview_email_address")
27155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	last_email = ""
27165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if os.path.exists(last_email_file_name):
27175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		try:
27185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			last_email_file = open(last_email_file_name, "r")
27195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			last_email = last_email_file.readline().strip("\n")
27205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			last_email_file.close()
27215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			prompt += " [%s]" % last_email
27225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		except IOError, e:
27235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			pass
27245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	email = raw_input(prompt + ": ").strip()
27255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if email:
27265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		try:
27275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			last_email_file = open(last_email_file_name, "w")
27285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			last_email_file.write(email)
27295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			last_email_file.close()
27305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		except IOError, e:
27315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			pass
27325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	else:
27335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		email = last_email
27345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return email
27355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def StatusUpdate(msg):
27385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Print a status message to stdout.
27395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	If 'verbosity' is greater than 0, print the message.
27415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Args:
27435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		msg: The string to print.
27445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
27455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if verbosity > 0:
27465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		print msg
27475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def ErrorExit(msg):
27505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Print an error message to stderr and exit."""
27515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	print >>sys.stderr, msg
27525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	sys.exit(1)
27535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class ClientLoginError(urllib2.HTTPError):
27565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Raised to indicate there was an error authenticating with ClientLogin."""
27575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def __init__(self, url, code, msg, headers, args):
27595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		urllib2.HTTPError.__init__(self, url, code, msg, headers, None)
27605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.args = args
27615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.reason = args["Error"]
27625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class AbstractRpcServer(object):
27655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Provides a common interface for a simple RPC server."""
27665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def __init__(self, host, auth_function, host_override=None, extra_headers={}, save_cookies=False):
27685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Creates a new HttpRpcServer.
27695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Args:
27715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			host: The host to send requests to.
27725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			auth_function: A function that takes no arguments and returns an
27735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				(email, password) tuple when called. Will be called if authentication
27745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				is required.
27755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			host_override: The host header to send to the server (defaults to host).
27765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			extra_headers: A dict of extra headers to append to every request.
27775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			save_cookies: If True, save the authentication cookies to local disk.
27785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				If False, use an in-memory cookiejar instead.  Subclasses must
27795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				implement this functionality.  Defaults to False.
27805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""
27815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.host = host
27825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.host_override = host_override
27835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.auth_function = auth_function
27845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.authenticated = False
27855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.extra_headers = extra_headers
27865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.save_cookies = save_cookies
27875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.opener = self._GetOpener()
27885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.host_override:
27895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			logging.info("Server: %s; Host: %s", self.host, self.host_override)
27905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
27915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			logging.info("Server: %s", self.host)
27925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def _GetOpener(self):
27945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Returns an OpenerDirector for making HTTP requests.
27955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
27965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Returns:
27975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			A urllib2.OpenerDirector object.
27985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""
27995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise NotImplementedError()
28005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
28015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def _CreateRequest(self, url, data=None):
28025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Creates a new urllib request."""
28035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		logging.debug("Creating request for: '%s' with payload:\n%s", url, data)
28045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		req = urllib2.Request(url, data=data)
28055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.host_override:
28065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			req.add_header("Host", self.host_override)
28075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for key, value in self.extra_headers.iteritems():
28085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			req.add_header(key, value)
28095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return req
28105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
28115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def _GetAuthToken(self, email, password):
28125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Uses ClientLogin to authenticate the user, returning an auth token.
28135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
28145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Args:
28155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			email:    The user's email address
28165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			password: The user's password
28175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
28185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Raises:
28195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ClientLoginError: If there was an error authenticating with ClientLogin.
28205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			HTTPError: If there was some other form of HTTP error.
28215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
28225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Returns:
28235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			The authentication token returned by ClientLogin.
28245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""
28255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		account_type = "GOOGLE"
28265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.host.endswith(".google.com") and not force_google_account:
28275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# Needed for use inside Google.
28285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			account_type = "HOSTED"
28295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		req = self._CreateRequest(
28305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				url="https://www.google.com/accounts/ClientLogin",
28315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				data=urllib.urlencode({
28325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)						"Email": email,
28335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)						"Passwd": password,
28345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)						"service": "ah",
28355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)						"source": "rietveld-codereview-upload",
28365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)						"accountType": account_type,
28375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				}),
28385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		)
28395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		try:
28405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			response = self.opener.open(req)
28415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			response_body = response.read()
28425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			response_dict = dict(x.split("=") for x in response_body.split("\n") if x)
28435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return response_dict["Auth"]
28445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		except urllib2.HTTPError, e:
28455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if e.code == 403:
28465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				body = e.read()
28475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				response_dict = dict(x.split("=", 1) for x in body.split("\n") if x)
28485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				raise ClientLoginError(req.get_full_url(), e.code, e.msg, e.headers, response_dict)
28495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			else:
28505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				raise
28515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
28525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def _GetAuthCookie(self, auth_token):
28535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Fetches authentication cookies for an authentication token.
28545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
28555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Args:
28565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			auth_token: The authentication token returned by ClientLogin.
28575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
28585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Raises:
28595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			HTTPError: If there was an error fetching the authentication cookies.
28605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""
28615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# This is a dummy value to allow us to identify when we're successful.
28625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		continue_location = "http://localhost/"
28635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		args = {"continue": continue_location, "auth": auth_token}
28645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		req = self._CreateRequest("http://%s/_ah/login?%s" % (self.host, urllib.urlencode(args)))
28655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		try:
28665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			response = self.opener.open(req)
28675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		except urllib2.HTTPError, e:
28685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			response = e
28695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if (response.code != 302 or
28705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				response.info()["location"] != continue_location):
28715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg, response.headers, response.fp)
28725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.authenticated = True
28735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
28745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def _Authenticate(self):
28755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Authenticates the user.
28765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
28775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		The authentication process works as follows:
28785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		1) We get a username and password from the user
28795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		2) We use ClientLogin to obtain an AUTH token for the user
28805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				(see http://code.google.com/apis/accounts/AuthForInstalledApps.html).
28815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		3) We pass the auth token to /_ah/login on the server to obtain an
28825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				authentication cookie. If login was successful, it tries to redirect
28835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				us to the URL we provided.
28845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
28855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		If we attempt to access the upload API without first obtaining an
28865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		authentication cookie, it returns a 401 response (or a 302) and
28875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		directs us to authenticate ourselves with ClientLogin.
28885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""
28895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for i in range(3):
28905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			credentials = self.auth_function()
28915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			try:
28925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				auth_token = self._GetAuthToken(credentials[0], credentials[1])
28935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			except ClientLoginError, e:
28945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if e.reason == "BadAuthentication":
28955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					print >>sys.stderr, "Invalid username or password."
28965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					continue
28975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if e.reason == "CaptchaRequired":
28985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					print >>sys.stderr, (
28995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)						"Please go to\n"
29005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)						"https://www.google.com/accounts/DisplayUnlockCaptcha\n"
29015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)						"and verify you are a human.  Then try again.")
29025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					break
29035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if e.reason == "NotVerified":
29045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					print >>sys.stderr, "Account not verified."
29055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					break
29065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if e.reason == "TermsNotAgreed":
29075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					print >>sys.stderr, "User has not agreed to TOS."
29085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					break
29095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if e.reason == "AccountDeleted":
29105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					print >>sys.stderr, "The user account has been deleted."
29115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					break
29125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if e.reason == "AccountDisabled":
29135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					print >>sys.stderr, "The user account has been disabled."
29145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					break
29155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if e.reason == "ServiceDisabled":
29165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					print >>sys.stderr, "The user's access to the service has been disabled."
29175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					break
29185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if e.reason == "ServiceUnavailable":
29195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					print >>sys.stderr, "The service is not available; try again later."
29205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					break
29215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				raise
29225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self._GetAuthCookie(auth_token)
29235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return
29245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
29255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def Send(self, request_path, payload=None,
29265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					content_type="application/octet-stream",
29275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					timeout=None,
29285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					**kwargs):
29295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Sends an RPC and returns the response.
29305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
29315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Args:
29325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			request_path: The path to send the request to, eg /api/appversion/create.
29335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			payload: The body of the request, or None to send an empty request.
29345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			content_type: The Content-Type header to use.
29355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			timeout: timeout in seconds; default None i.e. no timeout.
29365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				(Note: for large requests on OS X, the timeout doesn't work right.)
29375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			kwargs: Any keyword arguments are converted into query string parameters.
29385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
29395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Returns:
29405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			The response body, as a string.
29415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""
29425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# TODO: Don't require authentication.  Let the server say
29435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# whether it is necessary.
29445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not self.authenticated:
29455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self._Authenticate()
29465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
29475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		old_timeout = socket.getdefaulttimeout()
29485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		socket.setdefaulttimeout(timeout)
29495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		try:
29505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			tries = 0
29515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			while True:
29525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				tries += 1
29535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				args = dict(kwargs)
29545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				url = "http://%s%s" % (self.host, request_path)
29555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if args:
29565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					url += "?" + urllib.urlencode(args)
29575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				req = self._CreateRequest(url=url, data=payload)
29585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				req.add_header("Content-Type", content_type)
29595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				try:
29605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					f = self.opener.open(req)
29615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					response = f.read()
29625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					f.close()
29635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					return response
29645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				except urllib2.HTTPError, e:
29655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					if tries > 3:
29665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)						raise
29675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					elif e.code == 401 or e.code == 302:
29685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)						self._Authenticate()
29695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					else:
29705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)						raise
29715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		finally:
29725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			socket.setdefaulttimeout(old_timeout)
29735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
29745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
29755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class HttpRpcServer(AbstractRpcServer):
29765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Provides a simplified RPC-style interface for HTTP requests."""
29775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
29785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def _Authenticate(self):
29795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Save the cookie jar after authentication."""
29805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		super(HttpRpcServer, self)._Authenticate()
29815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.save_cookies:
29825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			StatusUpdate("Saving authentication cookies to %s" % self.cookie_file)
29835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.cookie_jar.save()
29845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
29855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def _GetOpener(self):
29865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Returns an OpenerDirector that supports cookies and ignores redirects.
29875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
29885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Returns:
29895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			A urllib2.OpenerDirector object.
29905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""
29915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		opener = urllib2.OpenerDirector()
29925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		opener.add_handler(urllib2.ProxyHandler())
29935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		opener.add_handler(urllib2.UnknownHandler())
29945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		opener.add_handler(urllib2.HTTPHandler())
29955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		opener.add_handler(urllib2.HTTPDefaultErrorHandler())
29965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		opener.add_handler(urllib2.HTTPSHandler())
29975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		opener.add_handler(urllib2.HTTPErrorProcessor())
29985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.save_cookies:
29995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.cookie_file = os.path.expanduser("~/.codereview_upload_cookies_" + server)
30005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.cookie_jar = cookielib.MozillaCookieJar(self.cookie_file)
30015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if os.path.exists(self.cookie_file):
30025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				try:
30035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					self.cookie_jar.load()
30045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					self.authenticated = True
30055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					StatusUpdate("Loaded authentication cookies from %s" % self.cookie_file)
30065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				except (cookielib.LoadError, IOError):
30075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					# Failed to load cookies - just ignore them.
30085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					pass
30095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			else:
30105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# Create an empty cookie file with mode 600
30115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				fd = os.open(self.cookie_file, os.O_CREAT, 0600)
30125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				os.close(fd)
30135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# Always chmod the cookie file
30145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			os.chmod(self.cookie_file, 0600)
30155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
30165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# Don't save cookies across runs of update.py.
30175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.cookie_jar = cookielib.CookieJar()
30185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar))
30195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return opener
30205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
30215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
30225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def GetRpcServer(options):
30235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Returns an instance of an AbstractRpcServer.
30245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
30255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Returns:
30265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		A new AbstractRpcServer, on which RPC calls can be made.
30275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
30285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
30295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	rpc_server_class = HttpRpcServer
30305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
30315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def GetUserCredentials():
30325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Prompts the user for a username and password."""
30335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Disable status prints so they don't obscure the password prompt.
30345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		global global_status
30355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		st = global_status
30365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		global_status = None
30375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
30385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		email = options.email
30395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if email is None:
30405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			email = GetEmail("Email (login for uploading to %s)" % options.server)
30415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		password = getpass.getpass("Password for %s: " % email)
30425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
30435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Put status back.
30445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		global_status = st
30455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return (email, password)
30465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
30475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	# If this is the dev_appserver, use fake authentication.
30485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	host = (options.host or options.server).lower()
30495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if host == "localhost" or host.startswith("localhost:"):
30505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		email = options.email
30515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if email is None:
30525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			email = "test@example.com"
30535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			logging.info("Using debug user %s.  Override with --email" % email)
30545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		server = rpc_server_class(
30555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				options.server,
30565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				lambda: (email, "password"),
30575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				host_override=options.host,
30585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				extra_headers={"Cookie": 'dev_appserver_login="%s:False"' % email},
30595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				save_cookies=options.save_cookies)
30605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Don't try to talk to ClientLogin.
30615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		server.authenticated = True
30625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return server
30635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
30645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return rpc_server_class(options.server, GetUserCredentials,
30655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		host_override=options.host, save_cookies=options.save_cookies)
30665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
30675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
30685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def EncodeMultipartFormData(fields, files):
30695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Encode form fields for multipart/form-data.
30705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
30715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Args:
30725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		fields: A sequence of (name, value) elements for regular form fields.
30735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		files: A sequence of (name, filename, value) elements for data to be
30745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					uploaded as files.
30755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Returns:
30765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		(content_type, body) ready for httplib.HTTP instance.
30775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
30785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Source:
30795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306
30805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
30815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	BOUNDARY = '-M-A-G-I-C---B-O-U-N-D-A-R-Y-'
30825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	CRLF = '\r\n'
30835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	lines = []
30845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for (key, value) in fields:
30855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		typecheck(key, str)
30865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		typecheck(value, str)
30875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		lines.append('--' + BOUNDARY)
30885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		lines.append('Content-Disposition: form-data; name="%s"' % key)
30895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		lines.append('')
30905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		lines.append(value)
30915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for (key, filename, value) in files:
30925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		typecheck(key, str)
30935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		typecheck(filename, str)
30945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		typecheck(value, str)
30955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		lines.append('--' + BOUNDARY)
30965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		lines.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
30975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		lines.append('Content-Type: %s' % GetContentType(filename))
30985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		lines.append('')
30995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		lines.append(value)
31005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	lines.append('--' + BOUNDARY + '--')
31015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	lines.append('')
31025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	body = CRLF.join(lines)
31035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
31045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return content_type, body
31055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def GetContentType(filename):
31085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Helper to guess the content-type from the filename."""
31095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
31105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use a shell for subcommands on Windows to get a PATH search.
31135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)use_shell = sys.platform.startswith("win")
31145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def RunShellWithReturnCode(command, print_output=False,
31165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		universal_newlines=True, env=os.environ):
31175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Executes a command and returns the output from stdout and the return code.
31185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Args:
31205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		command: Command to execute.
31215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		print_output: If True, the output is printed to stdout.
31225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			If False, both stdout and stderr are ignored.
31235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		universal_newlines: Use universal_newlines flag (default: True).
31245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Returns:
31265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Tuple (output, return code)
31275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
31285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	logging.info("Running %s", command)
31295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
31305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		shell=use_shell, universal_newlines=universal_newlines, env=env)
31315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if print_output:
31325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		output_array = []
31335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		while True:
31345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			line = p.stdout.readline()
31355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if not line:
31365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				break
31375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			print line.strip("\n")
31385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			output_array.append(line)
31395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		output = "".join(output_array)
31405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	else:
31415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		output = p.stdout.read()
31425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	p.wait()
31435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	errout = p.stderr.read()
31445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if print_output and errout:
31455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		print >>sys.stderr, errout
31465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	p.stdout.close()
31475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	p.stderr.close()
31485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return output, p.returncode
31495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def RunShell(command, silent_ok=False, universal_newlines=True,
31525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		print_output=False, env=os.environ):
31535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	data, retcode = RunShellWithReturnCode(command, print_output, universal_newlines, env)
31545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if retcode:
31555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ErrorExit("Got error status from %s:\n%s" % (command, data))
31565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if not silent_ok and not data:
31575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ErrorExit("No output from %s" % command)
31585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return data
31595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class VersionControlSystem(object):
31625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Abstract base class providing an interface to the VCS."""
31635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def __init__(self, options):
31655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Constructor.
31665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Args:
31685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			options: Command line options.
31695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""
31705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.options = options
31715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def GenerateDiff(self, args):
31735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Return the current diff as a string.
31745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Args:
31765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			args: Extra arguments to pass to the diff command.
31775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""
31785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise NotImplementedError(
31795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				"abstract method -- subclass %s must override" % self.__class__)
31805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def GetUnknownFiles(self):
31825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Return a list of files unknown to the VCS."""
31835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise NotImplementedError(
31845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				"abstract method -- subclass %s must override" % self.__class__)
31855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def CheckForUnknownFiles(self):
31875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Show an "are you sure?" prompt if there are unknown files."""
31885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		unknown_files = self.GetUnknownFiles()
31895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if unknown_files:
31905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			print "The following files are not added to version control:"
31915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for line in unknown_files:
31925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				print line
31935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			prompt = "Are you sure to continue?(y/N) "
31945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			answer = raw_input(prompt).strip()
31955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if answer != "y":
31965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ErrorExit("User aborted")
31975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
31985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def GetBaseFile(self, filename):
31995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Get the content of the upstream version of a file.
32005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
32015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Returns:
32025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			A tuple (base_content, new_content, is_binary, status)
32035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				base_content: The contents of the base file.
32045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				new_content: For text files, this is empty.  For binary files, this is
32055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					the contents of the new file, since the diff output won't contain
32065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					information to reconstruct the current file.
32075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				is_binary: True iff the file is binary.
32085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				status: The status of the file.
32095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""
32105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
32115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise NotImplementedError(
32125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				"abstract method -- subclass %s must override" % self.__class__)
32135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
32145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
32155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def GetBaseFiles(self, diff):
32165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Helper that calls GetBase file for each file in the patch.
32175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
32185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		Returns:
32195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			A dictionary that maps from filename to GetBaseFile's tuple.  Filenames
32205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			are retrieved based on lines that start with "Index:" or
32215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			"Property changes on:".
32225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""
32235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		files = {}
32245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for line in diff.splitlines(True):
32255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if line.startswith('Index:') or line.startswith('Property changes on:'):
32265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				unused, filename = line.split(':', 1)
32275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# On Windows if a file has property changes its filename uses '\'
32285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# instead of '/'.
32295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				filename = to_slash(filename.strip())
32305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				files[filename] = self.GetBaseFile(filename)
32315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return files
32325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
32335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
32345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def UploadBaseFiles(self, issue, rpc_server, patch_list, patchset, options,
32355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)											files):
32365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Uploads the base files (and if necessary, the current ones as well)."""
32375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
32385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		def UploadFile(filename, file_id, content, is_binary, status, is_base):
32395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			"""Uploads a file to the server."""
32405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			set_status("uploading " + filename)
32415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			file_too_large = False
32425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if is_base:
32435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				type = "base"
32445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			else:
32455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				type = "current"
32465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if len(content) > MAX_UPLOAD_SIZE:
32475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				print ("Not uploading the %s file for %s because it's too large." %
32485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)							(type, filename))
32495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				file_too_large = True
32505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				content = ""
32515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			checksum = md5(content).hexdigest()
32525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if options.verbose > 0 and not file_too_large:
32535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				print "Uploading %s file for %s" % (type, filename)
32545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			url = "/%d/upload_content/%d/%d" % (int(issue), int(patchset), file_id)
32555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			form_fields = [
32565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				("filename", filename),
32575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				("status", status),
32585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				("checksum", checksum),
32595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				("is_binary", str(is_binary)),
32605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				("is_current", str(not is_base)),
32615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			]
32625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if file_too_large:
32635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				form_fields.append(("file_too_large", "1"))
32645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if options.email:
32655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				form_fields.append(("user", options.email))
32665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ctype, body = EncodeMultipartFormData(form_fields, [("data", filename, content)])
32675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			response_body = rpc_server.Send(url, body, content_type=ctype)
32685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if not response_body.startswith("OK"):
32695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				StatusUpdate("  --> %s" % response_body)
32705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				sys.exit(1)
32715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
32725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Don't want to spawn too many threads, nor do we want to
32735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# hit Rietveld too hard, or it will start serving 500 errors.
32745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# When 8 works, it's no better than 4, and sometimes 8 is
32755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# too many for Rietveld to handle.
32765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		MAX_PARALLEL_UPLOADS = 4
32775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
32785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		sema = threading.BoundedSemaphore(MAX_PARALLEL_UPLOADS)
32795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		upload_threads = []
32805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		finished_upload_threads = []
32815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
32825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		class UploadFileThread(threading.Thread):
32835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			def __init__(self, args):
32845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				threading.Thread.__init__(self)
32855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				self.args = args
32865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			def run(self):
32875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				UploadFile(*self.args)
32885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				finished_upload_threads.append(self)
32895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				sema.release()
32905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
32915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		def StartUploadFile(*args):
32925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			sema.acquire()
32935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			while len(finished_upload_threads) > 0:
32945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				t = finished_upload_threads.pop()
32955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				upload_threads.remove(t)
32965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				t.join()
32975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			t = UploadFileThread(args)
32985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			upload_threads.append(t)
32995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			t.start()
33005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
33015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		def WaitForUploads():
33025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			for t in upload_threads:
33035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				t.join()
33045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
33055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		patches = dict()
33065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		[patches.setdefault(v, k) for k, v in patch_list]
33075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for filename in patches.keys():
33085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			base_content, new_content, is_binary, status = files[filename]
33095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			file_id_str = patches.get(filename)
33105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if file_id_str.find("nobase") != -1:
33115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				base_content = None
33125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				file_id_str = file_id_str[file_id_str.rfind("_") + 1:]
33135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			file_id = int(file_id_str)
33145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if base_content != None:
33155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				StartUploadFile(filename, file_id, base_content, is_binary, status, True)
33165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if new_content != None:
33175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				StartUploadFile(filename, file_id, new_content, is_binary, status, False)
33185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		WaitForUploads()
33195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
33205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def IsImage(self, filename):
33215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Returns true if the filename has an image extension."""
33225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		mimetype =  mimetypes.guess_type(filename)[0]
33235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not mimetype:
33245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return False
33255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return mimetype.startswith("image/")
33265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
33275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def IsBinary(self, filename):
33285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Returns true if the guessed mimetyped isnt't in text group."""
33295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		mimetype = mimetypes.guess_type(filename)[0]
33305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not mimetype:
33315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return False  # e.g. README, "real" binaries usually have an extension
33325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# special case for text files which don't start with text/
33335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if mimetype in TEXT_MIMETYPES:
33345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			return False
33355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return not mimetype.startswith("text/")
33365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
33375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
33385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class FakeMercurialUI(object):
33395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def __init__(self):
33405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.quiet = True
33415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.output = ''
33425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
33435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def write(self, *args, **opts):
33445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.output += ' '.join(args)
33455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def copy(self):
33465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return self
33475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def status(self, *args, **opts):
33485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		pass
33495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
33505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def formatter(self, topic, opts):
33515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		from mercurial.formatter import plainformatter
33525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return plainformatter(self, topic, opts)
33535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
33545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def readconfig(self, *args, **opts):
33555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		pass
33565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def expandpath(self, *args, **opts):
33575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return global_ui.expandpath(*args, **opts)
33585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def configitems(self, *args, **opts):
33595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return global_ui.configitems(*args, **opts)
33605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def config(self, *args, **opts):
33615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return global_ui.config(*args, **opts)
33625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
33635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)use_hg_shell = False	# set to True to shell out to hg always; slower
33645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
33655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class MercurialVCS(VersionControlSystem):
33665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Implementation of the VersionControlSystem interface for Mercurial."""
33675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
33685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def __init__(self, options, ui, repo):
33695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		super(MercurialVCS, self).__init__(options)
33705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.ui = ui
33715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.repo = repo
33725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.status = None
33735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Absolute path to repository (we can be in a subdir)
33745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.repo_dir = os.path.normpath(repo.root)
33755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Compute the subdir
33765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cwd = os.path.normpath(os.getcwd())
33775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		assert cwd.startswith(self.repo_dir)
33785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		self.subdir = cwd[len(self.repo_dir):].lstrip(r"\/")
33795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.options.revision:
33805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.base_rev = self.options.revision
33815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
33825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			mqparent, err = RunShellWithReturnCode(['hg', 'log', '--rev', 'qparent', '--template={node}'])
33835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if not err and mqparent != "":
33845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				self.base_rev = mqparent
33855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			else:
33865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				out = RunShell(["hg", "parents", "-q"], silent_ok=True).strip()
33875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if not out:
33885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					# No revisions; use 0 to mean a repository with nothing.
33895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					out = "0:0"
33905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				self.base_rev = out.split(':')[1].strip()
33915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def _GetRelPath(self, filename):
33925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Get relative path of a file according to the current directory,
33935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		given its logical path in the repo."""
33945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		assert filename.startswith(self.subdir), (filename, self.subdir)
33955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return filename[len(self.subdir):].lstrip(r"\/")
33965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
33975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def GenerateDiff(self, extra_args):
33985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# If no file specified, restrict to the current subdir
33995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		extra_args = extra_args or ["."]
34005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		cmd = ["hg", "diff", "--git", "-r", self.base_rev] + extra_args
34015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		data = RunShell(cmd, silent_ok=True)
34025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		svndiff = []
34035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		filecount = 0
34045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for line in data.splitlines():
34055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			m = re.match("diff --git a/(\S+) b/(\S+)", line)
34065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if m:
34075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# Modify line to make it look like as it comes from svn diff.
34085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# With this modification no changes on the server side are required
34095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# to make upload.py work with Mercurial repos.
34105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# NOTE: for proper handling of moved/copied files, we have to use
34115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# the second filename.
34125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				filename = m.group(2)
34135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				svndiff.append("Index: %s" % filename)
34145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				svndiff.append("=" * 67)
34155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				filecount += 1
34165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				logging.info(line)
34175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			else:
34185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				svndiff.append(line)
34195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not filecount:
34205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			ErrorExit("No valid patches found in output from hg diff")
34215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return "\n".join(svndiff) + "\n"
34225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
34235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def GetUnknownFiles(self):
34245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		"""Return a list of files unknown to the VCS."""
34255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		args = []
34265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		status = RunShell(["hg", "status", "--rev", self.base_rev, "-u", "."],
34275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				silent_ok=True)
34285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		unknown_files = []
34295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for line in status.splitlines():
34305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			st, fn = line.split(" ", 1)
34315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if st == "?":
34325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				unknown_files.append(fn)
34335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return unknown_files
34345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
34355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def get_hg_status(self, rev, path):
34365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# We'd like to use 'hg status -C path', but that is buggy
34375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# (see http://mercurial.selenic.com/bts/issue3023).
34385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# Instead, run 'hg status -C' without a path
34395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# and skim the output for the path we want.
34405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if self.status is None:
34415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if use_hg_shell:
34425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				out = RunShell(["hg", "status", "-C", "--rev", rev])
34435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			else:
34445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				fui = FakeMercurialUI()
34455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				ret = hg_commands.status(fui, self.repo, *[], **{'rev': [rev], 'copies': True})
34465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if ret:
34475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					raise hg_util.Abort(ret)
34485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				out = fui.output
34495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			self.status = out.splitlines()
34505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		for i in range(len(self.status)):
34515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# line is
34525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			#	A path
34535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			#	M path
34545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# etc
34555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			line = to_slash(self.status[i])
34565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if line[2:] == path:
34575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				if i+1 < len(self.status) and self.status[i+1][:2] == '  ':
34585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)					return self.status[i:i+2]
34595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				return self.status[i:i+1]
34605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		raise hg_util.Abort("no status for " + path)
34615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
34625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	def GetBaseFile(self, filename):
34635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		set_status("inspecting " + filename)
34645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# "hg status" and "hg cat" both take a path relative to the current subdir
34655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# rather than to the repo root, but "hg diff" has given us the full path
34665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		# to the repo root.
34675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		base_content = ""
34685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		new_content = None
34695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		is_binary = False
34705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		oldrelpath = relpath = self._GetRelPath(filename)
34715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		out = self.get_hg_status(self.base_rev, relpath)
34725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		status, what = out[0].split(' ', 1)
34735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if len(out) > 1 and status == "A" and what == relpath:
34745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			oldrelpath = out[1].strip()
34755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			status = "M"
34765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if ":" in self.base_rev:
34775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			base_rev = self.base_rev.split(":", 1)[0]
34785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		else:
34795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			base_rev = self.base_rev
34805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if status != "A":
34815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if use_hg_shell:
34825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath], silent_ok=True)
34835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			else:
34845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				base_content = str(self.repo[base_rev][oldrelpath].data())
34855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			is_binary = "\0" in base_content  # Mercurial's heuristic
34865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if status != "R":
34875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			new_content = open(relpath, "rb").read()
34885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			is_binary = is_binary or "\0" in new_content
34895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if is_binary and base_content and use_hg_shell:
34905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# Fetch again without converting newlines
34915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath],
34925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				silent_ok=True, universal_newlines=False)
34935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not is_binary or not self.IsImage(relpath):
34945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			new_content = None
34955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		return base_content, new_content, is_binary, status
34965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
34975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
34985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync.
34995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def SplitPatch(data):
35005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Splits a patch into separate pieces for each file.
35015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
35025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Args:
35035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		data: A string containing the output of svn diff.
35045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
35055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Returns:
35065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		A list of 2-tuple (filename, text) where text is the svn diff output
35075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			pertaining to filename.
35085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
35095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	patches = []
35105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	filename = None
35115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	diff = []
35125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for line in data.splitlines(True):
35135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		new_filename = None
35145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if line.startswith('Index:'):
35155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			unused, new_filename = line.split(':', 1)
35165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			new_filename = new_filename.strip()
35175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		elif line.startswith('Property changes on:'):
35185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			unused, temp_filename = line.split(':', 1)
35195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# When a file is modified, paths use '/' between directories, however
35205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# when a property is modified '\' is used on Windows.  Make them the same
35215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			# otherwise the file shows up twice.
35225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			temp_filename = to_slash(temp_filename.strip())
35235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if temp_filename != filename:
35245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				# File has property changes but no modifications, create a new diff.
35255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				new_filename = temp_filename
35265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if new_filename:
35275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			if filename and diff:
35285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				patches.append((filename, ''.join(diff)))
35295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			filename = new_filename
35305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			diff = [line]
35315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			continue
35325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if diff is not None:
35335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			diff.append(line)
35345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	if filename and diff:
35355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		patches.append((filename, ''.join(diff)))
35365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return patches
35375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
35385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
35395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def UploadSeparatePatches(issue, rpc_server, patchset, data, options):
35405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""Uploads a separate patch for each file in the diff output.
35415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
35425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	Returns a list of [patch_key, filename] for each file.
35435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	"""
35445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	patches = SplitPatch(data)
35455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	rv = []
35465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	for patch in patches:
35475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		set_status("uploading patch for " + patch[0])
35485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if len(patch[1]) > MAX_UPLOAD_SIZE:
35495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			print ("Not uploading the patch for " + patch[0] +
35505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)				" because the file is too large.")
35515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			continue
35525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		form_fields = [("filename", patch[0])]
35535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not options.download_base:
35545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			form_fields.append(("content_upload", "1"))
35555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		files = [("data", "data.diff", patch[1])]
35565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		ctype, body = EncodeMultipartFormData(form_fields, files)
35575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		url = "/%d/upload_patch/%d" % (int(issue), int(patchset))
35585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		print "Uploading patch for " + patch[0]
35595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		response_body = rpc_server.Send(url, body, content_type=ctype)
35605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		lines = response_body.splitlines()
35615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		if not lines or lines[0] != "OK":
35625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			StatusUpdate("  --> %s" % response_body)
35635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)			sys.exit(1)
35645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)		rv.append([lines[1], patch[0]])
35655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)	return rv
3566