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