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