Blame


1 26b4bea8 2011-08-02 rsc # coding=utf-8
2 26b4bea8 2011-08-02 rsc # (The line above is necessary so that I can use 世界 in the
3 26b4bea8 2011-08-02 rsc # *comment* below without Python getting all bent out of shape.)
4 26b4bea8 2011-08-02 rsc
5 ef27bfa4 2010-01-12 rsc # Copyright 2007-2009 Google Inc.
6 ef27bfa4 2010-01-12 rsc #
7 ef27bfa4 2010-01-12 rsc # Licensed under the Apache License, Version 2.0 (the "License");
8 ef27bfa4 2010-01-12 rsc # you may not use this file except in compliance with the License.
9 ef27bfa4 2010-01-12 rsc # You may obtain a copy of the License at
10 ef27bfa4 2010-01-12 rsc #
11 26b4bea8 2011-08-02 rsc # http://www.apache.org/licenses/LICENSE-2.0
12 ef27bfa4 2010-01-12 rsc #
13 ef27bfa4 2010-01-12 rsc # Unless required by applicable law or agreed to in writing, software
14 ef27bfa4 2010-01-12 rsc # distributed under the License is distributed on an "AS IS" BASIS,
15 ef27bfa4 2010-01-12 rsc # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 ef27bfa4 2010-01-12 rsc # See the License for the specific language governing permissions and
17 ef27bfa4 2010-01-12 rsc # limitations under the License.
18 ef27bfa4 2010-01-12 rsc
19 ef27bfa4 2010-01-12 rsc '''Mercurial interface to codereview.appspot.com.
20 ef27bfa4 2010-01-12 rsc
21 ef27bfa4 2010-01-12 rsc To configure, set the following options in
22 ef27bfa4 2010-01-12 rsc your repository's .hg/hgrc file.
23 ef27bfa4 2010-01-12 rsc
24 ef27bfa4 2010-01-12 rsc [extensions]
25 ef27bfa4 2010-01-12 rsc codereview = path/to/codereview.py
26 ef27bfa4 2010-01-12 rsc
27 ef27bfa4 2010-01-12 rsc [codereview]
28 ef27bfa4 2010-01-12 rsc server = codereview.appspot.com
29 ef27bfa4 2010-01-12 rsc
30 ef27bfa4 2010-01-12 rsc The server should be running Rietveld; see http://code.google.com/p/rietveld/.
31 ef27bfa4 2010-01-12 rsc
32 ef27bfa4 2010-01-12 rsc In addition to the new commands, this extension introduces
33 ef27bfa4 2010-01-12 rsc the file pattern syntax @nnnnnn, where nnnnnn is a change list
34 ef27bfa4 2010-01-12 rsc number, to mean the files included in that change list, which
35 ef27bfa4 2010-01-12 rsc must be associated with the current client.
36 ef27bfa4 2010-01-12 rsc
37 ef27bfa4 2010-01-12 rsc For example, if change 123456 contains the files x.go and y.go,
38 ef27bfa4 2010-01-12 rsc "hg diff @123456" is equivalent to"hg diff x.go y.go".
39 ef27bfa4 2010-01-12 rsc '''
40 ef27bfa4 2010-01-12 rsc
41 ef27bfa4 2010-01-12 rsc from mercurial import cmdutil, commands, hg, util, error, match
42 ef27bfa4 2010-01-12 rsc from mercurial.node import nullrev, hex, nullid, short
43 ef27bfa4 2010-01-12 rsc import os, re, time
44 ef27bfa4 2010-01-12 rsc import stat
45 ef27bfa4 2010-01-12 rsc import subprocess
46 ef27bfa4 2010-01-12 rsc import threading
47 ef27bfa4 2010-01-12 rsc from HTMLParser import HTMLParser
48 26b4bea8 2011-08-02 rsc
49 26b4bea8 2011-08-02 rsc # The standard 'json' package is new in Python 2.6.
50 26b4bea8 2011-08-02 rsc # Before that it was an external package named simplejson.
51 ef27bfa4 2010-01-12 rsc try:
52 26b4bea8 2011-08-02 rsc # Standard location in 2.6 and beyond.
53 26b4bea8 2011-08-02 rsc import json
54 26b4bea8 2011-08-02 rsc except Exception, e:
55 26b4bea8 2011-08-02 rsc try:
56 26b4bea8 2011-08-02 rsc # Conventional name for earlier package.
57 26b4bea8 2011-08-02 rsc import simplejson as json
58 26b4bea8 2011-08-02 rsc except:
59 26b4bea8 2011-08-02 rsc try:
60 26b4bea8 2011-08-02 rsc # Was also bundled with django, which is commonly installed.
61 26b4bea8 2011-08-02 rsc from django.utils import simplejson as json
62 26b4bea8 2011-08-02 rsc except:
63 26b4bea8 2011-08-02 rsc # We give up.
64 26b4bea8 2011-08-02 rsc raise e
65 ef27bfa4 2010-01-12 rsc
66 ef27bfa4 2010-01-12 rsc try:
67 ef27bfa4 2010-01-12 rsc hgversion = util.version()
68 ef27bfa4 2010-01-12 rsc except:
69 ef27bfa4 2010-01-12 rsc from mercurial.version import version as v
70 ef27bfa4 2010-01-12 rsc hgversion = v.get_version()
71 ef27bfa4 2010-01-12 rsc
72 a06877af 2010-08-05 rsc try:
73 a06877af 2010-08-05 rsc from mercurial.discovery import findcommonincoming
74 a06877af 2010-08-05 rsc except:
75 a06877af 2010-08-05 rsc def findcommonincoming(repo, remote):
76 a06877af 2010-08-05 rsc return repo.findcommonincoming(remote)
77 a06877af 2010-08-05 rsc
78 ef27bfa4 2010-01-12 rsc oldMessage = """
79 ef27bfa4 2010-01-12 rsc The code review extension requires Mercurial 1.3 or newer.
80 ef27bfa4 2010-01-12 rsc
81 ef27bfa4 2010-01-12 rsc To install a new Mercurial,
82 ef27bfa4 2010-01-12 rsc
83 ef27bfa4 2010-01-12 rsc sudo easy_install mercurial
84 ef27bfa4 2010-01-12 rsc
85 ef27bfa4 2010-01-12 rsc works on most systems.
86 ef27bfa4 2010-01-12 rsc """
87 ef27bfa4 2010-01-12 rsc
88 ef27bfa4 2010-01-12 rsc linuxMessage = """
89 ef27bfa4 2010-01-12 rsc You may need to clear your current Mercurial installation by running:
90 ef27bfa4 2010-01-12 rsc
91 ef27bfa4 2010-01-12 rsc sudo apt-get remove mercurial mercurial-common
92 ef27bfa4 2010-01-12 rsc sudo rm -rf /etc/mercurial
93 ef27bfa4 2010-01-12 rsc """
94 ef27bfa4 2010-01-12 rsc
95 ef27bfa4 2010-01-12 rsc if hgversion < '1.3':
96 ef27bfa4 2010-01-12 rsc msg = oldMessage
97 ef27bfa4 2010-01-12 rsc if os.access("/etc/mercurial", 0):
98 ef27bfa4 2010-01-12 rsc msg += linuxMessage
99 ef27bfa4 2010-01-12 rsc raise util.Abort(msg)
100 a06877af 2010-08-05 rsc
101 a06877af 2010-08-05 rsc def promptyesno(ui, msg):
102 a06877af 2010-08-05 rsc # Arguments to ui.prompt changed between 1.3 and 1.3.1.
103 a06877af 2010-08-05 rsc # Even so, some 1.3.1 distributions seem to have the old prompt!?!?
104 a06877af 2010-08-05 rsc # What a terrible way to maintain software.
105 a06877af 2010-08-05 rsc try:
106 a06877af 2010-08-05 rsc return ui.promptchoice(msg, ["&yes", "&no"], 0) == 0
107 a06877af 2010-08-05 rsc except AttributeError:
108 a06877af 2010-08-05 rsc return ui.prompt(msg, ["&yes", "&no"], "y") != "n"
109 ef27bfa4 2010-01-12 rsc
110 ef27bfa4 2010-01-12 rsc # To experiment with Mercurial in the python interpreter:
111 ef27bfa4 2010-01-12 rsc # >>> repo = hg.repository(ui.ui(), path = ".")
112 ef27bfa4 2010-01-12 rsc
113 ef27bfa4 2010-01-12 rsc #######################################################################
114 ef27bfa4 2010-01-12 rsc # Normally I would split this into multiple files, but it simplifies
115 ef27bfa4 2010-01-12 rsc # import path headaches to keep it all in one file. Sorry.
116 ef27bfa4 2010-01-12 rsc
117 ef27bfa4 2010-01-12 rsc import sys
118 ef27bfa4 2010-01-12 rsc if __name__ == "__main__":
119 ef27bfa4 2010-01-12 rsc print >>sys.stderr, "This is a Mercurial extension and should not be invoked directly."
120 ef27bfa4 2010-01-12 rsc sys.exit(2)
121 ef27bfa4 2010-01-12 rsc
122 ef27bfa4 2010-01-12 rsc server = "codereview.appspot.com"
123 ef27bfa4 2010-01-12 rsc server_url_base = None
124 ef27bfa4 2010-01-12 rsc defaultcc = None
125 a06877af 2010-08-05 rsc contributors = {}
126 26b4bea8 2011-08-02 rsc missing_codereview = None
127 26b4bea8 2011-08-02 rsc real_rollback = None
128 26b4bea8 2011-08-02 rsc releaseBranch = None
129 ef27bfa4 2010-01-12 rsc
130 ef27bfa4 2010-01-12 rsc #######################################################################
131 26b4bea8 2011-08-02 rsc # RE: UNICODE STRING HANDLING
132 26b4bea8 2011-08-02 rsc #
133 26b4bea8 2011-08-02 rsc # Python distinguishes between the str (string of bytes)
134 26b4bea8 2011-08-02 rsc # and unicode (string of code points) types. Most operations
135 26b4bea8 2011-08-02 rsc # work on either one just fine, but some (like regexp matching)
136 26b4bea8 2011-08-02 rsc # require unicode, and others (like write) require str.
137 26b4bea8 2011-08-02 rsc #
138 26b4bea8 2011-08-02 rsc # As befits the language, Python hides the distinction between
139 26b4bea8 2011-08-02 rsc # unicode and str by converting between them silently, but
140 26b4bea8 2011-08-02 rsc # *only* if all the bytes/code points involved are 7-bit ASCII.
141 26b4bea8 2011-08-02 rsc # This means that if you're not careful, your program works
142 26b4bea8 2011-08-02 rsc # fine on "hello, world" and fails on "hello, 世界". And of course,
143 26b4bea8 2011-08-02 rsc # the obvious way to be careful - use static types - is unavailable.
144 26b4bea8 2011-08-02 rsc # So the only way is trial and error to find where to put explicit
145 26b4bea8 2011-08-02 rsc # conversions.
146 26b4bea8 2011-08-02 rsc #
147 26b4bea8 2011-08-02 rsc # Because more functions do implicit conversion to str (string of bytes)
148 26b4bea8 2011-08-02 rsc # than do implicit conversion to unicode (string of code points),
149 26b4bea8 2011-08-02 rsc # the convention in this module is to represent all text as str,
150 26b4bea8 2011-08-02 rsc # converting to unicode only when calling a unicode-only function
151 26b4bea8 2011-08-02 rsc # and then converting back to str as soon as possible.
152 26b4bea8 2011-08-02 rsc
153 26b4bea8 2011-08-02 rsc def typecheck(s, t):
154 26b4bea8 2011-08-02 rsc if type(s) != t:
155 26b4bea8 2011-08-02 rsc raise util.Abort("type check failed: %s has type %s != %s" % (repr(s), type(s), t))
156 26b4bea8 2011-08-02 rsc
157 26b4bea8 2011-08-02 rsc # If we have to pass unicode instead of str, ustr does that conversion clearly.
158 26b4bea8 2011-08-02 rsc def ustr(s):
159 26b4bea8 2011-08-02 rsc typecheck(s, str)
160 26b4bea8 2011-08-02 rsc return s.decode("utf-8")
161 26b4bea8 2011-08-02 rsc
162 26b4bea8 2011-08-02 rsc # Even with those, Mercurial still sometimes turns unicode into str
163 26b4bea8 2011-08-02 rsc # and then tries to use it as ascii. Change Mercurial's default.
164 26b4bea8 2011-08-02 rsc def set_mercurial_encoding_to_utf8():
165 26b4bea8 2011-08-02 rsc from mercurial import encoding
166 26b4bea8 2011-08-02 rsc encoding.encoding = 'utf-8'
167 26b4bea8 2011-08-02 rsc
168 26b4bea8 2011-08-02 rsc set_mercurial_encoding_to_utf8()
169 26b4bea8 2011-08-02 rsc
170 26b4bea8 2011-08-02 rsc # Even with those we still run into problems.
171 26b4bea8 2011-08-02 rsc # I tried to do things by the book but could not convince
172 26b4bea8 2011-08-02 rsc # Mercurial to let me check in a change with UTF-8 in the
173 26b4bea8 2011-08-02 rsc # CL description or author field, no matter how many conversions
174 26b4bea8 2011-08-02 rsc # between str and unicode I inserted and despite changing the
175 26b4bea8 2011-08-02 rsc # default encoding. I'm tired of this game, so set the default
176 26b4bea8 2011-08-02 rsc # encoding for all of Python to 'utf-8', not 'ascii'.
177 26b4bea8 2011-08-02 rsc def default_to_utf8():
178 26b4bea8 2011-08-02 rsc import sys
179 26b4bea8 2011-08-02 rsc reload(sys) # site.py deleted setdefaultencoding; get it back
180 26b4bea8 2011-08-02 rsc sys.setdefaultencoding('utf-8')
181 26b4bea8 2011-08-02 rsc
182 26b4bea8 2011-08-02 rsc default_to_utf8()
183 26b4bea8 2011-08-02 rsc
184 26b4bea8 2011-08-02 rsc #######################################################################
185 ef27bfa4 2010-01-12 rsc # Change list parsing.
186 ef27bfa4 2010-01-12 rsc #
187 ef27bfa4 2010-01-12 rsc # Change lists are stored in .hg/codereview/cl.nnnnnn
188 ef27bfa4 2010-01-12 rsc # where nnnnnn is the number assigned by the code review server.
189 ef27bfa4 2010-01-12 rsc # Most data about a change list is stored on the code review server
190 ef27bfa4 2010-01-12 rsc # too: the description, reviewer, and cc list are all stored there.
191 ef27bfa4 2010-01-12 rsc # The only thing in the cl.nnnnnn file is the list of relevant files.
192 ef27bfa4 2010-01-12 rsc # Also, the existence of the cl.nnnnnn file marks this repository
193 ef27bfa4 2010-01-12 rsc # as the one where the change list lives.
194 ef27bfa4 2010-01-12 rsc
195 ef27bfa4 2010-01-12 rsc emptydiff = """Index: ~rietveld~placeholder~
196 ef27bfa4 2010-01-12 rsc ===================================================================
197 ef27bfa4 2010-01-12 rsc diff --git a/~rietveld~placeholder~ b/~rietveld~placeholder~
198 ef27bfa4 2010-01-12 rsc new file mode 100644
199 ef27bfa4 2010-01-12 rsc """
200 ef27bfa4 2010-01-12 rsc
201 ef27bfa4 2010-01-12 rsc class CL(object):
202 ef27bfa4 2010-01-12 rsc def __init__(self, name):
203 26b4bea8 2011-08-02 rsc typecheck(name, str)
204 ef27bfa4 2010-01-12 rsc self.name = name
205 ef27bfa4 2010-01-12 rsc self.desc = ''
206 ef27bfa4 2010-01-12 rsc self.files = []
207 ef27bfa4 2010-01-12 rsc self.reviewer = []
208 ef27bfa4 2010-01-12 rsc self.cc = []
209 ef27bfa4 2010-01-12 rsc self.url = ''
210 ef27bfa4 2010-01-12 rsc self.local = False
211 ef27bfa4 2010-01-12 rsc self.web = False
212 ef27bfa4 2010-01-12 rsc self.copied_from = None # None means current user
213 ef27bfa4 2010-01-12 rsc self.mailed = False
214 26b4bea8 2011-08-02 rsc self.private = False
215 ef27bfa4 2010-01-12 rsc
216 ef27bfa4 2010-01-12 rsc def DiskText(self):
217 ef27bfa4 2010-01-12 rsc cl = self
218 ef27bfa4 2010-01-12 rsc s = ""
219 ef27bfa4 2010-01-12 rsc if cl.copied_from:
220 ef27bfa4 2010-01-12 rsc s += "Author: " + cl.copied_from + "\n\n"
221 26b4bea8 2011-08-02 rsc if cl.private:
222 26b4bea8 2011-08-02 rsc s += "Private: " + str(self.private) + "\n"
223 ef27bfa4 2010-01-12 rsc s += "Mailed: " + str(self.mailed) + "\n"
224 ef27bfa4 2010-01-12 rsc s += "Description:\n"
225 ef27bfa4 2010-01-12 rsc s += Indent(cl.desc, "\t")
226 ef27bfa4 2010-01-12 rsc s += "Files:\n"
227 ef27bfa4 2010-01-12 rsc for f in cl.files:
228 ef27bfa4 2010-01-12 rsc s += "\t" + f + "\n"
229 26b4bea8 2011-08-02 rsc typecheck(s, str)
230 ef27bfa4 2010-01-12 rsc return s
231 ef27bfa4 2010-01-12 rsc
232 ef27bfa4 2010-01-12 rsc def EditorText(self):
233 ef27bfa4 2010-01-12 rsc cl = self
234 ef27bfa4 2010-01-12 rsc s = _change_prolog
235 ef27bfa4 2010-01-12 rsc s += "\n"
236 ef27bfa4 2010-01-12 rsc if cl.copied_from:
237 ef27bfa4 2010-01-12 rsc s += "Author: " + cl.copied_from + "\n"
238 ef27bfa4 2010-01-12 rsc if cl.url != '':
239 ef27bfa4 2010-01-12 rsc s += 'URL: ' + cl.url + ' # cannot edit\n\n'
240 26b4bea8 2011-08-02 rsc if cl.private:
241 26b4bea8 2011-08-02 rsc s += "Private: True\n"
242 ef27bfa4 2010-01-12 rsc s += "Reviewer: " + JoinComma(cl.reviewer) + "\n"
243 ef27bfa4 2010-01-12 rsc s += "CC: " + JoinComma(cl.cc) + "\n"
244 ef27bfa4 2010-01-12 rsc s += "\n"
245 ef27bfa4 2010-01-12 rsc s += "Description:\n"
246 ef27bfa4 2010-01-12 rsc if cl.desc == '':
247 ef27bfa4 2010-01-12 rsc s += "\t<enter description here>\n"
248 ef27bfa4 2010-01-12 rsc else:
249 ef27bfa4 2010-01-12 rsc s += Indent(cl.desc, "\t")
250 ef27bfa4 2010-01-12 rsc s += "\n"
251 ef27bfa4 2010-01-12 rsc if cl.local or cl.name == "new":
252 ef27bfa4 2010-01-12 rsc s += "Files:\n"
253 ef27bfa4 2010-01-12 rsc for f in cl.files:
254 ef27bfa4 2010-01-12 rsc s += "\t" + f + "\n"
255 ef27bfa4 2010-01-12 rsc s += "\n"
256 26b4bea8 2011-08-02 rsc typecheck(s, str)
257 ef27bfa4 2010-01-12 rsc return s
258 ef27bfa4 2010-01-12 rsc
259 ef27bfa4 2010-01-12 rsc def PendingText(self):
260 ef27bfa4 2010-01-12 rsc cl = self
261 ef27bfa4 2010-01-12 rsc s = cl.name + ":" + "\n"
262 ef27bfa4 2010-01-12 rsc s += Indent(cl.desc, "\t")
263 ef27bfa4 2010-01-12 rsc s += "\n"
264 ef27bfa4 2010-01-12 rsc if cl.copied_from:
265 ef27bfa4 2010-01-12 rsc s += "\tAuthor: " + cl.copied_from + "\n"
266 ef27bfa4 2010-01-12 rsc s += "\tReviewer: " + JoinComma(cl.reviewer) + "\n"
267 ef27bfa4 2010-01-12 rsc s += "\tCC: " + JoinComma(cl.cc) + "\n"
268 ef27bfa4 2010-01-12 rsc s += "\tFiles:\n"
269 ef27bfa4 2010-01-12 rsc for f in cl.files:
270 ef27bfa4 2010-01-12 rsc s += "\t\t" + f + "\n"
271 26b4bea8 2011-08-02 rsc typecheck(s, str)
272 ef27bfa4 2010-01-12 rsc return s
273 ef27bfa4 2010-01-12 rsc
274 ef27bfa4 2010-01-12 rsc def Flush(self, ui, repo):
275 ef27bfa4 2010-01-12 rsc if self.name == "new":
276 26b4bea8 2011-08-02 rsc self.Upload(ui, repo, gofmt_just_warn=True, creating=True)
277 ef27bfa4 2010-01-12 rsc dir = CodeReviewDir(ui, repo)
278 ef27bfa4 2010-01-12 rsc path = dir + '/cl.' + self.name
279 ef27bfa4 2010-01-12 rsc f = open(path+'!', "w")
280 ef27bfa4 2010-01-12 rsc f.write(self.DiskText())
281 ef27bfa4 2010-01-12 rsc f.close()
282 ef27bfa4 2010-01-12 rsc if sys.platform == "win32" and os.path.isfile(path):
283 ef27bfa4 2010-01-12 rsc os.remove(path)
284 ef27bfa4 2010-01-12 rsc os.rename(path+'!', path)
285 ef27bfa4 2010-01-12 rsc if self.web and not self.copied_from:
286 ef27bfa4 2010-01-12 rsc EditDesc(self.name, desc=self.desc,
287 26b4bea8 2011-08-02 rsc reviewers=JoinComma(self.reviewer), cc=JoinComma(self.cc),
288 26b4bea8 2011-08-02 rsc private=self.private)
289 ef27bfa4 2010-01-12 rsc
290 ef27bfa4 2010-01-12 rsc def Delete(self, ui, repo):
291 ef27bfa4 2010-01-12 rsc dir = CodeReviewDir(ui, repo)
292 ef27bfa4 2010-01-12 rsc os.unlink(dir + "/cl." + self.name)
293 ef27bfa4 2010-01-12 rsc
294 ef27bfa4 2010-01-12 rsc def Subject(self):
295 ef27bfa4 2010-01-12 rsc s = line1(self.desc)
296 ef27bfa4 2010-01-12 rsc if len(s) > 60:
297 ef27bfa4 2010-01-12 rsc s = s[0:55] + "..."
298 ef27bfa4 2010-01-12 rsc if self.name != "new":
299 ef27bfa4 2010-01-12 rsc s = "code review %s: %s" % (self.name, s)
300 26b4bea8 2011-08-02 rsc typecheck(s, str)
301 ef27bfa4 2010-01-12 rsc return s
302 ef27bfa4 2010-01-12 rsc
303 26b4bea8 2011-08-02 rsc def Upload(self, ui, repo, send_mail=False, gofmt=True, gofmt_just_warn=False, creating=False, quiet=False):
304 26b4bea8 2011-08-02 rsc if not self.files and not creating:
305 ef27bfa4 2010-01-12 rsc ui.warn("no files in change list\n")
306 ef27bfa4 2010-01-12 rsc if ui.configbool("codereview", "force_gofmt", True) and gofmt:
307 26b4bea8 2011-08-02 rsc CheckFormat(ui, repo, self.files, just_warn=gofmt_just_warn)
308 26b4bea8 2011-08-02 rsc set_status("uploading CL metadata + diffs")
309 ef27bfa4 2010-01-12 rsc os.chdir(repo.root)
310 ef27bfa4 2010-01-12 rsc form_fields = [
311 ef27bfa4 2010-01-12 rsc ("content_upload", "1"),
312 ef27bfa4 2010-01-12 rsc ("reviewers", JoinComma(self.reviewer)),
313 ef27bfa4 2010-01-12 rsc ("cc", JoinComma(self.cc)),
314 ef27bfa4 2010-01-12 rsc ("description", self.desc),
315 ef27bfa4 2010-01-12 rsc ("base_hashes", ""),
316 ef27bfa4 2010-01-12 rsc ]
317 ef27bfa4 2010-01-12 rsc
318 ef27bfa4 2010-01-12 rsc if self.name != "new":
319 ef27bfa4 2010-01-12 rsc form_fields.append(("issue", self.name))
320 ef27bfa4 2010-01-12 rsc vcs = None
321 26b4bea8 2011-08-02 rsc # We do not include files when creating the issue,
322 26b4bea8 2011-08-02 rsc # because we want the patch sets to record the repository
323 26b4bea8 2011-08-02 rsc # and base revision they are diffs against. We use the patch
324 26b4bea8 2011-08-02 rsc # set message for that purpose, but there is no message with
325 26b4bea8 2011-08-02 rsc # the first patch set. Instead the message gets used as the
326 26b4bea8 2011-08-02 rsc # new CL's overall subject. So omit the diffs when creating
327 26b4bea8 2011-08-02 rsc # and then we'll run an immediate upload.
328 26b4bea8 2011-08-02 rsc # This has the effect that every CL begins with an empty "Patch set 1".
329 26b4bea8 2011-08-02 rsc if self.files and not creating:
330 26b4bea8 2011-08-02 rsc vcs = MercurialVCS(upload_options, ui, repo)
331 ef27bfa4 2010-01-12 rsc data = vcs.GenerateDiff(self.files)
332 ef27bfa4 2010-01-12 rsc files = vcs.GetBaseFiles(data)
333 ef27bfa4 2010-01-12 rsc if len(data) > MAX_UPLOAD_SIZE:
334 ef27bfa4 2010-01-12 rsc uploaded_diff_file = []
335 ef27bfa4 2010-01-12 rsc form_fields.append(("separate_patches", "1"))
336 ef27bfa4 2010-01-12 rsc else:
337 ef27bfa4 2010-01-12 rsc uploaded_diff_file = [("data", "data.diff", data)]
338 ef27bfa4 2010-01-12 rsc else:
339 ef27bfa4 2010-01-12 rsc uploaded_diff_file = [("data", "data.diff", emptydiff)]
340 26b4bea8 2011-08-02 rsc
341 26b4bea8 2011-08-02 rsc if vcs and self.name != "new":
342 26b4bea8 2011-08-02 rsc form_fields.append(("subject", "diff -r " + vcs.base_rev + " " + getremote(ui, repo, {}).path))
343 26b4bea8 2011-08-02 rsc else:
344 26b4bea8 2011-08-02 rsc # First upload sets the subject for the CL itself.
345 26b4bea8 2011-08-02 rsc form_fields.append(("subject", self.Subject()))
346 ef27bfa4 2010-01-12 rsc ctype, body = EncodeMultipartFormData(form_fields, uploaded_diff_file)
347 ef27bfa4 2010-01-12 rsc response_body = MySend("/upload", body, content_type=ctype)
348 ef27bfa4 2010-01-12 rsc patchset = None
349 ef27bfa4 2010-01-12 rsc msg = response_body
350 ef27bfa4 2010-01-12 rsc lines = msg.splitlines()
351 ef27bfa4 2010-01-12 rsc if len(lines) >= 2:
352 ef27bfa4 2010-01-12 rsc msg = lines[0]
353 ef27bfa4 2010-01-12 rsc patchset = lines[1].strip()
354 ef27bfa4 2010-01-12 rsc patches = [x.split(" ", 1) for x in lines[2:]]
355 26b4bea8 2011-08-02 rsc if response_body.startswith("Issue updated.") and quiet:
356 26b4bea8 2011-08-02 rsc pass
357 26b4bea8 2011-08-02 rsc else:
358 26b4bea8 2011-08-02 rsc ui.status(msg + "\n")
359 26b4bea8 2011-08-02 rsc set_status("uploaded CL metadata + diffs")
360 ef27bfa4 2010-01-12 rsc if not response_body.startswith("Issue created.") and not response_body.startswith("Issue updated."):
361 ef27bfa4 2010-01-12 rsc raise util.Abort("failed to update issue: " + response_body)
362 ef27bfa4 2010-01-12 rsc issue = msg[msg.rfind("/")+1:]
363 ef27bfa4 2010-01-12 rsc self.name = issue
364 ef27bfa4 2010-01-12 rsc if not self.url:
365 ef27bfa4 2010-01-12 rsc self.url = server_url_base + self.name
366 ef27bfa4 2010-01-12 rsc if not uploaded_diff_file:
367 26b4bea8 2011-08-02 rsc set_status("uploading patches")
368 ef27bfa4 2010-01-12 rsc patches = UploadSeparatePatches(issue, rpc, patchset, data, upload_options)
369 ef27bfa4 2010-01-12 rsc if vcs:
370 26b4bea8 2011-08-02 rsc set_status("uploading base files")
371 ef27bfa4 2010-01-12 rsc vcs.UploadBaseFiles(issue, rpc, patches, patchset, upload_options, files)
372 ef27bfa4 2010-01-12 rsc if send_mail:
373 26b4bea8 2011-08-02 rsc set_status("sending mail")
374 ef27bfa4 2010-01-12 rsc MySend("/" + issue + "/mail", payload="")
375 ef27bfa4 2010-01-12 rsc self.web = True
376 26b4bea8 2011-08-02 rsc set_status("flushing changes to disk")
377 ef27bfa4 2010-01-12 rsc self.Flush(ui, repo)
378 ef27bfa4 2010-01-12 rsc return
379 ef27bfa4 2010-01-12 rsc
380 26b4bea8 2011-08-02 rsc def Mail(self, ui, repo):
381 ef27bfa4 2010-01-12 rsc pmsg = "Hello " + JoinComma(self.reviewer)
382 ef27bfa4 2010-01-12 rsc if self.cc:
383 ef27bfa4 2010-01-12 rsc pmsg += " (cc: %s)" % (', '.join(self.cc),)
384 ef27bfa4 2010-01-12 rsc pmsg += ",\n"
385 ef27bfa4 2010-01-12 rsc pmsg += "\n"
386 26b4bea8 2011-08-02 rsc repourl = getremote(ui, repo, {}).path
387 ef27bfa4 2010-01-12 rsc if not self.mailed:
388 26b4bea8 2011-08-02 rsc pmsg += "I'd like you to review this change to\n" + repourl + "\n"
389 ef27bfa4 2010-01-12 rsc else:
390 ef27bfa4 2010-01-12 rsc pmsg += "Please take another look.\n"
391 26b4bea8 2011-08-02 rsc typecheck(pmsg, str)
392 ef27bfa4 2010-01-12 rsc PostMessage(ui, self.name, pmsg, subject=self.Subject())
393 ef27bfa4 2010-01-12 rsc self.mailed = True
394 ef27bfa4 2010-01-12 rsc self.Flush(ui, repo)
395 ef27bfa4 2010-01-12 rsc
396 ef27bfa4 2010-01-12 rsc def GoodCLName(name):
397 26b4bea8 2011-08-02 rsc typecheck(name, str)
398 ef27bfa4 2010-01-12 rsc return re.match("^[0-9]+$", name)
399 ef27bfa4 2010-01-12 rsc
400 ef27bfa4 2010-01-12 rsc def ParseCL(text, name):
401 26b4bea8 2011-08-02 rsc typecheck(text, str)
402 26b4bea8 2011-08-02 rsc typecheck(name, str)
403 ef27bfa4 2010-01-12 rsc sname = None
404 ef27bfa4 2010-01-12 rsc lineno = 0
405 ef27bfa4 2010-01-12 rsc sections = {
406 ef27bfa4 2010-01-12 rsc 'Author': '',
407 ef27bfa4 2010-01-12 rsc 'Description': '',
408 ef27bfa4 2010-01-12 rsc 'Files': '',
409 ef27bfa4 2010-01-12 rsc 'URL': '',
410 ef27bfa4 2010-01-12 rsc 'Reviewer': '',
411 ef27bfa4 2010-01-12 rsc 'CC': '',
412 ef27bfa4 2010-01-12 rsc 'Mailed': '',
413 26b4bea8 2011-08-02 rsc 'Private': '',
414 ef27bfa4 2010-01-12 rsc }
415 ef27bfa4 2010-01-12 rsc for line in text.split('\n'):
416 ef27bfa4 2010-01-12 rsc lineno += 1
417 ef27bfa4 2010-01-12 rsc line = line.rstrip()
418 ef27bfa4 2010-01-12 rsc if line != '' and line[0] == '#':
419 ef27bfa4 2010-01-12 rsc continue
420 ef27bfa4 2010-01-12 rsc if line == '' or line[0] == ' ' or line[0] == '\t':
421 ef27bfa4 2010-01-12 rsc if sname == None and line != '':
422 ef27bfa4 2010-01-12 rsc return None, lineno, 'text outside section'
423 ef27bfa4 2010-01-12 rsc if sname != None:
424 ef27bfa4 2010-01-12 rsc sections[sname] += line + '\n'
425 ef27bfa4 2010-01-12 rsc continue
426 ef27bfa4 2010-01-12 rsc p = line.find(':')
427 ef27bfa4 2010-01-12 rsc if p >= 0:
428 ef27bfa4 2010-01-12 rsc s, val = line[:p].strip(), line[p+1:].strip()
429 ef27bfa4 2010-01-12 rsc if s in sections:
430 ef27bfa4 2010-01-12 rsc sname = s
431 ef27bfa4 2010-01-12 rsc if val != '':
432 ef27bfa4 2010-01-12 rsc sections[sname] += val + '\n'
433 ef27bfa4 2010-01-12 rsc continue
434 ef27bfa4 2010-01-12 rsc return None, lineno, 'malformed section header'
435 ef27bfa4 2010-01-12 rsc
436 ef27bfa4 2010-01-12 rsc for k in sections:
437 ef27bfa4 2010-01-12 rsc sections[k] = StripCommon(sections[k]).rstrip()
438 ef27bfa4 2010-01-12 rsc
439 ef27bfa4 2010-01-12 rsc cl = CL(name)
440 ef27bfa4 2010-01-12 rsc if sections['Author']:
441 ef27bfa4 2010-01-12 rsc cl.copied_from = sections['Author']
442 ef27bfa4 2010-01-12 rsc cl.desc = sections['Description']
443 ef27bfa4 2010-01-12 rsc for line in sections['Files'].split('\n'):
444 ef27bfa4 2010-01-12 rsc i = line.find('#')
445 ef27bfa4 2010-01-12 rsc if i >= 0:
446 ef27bfa4 2010-01-12 rsc line = line[0:i].rstrip()
447 26b4bea8 2011-08-02 rsc line = line.strip()
448 ef27bfa4 2010-01-12 rsc if line == '':
449 ef27bfa4 2010-01-12 rsc continue
450 ef27bfa4 2010-01-12 rsc cl.files.append(line)
451 ef27bfa4 2010-01-12 rsc cl.reviewer = SplitCommaSpace(sections['Reviewer'])
452 ef27bfa4 2010-01-12 rsc cl.cc = SplitCommaSpace(sections['CC'])
453 ef27bfa4 2010-01-12 rsc cl.url = sections['URL']
454 ef27bfa4 2010-01-12 rsc if sections['Mailed'] != 'False':
455 ef27bfa4 2010-01-12 rsc # Odd default, but avoids spurious mailings when
456 ef27bfa4 2010-01-12 rsc # reading old CLs that do not have a Mailed: line.
457 26b4bea8 2011-08-02 rsc # CLs created with this update will always have
458 ef27bfa4 2010-01-12 rsc # Mailed: False on disk.
459 ef27bfa4 2010-01-12 rsc cl.mailed = True
460 26b4bea8 2011-08-02 rsc if sections['Private'] in ('True', 'true', 'Yes', 'yes'):
461 26b4bea8 2011-08-02 rsc cl.private = True
462 ef27bfa4 2010-01-12 rsc if cl.desc == '<enter description here>':
463 ef27bfa4 2010-01-12 rsc cl.desc = ''
464 ef27bfa4 2010-01-12 rsc return cl, 0, ''
465 ef27bfa4 2010-01-12 rsc
466 ef27bfa4 2010-01-12 rsc def SplitCommaSpace(s):
467 26b4bea8 2011-08-02 rsc typecheck(s, str)
468 a06877af 2010-08-05 rsc s = s.strip()
469 a06877af 2010-08-05 rsc if s == "":
470 a06877af 2010-08-05 rsc return []
471 a06877af 2010-08-05 rsc return re.split(", *", s)
472 ef27bfa4 2010-01-12 rsc
473 ef27bfa4 2010-01-12 rsc def CutDomain(s):
474 26b4bea8 2011-08-02 rsc typecheck(s, str)
475 ef27bfa4 2010-01-12 rsc i = s.find('@')
476 ef27bfa4 2010-01-12 rsc if i >= 0:
477 ef27bfa4 2010-01-12 rsc s = s[0:i]
478 ef27bfa4 2010-01-12 rsc return s
479 ef27bfa4 2010-01-12 rsc
480 ef27bfa4 2010-01-12 rsc def JoinComma(l):
481 26b4bea8 2011-08-02 rsc for s in l:
482 26b4bea8 2011-08-02 rsc typecheck(s, str)
483 ef27bfa4 2010-01-12 rsc return ", ".join(l)
484 ef27bfa4 2010-01-12 rsc
485 ef27bfa4 2010-01-12 rsc def ExceptionDetail():
486 ef27bfa4 2010-01-12 rsc s = str(sys.exc_info()[0])
487 ef27bfa4 2010-01-12 rsc if s.startswith("<type '") and s.endswith("'>"):
488 ef27bfa4 2010-01-12 rsc s = s[7:-2]
489 ef27bfa4 2010-01-12 rsc elif s.startswith("<class '") and s.endswith("'>"):
490 ef27bfa4 2010-01-12 rsc s = s[8:-2]
491 ef27bfa4 2010-01-12 rsc arg = str(sys.exc_info()[1])
492 ef27bfa4 2010-01-12 rsc if len(arg) > 0:
493 ef27bfa4 2010-01-12 rsc s += ": " + arg
494 ef27bfa4 2010-01-12 rsc return s
495 ef27bfa4 2010-01-12 rsc
496 ef27bfa4 2010-01-12 rsc def IsLocalCL(ui, repo, name):
497 ef27bfa4 2010-01-12 rsc return GoodCLName(name) and os.access(CodeReviewDir(ui, repo) + "/cl." + name, 0)
498 ef27bfa4 2010-01-12 rsc
499 ef27bfa4 2010-01-12 rsc # Load CL from disk and/or the web.
500 ef27bfa4 2010-01-12 rsc def LoadCL(ui, repo, name, web=True):
501 26b4bea8 2011-08-02 rsc typecheck(name, str)
502 26b4bea8 2011-08-02 rsc set_status("loading CL " + name)
503 ef27bfa4 2010-01-12 rsc if not GoodCLName(name):
504 ef27bfa4 2010-01-12 rsc return None, "invalid CL name"
505 ef27bfa4 2010-01-12 rsc dir = CodeReviewDir(ui, repo)
506 ef27bfa4 2010-01-12 rsc path = dir + "cl." + name
507 ef27bfa4 2010-01-12 rsc if os.access(path, 0):
508 ef27bfa4 2010-01-12 rsc ff = open(path)
509 ef27bfa4 2010-01-12 rsc text = ff.read()
510 ef27bfa4 2010-01-12 rsc ff.close()
511 ef27bfa4 2010-01-12 rsc cl, lineno, err = ParseCL(text, name)
512 ef27bfa4 2010-01-12 rsc if err != "":
513 ef27bfa4 2010-01-12 rsc return None, "malformed CL data: "+err
514 ef27bfa4 2010-01-12 rsc cl.local = True
515 ef27bfa4 2010-01-12 rsc else:
516 ef27bfa4 2010-01-12 rsc cl = CL(name)
517 ef27bfa4 2010-01-12 rsc if web:
518 26b4bea8 2011-08-02 rsc set_status("getting issue metadata from web")
519 26b4bea8 2011-08-02 rsc d = JSONGet(ui, "/api/" + name + "?messages=true")
520 26b4bea8 2011-08-02 rsc set_status(None)
521 26b4bea8 2011-08-02 rsc if d is None:
522 26b4bea8 2011-08-02 rsc return None, "cannot load CL %s from server" % (name,)
523 26b4bea8 2011-08-02 rsc if 'owner_email' not in d or 'issue' not in d or str(d['issue']) != name:
524 ef27bfa4 2010-01-12 rsc return None, "malformed response loading CL data from code review server"
525 26b4bea8 2011-08-02 rsc cl.dict = d
526 26b4bea8 2011-08-02 rsc cl.reviewer = d.get('reviewers', [])
527 26b4bea8 2011-08-02 rsc cl.cc = d.get('cc', [])
528 ef27bfa4 2010-01-12 rsc if cl.local and cl.copied_from and cl.desc:
529 ef27bfa4 2010-01-12 rsc # local copy of CL written by someone else
530 ef27bfa4 2010-01-12 rsc # and we saved a description. use that one,
531 ef27bfa4 2010-01-12 rsc # so that committers can edit the description
532 ef27bfa4 2010-01-12 rsc # before doing hg submit.
533 ef27bfa4 2010-01-12 rsc pass
534 ef27bfa4 2010-01-12 rsc else:
535 26b4bea8 2011-08-02 rsc cl.desc = d.get('description', "")
536 ef27bfa4 2010-01-12 rsc cl.url = server_url_base + name
537 ef27bfa4 2010-01-12 rsc cl.web = True
538 26b4bea8 2011-08-02 rsc cl.private = d.get('private', False) != False
539 26b4bea8 2011-08-02 rsc set_status("loaded CL " + name)
540 ef27bfa4 2010-01-12 rsc return cl, ''
541 ef27bfa4 2010-01-12 rsc
542 26b4bea8 2011-08-02 rsc global_status = None
543 26b4bea8 2011-08-02 rsc
544 26b4bea8 2011-08-02 rsc def set_status(s):
545 26b4bea8 2011-08-02 rsc # print >>sys.stderr, "\t", time.asctime(), s
546 26b4bea8 2011-08-02 rsc global global_status
547 26b4bea8 2011-08-02 rsc global_status = s
548 26b4bea8 2011-08-02 rsc
549 26b4bea8 2011-08-02 rsc class StatusThread(threading.Thread):
550 26b4bea8 2011-08-02 rsc def __init__(self):
551 26b4bea8 2011-08-02 rsc threading.Thread.__init__(self)
552 26b4bea8 2011-08-02 rsc def run(self):
553 26b4bea8 2011-08-02 rsc # pause a reasonable amount of time before
554 26b4bea8 2011-08-02 rsc # starting to display status messages, so that
555 26b4bea8 2011-08-02 rsc # most hg commands won't ever see them.
556 26b4bea8 2011-08-02 rsc time.sleep(30)
557 26b4bea8 2011-08-02 rsc
558 26b4bea8 2011-08-02 rsc # now show status every 15 seconds
559 26b4bea8 2011-08-02 rsc while True:
560 26b4bea8 2011-08-02 rsc time.sleep(15 - time.time() % 15)
561 26b4bea8 2011-08-02 rsc s = global_status
562 26b4bea8 2011-08-02 rsc if s is None:
563 26b4bea8 2011-08-02 rsc continue
564 26b4bea8 2011-08-02 rsc if s == "":
565 26b4bea8 2011-08-02 rsc s = "(unknown status)"
566 26b4bea8 2011-08-02 rsc print >>sys.stderr, time.asctime(), s
567 26b4bea8 2011-08-02 rsc
568 26b4bea8 2011-08-02 rsc def start_status_thread():
569 26b4bea8 2011-08-02 rsc t = StatusThread()
570 26b4bea8 2011-08-02 rsc t.setDaemon(True) # allowed to exit if t is still running
571 26b4bea8 2011-08-02 rsc t.start()
572 26b4bea8 2011-08-02 rsc
573 ef27bfa4 2010-01-12 rsc class LoadCLThread(threading.Thread):
574 ef27bfa4 2010-01-12 rsc def __init__(self, ui, repo, dir, f, web):
575 ef27bfa4 2010-01-12 rsc threading.Thread.__init__(self)
576 ef27bfa4 2010-01-12 rsc self.ui = ui
577 ef27bfa4 2010-01-12 rsc self.repo = repo
578 ef27bfa4 2010-01-12 rsc self.dir = dir
579 ef27bfa4 2010-01-12 rsc self.f = f
580 ef27bfa4 2010-01-12 rsc self.web = web
581 ef27bfa4 2010-01-12 rsc self.cl = None
582 ef27bfa4 2010-01-12 rsc def run(self):
583 ef27bfa4 2010-01-12 rsc cl, err = LoadCL(self.ui, self.repo, self.f[3:], web=self.web)
584 ef27bfa4 2010-01-12 rsc if err != '':
585 ef27bfa4 2010-01-12 rsc self.ui.warn("loading "+self.dir+self.f+": " + err + "\n")
586 ef27bfa4 2010-01-12 rsc return
587 ef27bfa4 2010-01-12 rsc self.cl = cl
588 ef27bfa4 2010-01-12 rsc
589 ef27bfa4 2010-01-12 rsc # Load all the CLs from this repository.
590 ef27bfa4 2010-01-12 rsc def LoadAllCL(ui, repo, web=True):
591 ef27bfa4 2010-01-12 rsc dir = CodeReviewDir(ui, repo)
592 ef27bfa4 2010-01-12 rsc m = {}
593 ef27bfa4 2010-01-12 rsc files = [f for f in os.listdir(dir) if f.startswith('cl.')]
594 ef27bfa4 2010-01-12 rsc if not files:
595 ef27bfa4 2010-01-12 rsc return m
596 ef27bfa4 2010-01-12 rsc active = []
597 ef27bfa4 2010-01-12 rsc first = True
598 ef27bfa4 2010-01-12 rsc for f in files:
599 ef27bfa4 2010-01-12 rsc t = LoadCLThread(ui, repo, dir, f, web)
600 ef27bfa4 2010-01-12 rsc t.start()
601 ef27bfa4 2010-01-12 rsc if web and first:
602 ef27bfa4 2010-01-12 rsc # first request: wait in case it needs to authenticate
603 ef27bfa4 2010-01-12 rsc # otherwise we get lots of user/password prompts
604 ef27bfa4 2010-01-12 rsc # running in parallel.
605 ef27bfa4 2010-01-12 rsc t.join()
606 ef27bfa4 2010-01-12 rsc if t.cl:
607 ef27bfa4 2010-01-12 rsc m[t.cl.name] = t.cl
608 ef27bfa4 2010-01-12 rsc first = False
609 ef27bfa4 2010-01-12 rsc else:
610 ef27bfa4 2010-01-12 rsc active.append(t)
611 ef27bfa4 2010-01-12 rsc for t in active:
612 ef27bfa4 2010-01-12 rsc t.join()
613 ef27bfa4 2010-01-12 rsc if t.cl:
614 ef27bfa4 2010-01-12 rsc m[t.cl.name] = t.cl
615 ef27bfa4 2010-01-12 rsc return m
616 ef27bfa4 2010-01-12 rsc
617 ef27bfa4 2010-01-12 rsc # Find repository root. On error, ui.warn and return None
618 ef27bfa4 2010-01-12 rsc def RepoDir(ui, repo):
619 ef27bfa4 2010-01-12 rsc url = repo.url();
620 ef27bfa4 2010-01-12 rsc if not url.startswith('file:'):
621 ef27bfa4 2010-01-12 rsc ui.warn("repository %s is not in local file system\n" % (url,))
622 ef27bfa4 2010-01-12 rsc return None
623 ef27bfa4 2010-01-12 rsc url = url[5:]
624 ef27bfa4 2010-01-12 rsc if url.endswith('/'):
625 ef27bfa4 2010-01-12 rsc url = url[:-1]
626 26b4bea8 2011-08-02 rsc typecheck(url, str)
627 ef27bfa4 2010-01-12 rsc return url
628 ef27bfa4 2010-01-12 rsc
629 ef27bfa4 2010-01-12 rsc # Find (or make) code review directory. On error, ui.warn and return None
630 ef27bfa4 2010-01-12 rsc def CodeReviewDir(ui, repo):
631 ef27bfa4 2010-01-12 rsc dir = RepoDir(ui, repo)
632 ef27bfa4 2010-01-12 rsc if dir == None:
633 ef27bfa4 2010-01-12 rsc return None
634 ef27bfa4 2010-01-12 rsc dir += '/.hg/codereview/'
635 ef27bfa4 2010-01-12 rsc if not os.path.isdir(dir):
636 ef27bfa4 2010-01-12 rsc try:
637 ef27bfa4 2010-01-12 rsc os.mkdir(dir, 0700)
638 ef27bfa4 2010-01-12 rsc except:
639 ef27bfa4 2010-01-12 rsc ui.warn('cannot mkdir %s: %s\n' % (dir, ExceptionDetail()))
640 ef27bfa4 2010-01-12 rsc return None
641 26b4bea8 2011-08-02 rsc typecheck(dir, str)
642 ef27bfa4 2010-01-12 rsc return dir
643 ef27bfa4 2010-01-12 rsc
644 26b4bea8 2011-08-02 rsc # Turn leading tabs into spaces, so that the common white space
645 26b4bea8 2011-08-02 rsc # prefix doesn't get confused when people's editors write out
646 26b4bea8 2011-08-02 rsc # some lines with spaces, some with tabs. Only a heuristic
647 26b4bea8 2011-08-02 rsc # (some editors don't use 8 spaces either) but a useful one.
648 26b4bea8 2011-08-02 rsc def TabsToSpaces(line):
649 26b4bea8 2011-08-02 rsc i = 0
650 26b4bea8 2011-08-02 rsc while i < len(line) and line[i] == '\t':
651 26b4bea8 2011-08-02 rsc i += 1
652 26b4bea8 2011-08-02 rsc return ' '*(8*i) + line[i:]
653 26b4bea8 2011-08-02 rsc
654 ef27bfa4 2010-01-12 rsc # Strip maximal common leading white space prefix from text
655 ef27bfa4 2010-01-12 rsc def StripCommon(text):
656 26b4bea8 2011-08-02 rsc typecheck(text, str)
657 ef27bfa4 2010-01-12 rsc ws = None
658 ef27bfa4 2010-01-12 rsc for line in text.split('\n'):
659 ef27bfa4 2010-01-12 rsc line = line.rstrip()
660 ef27bfa4 2010-01-12 rsc if line == '':
661 ef27bfa4 2010-01-12 rsc continue
662 26b4bea8 2011-08-02 rsc line = TabsToSpaces(line)
663 ef27bfa4 2010-01-12 rsc white = line[:len(line)-len(line.lstrip())]
664 ef27bfa4 2010-01-12 rsc if ws == None:
665 ef27bfa4 2010-01-12 rsc ws = white
666 ef27bfa4 2010-01-12 rsc else:
667 ef27bfa4 2010-01-12 rsc common = ''
668 ef27bfa4 2010-01-12 rsc for i in range(min(len(white), len(ws))+1):
669 ef27bfa4 2010-01-12 rsc if white[0:i] == ws[0:i]:
670 ef27bfa4 2010-01-12 rsc common = white[0:i]
671 ef27bfa4 2010-01-12 rsc ws = common
672 ef27bfa4 2010-01-12 rsc if ws == '':
673 ef27bfa4 2010-01-12 rsc break
674 ef27bfa4 2010-01-12 rsc if ws == None:
675 ef27bfa4 2010-01-12 rsc return text
676 ef27bfa4 2010-01-12 rsc t = ''
677 ef27bfa4 2010-01-12 rsc for line in text.split('\n'):
678 ef27bfa4 2010-01-12 rsc line = line.rstrip()
679 26b4bea8 2011-08-02 rsc line = TabsToSpaces(line)
680 ef27bfa4 2010-01-12 rsc if line.startswith(ws):
681 ef27bfa4 2010-01-12 rsc line = line[len(ws):]
682 ef27bfa4 2010-01-12 rsc if line == '' and t == '':
683 ef27bfa4 2010-01-12 rsc continue
684 ef27bfa4 2010-01-12 rsc t += line + '\n'
685 ef27bfa4 2010-01-12 rsc while len(t) >= 2 and t[-2:] == '\n\n':
686 ef27bfa4 2010-01-12 rsc t = t[:-1]
687 26b4bea8 2011-08-02 rsc typecheck(t, str)
688 ef27bfa4 2010-01-12 rsc return t
689 ef27bfa4 2010-01-12 rsc
690 ef27bfa4 2010-01-12 rsc # Indent text with indent.
691 ef27bfa4 2010-01-12 rsc def Indent(text, indent):
692 26b4bea8 2011-08-02 rsc typecheck(text, str)
693 26b4bea8 2011-08-02 rsc typecheck(indent, str)
694 ef27bfa4 2010-01-12 rsc t = ''
695 ef27bfa4 2010-01-12 rsc for line in text.split('\n'):
696 ef27bfa4 2010-01-12 rsc t += indent + line + '\n'
697 26b4bea8 2011-08-02 rsc typecheck(t, str)
698 ef27bfa4 2010-01-12 rsc return t
699 ef27bfa4 2010-01-12 rsc
700 ef27bfa4 2010-01-12 rsc # Return the first line of l
701 ef27bfa4 2010-01-12 rsc def line1(text):
702 26b4bea8 2011-08-02 rsc typecheck(text, str)
703 ef27bfa4 2010-01-12 rsc return text.split('\n')[0]
704 ef27bfa4 2010-01-12 rsc
705 ef27bfa4 2010-01-12 rsc _change_prolog = """# Change list.
706 ef27bfa4 2010-01-12 rsc # Lines beginning with # are ignored.
707 ef27bfa4 2010-01-12 rsc # Multi-line values should be indented.
708 ef27bfa4 2010-01-12 rsc """
709 ef27bfa4 2010-01-12 rsc
710 ef27bfa4 2010-01-12 rsc #######################################################################
711 ef27bfa4 2010-01-12 rsc # Mercurial helper functions
712 ef27bfa4 2010-01-12 rsc
713 a06877af 2010-08-05 rsc # Get effective change nodes taking into account applied MQ patches
714 a06877af 2010-08-05 rsc def effective_revpair(repo):
715 a06877af 2010-08-05 rsc try:
716 a06877af 2010-08-05 rsc return cmdutil.revpair(repo, ['qparent'])
717 a06877af 2010-08-05 rsc except:
718 a06877af 2010-08-05 rsc return cmdutil.revpair(repo, None)
719 a06877af 2010-08-05 rsc
720 ef27bfa4 2010-01-12 rsc # Return list of changed files in repository that match pats.
721 26b4bea8 2011-08-02 rsc # Warn about patterns that did not match.
722 26b4bea8 2011-08-02 rsc def matchpats(ui, repo, pats, opts):
723 ef27bfa4 2010-01-12 rsc matcher = cmdutil.match(repo, pats, opts)
724 a06877af 2010-08-05 rsc node1, node2 = effective_revpair(repo)
725 26b4bea8 2011-08-02 rsc modified, added, removed, deleted, unknown, ignored, clean = repo.status(node1, node2, matcher, ignored=True, clean=True, unknown=True)
726 26b4bea8 2011-08-02 rsc return (modified, added, removed, deleted, unknown, ignored, clean)
727 26b4bea8 2011-08-02 rsc
728 26b4bea8 2011-08-02 rsc # Return list of changed files in repository that match pats.
729 26b4bea8 2011-08-02 rsc # The patterns came from the command line, so we warn
730 26b4bea8 2011-08-02 rsc # if they have no effect or cannot be understood.
731 26b4bea8 2011-08-02 rsc def ChangedFiles(ui, repo, pats, opts, taken=None):
732 26b4bea8 2011-08-02 rsc taken = taken or {}
733 26b4bea8 2011-08-02 rsc # Run each pattern separately so that we can warn about
734 26b4bea8 2011-08-02 rsc # patterns that didn't do anything useful.
735 26b4bea8 2011-08-02 rsc for p in pats:
736 26b4bea8 2011-08-02 rsc modified, added, removed, deleted, unknown, ignored, clean = matchpats(ui, repo, [p], opts)
737 26b4bea8 2011-08-02 rsc redo = False
738 26b4bea8 2011-08-02 rsc for f in unknown:
739 26b4bea8 2011-08-02 rsc promptadd(ui, repo, f)
740 26b4bea8 2011-08-02 rsc redo = True
741 26b4bea8 2011-08-02 rsc for f in deleted:
742 26b4bea8 2011-08-02 rsc promptremove(ui, repo, f)
743 26b4bea8 2011-08-02 rsc redo = True
744 26b4bea8 2011-08-02 rsc if redo:
745 26b4bea8 2011-08-02 rsc modified, added, removed, deleted, unknown, ignored, clean = matchpats(ui, repo, [p], opts)
746 26b4bea8 2011-08-02 rsc for f in modified + added + removed:
747 26b4bea8 2011-08-02 rsc if f in taken:
748 26b4bea8 2011-08-02 rsc ui.warn("warning: %s already in CL %s\n" % (f, taken[f].name))
749 26b4bea8 2011-08-02 rsc if not modified and not added and not removed:
750 26b4bea8 2011-08-02 rsc ui.warn("warning: %s did not match any modified files\n" % (p,))
751 26b4bea8 2011-08-02 rsc
752 26b4bea8 2011-08-02 rsc # Again, all at once (eliminates duplicates)
753 26b4bea8 2011-08-02 rsc modified, added, removed = matchpats(ui, repo, pats, opts)[:3]
754 ef27bfa4 2010-01-12 rsc l = modified + added + removed
755 ef27bfa4 2010-01-12 rsc l.sort()
756 26b4bea8 2011-08-02 rsc if taken:
757 26b4bea8 2011-08-02 rsc l = Sub(l, taken.keys())
758 ef27bfa4 2010-01-12 rsc return l
759 ef27bfa4 2010-01-12 rsc
760 ef27bfa4 2010-01-12 rsc # Return list of changed files in repository that match pats and still exist.
761 ef27bfa4 2010-01-12 rsc def ChangedExistingFiles(ui, repo, pats, opts):
762 26b4bea8 2011-08-02 rsc modified, added = matchpats(ui, repo, pats, opts)[:2]
763 ef27bfa4 2010-01-12 rsc l = modified + added
764 ef27bfa4 2010-01-12 rsc l.sort()
765 ef27bfa4 2010-01-12 rsc return l
766 ef27bfa4 2010-01-12 rsc
767 ef27bfa4 2010-01-12 rsc # Return list of files claimed by existing CLs
768 ef27bfa4 2010-01-12 rsc def Taken(ui, repo):
769 ef27bfa4 2010-01-12 rsc all = LoadAllCL(ui, repo, web=False)
770 ef27bfa4 2010-01-12 rsc taken = {}
771 ef27bfa4 2010-01-12 rsc for _, cl in all.items():
772 ef27bfa4 2010-01-12 rsc for f in cl.files:
773 ef27bfa4 2010-01-12 rsc taken[f] = cl
774 ef27bfa4 2010-01-12 rsc return taken
775 ef27bfa4 2010-01-12 rsc
776 ef27bfa4 2010-01-12 rsc # Return list of changed files that are not claimed by other CLs
777 ef27bfa4 2010-01-12 rsc def DefaultFiles(ui, repo, pats, opts):
778 26b4bea8 2011-08-02 rsc return ChangedFiles(ui, repo, pats, opts, taken=Taken(ui, repo))
779 ef27bfa4 2010-01-12 rsc
780 ef27bfa4 2010-01-12 rsc def Sub(l1, l2):
781 ef27bfa4 2010-01-12 rsc return [l for l in l1 if l not in l2]
782 ef27bfa4 2010-01-12 rsc
783 ef27bfa4 2010-01-12 rsc def Add(l1, l2):
784 ef27bfa4 2010-01-12 rsc l = l1 + Sub(l2, l1)
785 ef27bfa4 2010-01-12 rsc l.sort()
786 ef27bfa4 2010-01-12 rsc return l
787 ef27bfa4 2010-01-12 rsc
788 ef27bfa4 2010-01-12 rsc def Intersect(l1, l2):
789 ef27bfa4 2010-01-12 rsc return [l for l in l1 if l in l2]
790 ef27bfa4 2010-01-12 rsc
791 ef27bfa4 2010-01-12 rsc def getremote(ui, repo, opts):
792 ef27bfa4 2010-01-12 rsc # save $http_proxy; creating the HTTP repo object will
793 ef27bfa4 2010-01-12 rsc # delete it in an attempt to "help"
794 ef27bfa4 2010-01-12 rsc proxy = os.environ.get('http_proxy')
795 a06877af 2010-08-05 rsc source = hg.parseurl(ui.expandpath("default"), None)[0]
796 a06877af 2010-08-05 rsc try:
797 a06877af 2010-08-05 rsc remoteui = hg.remoteui # hg 1.6
798 26b4bea8 2011-08-02 rsc except:
799 a06877af 2010-08-05 rsc remoteui = cmdutil.remoteui
800 a06877af 2010-08-05 rsc other = hg.repository(remoteui(repo, opts), source)
801 ef27bfa4 2010-01-12 rsc if proxy is not None:
802 ef27bfa4 2010-01-12 rsc os.environ['http_proxy'] = proxy
803 ef27bfa4 2010-01-12 rsc return other
804 ef27bfa4 2010-01-12 rsc
805 ef27bfa4 2010-01-12 rsc def Incoming(ui, repo, opts):
806 a06877af 2010-08-05 rsc _, incoming, _ = findcommonincoming(repo, getremote(ui, repo, opts))
807 ef27bfa4 2010-01-12 rsc return incoming
808 ef27bfa4 2010-01-12 rsc
809 26b4bea8 2011-08-02 rsc desc_re = '^(.+: |(tag )?(release|weekly)\.|fix build|undo CL)'
810 26b4bea8 2011-08-02 rsc
811 26b4bea8 2011-08-02 rsc desc_msg = '''Your CL description appears not to use the standard form.
812 26b4bea8 2011-08-02 rsc
813 26b4bea8 2011-08-02 rsc The first line of your change description is conventionally a
814 26b4bea8 2011-08-02 rsc one-line summary of the change, prefixed by the primary affected package,
815 26b4bea8 2011-08-02 rsc and is used as the subject for code review mail; the rest of the description
816 26b4bea8 2011-08-02 rsc elaborates.
817 26b4bea8 2011-08-02 rsc
818 26b4bea8 2011-08-02 rsc Examples:
819 26b4bea8 2011-08-02 rsc
820 26b4bea8 2011-08-02 rsc encoding/rot13: new package
821 26b4bea8 2011-08-02 rsc
822 26b4bea8 2011-08-02 rsc math: add IsInf, IsNaN
823 26b4bea8 2011-08-02 rsc
824 26b4bea8 2011-08-02 rsc net: fix cname in LookupHost
825 26b4bea8 2011-08-02 rsc
826 26b4bea8 2011-08-02 rsc unicode: update to Unicode 5.0.2
827 26b4bea8 2011-08-02 rsc
828 26b4bea8 2011-08-02 rsc '''
829 26b4bea8 2011-08-02 rsc
830 26b4bea8 2011-08-02 rsc
831 26b4bea8 2011-08-02 rsc
832 26b4bea8 2011-08-02 rsc def promptremove(ui, repo, f):
833 26b4bea8 2011-08-02 rsc if promptyesno(ui, "hg remove %s (y/n)?" % (f,)):
834 26b4bea8 2011-08-02 rsc if commands.remove(ui, repo, 'path:'+f) != 0:
835 26b4bea8 2011-08-02 rsc ui.warn("error removing %s" % (f,))
836 26b4bea8 2011-08-02 rsc
837 26b4bea8 2011-08-02 rsc def promptadd(ui, repo, f):
838 26b4bea8 2011-08-02 rsc if promptyesno(ui, "hg add %s (y/n)?" % (f,)):
839 26b4bea8 2011-08-02 rsc if commands.add(ui, repo, 'path:'+f) != 0:
840 26b4bea8 2011-08-02 rsc ui.warn("error adding %s" % (f,))
841 26b4bea8 2011-08-02 rsc
842 ef27bfa4 2010-01-12 rsc def EditCL(ui, repo, cl):
843 26b4bea8 2011-08-02 rsc set_status(None) # do not show status
844 ef27bfa4 2010-01-12 rsc s = cl.EditorText()
845 ef27bfa4 2010-01-12 rsc while True:
846 ef27bfa4 2010-01-12 rsc s = ui.edit(s, ui.username())
847 ef27bfa4 2010-01-12 rsc clx, line, err = ParseCL(s, cl.name)
848 ef27bfa4 2010-01-12 rsc if err != '':
849 a06877af 2010-08-05 rsc if not promptyesno(ui, "error parsing change list: line %d: %s\nre-edit (y/n)?" % (line, err)):
850 ef27bfa4 2010-01-12 rsc return "change list not modified"
851 ef27bfa4 2010-01-12 rsc continue
852 26b4bea8 2011-08-02 rsc
853 26b4bea8 2011-08-02 rsc # Check description.
854 26b4bea8 2011-08-02 rsc if clx.desc == '':
855 26b4bea8 2011-08-02 rsc if promptyesno(ui, "change list should have a description\nre-edit (y/n)?"):
856 26b4bea8 2011-08-02 rsc continue
857 26b4bea8 2011-08-02 rsc elif re.search('<enter reason for undo>', clx.desc):
858 26b4bea8 2011-08-02 rsc if promptyesno(ui, "change list description omits reason for undo\nre-edit (y/n)?"):
859 26b4bea8 2011-08-02 rsc continue
860 26b4bea8 2011-08-02 rsc elif not re.match(desc_re, clx.desc.split('\n')[0]):
861 26b4bea8 2011-08-02 rsc if promptyesno(ui, desc_msg + "re-edit (y/n)?"):
862 26b4bea8 2011-08-02 rsc continue
863 26b4bea8 2011-08-02 rsc
864 26b4bea8 2011-08-02 rsc # Check file list for files that need to be hg added or hg removed
865 26b4bea8 2011-08-02 rsc # or simply aren't understood.
866 26b4bea8 2011-08-02 rsc pats = ['path:'+f for f in clx.files]
867 26b4bea8 2011-08-02 rsc modified, added, removed, deleted, unknown, ignored, clean = matchpats(ui, repo, pats, {})
868 26b4bea8 2011-08-02 rsc files = []
869 26b4bea8 2011-08-02 rsc for f in clx.files:
870 26b4bea8 2011-08-02 rsc if f in modified or f in added or f in removed:
871 26b4bea8 2011-08-02 rsc files.append(f)
872 26b4bea8 2011-08-02 rsc continue
873 26b4bea8 2011-08-02 rsc if f in deleted:
874 26b4bea8 2011-08-02 rsc promptremove(ui, repo, f)
875 26b4bea8 2011-08-02 rsc files.append(f)
876 26b4bea8 2011-08-02 rsc continue
877 26b4bea8 2011-08-02 rsc if f in unknown:
878 26b4bea8 2011-08-02 rsc promptadd(ui, repo, f)
879 26b4bea8 2011-08-02 rsc files.append(f)
880 26b4bea8 2011-08-02 rsc continue
881 26b4bea8 2011-08-02 rsc if f in ignored:
882 26b4bea8 2011-08-02 rsc ui.warn("error: %s is excluded by .hgignore; omitting\n" % (f,))
883 26b4bea8 2011-08-02 rsc continue
884 26b4bea8 2011-08-02 rsc if f in clean:
885 26b4bea8 2011-08-02 rsc ui.warn("warning: %s is listed in the CL but unchanged\n" % (f,))
886 26b4bea8 2011-08-02 rsc files.append(f)
887 26b4bea8 2011-08-02 rsc continue
888 26b4bea8 2011-08-02 rsc p = repo.root + '/' + f
889 26b4bea8 2011-08-02 rsc if os.path.isfile(p):
890 26b4bea8 2011-08-02 rsc ui.warn("warning: %s is a file but not known to hg\n" % (f,))
891 26b4bea8 2011-08-02 rsc files.append(f)
892 26b4bea8 2011-08-02 rsc continue
893 26b4bea8 2011-08-02 rsc if os.path.isdir(p):
894 26b4bea8 2011-08-02 rsc ui.warn("error: %s is a directory, not a file; omitting\n" % (f,))
895 26b4bea8 2011-08-02 rsc continue
896 26b4bea8 2011-08-02 rsc ui.warn("error: %s does not exist; omitting\n" % (f,))
897 26b4bea8 2011-08-02 rsc clx.files = files
898 26b4bea8 2011-08-02 rsc
899 26b4bea8 2011-08-02 rsc cl.desc = clx.desc
900 ef27bfa4 2010-01-12 rsc cl.reviewer = clx.reviewer
901 ef27bfa4 2010-01-12 rsc cl.cc = clx.cc
902 ef27bfa4 2010-01-12 rsc cl.files = clx.files
903 26b4bea8 2011-08-02 rsc cl.private = clx.private
904 ef27bfa4 2010-01-12 rsc break
905 ef27bfa4 2010-01-12 rsc return ""
906 ef27bfa4 2010-01-12 rsc
907 ef27bfa4 2010-01-12 rsc # For use by submit, etc. (NOT by change)
908 ef27bfa4 2010-01-12 rsc # Get change list number or list of files from command line.
909 ef27bfa4 2010-01-12 rsc # If files are given, make a new change list.
910 ef27bfa4 2010-01-12 rsc def CommandLineCL(ui, repo, pats, opts, defaultcc=None):
911 ef27bfa4 2010-01-12 rsc if len(pats) > 0 and GoodCLName(pats[0]):
912 ef27bfa4 2010-01-12 rsc if len(pats) != 1:
913 ef27bfa4 2010-01-12 rsc return None, "cannot specify change number and file names"
914 ef27bfa4 2010-01-12 rsc if opts.get('message'):
915 ef27bfa4 2010-01-12 rsc return None, "cannot use -m with existing CL"
916 ef27bfa4 2010-01-12 rsc cl, err = LoadCL(ui, repo, pats[0], web=True)
917 ef27bfa4 2010-01-12 rsc if err != "":
918 ef27bfa4 2010-01-12 rsc return None, err
919 ef27bfa4 2010-01-12 rsc else:
920 ef27bfa4 2010-01-12 rsc cl = CL("new")
921 ef27bfa4 2010-01-12 rsc cl.local = True
922 26b4bea8 2011-08-02 rsc cl.files = ChangedFiles(ui, repo, pats, opts, taken=Taken(ui, repo))
923 ef27bfa4 2010-01-12 rsc if not cl.files:
924 ef27bfa4 2010-01-12 rsc return None, "no files changed"
925 ef27bfa4 2010-01-12 rsc if opts.get('reviewer'):
926 ef27bfa4 2010-01-12 rsc cl.reviewer = Add(cl.reviewer, SplitCommaSpace(opts.get('reviewer')))
927 ef27bfa4 2010-01-12 rsc if opts.get('cc'):
928 ef27bfa4 2010-01-12 rsc cl.cc = Add(cl.cc, SplitCommaSpace(opts.get('cc')))
929 ef27bfa4 2010-01-12 rsc if defaultcc:
930 ef27bfa4 2010-01-12 rsc cl.cc = Add(cl.cc, defaultcc)
931 ef27bfa4 2010-01-12 rsc if cl.name == "new":
932 ef27bfa4 2010-01-12 rsc if opts.get('message'):
933 ef27bfa4 2010-01-12 rsc cl.desc = opts.get('message')
934 ef27bfa4 2010-01-12 rsc else:
935 ef27bfa4 2010-01-12 rsc err = EditCL(ui, repo, cl)
936 ef27bfa4 2010-01-12 rsc if err != '':
937 ef27bfa4 2010-01-12 rsc return None, err
938 ef27bfa4 2010-01-12 rsc return cl, ""
939 ef27bfa4 2010-01-12 rsc
940 ef27bfa4 2010-01-12 rsc # reposetup replaces cmdutil.match with this wrapper,
941 ef27bfa4 2010-01-12 rsc # which expands the syntax @clnumber to mean the files
942 ef27bfa4 2010-01-12 rsc # in that CL.
943 ef27bfa4 2010-01-12 rsc original_match = None
944 26b4bea8 2011-08-02 rsc def ReplacementForCmdutilMatch(repo, pats=None, opts=None, globbed=False, default='relpath'):
945 ef27bfa4 2010-01-12 rsc taken = []
946 ef27bfa4 2010-01-12 rsc files = []
947 26b4bea8 2011-08-02 rsc pats = pats or []
948 26b4bea8 2011-08-02 rsc opts = opts or {}
949 ef27bfa4 2010-01-12 rsc for p in pats:
950 ef27bfa4 2010-01-12 rsc if p.startswith('@'):
951 ef27bfa4 2010-01-12 rsc taken.append(p)
952 ef27bfa4 2010-01-12 rsc clname = p[1:]
953 ef27bfa4 2010-01-12 rsc if not GoodCLName(clname):
954 ef27bfa4 2010-01-12 rsc raise util.Abort("invalid CL name " + clname)
955 ef27bfa4 2010-01-12 rsc cl, err = LoadCL(repo.ui, repo, clname, web=False)
956 ef27bfa4 2010-01-12 rsc if err != '':
957 ef27bfa4 2010-01-12 rsc raise util.Abort("loading CL " + clname + ": " + err)
958 26b4bea8 2011-08-02 rsc if not cl.files:
959 ef27bfa4 2010-01-12 rsc raise util.Abort("no files in CL " + clname)
960 ef27bfa4 2010-01-12 rsc files = Add(files, cl.files)
961 a06877af 2010-08-05 rsc pats = Sub(pats, taken) + ['path:'+f for f in files]
962 ef27bfa4 2010-01-12 rsc return original_match(repo, pats=pats, opts=opts, globbed=globbed, default=default)
963 ef27bfa4 2010-01-12 rsc
964 ef27bfa4 2010-01-12 rsc def RelativePath(path, cwd):
965 ef27bfa4 2010-01-12 rsc n = len(cwd)
966 ef27bfa4 2010-01-12 rsc if path.startswith(cwd) and path[n] == '/':
967 ef27bfa4 2010-01-12 rsc return path[n+1:]
968 ef27bfa4 2010-01-12 rsc return path
969 ef27bfa4 2010-01-12 rsc
970 26b4bea8 2011-08-02 rsc def CheckFormat(ui, repo, files, just_warn=False):
971 26b4bea8 2011-08-02 rsc set_status("running gofmt")
972 26b4bea8 2011-08-02 rsc CheckGofmt(ui, repo, files, just_warn)
973 26b4bea8 2011-08-02 rsc CheckTabfmt(ui, repo, files, just_warn)
974 26b4bea8 2011-08-02 rsc
975 ef27bfa4 2010-01-12 rsc # Check that gofmt run on the list of files does not change them
976 26b4bea8 2011-08-02 rsc def CheckGofmt(ui, repo, files, just_warn):
977 ef27bfa4 2010-01-12 rsc files = [f for f in files if (f.startswith('src/') or f.startswith('test/bench/')) and f.endswith('.go')]
978 ef27bfa4 2010-01-12 rsc if not files:
979 ef27bfa4 2010-01-12 rsc return
980 ef27bfa4 2010-01-12 rsc cwd = os.getcwd()
981 ef27bfa4 2010-01-12 rsc files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
982 ef27bfa4 2010-01-12 rsc files = [f for f in files if os.access(f, 0)]
983 a06877af 2010-08-05 rsc if not files:
984 a06877af 2010-08-05 rsc return
985 ef27bfa4 2010-01-12 rsc try:
986 26b4bea8 2011-08-02 rsc cmd = subprocess.Popen(["gofmt", "-l"] + files, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=sys.platform != "win32")
987 ef27bfa4 2010-01-12 rsc cmd.stdin.close()
988 ef27bfa4 2010-01-12 rsc except:
989 ef27bfa4 2010-01-12 rsc raise util.Abort("gofmt: " + ExceptionDetail())
990 ef27bfa4 2010-01-12 rsc data = cmd.stdout.read()
991 ef27bfa4 2010-01-12 rsc errors = cmd.stderr.read()
992 ef27bfa4 2010-01-12 rsc cmd.wait()
993 26b4bea8 2011-08-02 rsc set_status("done with gofmt")
994 ef27bfa4 2010-01-12 rsc if len(errors) > 0:
995 ef27bfa4 2010-01-12 rsc ui.warn("gofmt errors:\n" + errors.rstrip() + "\n")
996 ef27bfa4 2010-01-12 rsc return
997 ef27bfa4 2010-01-12 rsc if len(data) > 0:
998 ef27bfa4 2010-01-12 rsc msg = "gofmt needs to format these files (run hg gofmt):\n" + Indent(data, "\t").rstrip()
999 26b4bea8 2011-08-02 rsc if just_warn:
1000 26b4bea8 2011-08-02 rsc ui.warn("warning: " + msg + "\n")
1001 26b4bea8 2011-08-02 rsc else:
1002 26b4bea8 2011-08-02 rsc raise util.Abort(msg)
1003 26b4bea8 2011-08-02 rsc return
1004 26b4bea8 2011-08-02 rsc
1005 26b4bea8 2011-08-02 rsc # Check that *.[chys] files indent using tabs.
1006 26b4bea8 2011-08-02 rsc def CheckTabfmt(ui, repo, files, just_warn):
1007 26b4bea8 2011-08-02 rsc files = [f for f in files if f.startswith('src/') and re.search(r"\.[chys]$", f)]
1008 26b4bea8 2011-08-02 rsc if not files:
1009 26b4bea8 2011-08-02 rsc return
1010 26b4bea8 2011-08-02 rsc cwd = os.getcwd()
1011 26b4bea8 2011-08-02 rsc files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
1012 26b4bea8 2011-08-02 rsc files = [f for f in files if os.access(f, 0)]
1013 26b4bea8 2011-08-02 rsc badfiles = []
1014 26b4bea8 2011-08-02 rsc for f in files:
1015 26b4bea8 2011-08-02 rsc try:
1016 26b4bea8 2011-08-02 rsc for line in open(f, 'r'):
1017 26b4bea8 2011-08-02 rsc # Four leading spaces is enough to complain about,
1018 26b4bea8 2011-08-02 rsc # except that some Plan 9 code uses four spaces as the label indent,
1019 26b4bea8 2011-08-02 rsc # so allow that.
1020 26b4bea8 2011-08-02 rsc if line.startswith(' ') and not re.match(' [A-Za-z0-9_]+:', line):
1021 26b4bea8 2011-08-02 rsc badfiles.append(f)
1022 26b4bea8 2011-08-02 rsc break
1023 26b4bea8 2011-08-02 rsc except:
1024 26b4bea8 2011-08-02 rsc # ignore cannot open file, etc.
1025 26b4bea8 2011-08-02 rsc pass
1026 26b4bea8 2011-08-02 rsc if len(badfiles) > 0:
1027 26b4bea8 2011-08-02 rsc msg = "these files use spaces for indentation (use tabs instead):\n\t" + "\n\t".join(badfiles)
1028 ef27bfa4 2010-01-12 rsc if just_warn:
1029 ef27bfa4 2010-01-12 rsc ui.warn("warning: " + msg + "\n")
1030 ef27bfa4 2010-01-12 rsc else:
1031 ef27bfa4 2010-01-12 rsc raise util.Abort(msg)
1032 ef27bfa4 2010-01-12 rsc return
1033 ef27bfa4 2010-01-12 rsc
1034 ef27bfa4 2010-01-12 rsc #######################################################################
1035 ef27bfa4 2010-01-12 rsc # Mercurial commands
1036 ef27bfa4 2010-01-12 rsc
1037 ef27bfa4 2010-01-12 rsc # every command must take a ui and and repo as arguments.
1038 ef27bfa4 2010-01-12 rsc # opts is a dict where you can find other command line flags
1039 ef27bfa4 2010-01-12 rsc #
1040 ef27bfa4 2010-01-12 rsc # Other parameters are taken in order from items on the command line that
1041 ef27bfa4 2010-01-12 rsc # don't start with a dash. If no default value is given in the parameter list,
1042 ef27bfa4 2010-01-12 rsc # they are required.
1043 ef27bfa4 2010-01-12 rsc #
1044 ef27bfa4 2010-01-12 rsc
1045 ef27bfa4 2010-01-12 rsc def change(ui, repo, *pats, **opts):
1046 a06877af 2010-08-05 rsc """create, edit or delete a change list
1047 ef27bfa4 2010-01-12 rsc
1048 a06877af 2010-08-05 rsc Create, edit or delete a change list.
1049 ef27bfa4 2010-01-12 rsc A change list is a group of files to be reviewed and submitted together,
1050 ef27bfa4 2010-01-12 rsc plus a textual description of the change.
1051 ef27bfa4 2010-01-12 rsc Change lists are referred to by simple alphanumeric names.
1052 ef27bfa4 2010-01-12 rsc
1053 ef27bfa4 2010-01-12 rsc Changes must be reviewed before they can be submitted.
1054 ef27bfa4 2010-01-12 rsc
1055 ef27bfa4 2010-01-12 rsc In the absence of options, the change command opens the
1056 ef27bfa4 2010-01-12 rsc change list for editing in the default editor.
1057 ef27bfa4 2010-01-12 rsc
1058 ef27bfa4 2010-01-12 rsc Deleting a change with the -d or -D flag does not affect
1059 ef27bfa4 2010-01-12 rsc the contents of the files listed in that change. To revert
1060 ef27bfa4 2010-01-12 rsc the files listed in a change, use
1061 ef27bfa4 2010-01-12 rsc
1062 ef27bfa4 2010-01-12 rsc hg revert @123456
1063 ef27bfa4 2010-01-12 rsc
1064 ef27bfa4 2010-01-12 rsc before running hg change -d 123456.
1065 ef27bfa4 2010-01-12 rsc """
1066 ef27bfa4 2010-01-12 rsc
1067 26b4bea8 2011-08-02 rsc if missing_codereview:
1068 26b4bea8 2011-08-02 rsc return missing_codereview
1069 26b4bea8 2011-08-02 rsc
1070 ef27bfa4 2010-01-12 rsc dirty = {}
1071 ef27bfa4 2010-01-12 rsc if len(pats) > 0 and GoodCLName(pats[0]):
1072 ef27bfa4 2010-01-12 rsc name = pats[0]
1073 ef27bfa4 2010-01-12 rsc if len(pats) != 1:
1074 ef27bfa4 2010-01-12 rsc return "cannot specify CL name and file patterns"
1075 ef27bfa4 2010-01-12 rsc pats = pats[1:]
1076 ef27bfa4 2010-01-12 rsc cl, err = LoadCL(ui, repo, name, web=True)
1077 ef27bfa4 2010-01-12 rsc if err != '':
1078 ef27bfa4 2010-01-12 rsc return err
1079 ef27bfa4 2010-01-12 rsc if not cl.local and (opts["stdin"] or not opts["stdout"]):
1080 ef27bfa4 2010-01-12 rsc return "cannot change non-local CL " + name
1081 ef27bfa4 2010-01-12 rsc else:
1082 26b4bea8 2011-08-02 rsc if repo[None].branch() != "default":
1083 26b4bea8 2011-08-02 rsc return "cannot run hg change outside default branch"
1084 ef27bfa4 2010-01-12 rsc name = "new"
1085 ef27bfa4 2010-01-12 rsc cl = CL("new")
1086 ef27bfa4 2010-01-12 rsc dirty[cl] = True
1087 26b4bea8 2011-08-02 rsc files = ChangedFiles(ui, repo, pats, opts, taken=Taken(ui, repo))
1088 ef27bfa4 2010-01-12 rsc
1089 ef27bfa4 2010-01-12 rsc if opts["delete"] or opts["deletelocal"]:
1090 ef27bfa4 2010-01-12 rsc if opts["delete"] and opts["deletelocal"]:
1091 ef27bfa4 2010-01-12 rsc return "cannot use -d and -D together"
1092 ef27bfa4 2010-01-12 rsc flag = "-d"
1093 ef27bfa4 2010-01-12 rsc if opts["deletelocal"]:
1094 ef27bfa4 2010-01-12 rsc flag = "-D"
1095 ef27bfa4 2010-01-12 rsc if name == "new":
1096 ef27bfa4 2010-01-12 rsc return "cannot use "+flag+" with file patterns"
1097 ef27bfa4 2010-01-12 rsc if opts["stdin"] or opts["stdout"]:
1098 ef27bfa4 2010-01-12 rsc return "cannot use "+flag+" with -i or -o"
1099 ef27bfa4 2010-01-12 rsc if not cl.local:
1100 ef27bfa4 2010-01-12 rsc return "cannot change non-local CL " + name
1101 ef27bfa4 2010-01-12 rsc if opts["delete"]:
1102 ef27bfa4 2010-01-12 rsc if cl.copied_from:
1103 ef27bfa4 2010-01-12 rsc return "original author must delete CL; hg change -D will remove locally"
1104 26b4bea8 2011-08-02 rsc PostMessage(ui, cl.name, "*** Abandoned ***", send_mail=cl.mailed)
1105 26b4bea8 2011-08-02 rsc EditDesc(cl.name, closed=True, private=cl.private)
1106 ef27bfa4 2010-01-12 rsc cl.Delete(ui, repo)
1107 ef27bfa4 2010-01-12 rsc return
1108 ef27bfa4 2010-01-12 rsc
1109 ef27bfa4 2010-01-12 rsc if opts["stdin"]:
1110 ef27bfa4 2010-01-12 rsc s = sys.stdin.read()
1111 ef27bfa4 2010-01-12 rsc clx, line, err = ParseCL(s, name)
1112 ef27bfa4 2010-01-12 rsc if err != '':
1113 ef27bfa4 2010-01-12 rsc return "error parsing change list: line %d: %s" % (line, err)
1114 ef27bfa4 2010-01-12 rsc if clx.desc is not None:
1115 ef27bfa4 2010-01-12 rsc cl.desc = clx.desc;
1116 ef27bfa4 2010-01-12 rsc dirty[cl] = True
1117 ef27bfa4 2010-01-12 rsc if clx.reviewer is not None:
1118 ef27bfa4 2010-01-12 rsc cl.reviewer = clx.reviewer
1119 ef27bfa4 2010-01-12 rsc dirty[cl] = True
1120 ef27bfa4 2010-01-12 rsc if clx.cc is not None:
1121 ef27bfa4 2010-01-12 rsc cl.cc = clx.cc
1122 ef27bfa4 2010-01-12 rsc dirty[cl] = True
1123 ef27bfa4 2010-01-12 rsc if clx.files is not None:
1124 ef27bfa4 2010-01-12 rsc cl.files = clx.files
1125 ef27bfa4 2010-01-12 rsc dirty[cl] = True
1126 26b4bea8 2011-08-02 rsc if clx.private != cl.private:
1127 26b4bea8 2011-08-02 rsc cl.private = clx.private
1128 26b4bea8 2011-08-02 rsc dirty[cl] = True
1129 ef27bfa4 2010-01-12 rsc
1130 ef27bfa4 2010-01-12 rsc if not opts["stdin"] and not opts["stdout"]:
1131 ef27bfa4 2010-01-12 rsc if name == "new":
1132 ef27bfa4 2010-01-12 rsc cl.files = files
1133 ef27bfa4 2010-01-12 rsc err = EditCL(ui, repo, cl)
1134 ef27bfa4 2010-01-12 rsc if err != "":
1135 ef27bfa4 2010-01-12 rsc return err
1136 ef27bfa4 2010-01-12 rsc dirty[cl] = True
1137 ef27bfa4 2010-01-12 rsc
1138 ef27bfa4 2010-01-12 rsc for d, _ in dirty.items():
1139 26b4bea8 2011-08-02 rsc name = d.name
1140 ef27bfa4 2010-01-12 rsc d.Flush(ui, repo)
1141 26b4bea8 2011-08-02 rsc if name == "new":
1142 26b4bea8 2011-08-02 rsc d.Upload(ui, repo, quiet=True)
1143 ef27bfa4 2010-01-12 rsc
1144 ef27bfa4 2010-01-12 rsc if opts["stdout"]:
1145 ef27bfa4 2010-01-12 rsc ui.write(cl.EditorText())
1146 26b4bea8 2011-08-02 rsc elif opts["pending"]:
1147 26b4bea8 2011-08-02 rsc ui.write(cl.PendingText())
1148 ef27bfa4 2010-01-12 rsc elif name == "new":
1149 ef27bfa4 2010-01-12 rsc if ui.quiet:
1150 ef27bfa4 2010-01-12 rsc ui.write(cl.name)
1151 ef27bfa4 2010-01-12 rsc else:
1152 ef27bfa4 2010-01-12 rsc ui.write("CL created: " + cl.url + "\n")
1153 ef27bfa4 2010-01-12 rsc return
1154 ef27bfa4 2010-01-12 rsc
1155 ef27bfa4 2010-01-12 rsc def code_login(ui, repo, **opts):
1156 ef27bfa4 2010-01-12 rsc """log in to code review server
1157 ef27bfa4 2010-01-12 rsc
1158 ef27bfa4 2010-01-12 rsc Logs in to the code review server, saving a cookie in
1159 ef27bfa4 2010-01-12 rsc a file in your home directory.
1160 ef27bfa4 2010-01-12 rsc """
1161 26b4bea8 2011-08-02 rsc if missing_codereview:
1162 26b4bea8 2011-08-02 rsc return missing_codereview
1163 26b4bea8 2011-08-02 rsc
1164 ef27bfa4 2010-01-12 rsc MySend(None)
1165 ef27bfa4 2010-01-12 rsc
1166 ef27bfa4 2010-01-12 rsc def clpatch(ui, repo, clname, **opts):
1167 ef27bfa4 2010-01-12 rsc """import a patch from the code review server
1168 ef27bfa4 2010-01-12 rsc
1169 ef27bfa4 2010-01-12 rsc Imports a patch from the code review server into the local client.
1170 ef27bfa4 2010-01-12 rsc If the local client has already modified any of the files that the
1171 ef27bfa4 2010-01-12 rsc patch modifies, this command will refuse to apply the patch.
1172 ef27bfa4 2010-01-12 rsc
1173 ef27bfa4 2010-01-12 rsc Submitting an imported patch will keep the original author's
1174 ef27bfa4 2010-01-12 rsc name as the Author: line but add your own name to a Committer: line.
1175 ef27bfa4 2010-01-12 rsc """
1176 26b4bea8 2011-08-02 rsc if repo[None].branch() != "default":
1177 26b4bea8 2011-08-02 rsc return "cannot run hg clpatch outside default branch"
1178 26b4bea8 2011-08-02 rsc return clpatch_or_undo(ui, repo, clname, opts, mode="clpatch")
1179 26b4bea8 2011-08-02 rsc
1180 26b4bea8 2011-08-02 rsc def undo(ui, repo, clname, **opts):
1181 26b4bea8 2011-08-02 rsc """undo the effect of a CL
1182 26b4bea8 2011-08-02 rsc
1183 26b4bea8 2011-08-02 rsc Creates a new CL that undoes an earlier CL.
1184 26b4bea8 2011-08-02 rsc After creating the CL, opens the CL text for editing so that
1185 26b4bea8 2011-08-02 rsc you can add the reason for the undo to the description.
1186 26b4bea8 2011-08-02 rsc """
1187 26b4bea8 2011-08-02 rsc if repo[None].branch() != "default":
1188 26b4bea8 2011-08-02 rsc return "cannot run hg undo outside default branch"
1189 26b4bea8 2011-08-02 rsc return clpatch_or_undo(ui, repo, clname, opts, mode="undo")
1190 26b4bea8 2011-08-02 rsc
1191 26b4bea8 2011-08-02 rsc def release_apply(ui, repo, clname, **opts):
1192 26b4bea8 2011-08-02 rsc """apply a CL to the release branch
1193 26b4bea8 2011-08-02 rsc
1194 26b4bea8 2011-08-02 rsc Creates a new CL copying a previously committed change
1195 26b4bea8 2011-08-02 rsc from the main branch to the release branch.
1196 26b4bea8 2011-08-02 rsc The current client must either be clean or already be in
1197 26b4bea8 2011-08-02 rsc the release branch.
1198 26b4bea8 2011-08-02 rsc
1199 26b4bea8 2011-08-02 rsc The release branch must be created by starting with a
1200 26b4bea8 2011-08-02 rsc clean client, disabling the code review plugin, and running:
1201 26b4bea8 2011-08-02 rsc
1202 26b4bea8 2011-08-02 rsc hg update weekly.YYYY-MM-DD
1203 26b4bea8 2011-08-02 rsc hg branch release-branch.rNN
1204 26b4bea8 2011-08-02 rsc hg commit -m 'create release-branch.rNN'
1205 26b4bea8 2011-08-02 rsc hg push --new-branch
1206 26b4bea8 2011-08-02 rsc
1207 26b4bea8 2011-08-02 rsc Then re-enable the code review plugin.
1208 26b4bea8 2011-08-02 rsc
1209 26b4bea8 2011-08-02 rsc People can test the release branch by running
1210 26b4bea8 2011-08-02 rsc
1211 26b4bea8 2011-08-02 rsc hg update release-branch.rNN
1212 26b4bea8 2011-08-02 rsc
1213 26b4bea8 2011-08-02 rsc in a clean client. To return to the normal tree,
1214 26b4bea8 2011-08-02 rsc
1215 26b4bea8 2011-08-02 rsc hg update default
1216 26b4bea8 2011-08-02 rsc
1217 26b4bea8 2011-08-02 rsc Move changes since the weekly into the release branch
1218 26b4bea8 2011-08-02 rsc using hg release-apply followed by the usual code review
1219 26b4bea8 2011-08-02 rsc process and hg submit.
1220 26b4bea8 2011-08-02 rsc
1221 26b4bea8 2011-08-02 rsc When it comes time to tag the release, record the
1222 26b4bea8 2011-08-02 rsc final long-form tag of the release-branch.rNN
1223 26b4bea8 2011-08-02 rsc in the *default* branch's .hgtags file. That is, run
1224 26b4bea8 2011-08-02 rsc
1225 26b4bea8 2011-08-02 rsc hg update default
1226 26b4bea8 2011-08-02 rsc
1227 26b4bea8 2011-08-02 rsc and then edit .hgtags as you would for a weekly.
1228 26b4bea8 2011-08-02 rsc
1229 26b4bea8 2011-08-02 rsc """
1230 26b4bea8 2011-08-02 rsc c = repo[None]
1231 26b4bea8 2011-08-02 rsc if not releaseBranch:
1232 26b4bea8 2011-08-02 rsc return "no active release branches"
1233 26b4bea8 2011-08-02 rsc if c.branch() != releaseBranch:
1234 26b4bea8 2011-08-02 rsc if c.modified() or c.added() or c.removed():
1235 26b4bea8 2011-08-02 rsc raise util.Abort("uncommitted local changes - cannot switch branches")
1236 26b4bea8 2011-08-02 rsc err = hg.clean(repo, releaseBranch)
1237 26b4bea8 2011-08-02 rsc if err:
1238 26b4bea8 2011-08-02 rsc return err
1239 ef27bfa4 2010-01-12 rsc try:
1240 26b4bea8 2011-08-02 rsc err = clpatch_or_undo(ui, repo, clname, opts, mode="backport")
1241 26b4bea8 2011-08-02 rsc if err:
1242 26b4bea8 2011-08-02 rsc raise util.Abort(err)
1243 26b4bea8 2011-08-02 rsc except Exception, e:
1244 26b4bea8 2011-08-02 rsc hg.clean(repo, "default")
1245 26b4bea8 2011-08-02 rsc raise e
1246 26b4bea8 2011-08-02 rsc return None
1247 26b4bea8 2011-08-02 rsc
1248 26b4bea8 2011-08-02 rsc def rev2clname(rev):
1249 26b4bea8 2011-08-02 rsc # Extract CL name from revision description.
1250 26b4bea8 2011-08-02 rsc # The last line in the description that is a codereview URL is the real one.
1251 26b4bea8 2011-08-02 rsc # Earlier lines might be part of the user-written description.
1252 26b4bea8 2011-08-02 rsc all = re.findall('(?m)^http://codereview.appspot.com/([0-9]+)$', rev.description())
1253 26b4bea8 2011-08-02 rsc if len(all) > 0:
1254 26b4bea8 2011-08-02 rsc return all[-1]
1255 26b4bea8 2011-08-02 rsc return ""
1256 26b4bea8 2011-08-02 rsc
1257 26b4bea8 2011-08-02 rsc undoHeader = """undo CL %s / %s
1258 26b4bea8 2011-08-02 rsc
1259 26b4bea8 2011-08-02 rsc <enter reason for undo>
1260 26b4bea8 2011-08-02 rsc
1261 26b4bea8 2011-08-02 rsc ««« original CL description
1262 26b4bea8 2011-08-02 rsc """
1263 26b4bea8 2011-08-02 rsc
1264 26b4bea8 2011-08-02 rsc undoFooter = """
1265 26b4bea8 2011-08-02 rsc »»»
1266 26b4bea8 2011-08-02 rsc """
1267 26b4bea8 2011-08-02 rsc
1268 26b4bea8 2011-08-02 rsc backportHeader = """[%s] %s
1269 26b4bea8 2011-08-02 rsc
1270 26b4bea8 2011-08-02 rsc ««« CL %s / %s
1271 26b4bea8 2011-08-02 rsc """
1272 26b4bea8 2011-08-02 rsc
1273 26b4bea8 2011-08-02 rsc backportFooter = """
1274 26b4bea8 2011-08-02 rsc »»»
1275 26b4bea8 2011-08-02 rsc """
1276 26b4bea8 2011-08-02 rsc
1277 26b4bea8 2011-08-02 rsc # Implementation of clpatch/undo.
1278 26b4bea8 2011-08-02 rsc def clpatch_or_undo(ui, repo, clname, opts, mode):
1279 26b4bea8 2011-08-02 rsc if missing_codereview:
1280 26b4bea8 2011-08-02 rsc return missing_codereview
1281 26b4bea8 2011-08-02 rsc
1282 26b4bea8 2011-08-02 rsc if mode == "undo" or mode == "backport":
1283 26b4bea8 2011-08-02 rsc if hgversion < '1.4':
1284 26b4bea8 2011-08-02 rsc # Don't have cmdutil.match (see implementation of sync command).
1285 26b4bea8 2011-08-02 rsc return "hg is too old to run hg %s - update to 1.4 or newer" % mode
1286 26b4bea8 2011-08-02 rsc
1287 26b4bea8 2011-08-02 rsc # Find revision in Mercurial repository.
1288 26b4bea8 2011-08-02 rsc # Assume CL number is 7+ decimal digits.
1289 26b4bea8 2011-08-02 rsc # Otherwise is either change log sequence number (fewer decimal digits),
1290 26b4bea8 2011-08-02 rsc # hexadecimal hash, or tag name.
1291 26b4bea8 2011-08-02 rsc # Mercurial will fall over long before the change log
1292 26b4bea8 2011-08-02 rsc # sequence numbers get to be 7 digits long.
1293 26b4bea8 2011-08-02 rsc if re.match('^[0-9]{7,}$', clname):
1294 26b4bea8 2011-08-02 rsc found = False
1295 26b4bea8 2011-08-02 rsc matchfn = cmdutil.match(repo, [], {'rev': None})
1296 26b4bea8 2011-08-02 rsc def prep(ctx, fns):
1297 26b4bea8 2011-08-02 rsc pass
1298 26b4bea8 2011-08-02 rsc for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev': None}, prep):
1299 26b4bea8 2011-08-02 rsc rev = repo[ctx.rev()]
1300 26b4bea8 2011-08-02 rsc # Last line with a code review URL is the actual review URL.
1301 26b4bea8 2011-08-02 rsc # Earlier ones might be part of the CL description.
1302 26b4bea8 2011-08-02 rsc n = rev2clname(rev)
1303 26b4bea8 2011-08-02 rsc if n == clname:
1304 26b4bea8 2011-08-02 rsc found = True
1305 26b4bea8 2011-08-02 rsc break
1306 26b4bea8 2011-08-02 rsc if not found:
1307 26b4bea8 2011-08-02 rsc return "cannot find CL %s in local repository" % clname
1308 26b4bea8 2011-08-02 rsc else:
1309 26b4bea8 2011-08-02 rsc rev = repo[clname]
1310 26b4bea8 2011-08-02 rsc if not rev:
1311 26b4bea8 2011-08-02 rsc return "unknown revision %s" % clname
1312 26b4bea8 2011-08-02 rsc clname = rev2clname(rev)
1313 26b4bea8 2011-08-02 rsc if clname == "":
1314 26b4bea8 2011-08-02 rsc return "cannot find CL name in revision description"
1315 26b4bea8 2011-08-02 rsc
1316 26b4bea8 2011-08-02 rsc # Create fresh CL and start with patch that would reverse the change.
1317 26b4bea8 2011-08-02 rsc vers = short(rev.node())
1318 26b4bea8 2011-08-02 rsc cl = CL("new")
1319 26b4bea8 2011-08-02 rsc desc = str(rev.description())
1320 26b4bea8 2011-08-02 rsc if mode == "undo":
1321 26b4bea8 2011-08-02 rsc cl.desc = (undoHeader % (clname, vers)) + desc + undoFooter
1322 26b4bea8 2011-08-02 rsc else:
1323 26b4bea8 2011-08-02 rsc cl.desc = (backportHeader % (releaseBranch, line1(desc), clname, vers)) + desc + undoFooter
1324 26b4bea8 2011-08-02 rsc v1 = vers
1325 26b4bea8 2011-08-02 rsc v0 = short(rev.parents()[0].node())
1326 26b4bea8 2011-08-02 rsc if mode == "undo":
1327 26b4bea8 2011-08-02 rsc arg = v1 + ":" + v0
1328 26b4bea8 2011-08-02 rsc else:
1329 26b4bea8 2011-08-02 rsc vers = v0
1330 26b4bea8 2011-08-02 rsc arg = v0 + ":" + v1
1331 26b4bea8 2011-08-02 rsc patch = RunShell(["hg", "diff", "--git", "-r", arg])
1332 26b4bea8 2011-08-02 rsc
1333 26b4bea8 2011-08-02 rsc else: # clpatch
1334 26b4bea8 2011-08-02 rsc cl, vers, patch, err = DownloadCL(ui, repo, clname)
1335 26b4bea8 2011-08-02 rsc if err != "":
1336 26b4bea8 2011-08-02 rsc return err
1337 26b4bea8 2011-08-02 rsc if patch == emptydiff:
1338 26b4bea8 2011-08-02 rsc return "codereview issue %s has no diff" % clname
1339 26b4bea8 2011-08-02 rsc
1340 26b4bea8 2011-08-02 rsc # find current hg version (hg identify)
1341 26b4bea8 2011-08-02 rsc ctx = repo[None]
1342 26b4bea8 2011-08-02 rsc parents = ctx.parents()
1343 26b4bea8 2011-08-02 rsc id = '+'.join([short(p.node()) for p in parents])
1344 26b4bea8 2011-08-02 rsc
1345 26b4bea8 2011-08-02 rsc # if version does not match the patch version,
1346 26b4bea8 2011-08-02 rsc # try to update the patch line numbers.
1347 26b4bea8 2011-08-02 rsc if vers != "" and id != vers:
1348 26b4bea8 2011-08-02 rsc # "vers in repo" gives the wrong answer
1349 26b4bea8 2011-08-02 rsc # on some versions of Mercurial. Instead, do the actual
1350 26b4bea8 2011-08-02 rsc # lookup and catch the exception.
1351 26b4bea8 2011-08-02 rsc try:
1352 26b4bea8 2011-08-02 rsc repo[vers].description()
1353 26b4bea8 2011-08-02 rsc except:
1354 26b4bea8 2011-08-02 rsc return "local repository is out of date; sync to get %s" % (vers)
1355 26b4bea8 2011-08-02 rsc patch1, err = portPatch(repo, patch, vers, id)
1356 26b4bea8 2011-08-02 rsc if err != "":
1357 26b4bea8 2011-08-02 rsc if not opts["ignore_hgpatch_failure"]:
1358 26b4bea8 2011-08-02 rsc return "codereview issue %s is out of date: %s (%s->%s)" % (clname, err, vers, id)
1359 26b4bea8 2011-08-02 rsc else:
1360 26b4bea8 2011-08-02 rsc patch = patch1
1361 26b4bea8 2011-08-02 rsc argv = ["hgpatch"]
1362 26b4bea8 2011-08-02 rsc if opts["no_incoming"] or mode == "backport":
1363 26b4bea8 2011-08-02 rsc argv += ["--checksync=false"]
1364 26b4bea8 2011-08-02 rsc try:
1365 26b4bea8 2011-08-02 rsc cmd = subprocess.Popen(argv, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None, close_fds=sys.platform != "win32")
1366 ef27bfa4 2010-01-12 rsc except:
1367 ef27bfa4 2010-01-12 rsc return "hgpatch: " + ExceptionDetail()
1368 26b4bea8 2011-08-02 rsc
1369 26b4bea8 2011-08-02 rsc out, err = cmd.communicate(patch)
1370 26b4bea8 2011-08-02 rsc if cmd.returncode != 0 and not opts["ignore_hgpatch_failure"]:
1371 ef27bfa4 2010-01-12 rsc return "hgpatch failed"
1372 ef27bfa4 2010-01-12 rsc cl.local = True
1373 ef27bfa4 2010-01-12 rsc cl.files = out.strip().split()
1374 26b4bea8 2011-08-02 rsc if not cl.files and not opts["ignore_hgpatch_failure"]:
1375 26b4bea8 2011-08-02 rsc return "codereview issue %s has no changed files" % clname
1376 ef27bfa4 2010-01-12 rsc files = ChangedFiles(ui, repo, [], opts)
1377 ef27bfa4 2010-01-12 rsc extra = Sub(cl.files, files)
1378 ef27bfa4 2010-01-12 rsc if extra:
1379 ef27bfa4 2010-01-12 rsc ui.warn("warning: these files were listed in the patch but not changed:\n\t" + "\n\t".join(extra) + "\n")
1380 ef27bfa4 2010-01-12 rsc cl.Flush(ui, repo)
1381 26b4bea8 2011-08-02 rsc if mode == "undo":
1382 26b4bea8 2011-08-02 rsc err = EditCL(ui, repo, cl)
1383 26b4bea8 2011-08-02 rsc if err != "":
1384 26b4bea8 2011-08-02 rsc return "CL created, but error editing: " + err
1385 26b4bea8 2011-08-02 rsc cl.Flush(ui, repo)
1386 26b4bea8 2011-08-02 rsc else:
1387 26b4bea8 2011-08-02 rsc ui.write(cl.PendingText() + "\n")
1388 26b4bea8 2011-08-02 rsc
1389 26b4bea8 2011-08-02 rsc # portPatch rewrites patch from being a patch against
1390 26b4bea8 2011-08-02 rsc # oldver to being a patch against newver.
1391 26b4bea8 2011-08-02 rsc def portPatch(repo, patch, oldver, newver):
1392 26b4bea8 2011-08-02 rsc lines = patch.splitlines(True) # True = keep \n
1393 26b4bea8 2011-08-02 rsc delta = None
1394 26b4bea8 2011-08-02 rsc for i in range(len(lines)):
1395 26b4bea8 2011-08-02 rsc line = lines[i]
1396 26b4bea8 2011-08-02 rsc if line.startswith('--- a/'):
1397 26b4bea8 2011-08-02 rsc file = line[6:-1]
1398 26b4bea8 2011-08-02 rsc delta = fileDeltas(repo, file, oldver, newver)
1399 26b4bea8 2011-08-02 rsc if not delta or not line.startswith('@@ '):
1400 26b4bea8 2011-08-02 rsc continue
1401 26b4bea8 2011-08-02 rsc # @@ -x,y +z,w @@ means the patch chunk replaces
1402 26b4bea8 2011-08-02 rsc # the original file's line numbers x up to x+y with the
1403 26b4bea8 2011-08-02 rsc # line numbers z up to z+w in the new file.
1404 26b4bea8 2011-08-02 rsc # Find the delta from x in the original to the same
1405 26b4bea8 2011-08-02 rsc # line in the current version and add that delta to both
1406 26b4bea8 2011-08-02 rsc # x and z.
1407 26b4bea8 2011-08-02 rsc m = re.match('@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@', line)
1408 26b4bea8 2011-08-02 rsc if not m:
1409 26b4bea8 2011-08-02 rsc return None, "error parsing patch line numbers"
1410 26b4bea8 2011-08-02 rsc n1, len1, n2, len2 = int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
1411 26b4bea8 2011-08-02 rsc d, err = lineDelta(delta, n1, len1)
1412 26b4bea8 2011-08-02 rsc if err != "":
1413 26b4bea8 2011-08-02 rsc return "", err
1414 26b4bea8 2011-08-02 rsc n1 += d
1415 26b4bea8 2011-08-02 rsc n2 += d
1416 26b4bea8 2011-08-02 rsc lines[i] = "@@ -%d,%d +%d,%d @@\n" % (n1, len1, n2, len2)
1417 26b4bea8 2011-08-02 rsc
1418 26b4bea8 2011-08-02 rsc newpatch = ''.join(lines)
1419 26b4bea8 2011-08-02 rsc return newpatch, ""
1420 ef27bfa4 2010-01-12 rsc
1421 26b4bea8 2011-08-02 rsc # fileDelta returns the line number deltas for the given file's
1422 26b4bea8 2011-08-02 rsc # changes from oldver to newver.
1423 26b4bea8 2011-08-02 rsc # The deltas are a list of (n, len, newdelta) triples that say
1424 26b4bea8 2011-08-02 rsc # lines [n, n+len) were modified, and after that range the
1425 26b4bea8 2011-08-02 rsc # line numbers are +newdelta from what they were before.
1426 26b4bea8 2011-08-02 rsc def fileDeltas(repo, file, oldver, newver):
1427 26b4bea8 2011-08-02 rsc cmd = ["hg", "diff", "--git", "-r", oldver + ":" + newver, "path:" + file]
1428 26b4bea8 2011-08-02 rsc data = RunShell(cmd, silent_ok=True)
1429 26b4bea8 2011-08-02 rsc deltas = []
1430 26b4bea8 2011-08-02 rsc for line in data.splitlines():
1431 26b4bea8 2011-08-02 rsc m = re.match('@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@', line)
1432 26b4bea8 2011-08-02 rsc if not m:
1433 26b4bea8 2011-08-02 rsc continue
1434 26b4bea8 2011-08-02 rsc n1, len1, n2, len2 = int(m.group(1)), int(m.group(2)), int(m.group(3)), int(m.group(4))
1435 26b4bea8 2011-08-02 rsc deltas.append((n1, len1, n2+len2-(n1+len1)))
1436 26b4bea8 2011-08-02 rsc return deltas
1437 26b4bea8 2011-08-02 rsc
1438 26b4bea8 2011-08-02 rsc # lineDelta finds the appropriate line number delta to apply to the lines [n, n+len).
1439 26b4bea8 2011-08-02 rsc # It returns an error if those lines were rewritten by the patch.
1440 26b4bea8 2011-08-02 rsc def lineDelta(deltas, n, len):
1441 26b4bea8 2011-08-02 rsc d = 0
1442 26b4bea8 2011-08-02 rsc for (old, oldlen, newdelta) in deltas:
1443 26b4bea8 2011-08-02 rsc if old >= n+len:
1444 26b4bea8 2011-08-02 rsc break
1445 26b4bea8 2011-08-02 rsc if old+len > n:
1446 26b4bea8 2011-08-02 rsc return 0, "patch and recent changes conflict"
1447 26b4bea8 2011-08-02 rsc d = newdelta
1448 26b4bea8 2011-08-02 rsc return d, ""
1449 26b4bea8 2011-08-02 rsc
1450 ef27bfa4 2010-01-12 rsc def download(ui, repo, clname, **opts):
1451 ef27bfa4 2010-01-12 rsc """download a change from the code review server
1452 ef27bfa4 2010-01-12 rsc
1453 ef27bfa4 2010-01-12 rsc Download prints a description of the given change list
1454 ef27bfa4 2010-01-12 rsc followed by its diff, downloaded from the code review server.
1455 ef27bfa4 2010-01-12 rsc """
1456 26b4bea8 2011-08-02 rsc if missing_codereview:
1457 26b4bea8 2011-08-02 rsc return missing_codereview
1458 26b4bea8 2011-08-02 rsc
1459 26b4bea8 2011-08-02 rsc cl, vers, patch, err = DownloadCL(ui, repo, clname)
1460 ef27bfa4 2010-01-12 rsc if err != "":
1461 ef27bfa4 2010-01-12 rsc return err
1462 ef27bfa4 2010-01-12 rsc ui.write(cl.EditorText() + "\n")
1463 ef27bfa4 2010-01-12 rsc ui.write(patch + "\n")
1464 ef27bfa4 2010-01-12 rsc return
1465 ef27bfa4 2010-01-12 rsc
1466 ef27bfa4 2010-01-12 rsc def file(ui, repo, clname, pat, *pats, **opts):
1467 ef27bfa4 2010-01-12 rsc """assign files to or remove files from a change list
1468 ef27bfa4 2010-01-12 rsc
1469 ef27bfa4 2010-01-12 rsc Assign files to or (with -d) remove files from a change list.
1470 ef27bfa4 2010-01-12 rsc
1471 ef27bfa4 2010-01-12 rsc The -d option only removes files from the change list.
1472 ef27bfa4 2010-01-12 rsc It does not edit them or remove them from the repository.
1473 ef27bfa4 2010-01-12 rsc """
1474 26b4bea8 2011-08-02 rsc if missing_codereview:
1475 26b4bea8 2011-08-02 rsc return missing_codereview
1476 26b4bea8 2011-08-02 rsc
1477 ef27bfa4 2010-01-12 rsc pats = tuple([pat] + list(pats))
1478 ef27bfa4 2010-01-12 rsc if not GoodCLName(clname):
1479 ef27bfa4 2010-01-12 rsc return "invalid CL name " + clname
1480 ef27bfa4 2010-01-12 rsc
1481 ef27bfa4 2010-01-12 rsc dirty = {}
1482 ef27bfa4 2010-01-12 rsc cl, err = LoadCL(ui, repo, clname, web=False)
1483 ef27bfa4 2010-01-12 rsc if err != '':
1484 ef27bfa4 2010-01-12 rsc return err
1485 ef27bfa4 2010-01-12 rsc if not cl.local:
1486 ef27bfa4 2010-01-12 rsc return "cannot change non-local CL " + clname
1487 ef27bfa4 2010-01-12 rsc
1488 ef27bfa4 2010-01-12 rsc files = ChangedFiles(ui, repo, pats, opts)
1489 ef27bfa4 2010-01-12 rsc
1490 ef27bfa4 2010-01-12 rsc if opts["delete"]:
1491 ef27bfa4 2010-01-12 rsc oldfiles = Intersect(files, cl.files)
1492 ef27bfa4 2010-01-12 rsc if oldfiles:
1493 ef27bfa4 2010-01-12 rsc if not ui.quiet:
1494 ef27bfa4 2010-01-12 rsc ui.status("# Removing files from CL. To undo:\n")
1495 ef27bfa4 2010-01-12 rsc ui.status("# cd %s\n" % (repo.root))
1496 ef27bfa4 2010-01-12 rsc for f in oldfiles:
1497 ef27bfa4 2010-01-12 rsc ui.status("# hg file %s %s\n" % (cl.name, f))
1498 ef27bfa4 2010-01-12 rsc cl.files = Sub(cl.files, oldfiles)
1499 ef27bfa4 2010-01-12 rsc cl.Flush(ui, repo)
1500 ef27bfa4 2010-01-12 rsc else:
1501 ef27bfa4 2010-01-12 rsc ui.status("no such files in CL")
1502 ef27bfa4 2010-01-12 rsc return
1503 ef27bfa4 2010-01-12 rsc
1504 ef27bfa4 2010-01-12 rsc if not files:
1505 ef27bfa4 2010-01-12 rsc return "no such modified files"
1506 ef27bfa4 2010-01-12 rsc
1507 ef27bfa4 2010-01-12 rsc files = Sub(files, cl.files)
1508 ef27bfa4 2010-01-12 rsc taken = Taken(ui, repo)
1509 ef27bfa4 2010-01-12 rsc warned = False
1510 ef27bfa4 2010-01-12 rsc for f in files:
1511 ef27bfa4 2010-01-12 rsc if f in taken:
1512 ef27bfa4 2010-01-12 rsc if not warned and not ui.quiet:
1513 ef27bfa4 2010-01-12 rsc ui.status("# Taking files from other CLs. To undo:\n")
1514 ef27bfa4 2010-01-12 rsc ui.status("# cd %s\n" % (repo.root))
1515 ef27bfa4 2010-01-12 rsc warned = True
1516 ef27bfa4 2010-01-12 rsc ocl = taken[f]
1517 ef27bfa4 2010-01-12 rsc if not ui.quiet:
1518 ef27bfa4 2010-01-12 rsc ui.status("# hg file %s %s\n" % (ocl.name, f))
1519 ef27bfa4 2010-01-12 rsc if ocl not in dirty:
1520 ef27bfa4 2010-01-12 rsc ocl.files = Sub(ocl.files, files)
1521 ef27bfa4 2010-01-12 rsc dirty[ocl] = True
1522 ef27bfa4 2010-01-12 rsc cl.files = Add(cl.files, files)
1523 ef27bfa4 2010-01-12 rsc dirty[cl] = True
1524 ef27bfa4 2010-01-12 rsc for d, _ in dirty.items():
1525 ef27bfa4 2010-01-12 rsc d.Flush(ui, repo)
1526 ef27bfa4 2010-01-12 rsc return
1527 ef27bfa4 2010-01-12 rsc
1528 ef27bfa4 2010-01-12 rsc def gofmt(ui, repo, *pats, **opts):
1529 ef27bfa4 2010-01-12 rsc """apply gofmt to modified files
1530 ef27bfa4 2010-01-12 rsc
1531 ef27bfa4 2010-01-12 rsc Applies gofmt to the modified files in the repository that match
1532 ef27bfa4 2010-01-12 rsc the given patterns.
1533 ef27bfa4 2010-01-12 rsc """
1534 26b4bea8 2011-08-02 rsc if missing_codereview:
1535 26b4bea8 2011-08-02 rsc return missing_codereview
1536 26b4bea8 2011-08-02 rsc
1537 ef27bfa4 2010-01-12 rsc files = ChangedExistingFiles(ui, repo, pats, opts)
1538 ef27bfa4 2010-01-12 rsc files = [f for f in files if f.endswith(".go")]
1539 ef27bfa4 2010-01-12 rsc if not files:
1540 ef27bfa4 2010-01-12 rsc return "no modified go files"
1541 ef27bfa4 2010-01-12 rsc cwd = os.getcwd()
1542 ef27bfa4 2010-01-12 rsc files = [RelativePath(repo.root + '/' + f, cwd) for f in files]
1543 ef27bfa4 2010-01-12 rsc try:
1544 ef27bfa4 2010-01-12 rsc cmd = ["gofmt", "-l"]
1545 ef27bfa4 2010-01-12 rsc if not opts["list"]:
1546 ef27bfa4 2010-01-12 rsc cmd += ["-w"]
1547 ef27bfa4 2010-01-12 rsc if os.spawnvp(os.P_WAIT, "gofmt", cmd + files) != 0:
1548 ef27bfa4 2010-01-12 rsc raise util.Abort("gofmt did not exit cleanly")
1549 ef27bfa4 2010-01-12 rsc except error.Abort, e:
1550 ef27bfa4 2010-01-12 rsc raise
1551 ef27bfa4 2010-01-12 rsc except:
1552 ef27bfa4 2010-01-12 rsc raise util.Abort("gofmt: " + ExceptionDetail())
1553 ef27bfa4 2010-01-12 rsc return
1554 ef27bfa4 2010-01-12 rsc
1555 ef27bfa4 2010-01-12 rsc def mail(ui, repo, *pats, **opts):
1556 ef27bfa4 2010-01-12 rsc """mail a change for review
1557 ef27bfa4 2010-01-12 rsc
1558 ef27bfa4 2010-01-12 rsc Uploads a patch to the code review server and then sends mail
1559 ef27bfa4 2010-01-12 rsc to the reviewer and CC list asking for a review.
1560 ef27bfa4 2010-01-12 rsc """
1561 26b4bea8 2011-08-02 rsc if missing_codereview:
1562 26b4bea8 2011-08-02 rsc return missing_codereview
1563 26b4bea8 2011-08-02 rsc
1564 ef27bfa4 2010-01-12 rsc cl, err = CommandLineCL(ui, repo, pats, opts, defaultcc=defaultcc)
1565 ef27bfa4 2010-01-12 rsc if err != "":
1566 ef27bfa4 2010-01-12 rsc return err
1567 ef27bfa4 2010-01-12 rsc cl.Upload(ui, repo, gofmt_just_warn=True)
1568 a06877af 2010-08-05 rsc if not cl.reviewer:
1569 a06877af 2010-08-05 rsc # If no reviewer is listed, assign the review to defaultcc.
1570 26b4bea8 2011-08-02 rsc # This makes sure that it appears in the
1571 a06877af 2010-08-05 rsc # codereview.appspot.com/user/defaultcc
1572 a06877af 2010-08-05 rsc # page, so that it doesn't get dropped on the floor.
1573 a06877af 2010-08-05 rsc if not defaultcc:
1574 a06877af 2010-08-05 rsc return "no reviewers listed in CL"
1575 a06877af 2010-08-05 rsc cl.cc = Sub(cl.cc, defaultcc)
1576 a06877af 2010-08-05 rsc cl.reviewer = defaultcc
1577 a06877af 2010-08-05 rsc cl.Flush(ui, repo)
1578 ef27bfa4 2010-01-12 rsc
1579 26b4bea8 2011-08-02 rsc if cl.files == []:
1580 26b4bea8 2011-08-02 rsc return "no changed files, not sending mail"
1581 ef27bfa4 2010-01-12 rsc
1582 26b4bea8 2011-08-02 rsc cl.Mail(ui, repo)
1583 26b4bea8 2011-08-02 rsc
1584 ef27bfa4 2010-01-12 rsc def pending(ui, repo, *pats, **opts):
1585 ef27bfa4 2010-01-12 rsc """show pending changes
1586 ef27bfa4 2010-01-12 rsc
1587 ef27bfa4 2010-01-12 rsc Lists pending changes followed by a list of unassigned but modified files.
1588 ef27bfa4 2010-01-12 rsc """
1589 26b4bea8 2011-08-02 rsc if missing_codereview:
1590 26b4bea8 2011-08-02 rsc return missing_codereview
1591 26b4bea8 2011-08-02 rsc
1592 ef27bfa4 2010-01-12 rsc m = LoadAllCL(ui, repo, web=True)
1593 ef27bfa4 2010-01-12 rsc names = m.keys()
1594 ef27bfa4 2010-01-12 rsc names.sort()
1595 ef27bfa4 2010-01-12 rsc for name in names:
1596 ef27bfa4 2010-01-12 rsc cl = m[name]
1597 ef27bfa4 2010-01-12 rsc ui.write(cl.PendingText() + "\n")
1598 ef27bfa4 2010-01-12 rsc
1599 ef27bfa4 2010-01-12 rsc files = DefaultFiles(ui, repo, [], opts)
1600 ef27bfa4 2010-01-12 rsc if len(files) > 0:
1601 ef27bfa4 2010-01-12 rsc s = "Changed files not in any CL:\n"
1602 ef27bfa4 2010-01-12 rsc for f in files:
1603 ef27bfa4 2010-01-12 rsc s += "\t" + f + "\n"
1604 ef27bfa4 2010-01-12 rsc ui.write(s)
1605 ef27bfa4 2010-01-12 rsc
1606 ef27bfa4 2010-01-12 rsc def reposetup(ui, repo):
1607 ef27bfa4 2010-01-12 rsc global original_match
1608 ef27bfa4 2010-01-12 rsc if original_match is None:
1609 26b4bea8 2011-08-02 rsc start_status_thread()
1610 ef27bfa4 2010-01-12 rsc original_match = cmdutil.match
1611 ef27bfa4 2010-01-12 rsc cmdutil.match = ReplacementForCmdutilMatch
1612 ef27bfa4 2010-01-12 rsc RietveldSetup(ui, repo)
1613 ef27bfa4 2010-01-12 rsc
1614 ef27bfa4 2010-01-12 rsc def CheckContributor(ui, repo, user=None):
1615 26b4bea8 2011-08-02 rsc set_status("checking CONTRIBUTORS file")
1616 26b4bea8 2011-08-02 rsc user, userline = FindContributor(ui, repo, user, warn=False)
1617 ef27bfa4 2010-01-12 rsc if not userline:
1618 ef27bfa4 2010-01-12 rsc raise util.Abort("cannot find %s in CONTRIBUTORS" % (user,))
1619 ef27bfa4 2010-01-12 rsc return userline
1620 ef27bfa4 2010-01-12 rsc
1621 26b4bea8 2011-08-02 rsc def FindContributor(ui, repo, user=None, warn=True):
1622 26b4bea8 2011-08-02 rsc if not user:
1623 26b4bea8 2011-08-02 rsc user = ui.config("ui", "username")
1624 26b4bea8 2011-08-02 rsc if not user:
1625 26b4bea8 2011-08-02 rsc raise util.Abort("[ui] username is not configured in .hgrc")
1626 26b4bea8 2011-08-02 rsc user = user.lower()
1627 a06877af 2010-08-05 rsc m = re.match(r".*<(.*)>", user)
1628 a06877af 2010-08-05 rsc if m:
1629 26b4bea8 2011-08-02 rsc user = m.group(1)
1630 ef27bfa4 2010-01-12 rsc
1631 a06877af 2010-08-05 rsc if user not in contributors:
1632 a06877af 2010-08-05 rsc if warn:
1633 a06877af 2010-08-05 rsc ui.warn("warning: cannot find %s in CONTRIBUTORS\n" % (user,))
1634 26b4bea8 2011-08-02 rsc return user, None
1635 a06877af 2010-08-05 rsc
1636 a06877af 2010-08-05 rsc user, email = contributors[user]
1637 a06877af 2010-08-05 rsc return email, "%s <%s>" % (user, email)
1638 a06877af 2010-08-05 rsc
1639 ef27bfa4 2010-01-12 rsc def submit(ui, repo, *pats, **opts):
1640 ef27bfa4 2010-01-12 rsc """submit change to remote repository
1641 ef27bfa4 2010-01-12 rsc
1642 ef27bfa4 2010-01-12 rsc Submits change to remote repository.
1643 ef27bfa4 2010-01-12 rsc Bails out if the local repository is not in sync with the remote one.
1644 ef27bfa4 2010-01-12 rsc """
1645 26b4bea8 2011-08-02 rsc if missing_codereview:
1646 26b4bea8 2011-08-02 rsc return missing_codereview
1647 26b4bea8 2011-08-02 rsc
1648 26b4bea8 2011-08-02 rsc # We already called this on startup but sometimes Mercurial forgets.
1649 26b4bea8 2011-08-02 rsc set_mercurial_encoding_to_utf8()
1650 26b4bea8 2011-08-02 rsc
1651 ef27bfa4 2010-01-12 rsc repo.ui.quiet = True
1652 ef27bfa4 2010-01-12 rsc if not opts["no_incoming"] and Incoming(ui, repo, opts):
1653 ef27bfa4 2010-01-12 rsc return "local repository out of date; must sync before submit"
1654 ef27bfa4 2010-01-12 rsc
1655 ef27bfa4 2010-01-12 rsc cl, err = CommandLineCL(ui, repo, pats, opts, defaultcc=defaultcc)
1656 ef27bfa4 2010-01-12 rsc if err != "":
1657 ef27bfa4 2010-01-12 rsc return err
1658 ef27bfa4 2010-01-12 rsc
1659 ef27bfa4 2010-01-12 rsc user = None
1660 ef27bfa4 2010-01-12 rsc if cl.copied_from:
1661 ef27bfa4 2010-01-12 rsc user = cl.copied_from
1662 ef27bfa4 2010-01-12 rsc userline = CheckContributor(ui, repo, user)
1663 26b4bea8 2011-08-02 rsc typecheck(userline, str)
1664 ef27bfa4 2010-01-12 rsc
1665 ef27bfa4 2010-01-12 rsc about = ""
1666 ef27bfa4 2010-01-12 rsc if cl.reviewer:
1667 ef27bfa4 2010-01-12 rsc about += "R=" + JoinComma([CutDomain(s) for s in cl.reviewer]) + "\n"
1668 ef27bfa4 2010-01-12 rsc if opts.get('tbr'):
1669 ef27bfa4 2010-01-12 rsc tbr = SplitCommaSpace(opts.get('tbr'))
1670 ef27bfa4 2010-01-12 rsc cl.reviewer = Add(cl.reviewer, tbr)
1671 ef27bfa4 2010-01-12 rsc about += "TBR=" + JoinComma([CutDomain(s) for s in tbr]) + "\n"
1672 ef27bfa4 2010-01-12 rsc if cl.cc:
1673 ef27bfa4 2010-01-12 rsc about += "CC=" + JoinComma([CutDomain(s) for s in cl.cc]) + "\n"
1674 ef27bfa4 2010-01-12 rsc
1675 ef27bfa4 2010-01-12 rsc if not cl.reviewer:
1676 ef27bfa4 2010-01-12 rsc return "no reviewers listed in CL"
1677 ef27bfa4 2010-01-12 rsc
1678 ef27bfa4 2010-01-12 rsc if not cl.local:
1679 ef27bfa4 2010-01-12 rsc return "cannot submit non-local CL"
1680 ef27bfa4 2010-01-12 rsc
1681 ef27bfa4 2010-01-12 rsc # upload, to sync current patch and also get change number if CL is new.
1682 ef27bfa4 2010-01-12 rsc if not cl.copied_from:
1683 ef27bfa4 2010-01-12 rsc cl.Upload(ui, repo, gofmt_just_warn=True)
1684 ef27bfa4 2010-01-12 rsc
1685 ef27bfa4 2010-01-12 rsc # check gofmt for real; allowed upload to warn in order to save CL.
1686 ef27bfa4 2010-01-12 rsc cl.Flush(ui, repo)
1687 26b4bea8 2011-08-02 rsc CheckFormat(ui, repo, cl.files)
1688 ef27bfa4 2010-01-12 rsc
1689 ef27bfa4 2010-01-12 rsc about += "%s%s\n" % (server_url_base, cl.name)
1690 ef27bfa4 2010-01-12 rsc
1691 ef27bfa4 2010-01-12 rsc if cl.copied_from:
1692 ef27bfa4 2010-01-12 rsc about += "\nCommitter: " + CheckContributor(ui, repo, None) + "\n"
1693 26b4bea8 2011-08-02 rsc typecheck(about, str)
1694 ef27bfa4 2010-01-12 rsc
1695 ef27bfa4 2010-01-12 rsc if not cl.mailed and not cl.copied_from: # in case this is TBR
1696 ef27bfa4 2010-01-12 rsc cl.Mail(ui, repo)
1697 ef27bfa4 2010-01-12 rsc
1698 ef27bfa4 2010-01-12 rsc # submit changes locally
1699 ef27bfa4 2010-01-12 rsc date = opts.get('date')
1700 ef27bfa4 2010-01-12 rsc if date:
1701 ef27bfa4 2010-01-12 rsc opts['date'] = util.parsedate(date)
1702 26b4bea8 2011-08-02 rsc typecheck(opts['date'], str)
1703 ef27bfa4 2010-01-12 rsc opts['message'] = cl.desc.rstrip() + "\n\n" + about
1704 26b4bea8 2011-08-02 rsc typecheck(opts['message'], str)
1705 ef27bfa4 2010-01-12 rsc
1706 ef27bfa4 2010-01-12 rsc if opts['dryrun']:
1707 ef27bfa4 2010-01-12 rsc print "NOT SUBMITTING:"
1708 ef27bfa4 2010-01-12 rsc print "User: ", userline
1709 ef27bfa4 2010-01-12 rsc print "Message:"
1710 ef27bfa4 2010-01-12 rsc print Indent(opts['message'], "\t")
1711 ef27bfa4 2010-01-12 rsc print "Files:"
1712 ef27bfa4 2010-01-12 rsc print Indent('\n'.join(cl.files), "\t")
1713 ef27bfa4 2010-01-12 rsc return "dry run; not submitted"
1714 ef27bfa4 2010-01-12 rsc
1715 ef27bfa4 2010-01-12 rsc m = match.exact(repo.root, repo.getcwd(), cl.files)
1716 26b4bea8 2011-08-02 rsc node = repo.commit(ustr(opts['message']), ustr(userline), opts.get('date'), m)
1717 ef27bfa4 2010-01-12 rsc if not node:
1718 ef27bfa4 2010-01-12 rsc return "nothing changed"
1719 ef27bfa4 2010-01-12 rsc
1720 ef27bfa4 2010-01-12 rsc # push to remote; if it fails for any reason, roll back
1721 ef27bfa4 2010-01-12 rsc try:
1722 ef27bfa4 2010-01-12 rsc log = repo.changelog
1723 ef27bfa4 2010-01-12 rsc rev = log.rev(node)
1724 ef27bfa4 2010-01-12 rsc parents = log.parentrevs(rev)
1725 ef27bfa4 2010-01-12 rsc if (rev-1 not in parents and
1726 ef27bfa4 2010-01-12 rsc (parents == (nullrev, nullrev) or
1727 ef27bfa4 2010-01-12 rsc len(log.heads(log.node(parents[0]))) > 1 and
1728 ef27bfa4 2010-01-12 rsc (parents[1] == nullrev or len(log.heads(log.node(parents[1]))) > 1))):
1729 ef27bfa4 2010-01-12 rsc # created new head
1730 ef27bfa4 2010-01-12 rsc raise util.Abort("local repository out of date; must sync before submit")
1731 ef27bfa4 2010-01-12 rsc
1732 ef27bfa4 2010-01-12 rsc # push changes to remote.
1733 ef27bfa4 2010-01-12 rsc # if it works, we're committed.
1734 ef27bfa4 2010-01-12 rsc # if not, roll back
1735 ef27bfa4 2010-01-12 rsc other = getremote(ui, repo, opts)
1736 ef27bfa4 2010-01-12 rsc r = repo.push(other, False, None)
1737 ef27bfa4 2010-01-12 rsc if r == 0:
1738 ef27bfa4 2010-01-12 rsc raise util.Abort("local repository out of date; must sync before submit")
1739 ef27bfa4 2010-01-12 rsc except:
1740 26b4bea8 2011-08-02 rsc real_rollback()
1741 ef27bfa4 2010-01-12 rsc raise
1742 ef27bfa4 2010-01-12 rsc
1743 ef27bfa4 2010-01-12 rsc # we're committed. upload final patch, close review, add commit message
1744 ef27bfa4 2010-01-12 rsc changeURL = short(node)
1745 ef27bfa4 2010-01-12 rsc url = other.url()
1746 26b4bea8 2011-08-02 rsc m = re.match("^https?://([^@/]+@)?([^.]+)\.googlecode\.com/hg/?", url)
1747 ef27bfa4 2010-01-12 rsc if m:
1748 ef27bfa4 2010-01-12 rsc changeURL = "http://code.google.com/p/%s/source/detail?r=%s" % (m.group(2), changeURL)
1749 ef27bfa4 2010-01-12 rsc else:
1750 ef27bfa4 2010-01-12 rsc print >>sys.stderr, "URL: ", url
1751 ef27bfa4 2010-01-12 rsc pmsg = "*** Submitted as " + changeURL + " ***\n\n" + opts['message']
1752 ef27bfa4 2010-01-12 rsc
1753 ef27bfa4 2010-01-12 rsc # When posting, move reviewers to CC line,
1754 ef27bfa4 2010-01-12 rsc # so that the issue stops showing up in their "My Issues" page.
1755 ef27bfa4 2010-01-12 rsc PostMessage(ui, cl.name, pmsg, reviewers="", cc=JoinComma(cl.reviewer+cl.cc))
1756 ef27bfa4 2010-01-12 rsc
1757 ef27bfa4 2010-01-12 rsc if not cl.copied_from:
1758 26b4bea8 2011-08-02 rsc EditDesc(cl.name, closed=True, private=cl.private)
1759 ef27bfa4 2010-01-12 rsc cl.Delete(ui, repo)
1760 26b4bea8 2011-08-02 rsc
1761 26b4bea8 2011-08-02 rsc c = repo[None]
1762 26b4bea8 2011-08-02 rsc if c.branch() == releaseBranch and not c.modified() and not c.added() and not c.removed():
1763 26b4bea8 2011-08-02 rsc ui.write("switching from %s to default branch.\n" % releaseBranch)
1764 26b4bea8 2011-08-02 rsc err = hg.clean(repo, "default")
1765 26b4bea8 2011-08-02 rsc if err:
1766 26b4bea8 2011-08-02 rsc return err
1767 26b4bea8 2011-08-02 rsc return None
1768 ef27bfa4 2010-01-12 rsc
1769 ef27bfa4 2010-01-12 rsc def sync(ui, repo, **opts):
1770 ef27bfa4 2010-01-12 rsc """synchronize with remote repository
1771 ef27bfa4 2010-01-12 rsc
1772 ef27bfa4 2010-01-12 rsc Incorporates recent changes from the remote repository
1773 ef27bfa4 2010-01-12 rsc into the local repository.
1774 ef27bfa4 2010-01-12 rsc """
1775 26b4bea8 2011-08-02 rsc if missing_codereview:
1776 26b4bea8 2011-08-02 rsc return missing_codereview
1777 26b4bea8 2011-08-02 rsc
1778 ef27bfa4 2010-01-12 rsc if not opts["local"]:
1779 ef27bfa4 2010-01-12 rsc ui.status = sync_note
1780 ef27bfa4 2010-01-12 rsc ui.note = sync_note
1781 ef27bfa4 2010-01-12 rsc other = getremote(ui, repo, opts)
1782 ef27bfa4 2010-01-12 rsc modheads = repo.pull(other)
1783 ef27bfa4 2010-01-12 rsc err = commands.postincoming(ui, repo, modheads, True, "tip")
1784 ef27bfa4 2010-01-12 rsc if err:
1785 ef27bfa4 2010-01-12 rsc return err
1786 26b4bea8 2011-08-02 rsc commands.update(ui, repo, rev="default")
1787 ef27bfa4 2010-01-12 rsc sync_changes(ui, repo)
1788 ef27bfa4 2010-01-12 rsc
1789 ef27bfa4 2010-01-12 rsc def sync_note(msg):
1790 ef27bfa4 2010-01-12 rsc # we run sync (pull -u) in verbose mode to get the
1791 ef27bfa4 2010-01-12 rsc # list of files being updated, but that drags along
1792 ef27bfa4 2010-01-12 rsc # a bunch of messages we don't care about.
1793 ef27bfa4 2010-01-12 rsc # omit them.
1794 ef27bfa4 2010-01-12 rsc if msg == 'resolving manifests\n':
1795 ef27bfa4 2010-01-12 rsc return
1796 ef27bfa4 2010-01-12 rsc if msg == 'searching for changes\n':
1797 ef27bfa4 2010-01-12 rsc return
1798 ef27bfa4 2010-01-12 rsc if msg == "couldn't find merge tool hgmerge\n":
1799 ef27bfa4 2010-01-12 rsc return
1800 ef27bfa4 2010-01-12 rsc sys.stdout.write(msg)
1801 ef27bfa4 2010-01-12 rsc
1802 ef27bfa4 2010-01-12 rsc def sync_changes(ui, repo):
1803 ef27bfa4 2010-01-12 rsc # Look through recent change log descriptions to find
1804 ef27bfa4 2010-01-12 rsc # potential references to http://.*/our-CL-number.
1805 ef27bfa4 2010-01-12 rsc # Double-check them by looking at the Rietveld log.
1806 ef27bfa4 2010-01-12 rsc def Rev(rev):
1807 ef27bfa4 2010-01-12 rsc desc = repo[rev].description().strip()
1808 ef27bfa4 2010-01-12 rsc for clname in re.findall('(?m)^http://(?:[^\n]+)/([0-9]+)$', desc):
1809 ef27bfa4 2010-01-12 rsc if IsLocalCL(ui, repo, clname) and IsRietveldSubmitted(ui, clname, repo[rev].hex()):
1810 ef27bfa4 2010-01-12 rsc ui.warn("CL %s submitted as %s; closing\n" % (clname, repo[rev]))
1811 ef27bfa4 2010-01-12 rsc cl, err = LoadCL(ui, repo, clname, web=False)
1812 ef27bfa4 2010-01-12 rsc if err != "":
1813 ef27bfa4 2010-01-12 rsc ui.warn("loading CL %s: %s\n" % (clname, err))
1814 ef27bfa4 2010-01-12 rsc continue
1815 ef27bfa4 2010-01-12 rsc if not cl.copied_from:
1816 26b4bea8 2011-08-02 rsc EditDesc(cl.name, closed=True, private=cl.private)
1817 ef27bfa4 2010-01-12 rsc cl.Delete(ui, repo)
1818 ef27bfa4 2010-01-12 rsc
1819 ef27bfa4 2010-01-12 rsc if hgversion < '1.4':
1820 ef27bfa4 2010-01-12 rsc get = util.cachefunc(lambda r: repo[r].changeset())
1821 ef27bfa4 2010-01-12 rsc changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, [], get, {'rev': None})
1822 ef27bfa4 2010-01-12 rsc n = 0
1823 ef27bfa4 2010-01-12 rsc for st, rev, fns in changeiter:
1824 ef27bfa4 2010-01-12 rsc if st != 'iter':
1825 ef27bfa4 2010-01-12 rsc continue
1826 ef27bfa4 2010-01-12 rsc n += 1
1827 ef27bfa4 2010-01-12 rsc if n > 100:
1828 ef27bfa4 2010-01-12 rsc break
1829 ef27bfa4 2010-01-12 rsc Rev(rev)
1830 ef27bfa4 2010-01-12 rsc else:
1831 ef27bfa4 2010-01-12 rsc matchfn = cmdutil.match(repo, [], {'rev': None})
1832 ef27bfa4 2010-01-12 rsc def prep(ctx, fns):
1833 ef27bfa4 2010-01-12 rsc pass
1834 ef27bfa4 2010-01-12 rsc for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev': None}, prep):
1835 ef27bfa4 2010-01-12 rsc Rev(ctx.rev())
1836 ef27bfa4 2010-01-12 rsc
1837 ef27bfa4 2010-01-12 rsc # Remove files that are not modified from the CLs in which they appear.
1838 ef27bfa4 2010-01-12 rsc all = LoadAllCL(ui, repo, web=False)
1839 ef27bfa4 2010-01-12 rsc changed = ChangedFiles(ui, repo, [], {})
1840 ef27bfa4 2010-01-12 rsc for _, cl in all.items():
1841 ef27bfa4 2010-01-12 rsc extra = Sub(cl.files, changed)
1842 ef27bfa4 2010-01-12 rsc if extra:
1843 ef27bfa4 2010-01-12 rsc ui.warn("Removing unmodified files from CL %s:\n" % (cl.name,))
1844 ef27bfa4 2010-01-12 rsc for f in extra:
1845 ef27bfa4 2010-01-12 rsc ui.warn("\t%s\n" % (f,))
1846 ef27bfa4 2010-01-12 rsc cl.files = Sub(cl.files, extra)
1847 ef27bfa4 2010-01-12 rsc cl.Flush(ui, repo)
1848 ef27bfa4 2010-01-12 rsc if not cl.files:
1849 26b4bea8 2011-08-02 rsc if not cl.copied_from:
1850 26b4bea8 2011-08-02 rsc ui.warn("CL %s has no files; delete (abandon) with hg change -d %s\n" % (cl.name, cl.name))
1851 26b4bea8 2011-08-02 rsc else:
1852 26b4bea8 2011-08-02 rsc ui.warn("CL %s has no files; delete locally with hg change -D %s\n" % (cl.name, cl.name))
1853 ef27bfa4 2010-01-12 rsc return
1854 ef27bfa4 2010-01-12 rsc
1855 ef27bfa4 2010-01-12 rsc def upload(ui, repo, name, **opts):
1856 ef27bfa4 2010-01-12 rsc """upload diffs to the code review server
1857 ef27bfa4 2010-01-12 rsc
1858 ef27bfa4 2010-01-12 rsc Uploads the current modifications for a given change to the server.
1859 ef27bfa4 2010-01-12 rsc """
1860 26b4bea8 2011-08-02 rsc if missing_codereview:
1861 26b4bea8 2011-08-02 rsc return missing_codereview
1862 26b4bea8 2011-08-02 rsc
1863 ef27bfa4 2010-01-12 rsc repo.ui.quiet = True
1864 ef27bfa4 2010-01-12 rsc cl, err = LoadCL(ui, repo, name, web=True)
1865 ef27bfa4 2010-01-12 rsc if err != "":
1866 ef27bfa4 2010-01-12 rsc return err
1867 ef27bfa4 2010-01-12 rsc if not cl.local:
1868 ef27bfa4 2010-01-12 rsc return "cannot upload non-local change"
1869 ef27bfa4 2010-01-12 rsc cl.Upload(ui, repo)
1870 ef27bfa4 2010-01-12 rsc print "%s%s\n" % (server_url_base, cl.name)
1871 ef27bfa4 2010-01-12 rsc return
1872 ef27bfa4 2010-01-12 rsc
1873 ef27bfa4 2010-01-12 rsc review_opts = [
1874 ef27bfa4 2010-01-12 rsc ('r', 'reviewer', '', 'add reviewer'),
1875 ef27bfa4 2010-01-12 rsc ('', 'cc', '', 'add cc'),
1876 ef27bfa4 2010-01-12 rsc ('', 'tbr', '', 'add future reviewer'),
1877 ef27bfa4 2010-01-12 rsc ('m', 'message', '', 'change description (for new change)'),
1878 ef27bfa4 2010-01-12 rsc ]
1879 ef27bfa4 2010-01-12 rsc
1880 ef27bfa4 2010-01-12 rsc cmdtable = {
1881 ef27bfa4 2010-01-12 rsc # The ^ means to show this command in the help text that
1882 ef27bfa4 2010-01-12 rsc # is printed when running hg with no arguments.
1883 ef27bfa4 2010-01-12 rsc "^change": (
1884 ef27bfa4 2010-01-12 rsc change,
1885 ef27bfa4 2010-01-12 rsc [
1886 ef27bfa4 2010-01-12 rsc ('d', 'delete', None, 'delete existing change list'),
1887 ef27bfa4 2010-01-12 rsc ('D', 'deletelocal', None, 'delete locally, but do not change CL on server'),
1888 ef27bfa4 2010-01-12 rsc ('i', 'stdin', None, 'read change list from standard input'),
1889 ef27bfa4 2010-01-12 rsc ('o', 'stdout', None, 'print change list to standard output'),
1890 26b4bea8 2011-08-02 rsc ('p', 'pending', None, 'print pending summary to standard output'),
1891 ef27bfa4 2010-01-12 rsc ],
1892 ef27bfa4 2010-01-12 rsc "[-d | -D] [-i] [-o] change# or FILE ..."
1893 ef27bfa4 2010-01-12 rsc ),
1894 ef27bfa4 2010-01-12 rsc "^clpatch": (
1895 ef27bfa4 2010-01-12 rsc clpatch,
1896 ef27bfa4 2010-01-12 rsc [
1897 ef27bfa4 2010-01-12 rsc ('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
1898 ef27bfa4 2010-01-12 rsc ('', 'no_incoming', None, 'disable check for incoming changes'),
1899 ef27bfa4 2010-01-12 rsc ],
1900 ef27bfa4 2010-01-12 rsc "change#"
1901 ef27bfa4 2010-01-12 rsc ),
1902 ef27bfa4 2010-01-12 rsc # Would prefer to call this codereview-login, but then
1903 ef27bfa4 2010-01-12 rsc # hg help codereview prints the help for this command
1904 ef27bfa4 2010-01-12 rsc # instead of the help for the extension.
1905 ef27bfa4 2010-01-12 rsc "code-login": (
1906 ef27bfa4 2010-01-12 rsc code_login,
1907 ef27bfa4 2010-01-12 rsc [],
1908 ef27bfa4 2010-01-12 rsc "",
1909 ef27bfa4 2010-01-12 rsc ),
1910 ef27bfa4 2010-01-12 rsc "^download": (
1911 ef27bfa4 2010-01-12 rsc download,
1912 ef27bfa4 2010-01-12 rsc [],
1913 ef27bfa4 2010-01-12 rsc "change#"
1914 ef27bfa4 2010-01-12 rsc ),
1915 ef27bfa4 2010-01-12 rsc "^file": (
1916 ef27bfa4 2010-01-12 rsc file,
1917 ef27bfa4 2010-01-12 rsc [
1918 ef27bfa4 2010-01-12 rsc ('d', 'delete', None, 'delete files from change list (but not repository)'),
1919 ef27bfa4 2010-01-12 rsc ],
1920 ef27bfa4 2010-01-12 rsc "[-d] change# FILE ..."
1921 ef27bfa4 2010-01-12 rsc ),
1922 ef27bfa4 2010-01-12 rsc "^gofmt": (
1923 ef27bfa4 2010-01-12 rsc gofmt,
1924 ef27bfa4 2010-01-12 rsc [
1925 ef27bfa4 2010-01-12 rsc ('l', 'list', None, 'list files that would change, but do not edit them'),
1926 ef27bfa4 2010-01-12 rsc ],
1927 ef27bfa4 2010-01-12 rsc "FILE ..."
1928 ef27bfa4 2010-01-12 rsc ),
1929 ef27bfa4 2010-01-12 rsc "^pending|p": (
1930 ef27bfa4 2010-01-12 rsc pending,
1931 ef27bfa4 2010-01-12 rsc [],
1932 ef27bfa4 2010-01-12 rsc "[FILE ...]"
1933 ef27bfa4 2010-01-12 rsc ),
1934 ef27bfa4 2010-01-12 rsc "^mail": (
1935 ef27bfa4 2010-01-12 rsc mail,
1936 ef27bfa4 2010-01-12 rsc review_opts + [
1937 ef27bfa4 2010-01-12 rsc ] + commands.walkopts,
1938 ef27bfa4 2010-01-12 rsc "[-r reviewer] [--cc cc] [change# | file ...]"
1939 ef27bfa4 2010-01-12 rsc ),
1940 26b4bea8 2011-08-02 rsc "^release-apply": (
1941 26b4bea8 2011-08-02 rsc release_apply,
1942 26b4bea8 2011-08-02 rsc [
1943 26b4bea8 2011-08-02 rsc ('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
1944 26b4bea8 2011-08-02 rsc ('', 'no_incoming', None, 'disable check for incoming changes'),
1945 26b4bea8 2011-08-02 rsc ],
1946 26b4bea8 2011-08-02 rsc "change#"
1947 26b4bea8 2011-08-02 rsc ),
1948 26b4bea8 2011-08-02 rsc # TODO: release-start, release-tag, weekly-tag
1949 ef27bfa4 2010-01-12 rsc "^submit": (
1950 ef27bfa4 2010-01-12 rsc submit,
1951 ef27bfa4 2010-01-12 rsc review_opts + [
1952 ef27bfa4 2010-01-12 rsc ('', 'no_incoming', None, 'disable initial incoming check (for testing)'),
1953 ef27bfa4 2010-01-12 rsc ('n', 'dryrun', None, 'make change only locally (for testing)'),
1954 ef27bfa4 2010-01-12 rsc ] + commands.walkopts + commands.commitopts + commands.commitopts2,
1955 ef27bfa4 2010-01-12 rsc "[-r reviewer] [--cc cc] [change# | file ...]"
1956 ef27bfa4 2010-01-12 rsc ),
1957 ef27bfa4 2010-01-12 rsc "^sync": (
1958 ef27bfa4 2010-01-12 rsc sync,
1959 ef27bfa4 2010-01-12 rsc [
1960 ef27bfa4 2010-01-12 rsc ('', 'local', None, 'do not pull changes from remote repository')
1961 ef27bfa4 2010-01-12 rsc ],
1962 ef27bfa4 2010-01-12 rsc "[--local]",
1963 ef27bfa4 2010-01-12 rsc ),
1964 26b4bea8 2011-08-02 rsc "^undo": (
1965 26b4bea8 2011-08-02 rsc undo,
1966 26b4bea8 2011-08-02 rsc [
1967 26b4bea8 2011-08-02 rsc ('', 'ignore_hgpatch_failure', None, 'create CL metadata even if hgpatch fails'),
1968 26b4bea8 2011-08-02 rsc ('', 'no_incoming', None, 'disable check for incoming changes'),
1969 26b4bea8 2011-08-02 rsc ],
1970 26b4bea8 2011-08-02 rsc "change#"
1971 26b4bea8 2011-08-02 rsc ),
1972 ef27bfa4 2010-01-12 rsc "^upload": (
1973 ef27bfa4 2010-01-12 rsc upload,
1974 ef27bfa4 2010-01-12 rsc [],
1975 ef27bfa4 2010-01-12 rsc "change#"
1976 ef27bfa4 2010-01-12 rsc ),
1977 ef27bfa4 2010-01-12 rsc }
1978 ef27bfa4 2010-01-12 rsc
1979 ef27bfa4 2010-01-12 rsc
1980 ef27bfa4 2010-01-12 rsc #######################################################################
1981 ef27bfa4 2010-01-12 rsc # Wrappers around upload.py for interacting with Rietveld
1982 ef27bfa4 2010-01-12 rsc
1983 ef27bfa4 2010-01-12 rsc # HTML form parser
1984 ef27bfa4 2010-01-12 rsc class FormParser(HTMLParser):
1985 ef27bfa4 2010-01-12 rsc def __init__(self):
1986 ef27bfa4 2010-01-12 rsc self.map = {}
1987 ef27bfa4 2010-01-12 rsc self.curtag = None
1988 ef27bfa4 2010-01-12 rsc self.curdata = None
1989 ef27bfa4 2010-01-12 rsc HTMLParser.__init__(self)
1990 ef27bfa4 2010-01-12 rsc def handle_starttag(self, tag, attrs):
1991 ef27bfa4 2010-01-12 rsc if tag == "input":
1992 ef27bfa4 2010-01-12 rsc key = None
1993 ef27bfa4 2010-01-12 rsc value = ''
1994 ef27bfa4 2010-01-12 rsc for a in attrs:
1995 ef27bfa4 2010-01-12 rsc if a[0] == 'name':
1996 ef27bfa4 2010-01-12 rsc key = a[1]
1997 ef27bfa4 2010-01-12 rsc if a[0] == 'value':
1998 ef27bfa4 2010-01-12 rsc value = a[1]
1999 ef27bfa4 2010-01-12 rsc if key is not None:
2000 ef27bfa4 2010-01-12 rsc self.map[key] = value
2001 ef27bfa4 2010-01-12 rsc if tag == "textarea":
2002 ef27bfa4 2010-01-12 rsc key = None
2003 ef27bfa4 2010-01-12 rsc for a in attrs:
2004 ef27bfa4 2010-01-12 rsc if a[0] == 'name':
2005 ef27bfa4 2010-01-12 rsc key = a[1]
2006 ef27bfa4 2010-01-12 rsc if key is not None:
2007 ef27bfa4 2010-01-12 rsc self.curtag = key
2008 ef27bfa4 2010-01-12 rsc self.curdata = ''
2009 ef27bfa4 2010-01-12 rsc def handle_endtag(self, tag):
2010 ef27bfa4 2010-01-12 rsc if tag == "textarea" and self.curtag is not None:
2011 ef27bfa4 2010-01-12 rsc self.map[self.curtag] = self.curdata
2012 ef27bfa4 2010-01-12 rsc self.curtag = None
2013 ef27bfa4 2010-01-12 rsc self.curdata = None
2014 ef27bfa4 2010-01-12 rsc def handle_charref(self, name):
2015 ef27bfa4 2010-01-12 rsc self.handle_data(unichr(int(name)))
2016 ef27bfa4 2010-01-12 rsc def handle_entityref(self, name):
2017 ef27bfa4 2010-01-12 rsc import htmlentitydefs
2018 ef27bfa4 2010-01-12 rsc if name in htmlentitydefs.entitydefs:
2019 ef27bfa4 2010-01-12 rsc self.handle_data(htmlentitydefs.entitydefs[name])
2020 ef27bfa4 2010-01-12 rsc else:
2021 ef27bfa4 2010-01-12 rsc self.handle_data("&" + name + ";")
2022 ef27bfa4 2010-01-12 rsc def handle_data(self, data):
2023 ef27bfa4 2010-01-12 rsc if self.curdata is not None:
2024 26b4bea8 2011-08-02 rsc self.curdata += data
2025 ef27bfa4 2010-01-12 rsc
2026 26b4bea8 2011-08-02 rsc def JSONGet(ui, path):
2027 ef27bfa4 2010-01-12 rsc try:
2028 26b4bea8 2011-08-02 rsc data = MySend(path, force_auth=False)
2029 26b4bea8 2011-08-02 rsc typecheck(data, str)
2030 26b4bea8 2011-08-02 rsc d = fix_json(json.loads(data))
2031 ef27bfa4 2010-01-12 rsc except:
2032 26b4bea8 2011-08-02 rsc ui.warn("JSONGet %s: %s\n" % (path, ExceptionDetail()))
2033 ef27bfa4 2010-01-12 rsc return None
2034 26b4bea8 2011-08-02 rsc return d
2035 ef27bfa4 2010-01-12 rsc
2036 26b4bea8 2011-08-02 rsc # Clean up json parser output to match our expectations:
2037 26b4bea8 2011-08-02 rsc # * all strings are UTF-8-encoded str, not unicode.
2038 26b4bea8 2011-08-02 rsc # * missing fields are missing, not None,
2039 26b4bea8 2011-08-02 rsc # so that d.get("foo", defaultvalue) works.
2040 26b4bea8 2011-08-02 rsc def fix_json(x):
2041 26b4bea8 2011-08-02 rsc if type(x) in [str, int, float, bool, type(None)]:
2042 26b4bea8 2011-08-02 rsc pass
2043 26b4bea8 2011-08-02 rsc elif type(x) is unicode:
2044 26b4bea8 2011-08-02 rsc x = x.encode("utf-8")
2045 26b4bea8 2011-08-02 rsc elif type(x) is list:
2046 26b4bea8 2011-08-02 rsc for i in range(len(x)):
2047 26b4bea8 2011-08-02 rsc x[i] = fix_json(x[i])
2048 26b4bea8 2011-08-02 rsc elif type(x) is dict:
2049 26b4bea8 2011-08-02 rsc todel = []
2050 26b4bea8 2011-08-02 rsc for k in x:
2051 26b4bea8 2011-08-02 rsc if x[k] is None:
2052 26b4bea8 2011-08-02 rsc todel.append(k)
2053 26b4bea8 2011-08-02 rsc else:
2054 26b4bea8 2011-08-02 rsc x[k] = fix_json(x[k])
2055 26b4bea8 2011-08-02 rsc for k in todel:
2056 26b4bea8 2011-08-02 rsc del x[k]
2057 26b4bea8 2011-08-02 rsc else:
2058 26b4bea8 2011-08-02 rsc raise util.Abort("unknown type " + str(type(x)) + " in fix_json")
2059 26b4bea8 2011-08-02 rsc if type(x) is str:
2060 26b4bea8 2011-08-02 rsc x = x.replace('\r\n', '\n')
2061 26b4bea8 2011-08-02 rsc return x
2062 26b4bea8 2011-08-02 rsc
2063 26b4bea8 2011-08-02 rsc def IsRietveldSubmitted(ui, clname, hex):
2064 26b4bea8 2011-08-02 rsc dict = JSONGet(ui, "/api/" + clname + "?messages=true")
2065 26b4bea8 2011-08-02 rsc if dict is None:
2066 ef27bfa4 2010-01-12 rsc return False
2067 26b4bea8 2011-08-02 rsc for msg in dict.get("messages", []):
2068 26b4bea8 2011-08-02 rsc text = msg.get("text", "")
2069 ef27bfa4 2010-01-12 rsc m = re.match('\*\*\* Submitted as [^*]*?([0-9a-f]+) \*\*\*', text)
2070 ef27bfa4 2010-01-12 rsc if m is not None and len(m.group(1)) >= 8 and hex.startswith(m.group(1)):
2071 ef27bfa4 2010-01-12 rsc return True
2072 ef27bfa4 2010-01-12 rsc return False
2073 ef27bfa4 2010-01-12 rsc
2074 26b4bea8 2011-08-02 rsc def IsRietveldMailed(cl):
2075 26b4bea8 2011-08-02 rsc for msg in cl.dict.get("messages", []):
2076 26b4bea8 2011-08-02 rsc if msg.get("text", "").find("I'd like you to review this change") >= 0:
2077 26b4bea8 2011-08-02 rsc return True
2078 26b4bea8 2011-08-02 rsc return False
2079 26b4bea8 2011-08-02 rsc
2080 ef27bfa4 2010-01-12 rsc def DownloadCL(ui, repo, clname):
2081 26b4bea8 2011-08-02 rsc set_status("downloading CL " + clname)
2082 26b4bea8 2011-08-02 rsc cl, err = LoadCL(ui, repo, clname, web=True)
2083 ef27bfa4 2010-01-12 rsc if err != "":
2084 26b4bea8 2011-08-02 rsc return None, None, None, "error loading CL %s: %s" % (clname, err)
2085 ef27bfa4 2010-01-12 rsc
2086 ef27bfa4 2010-01-12 rsc # Find most recent diff
2087 26b4bea8 2011-08-02 rsc diffs = cl.dict.get("patchsets", [])
2088 26b4bea8 2011-08-02 rsc if not diffs:
2089 26b4bea8 2011-08-02 rsc return None, None, None, "CL has no patch sets"
2090 26b4bea8 2011-08-02 rsc patchid = diffs[-1]
2091 26b4bea8 2011-08-02 rsc
2092 26b4bea8 2011-08-02 rsc patchset = JSONGet(ui, "/api/" + clname + "/" + str(patchid))
2093 26b4bea8 2011-08-02 rsc if patchset is None:
2094 26b4bea8 2011-08-02 rsc return None, None, None, "error loading CL patchset %s/%d" % (clname, patchid)
2095 26b4bea8 2011-08-02 rsc if patchset.get("patchset", 0) != patchid:
2096 26b4bea8 2011-08-02 rsc return None, None, None, "malformed patchset information"
2097 26b4bea8 2011-08-02 rsc
2098 26b4bea8 2011-08-02 rsc vers = ""
2099 26b4bea8 2011-08-02 rsc msg = patchset.get("message", "").split()
2100 26b4bea8 2011-08-02 rsc if len(msg) >= 3 and msg[0] == "diff" and msg[1] == "-r":
2101 26b4bea8 2011-08-02 rsc vers = msg[2]
2102 26b4bea8 2011-08-02 rsc diff = "/download/issue" + clname + "_" + str(patchid) + ".diff"
2103 ef27bfa4 2010-01-12 rsc
2104 26b4bea8 2011-08-02 rsc diffdata = MySend(diff, force_auth=False)
2105 26b4bea8 2011-08-02 rsc
2106 ef27bfa4 2010-01-12 rsc # Print warning if email is not in CONTRIBUTORS file.
2107 26b4bea8 2011-08-02 rsc email = cl.dict.get("owner_email", "")
2108 26b4bea8 2011-08-02 rsc if not email:
2109 26b4bea8 2011-08-02 rsc return None, None, None, "cannot find owner for %s" % (clname)
2110 26b4bea8 2011-08-02 rsc him = FindContributor(ui, repo, email)
2111 26b4bea8 2011-08-02 rsc me = FindContributor(ui, repo, None)
2112 26b4bea8 2011-08-02 rsc if him == me:
2113 26b4bea8 2011-08-02 rsc cl.mailed = IsRietveldMailed(cl)
2114 26b4bea8 2011-08-02 rsc else:
2115 26b4bea8 2011-08-02 rsc cl.copied_from = email
2116 ef27bfa4 2010-01-12 rsc
2117 26b4bea8 2011-08-02 rsc return cl, vers, diffdata, ""
2118 ef27bfa4 2010-01-12 rsc
2119 ef27bfa4 2010-01-12 rsc def MySend(request_path, payload=None,
2120 26b4bea8 2011-08-02 rsc content_type="application/octet-stream",
2121 26b4bea8 2011-08-02 rsc timeout=None, force_auth=True,
2122 26b4bea8 2011-08-02 rsc **kwargs):
2123 26b4bea8 2011-08-02 rsc """Run MySend1 maybe twice, because Rietveld is unreliable."""
2124 26b4bea8 2011-08-02 rsc try:
2125 26b4bea8 2011-08-02 rsc return MySend1(request_path, payload, content_type, timeout, force_auth, **kwargs)
2126 26b4bea8 2011-08-02 rsc except Exception, e:
2127 26b4bea8 2011-08-02 rsc if type(e) != urllib2.HTTPError or e.code != 500: # only retry on HTTP 500 error
2128 26b4bea8 2011-08-02 rsc raise
2129 26b4bea8 2011-08-02 rsc print >>sys.stderr, "Loading "+request_path+": "+ExceptionDetail()+"; trying again in 2 seconds."
2130 26b4bea8 2011-08-02 rsc time.sleep(2)
2131 26b4bea8 2011-08-02 rsc return MySend1(request_path, payload, content_type, timeout, force_auth, **kwargs)
2132 ef27bfa4 2010-01-12 rsc
2133 ef27bfa4 2010-01-12 rsc # Like upload.py Send but only authenticates when the
2134 ef27bfa4 2010-01-12 rsc # redirect is to www.google.com/accounts. This keeps
2135 ef27bfa4 2010-01-12 rsc # unnecessary redirects from happening during testing.
2136 ef27bfa4 2010-01-12 rsc def MySend1(request_path, payload=None,
2137 26b4bea8 2011-08-02 rsc content_type="application/octet-stream",
2138 26b4bea8 2011-08-02 rsc timeout=None, force_auth=True,
2139 26b4bea8 2011-08-02 rsc **kwargs):
2140 26b4bea8 2011-08-02 rsc """Sends an RPC and returns the response.
2141 ef27bfa4 2010-01-12 rsc
2142 26b4bea8 2011-08-02 rsc Args:
2143 26b4bea8 2011-08-02 rsc request_path: The path to send the request to, eg /api/appversion/create.
2144 26b4bea8 2011-08-02 rsc payload: The body of the request, or None to send an empty request.
2145 26b4bea8 2011-08-02 rsc content_type: The Content-Type header to use.
2146 26b4bea8 2011-08-02 rsc timeout: timeout in seconds; default None i.e. no timeout.
2147 26b4bea8 2011-08-02 rsc (Note: for large requests on OS X, the timeout doesn't work right.)
2148 26b4bea8 2011-08-02 rsc kwargs: Any keyword arguments are converted into query string parameters.
2149 ef27bfa4 2010-01-12 rsc
2150 26b4bea8 2011-08-02 rsc Returns:
2151 26b4bea8 2011-08-02 rsc The response body, as a string.
2152 26b4bea8 2011-08-02 rsc """
2153 26b4bea8 2011-08-02 rsc # TODO: Don't require authentication. Let the server say
2154 26b4bea8 2011-08-02 rsc # whether it is necessary.
2155 26b4bea8 2011-08-02 rsc global rpc
2156 26b4bea8 2011-08-02 rsc if rpc == None:
2157 26b4bea8 2011-08-02 rsc rpc = GetRpcServer(upload_options)
2158 26b4bea8 2011-08-02 rsc self = rpc
2159 26b4bea8 2011-08-02 rsc if not self.authenticated and force_auth:
2160 26b4bea8 2011-08-02 rsc self._Authenticate()
2161 26b4bea8 2011-08-02 rsc if request_path is None:
2162 26b4bea8 2011-08-02 rsc return
2163 ef27bfa4 2010-01-12 rsc
2164 26b4bea8 2011-08-02 rsc old_timeout = socket.getdefaulttimeout()
2165 26b4bea8 2011-08-02 rsc socket.setdefaulttimeout(timeout)
2166 26b4bea8 2011-08-02 rsc try:
2167 26b4bea8 2011-08-02 rsc tries = 0
2168 26b4bea8 2011-08-02 rsc while True:
2169 26b4bea8 2011-08-02 rsc tries += 1
2170 26b4bea8 2011-08-02 rsc args = dict(kwargs)
2171 26b4bea8 2011-08-02 rsc url = "http://%s%s" % (self.host, request_path)
2172 26b4bea8 2011-08-02 rsc if args:
2173 26b4bea8 2011-08-02 rsc url += "?" + urllib.urlencode(args)
2174 26b4bea8 2011-08-02 rsc req = self._CreateRequest(url=url, data=payload)
2175 26b4bea8 2011-08-02 rsc req.add_header("Content-Type", content_type)
2176 26b4bea8 2011-08-02 rsc try:
2177 26b4bea8 2011-08-02 rsc f = self.opener.open(req)
2178 26b4bea8 2011-08-02 rsc response = f.read()
2179 26b4bea8 2011-08-02 rsc f.close()
2180 26b4bea8 2011-08-02 rsc # Translate \r\n into \n, because Rietveld doesn't.
2181 26b4bea8 2011-08-02 rsc response = response.replace('\r\n', '\n')
2182 26b4bea8 2011-08-02 rsc # who knows what urllib will give us
2183 26b4bea8 2011-08-02 rsc if type(response) == unicode:
2184 26b4bea8 2011-08-02 rsc response = response.encode("utf-8")
2185 26b4bea8 2011-08-02 rsc typecheck(response, str)
2186 26b4bea8 2011-08-02 rsc return response
2187 26b4bea8 2011-08-02 rsc except urllib2.HTTPError, e:
2188 26b4bea8 2011-08-02 rsc if tries > 3:
2189 26b4bea8 2011-08-02 rsc raise
2190 26b4bea8 2011-08-02 rsc elif e.code == 401:
2191 26b4bea8 2011-08-02 rsc self._Authenticate()
2192 26b4bea8 2011-08-02 rsc elif e.code == 302:
2193 26b4bea8 2011-08-02 rsc loc = e.info()["location"]
2194 26b4bea8 2011-08-02 rsc if not loc.startswith('https://www.google.com/a') or loc.find('/ServiceLogin') < 0:
2195 26b4bea8 2011-08-02 rsc return ''
2196 26b4bea8 2011-08-02 rsc self._Authenticate()
2197 26b4bea8 2011-08-02 rsc else:
2198 26b4bea8 2011-08-02 rsc raise
2199 26b4bea8 2011-08-02 rsc finally:
2200 26b4bea8 2011-08-02 rsc socket.setdefaulttimeout(old_timeout)
2201 26b4bea8 2011-08-02 rsc
2202 ef27bfa4 2010-01-12 rsc def GetForm(url):
2203 ef27bfa4 2010-01-12 rsc f = FormParser()
2204 26b4bea8 2011-08-02 rsc f.feed(ustr(MySend(url))) # f.feed wants unicode
2205 ef27bfa4 2010-01-12 rsc f.close()
2206 26b4bea8 2011-08-02 rsc # convert back to utf-8 to restore sanity
2207 26b4bea8 2011-08-02 rsc m = {}
2208 ef27bfa4 2010-01-12 rsc for k,v in f.map.items():
2209 26b4bea8 2011-08-02 rsc m[k.encode("utf-8")] = v.replace("\r\n", "\n").encode("utf-8")
2210 26b4bea8 2011-08-02 rsc return m
2211 ef27bfa4 2010-01-12 rsc
2212 26b4bea8 2011-08-02 rsc def EditDesc(issue, subject=None, desc=None, reviewers=None, cc=None, closed=False, private=False):
2213 26b4bea8 2011-08-02 rsc set_status("uploading change to description")
2214 ef27bfa4 2010-01-12 rsc form_fields = GetForm("/" + issue + "/edit")
2215 ef27bfa4 2010-01-12 rsc if subject is not None:
2216 ef27bfa4 2010-01-12 rsc form_fields['subject'] = subject
2217 ef27bfa4 2010-01-12 rsc if desc is not None:
2218 ef27bfa4 2010-01-12 rsc form_fields['description'] = desc
2219 ef27bfa4 2010-01-12 rsc if reviewers is not None:
2220 ef27bfa4 2010-01-12 rsc form_fields['reviewers'] = reviewers
2221 ef27bfa4 2010-01-12 rsc if cc is not None:
2222 ef27bfa4 2010-01-12 rsc form_fields['cc'] = cc
2223 26b4bea8 2011-08-02 rsc if closed:
2224 26b4bea8 2011-08-02 rsc form_fields['closed'] = "checked"
2225 26b4bea8 2011-08-02 rsc if private:
2226 26b4bea8 2011-08-02 rsc form_fields['private'] = "checked"
2227 ef27bfa4 2010-01-12 rsc ctype, body = EncodeMultipartFormData(form_fields.items(), [])
2228 ef27bfa4 2010-01-12 rsc response = MySend("/" + issue + "/edit", body, content_type=ctype)
2229 ef27bfa4 2010-01-12 rsc if response != "":
2230 ef27bfa4 2010-01-12 rsc print >>sys.stderr, "Error editing description:\n" + "Sent form: \n", form_fields, "\n", response
2231 ef27bfa4 2010-01-12 rsc sys.exit(2)
2232 ef27bfa4 2010-01-12 rsc
2233 ef27bfa4 2010-01-12 rsc def PostMessage(ui, issue, message, reviewers=None, cc=None, send_mail=True, subject=None):
2234 26b4bea8 2011-08-02 rsc set_status("uploading message")
2235 ef27bfa4 2010-01-12 rsc form_fields = GetForm("/" + issue + "/publish")
2236 ef27bfa4 2010-01-12 rsc if reviewers is not None:
2237 ef27bfa4 2010-01-12 rsc form_fields['reviewers'] = reviewers
2238 ef27bfa4 2010-01-12 rsc if cc is not None:
2239 ef27bfa4 2010-01-12 rsc form_fields['cc'] = cc
2240 ef27bfa4 2010-01-12 rsc if send_mail:
2241 ef27bfa4 2010-01-12 rsc form_fields['send_mail'] = "checked"
2242 ef27bfa4 2010-01-12 rsc else:
2243 ef27bfa4 2010-01-12 rsc del form_fields['send_mail']
2244 ef27bfa4 2010-01-12 rsc if subject is not None:
2245 ef27bfa4 2010-01-12 rsc form_fields['subject'] = subject
2246 ef27bfa4 2010-01-12 rsc form_fields['message'] = message
2247 26b4bea8 2011-08-02 rsc
2248 ef27bfa4 2010-01-12 rsc form_fields['message_only'] = '1' # Don't include draft comments
2249 ef27bfa4 2010-01-12 rsc if reviewers is not None or cc is not None:
2250 ef27bfa4 2010-01-12 rsc form_fields['message_only'] = '' # Must set '' in order to override cc/reviewer
2251 ef27bfa4 2010-01-12 rsc ctype = "applications/x-www-form-urlencoded"
2252 ef27bfa4 2010-01-12 rsc body = urllib.urlencode(form_fields)
2253 ef27bfa4 2010-01-12 rsc response = MySend("/" + issue + "/publish", body, content_type=ctype)
2254 ef27bfa4 2010-01-12 rsc if response != "":
2255 ef27bfa4 2010-01-12 rsc print response
2256 ef27bfa4 2010-01-12 rsc sys.exit(2)
2257 ef27bfa4 2010-01-12 rsc
2258 ef27bfa4 2010-01-12 rsc class opt(object):
2259 ef27bfa4 2010-01-12 rsc pass
2260 ef27bfa4 2010-01-12 rsc
2261 26b4bea8 2011-08-02 rsc def nocommit(*pats, **opts):
2262 26b4bea8 2011-08-02 rsc """(disabled when using this extension)"""
2263 26b4bea8 2011-08-02 rsc raise util.Abort("codereview extension enabled; use mail, upload, or submit instead of commit")
2264 26b4bea8 2011-08-02 rsc
2265 26b4bea8 2011-08-02 rsc def nobackout(*pats, **opts):
2266 26b4bea8 2011-08-02 rsc """(disabled when using this extension)"""
2267 26b4bea8 2011-08-02 rsc raise util.Abort("codereview extension enabled; use undo instead of backout")
2268 26b4bea8 2011-08-02 rsc
2269 26b4bea8 2011-08-02 rsc def norollback(*pats, **opts):
2270 26b4bea8 2011-08-02 rsc """(disabled when using this extension)"""
2271 26b4bea8 2011-08-02 rsc raise util.Abort("codereview extension enabled; use undo instead of rollback")
2272 26b4bea8 2011-08-02 rsc
2273 ef27bfa4 2010-01-12 rsc def RietveldSetup(ui, repo):
2274 a06877af 2010-08-05 rsc global defaultcc, upload_options, rpc, server, server_url_base, force_google_account, verbosity, contributors
2275 26b4bea8 2011-08-02 rsc global missing_codereview
2276 ef27bfa4 2010-01-12 rsc
2277 26b4bea8 2011-08-02 rsc repo_config_path = ''
2278 ef27bfa4 2010-01-12 rsc # Read repository-specific options from lib/codereview/codereview.cfg
2279 ef27bfa4 2010-01-12 rsc try:
2280 26b4bea8 2011-08-02 rsc repo_config_path = repo.root + '/lib/codereview/codereview.cfg'
2281 26b4bea8 2011-08-02 rsc f = open(repo_config_path)
2282 ef27bfa4 2010-01-12 rsc for line in f:
2283 ef27bfa4 2010-01-12 rsc if line.startswith('defaultcc: '):
2284 ef27bfa4 2010-01-12 rsc defaultcc = SplitCommaSpace(line[10:])
2285 ef27bfa4 2010-01-12 rsc except:
2286 a06877af 2010-08-05 rsc # If there are no options, chances are good this is not
2287 a06877af 2010-08-05 rsc # a code review repository; stop now before we foul
2288 a06877af 2010-08-05 rsc # things up even worse. Might also be that repo doesn't
2289 a06877af 2010-08-05 rsc # even have a root. See issue 959.
2290 26b4bea8 2011-08-02 rsc if repo_config_path == '':
2291 26b4bea8 2011-08-02 rsc missing_codereview = 'codereview disabled: repository has no root'
2292 26b4bea8 2011-08-02 rsc else:
2293 26b4bea8 2011-08-02 rsc missing_codereview = 'codereview disabled: cannot open ' + repo_config_path
2294 a06877af 2010-08-05 rsc return
2295 ef27bfa4 2010-01-12 rsc
2296 26b4bea8 2011-08-02 rsc # Should only modify repository with hg submit.
2297 26b4bea8 2011-08-02 rsc # Disable the built-in Mercurial commands that might
2298 26b4bea8 2011-08-02 rsc # trip things up.
2299 26b4bea8 2011-08-02 rsc cmdutil.commit = nocommit
2300 26b4bea8 2011-08-02 rsc global real_rollback
2301 26b4bea8 2011-08-02 rsc real_rollback = repo.rollback
2302 26b4bea8 2011-08-02 rsc repo.rollback = norollback
2303 26b4bea8 2011-08-02 rsc # would install nobackout if we could; oh well
2304 26b4bea8 2011-08-02 rsc
2305 a06877af 2010-08-05 rsc try:
2306 a06877af 2010-08-05 rsc f = open(repo.root + '/CONTRIBUTORS', 'r')
2307 a06877af 2010-08-05 rsc except:
2308 a06877af 2010-08-05 rsc raise util.Abort("cannot open %s: %s" % (repo.root+'/CONTRIBUTORS', ExceptionDetail()))
2309 a06877af 2010-08-05 rsc for line in f:
2310 a06877af 2010-08-05 rsc # CONTRIBUTORS is a list of lines like:
2311 a06877af 2010-08-05 rsc # Person <email>
2312 a06877af 2010-08-05 rsc # Person <email> <alt-email>
2313 a06877af 2010-08-05 rsc # The first email address is the one used in commit logs.
2314 a06877af 2010-08-05 rsc if line.startswith('#'):
2315 a06877af 2010-08-05 rsc continue
2316 a06877af 2010-08-05 rsc m = re.match(r"([^<>]+\S)\s+(<[^<>\s]+>)((\s+<[^<>\s]+>)*)\s*$", line)
2317 a06877af 2010-08-05 rsc if m:
2318 a06877af 2010-08-05 rsc name = m.group(1)
2319 a06877af 2010-08-05 rsc email = m.group(2)[1:-1]
2320 a06877af 2010-08-05 rsc contributors[email.lower()] = (name, email)
2321 a06877af 2010-08-05 rsc for extra in m.group(3).split():
2322 a06877af 2010-08-05 rsc contributors[extra[1:-1].lower()] = (name, email)
2323 a06877af 2010-08-05 rsc
2324 ef27bfa4 2010-01-12 rsc if not ui.verbose:
2325 ef27bfa4 2010-01-12 rsc verbosity = 0
2326 ef27bfa4 2010-01-12 rsc
2327 ef27bfa4 2010-01-12 rsc # Config options.
2328 ef27bfa4 2010-01-12 rsc x = ui.config("codereview", "server")
2329 ef27bfa4 2010-01-12 rsc if x is not None:
2330 ef27bfa4 2010-01-12 rsc server = x
2331 ef27bfa4 2010-01-12 rsc
2332 ef27bfa4 2010-01-12 rsc # TODO(rsc): Take from ui.username?
2333 ef27bfa4 2010-01-12 rsc email = None
2334 ef27bfa4 2010-01-12 rsc x = ui.config("codereview", "email")
2335 ef27bfa4 2010-01-12 rsc if x is not None:
2336 ef27bfa4 2010-01-12 rsc email = x
2337 ef27bfa4 2010-01-12 rsc
2338 ef27bfa4 2010-01-12 rsc server_url_base = "http://" + server + "/"
2339 ef27bfa4 2010-01-12 rsc
2340 ef27bfa4 2010-01-12 rsc testing = ui.config("codereview", "testing")
2341 ef27bfa4 2010-01-12 rsc force_google_account = ui.configbool("codereview", "force_google_account", False)
2342 ef27bfa4 2010-01-12 rsc
2343 ef27bfa4 2010-01-12 rsc upload_options = opt()
2344 ef27bfa4 2010-01-12 rsc upload_options.email = email
2345 ef27bfa4 2010-01-12 rsc upload_options.host = None
2346 ef27bfa4 2010-01-12 rsc upload_options.verbose = 0
2347 ef27bfa4 2010-01-12 rsc upload_options.description = None
2348 ef27bfa4 2010-01-12 rsc upload_options.description_file = None
2349 ef27bfa4 2010-01-12 rsc upload_options.reviewers = None
2350 ef27bfa4 2010-01-12 rsc upload_options.cc = None
2351 ef27bfa4 2010-01-12 rsc upload_options.message = None
2352 ef27bfa4 2010-01-12 rsc upload_options.issue = None
2353 ef27bfa4 2010-01-12 rsc upload_options.download_base = False
2354 ef27bfa4 2010-01-12 rsc upload_options.revision = None
2355 ef27bfa4 2010-01-12 rsc upload_options.send_mail = False
2356 ef27bfa4 2010-01-12 rsc upload_options.vcs = None
2357 ef27bfa4 2010-01-12 rsc upload_options.server = server
2358 ef27bfa4 2010-01-12 rsc upload_options.save_cookies = True
2359 ef27bfa4 2010-01-12 rsc
2360 ef27bfa4 2010-01-12 rsc if testing:
2361 ef27bfa4 2010-01-12 rsc upload_options.save_cookies = False
2362 ef27bfa4 2010-01-12 rsc upload_options.email = "test@example.com"
2363 ef27bfa4 2010-01-12 rsc
2364 ef27bfa4 2010-01-12 rsc rpc = None
2365 26b4bea8 2011-08-02 rsc
2366 26b4bea8 2011-08-02 rsc global releaseBranch
2367 26b4bea8 2011-08-02 rsc tags = repo.branchtags().keys()
2368 26b4bea8 2011-08-02 rsc if 'release-branch.r100' in tags:
2369 26b4bea8 2011-08-02 rsc # NOTE(rsc): This tags.sort is going to get the wrong
2370 26b4bea8 2011-08-02 rsc # answer when comparing release-branch.r99 with
2371 26b4bea8 2011-08-02 rsc # release-branch.r100. If we do ten releases a year
2372 26b4bea8 2011-08-02 rsc # that gives us 4 years before we have to worry about this.
2373 26b4bea8 2011-08-02 rsc raise util.Abort('tags.sort needs to be fixed for release-branch.r100')
2374 26b4bea8 2011-08-02 rsc tags.sort()
2375 26b4bea8 2011-08-02 rsc for t in tags:
2376 26b4bea8 2011-08-02 rsc if t.startswith('release-branch.'):
2377 26b4bea8 2011-08-02 rsc releaseBranch = t
2378 ef27bfa4 2010-01-12 rsc
2379 ef27bfa4 2010-01-12 rsc #######################################################################
2380 26b4bea8 2011-08-02 rsc # http://codereview.appspot.com/static/upload.py, heavily edited.
2381 ef27bfa4 2010-01-12 rsc
2382 ef27bfa4 2010-01-12 rsc #!/usr/bin/env python
2383 ef27bfa4 2010-01-12 rsc #
2384 ef27bfa4 2010-01-12 rsc # Copyright 2007 Google Inc.
2385 ef27bfa4 2010-01-12 rsc #
2386 ef27bfa4 2010-01-12 rsc # Licensed under the Apache License, Version 2.0 (the "License");
2387 ef27bfa4 2010-01-12 rsc # you may not use this file except in compliance with the License.
2388 ef27bfa4 2010-01-12 rsc # You may obtain a copy of the License at
2389 ef27bfa4 2010-01-12 rsc #
2390 26b4bea8 2011-08-02 rsc # http://www.apache.org/licenses/LICENSE-2.0
2391 ef27bfa4 2010-01-12 rsc #
2392 ef27bfa4 2010-01-12 rsc # Unless required by applicable law or agreed to in writing, software
2393 ef27bfa4 2010-01-12 rsc # distributed under the License is distributed on an "AS IS" BASIS,
2394 ef27bfa4 2010-01-12 rsc # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2395 ef27bfa4 2010-01-12 rsc # See the License for the specific language governing permissions and
2396 ef27bfa4 2010-01-12 rsc # limitations under the License.
2397 ef27bfa4 2010-01-12 rsc
2398 ef27bfa4 2010-01-12 rsc """Tool for uploading diffs from a version control system to the codereview app.
2399 ef27bfa4 2010-01-12 rsc
2400 ef27bfa4 2010-01-12 rsc Usage summary: upload.py [options] [-- diff_options]
2401 ef27bfa4 2010-01-12 rsc
2402 ef27bfa4 2010-01-12 rsc Diff options are passed to the diff command of the underlying system.
2403 ef27bfa4 2010-01-12 rsc
2404 ef27bfa4 2010-01-12 rsc Supported version control systems:
2405 26b4bea8 2011-08-02 rsc Git
2406 26b4bea8 2011-08-02 rsc Mercurial
2407 26b4bea8 2011-08-02 rsc Subversion
2408 ef27bfa4 2010-01-12 rsc
2409 ef27bfa4 2010-01-12 rsc It is important for Git/Mercurial users to specify a tree/node/branch to diff
2410 ef27bfa4 2010-01-12 rsc against by using the '--rev' option.
2411 ef27bfa4 2010-01-12 rsc """
2412 ef27bfa4 2010-01-12 rsc # This code is derived from appcfg.py in the App Engine SDK (open source),
2413 ef27bfa4 2010-01-12 rsc # and from ASPN recipe #146306.
2414 ef27bfa4 2010-01-12 rsc
2415 ef27bfa4 2010-01-12 rsc import cookielib
2416 ef27bfa4 2010-01-12 rsc import getpass
2417 ef27bfa4 2010-01-12 rsc import logging
2418 ef27bfa4 2010-01-12 rsc import mimetypes
2419 ef27bfa4 2010-01-12 rsc import optparse
2420 ef27bfa4 2010-01-12 rsc import os
2421 ef27bfa4 2010-01-12 rsc import re
2422 ef27bfa4 2010-01-12 rsc import socket
2423 ef27bfa4 2010-01-12 rsc import subprocess
2424 ef27bfa4 2010-01-12 rsc import sys
2425 ef27bfa4 2010-01-12 rsc import urllib
2426 ef27bfa4 2010-01-12 rsc import urllib2
2427 ef27bfa4 2010-01-12 rsc import urlparse
2428 ef27bfa4 2010-01-12 rsc
2429 ef27bfa4 2010-01-12 rsc # The md5 module was deprecated in Python 2.5.
2430 ef27bfa4 2010-01-12 rsc try:
2431 26b4bea8 2011-08-02 rsc from hashlib import md5
2432 ef27bfa4 2010-01-12 rsc except ImportError:
2433 26b4bea8 2011-08-02 rsc from md5 import md5
2434 ef27bfa4 2010-01-12 rsc
2435 ef27bfa4 2010-01-12 rsc try:
2436 26b4bea8 2011-08-02 rsc import readline
2437 ef27bfa4 2010-01-12 rsc except ImportError:
2438 26b4bea8 2011-08-02 rsc pass
2439 ef27bfa4 2010-01-12 rsc
2440 ef27bfa4 2010-01-12 rsc # The logging verbosity:
2441 ef27bfa4 2010-01-12 rsc # 0: Errors only.
2442 ef27bfa4 2010-01-12 rsc # 1: Status messages.
2443 ef27bfa4 2010-01-12 rsc # 2: Info logs.
2444 ef27bfa4 2010-01-12 rsc # 3: Debug logs.
2445 ef27bfa4 2010-01-12 rsc verbosity = 1
2446 ef27bfa4 2010-01-12 rsc
2447 ef27bfa4 2010-01-12 rsc # Max size of patch or base file.
2448 ef27bfa4 2010-01-12 rsc MAX_UPLOAD_SIZE = 900 * 1024
2449 ef27bfa4 2010-01-12 rsc
2450 ef27bfa4 2010-01-12 rsc # whitelist for non-binary filetypes which do not start with "text/"
2451 ef27bfa4 2010-01-12 rsc # .mm (Objective-C) shows up as application/x-freemind on my Linux box.
2452 26b4bea8 2011-08-02 rsc TEXT_MIMETYPES = [
2453 26b4bea8 2011-08-02 rsc 'application/javascript',
2454 26b4bea8 2011-08-02 rsc 'application/x-javascript',
2455 26b4bea8 2011-08-02 rsc 'application/x-freemind'
2456 26b4bea8 2011-08-02 rsc ]
2457 ef27bfa4 2010-01-12 rsc
2458 ef27bfa4 2010-01-12 rsc def GetEmail(prompt):
2459 26b4bea8 2011-08-02 rsc """Prompts the user for their email address and returns it.
2460 ef27bfa4 2010-01-12 rsc
2461 26b4bea8 2011-08-02 rsc The last used email address is saved to a file and offered up as a suggestion
2462 26b4bea8 2011-08-02 rsc to the user. If the user presses enter without typing in anything the last
2463 26b4bea8 2011-08-02 rsc used email address is used. If the user enters a new address, it is saved
2464 26b4bea8 2011-08-02 rsc for next time we prompt.
2465 ef27bfa4 2010-01-12 rsc
2466 26b4bea8 2011-08-02 rsc """
2467 26b4bea8 2011-08-02 rsc last_email_file_name = os.path.expanduser("~/.last_codereview_email_address")
2468 26b4bea8 2011-08-02 rsc last_email = ""
2469 26b4bea8 2011-08-02 rsc if os.path.exists(last_email_file_name):
2470 26b4bea8 2011-08-02 rsc try:
2471 26b4bea8 2011-08-02 rsc last_email_file = open(last_email_file_name, "r")
2472 26b4bea8 2011-08-02 rsc last_email = last_email_file.readline().strip("\n")
2473 26b4bea8 2011-08-02 rsc last_email_file.close()
2474 26b4bea8 2011-08-02 rsc prompt += " [%s]" % last_email
2475 26b4bea8 2011-08-02 rsc except IOError, e:
2476 26b4bea8 2011-08-02 rsc pass
2477 26b4bea8 2011-08-02 rsc email = raw_input(prompt + ": ").strip()
2478 26b4bea8 2011-08-02 rsc if email:
2479 26b4bea8 2011-08-02 rsc try:
2480 26b4bea8 2011-08-02 rsc last_email_file = open(last_email_file_name, "w")
2481 26b4bea8 2011-08-02 rsc last_email_file.write(email)
2482 26b4bea8 2011-08-02 rsc last_email_file.close()
2483 26b4bea8 2011-08-02 rsc except IOError, e:
2484 26b4bea8 2011-08-02 rsc pass
2485 26b4bea8 2011-08-02 rsc else:
2486 26b4bea8 2011-08-02 rsc email = last_email
2487 26b4bea8 2011-08-02 rsc return email
2488 ef27bfa4 2010-01-12 rsc
2489 ef27bfa4 2010-01-12 rsc
2490 ef27bfa4 2010-01-12 rsc def StatusUpdate(msg):
2491 26b4bea8 2011-08-02 rsc """Print a status message to stdout.
2492 ef27bfa4 2010-01-12 rsc
2493 26b4bea8 2011-08-02 rsc If 'verbosity' is greater than 0, print the message.
2494 ef27bfa4 2010-01-12 rsc
2495 26b4bea8 2011-08-02 rsc Args:
2496 26b4bea8 2011-08-02 rsc msg: The string to print.
2497 26b4bea8 2011-08-02 rsc """
2498 26b4bea8 2011-08-02 rsc if verbosity > 0:
2499 26b4bea8 2011-08-02 rsc print msg
2500 ef27bfa4 2010-01-12 rsc
2501 ef27bfa4 2010-01-12 rsc
2502 ef27bfa4 2010-01-12 rsc def ErrorExit(msg):
2503 26b4bea8 2011-08-02 rsc """Print an error message to stderr and exit."""
2504 26b4bea8 2011-08-02 rsc print >>sys.stderr, msg
2505 26b4bea8 2011-08-02 rsc sys.exit(1)
2506 ef27bfa4 2010-01-12 rsc
2507 ef27bfa4 2010-01-12 rsc
2508 ef27bfa4 2010-01-12 rsc class ClientLoginError(urllib2.HTTPError):
2509 26b4bea8 2011-08-02 rsc """Raised to indicate there was an error authenticating with ClientLogin."""
2510 ef27bfa4 2010-01-12 rsc
2511 26b4bea8 2011-08-02 rsc def __init__(self, url, code, msg, headers, args):
2512 26b4bea8 2011-08-02 rsc urllib2.HTTPError.__init__(self, url, code, msg, headers, None)
2513 26b4bea8 2011-08-02 rsc self.args = args
2514 26b4bea8 2011-08-02 rsc self.reason = args["Error"]
2515 ef27bfa4 2010-01-12 rsc
2516 ef27bfa4 2010-01-12 rsc
2517 ef27bfa4 2010-01-12 rsc class AbstractRpcServer(object):
2518 26b4bea8 2011-08-02 rsc """Provides a common interface for a simple RPC server."""
2519 ef27bfa4 2010-01-12 rsc
2520 26b4bea8 2011-08-02 rsc def __init__(self, host, auth_function, host_override=None, extra_headers={}, save_cookies=False):
2521 26b4bea8 2011-08-02 rsc """Creates a new HttpRpcServer.
2522 ef27bfa4 2010-01-12 rsc
2523 26b4bea8 2011-08-02 rsc Args:
2524 26b4bea8 2011-08-02 rsc host: The host to send requests to.
2525 26b4bea8 2011-08-02 rsc auth_function: A function that takes no arguments and returns an
2526 26b4bea8 2011-08-02 rsc (email, password) tuple when called. Will be called if authentication
2527 26b4bea8 2011-08-02 rsc is required.
2528 26b4bea8 2011-08-02 rsc host_override: The host header to send to the server (defaults to host).
2529 26b4bea8 2011-08-02 rsc extra_headers: A dict of extra headers to append to every request.
2530 26b4bea8 2011-08-02 rsc save_cookies: If True, save the authentication cookies to local disk.
2531 26b4bea8 2011-08-02 rsc If False, use an in-memory cookiejar instead. Subclasses must
2532 26b4bea8 2011-08-02 rsc implement this functionality. Defaults to False.
2533 26b4bea8 2011-08-02 rsc """
2534 26b4bea8 2011-08-02 rsc self.host = host
2535 26b4bea8 2011-08-02 rsc self.host_override = host_override
2536 26b4bea8 2011-08-02 rsc self.auth_function = auth_function
2537 26b4bea8 2011-08-02 rsc self.authenticated = False
2538 26b4bea8 2011-08-02 rsc self.extra_headers = extra_headers
2539 26b4bea8 2011-08-02 rsc self.save_cookies = save_cookies
2540 26b4bea8 2011-08-02 rsc self.opener = self._GetOpener()
2541 26b4bea8 2011-08-02 rsc if self.host_override:
2542 26b4bea8 2011-08-02 rsc logging.info("Server: %s; Host: %s", self.host, self.host_override)
2543 26b4bea8 2011-08-02 rsc else:
2544 26b4bea8 2011-08-02 rsc logging.info("Server: %s", self.host)
2545 ef27bfa4 2010-01-12 rsc
2546 26b4bea8 2011-08-02 rsc def _GetOpener(self):
2547 26b4bea8 2011-08-02 rsc """Returns an OpenerDirector for making HTTP requests.
2548 ef27bfa4 2010-01-12 rsc
2549 26b4bea8 2011-08-02 rsc Returns:
2550 26b4bea8 2011-08-02 rsc A urllib2.OpenerDirector object.
2551 26b4bea8 2011-08-02 rsc """
2552 26b4bea8 2011-08-02 rsc raise NotImplementedError()
2553 ef27bfa4 2010-01-12 rsc
2554 26b4bea8 2011-08-02 rsc def _CreateRequest(self, url, data=None):
2555 26b4bea8 2011-08-02 rsc """Creates a new urllib request."""
2556 26b4bea8 2011-08-02 rsc logging.debug("Creating request for: '%s' with payload:\n%s", url, data)
2557 26b4bea8 2011-08-02 rsc req = urllib2.Request(url, data=data)
2558 26b4bea8 2011-08-02 rsc if self.host_override:
2559 26b4bea8 2011-08-02 rsc req.add_header("Host", self.host_override)
2560 26b4bea8 2011-08-02 rsc for key, value in self.extra_headers.iteritems():
2561 26b4bea8 2011-08-02 rsc req.add_header(key, value)
2562 26b4bea8 2011-08-02 rsc return req
2563 ef27bfa4 2010-01-12 rsc
2564 26b4bea8 2011-08-02 rsc def _GetAuthToken(self, email, password):
2565 26b4bea8 2011-08-02 rsc """Uses ClientLogin to authenticate the user, returning an auth token.
2566 ef27bfa4 2010-01-12 rsc
2567 26b4bea8 2011-08-02 rsc Args:
2568 26b4bea8 2011-08-02 rsc email: The user's email address
2569 26b4bea8 2011-08-02 rsc password: The user's password
2570 ef27bfa4 2010-01-12 rsc
2571 26b4bea8 2011-08-02 rsc Raises:
2572 26b4bea8 2011-08-02 rsc ClientLoginError: If there was an error authenticating with ClientLogin.
2573 26b4bea8 2011-08-02 rsc HTTPError: If there was some other form of HTTP error.
2574 ef27bfa4 2010-01-12 rsc
2575 26b4bea8 2011-08-02 rsc Returns:
2576 26b4bea8 2011-08-02 rsc The authentication token returned by ClientLogin.
2577 26b4bea8 2011-08-02 rsc """
2578 26b4bea8 2011-08-02 rsc account_type = "GOOGLE"
2579 26b4bea8 2011-08-02 rsc if self.host.endswith(".google.com") and not force_google_account:
2580 26b4bea8 2011-08-02 rsc # Needed for use inside Google.
2581 26b4bea8 2011-08-02 rsc account_type = "HOSTED"
2582 26b4bea8 2011-08-02 rsc req = self._CreateRequest(
2583 26b4bea8 2011-08-02 rsc url="https://www.google.com/accounts/ClientLogin",
2584 26b4bea8 2011-08-02 rsc data=urllib.urlencode({
2585 26b4bea8 2011-08-02 rsc "Email": email,
2586 26b4bea8 2011-08-02 rsc "Passwd": password,
2587 26b4bea8 2011-08-02 rsc "service": "ah",
2588 26b4bea8 2011-08-02 rsc "source": "rietveld-codereview-upload",
2589 26b4bea8 2011-08-02 rsc "accountType": account_type,
2590 26b4bea8 2011-08-02 rsc }),
2591 26b4bea8 2011-08-02 rsc )
2592 26b4bea8 2011-08-02 rsc try:
2593 26b4bea8 2011-08-02 rsc response = self.opener.open(req)
2594 26b4bea8 2011-08-02 rsc response_body = response.read()
2595 26b4bea8 2011-08-02 rsc response_dict = dict(x.split("=") for x in response_body.split("\n") if x)
2596 26b4bea8 2011-08-02 rsc return response_dict["Auth"]
2597 26b4bea8 2011-08-02 rsc except urllib2.HTTPError, e:
2598 26b4bea8 2011-08-02 rsc if e.code == 403:
2599 26b4bea8 2011-08-02 rsc body = e.read()
2600 26b4bea8 2011-08-02 rsc response_dict = dict(x.split("=", 1) for x in body.split("\n") if x)
2601 26b4bea8 2011-08-02 rsc raise ClientLoginError(req.get_full_url(), e.code, e.msg, e.headers, response_dict)
2602 26b4bea8 2011-08-02 rsc else:
2603 26b4bea8 2011-08-02 rsc raise
2604 ef27bfa4 2010-01-12 rsc
2605 26b4bea8 2011-08-02 rsc def _GetAuthCookie(self, auth_token):
2606 26b4bea8 2011-08-02 rsc """Fetches authentication cookies for an authentication token.
2607 ef27bfa4 2010-01-12 rsc
2608 26b4bea8 2011-08-02 rsc Args:
2609 26b4bea8 2011-08-02 rsc auth_token: The authentication token returned by ClientLogin.
2610 ef27bfa4 2010-01-12 rsc
2611 26b4bea8 2011-08-02 rsc Raises:
2612 26b4bea8 2011-08-02 rsc HTTPError: If there was an error fetching the authentication cookies.
2613 26b4bea8 2011-08-02 rsc """
2614 26b4bea8 2011-08-02 rsc # This is a dummy value to allow us to identify when we're successful.
2615 26b4bea8 2011-08-02 rsc continue_location = "http://localhost/"
2616 26b4bea8 2011-08-02 rsc args = {"continue": continue_location, "auth": auth_token}
2617 26b4bea8 2011-08-02 rsc req = self._CreateRequest("http://%s/_ah/login?%s" % (self.host, urllib.urlencode(args)))
2618 26b4bea8 2011-08-02 rsc try:
2619 26b4bea8 2011-08-02 rsc response = self.opener.open(req)
2620 26b4bea8 2011-08-02 rsc except urllib2.HTTPError, e:
2621 26b4bea8 2011-08-02 rsc response = e
2622 26b4bea8 2011-08-02 rsc if (response.code != 302 or
2623 26b4bea8 2011-08-02 rsc response.info()["location"] != continue_location):
2624 26b4bea8 2011-08-02 rsc raise urllib2.HTTPError(req.get_full_url(), response.code, response.msg, response.headers, response.fp)
2625 26b4bea8 2011-08-02 rsc self.authenticated = True
2626 ef27bfa4 2010-01-12 rsc
2627 26b4bea8 2011-08-02 rsc def _Authenticate(self):
2628 26b4bea8 2011-08-02 rsc """Authenticates the user.
2629 ef27bfa4 2010-01-12 rsc
2630 26b4bea8 2011-08-02 rsc The authentication process works as follows:
2631 26b4bea8 2011-08-02 rsc 1) We get a username and password from the user
2632 26b4bea8 2011-08-02 rsc 2) We use ClientLogin to obtain an AUTH token for the user
2633 26b4bea8 2011-08-02 rsc (see http://code.google.com/apis/accounts/AuthForInstalledApps.html).
2634 26b4bea8 2011-08-02 rsc 3) We pass the auth token to /_ah/login on the server to obtain an
2635 26b4bea8 2011-08-02 rsc authentication cookie. If login was successful, it tries to redirect
2636 26b4bea8 2011-08-02 rsc us to the URL we provided.
2637 ef27bfa4 2010-01-12 rsc
2638 26b4bea8 2011-08-02 rsc If we attempt to access the upload API without first obtaining an
2639 26b4bea8 2011-08-02 rsc authentication cookie, it returns a 401 response (or a 302) and
2640 26b4bea8 2011-08-02 rsc directs us to authenticate ourselves with ClientLogin.
2641 26b4bea8 2011-08-02 rsc """
2642 26b4bea8 2011-08-02 rsc for i in range(3):
2643 26b4bea8 2011-08-02 rsc credentials = self.auth_function()
2644 26b4bea8 2011-08-02 rsc try:
2645 26b4bea8 2011-08-02 rsc auth_token = self._GetAuthToken(credentials[0], credentials[1])
2646 26b4bea8 2011-08-02 rsc except ClientLoginError, e:
2647 26b4bea8 2011-08-02 rsc if e.reason == "BadAuthentication":
2648 26b4bea8 2011-08-02 rsc print >>sys.stderr, "Invalid username or password."
2649 26b4bea8 2011-08-02 rsc continue
2650 26b4bea8 2011-08-02 rsc if e.reason == "CaptchaRequired":
2651 26b4bea8 2011-08-02 rsc print >>sys.stderr, (
2652 26b4bea8 2011-08-02 rsc "Please go to\n"
2653 26b4bea8 2011-08-02 rsc "https://www.google.com/accounts/DisplayUnlockCaptcha\n"
2654 26b4bea8 2011-08-02 rsc "and verify you are a human. Then try again.")
2655 26b4bea8 2011-08-02 rsc break
2656 26b4bea8 2011-08-02 rsc if e.reason == "NotVerified":
2657 26b4bea8 2011-08-02 rsc print >>sys.stderr, "Account not verified."
2658 26b4bea8 2011-08-02 rsc break
2659 26b4bea8 2011-08-02 rsc if e.reason == "TermsNotAgreed":
2660 26b4bea8 2011-08-02 rsc print >>sys.stderr, "User has not agreed to TOS."
2661 26b4bea8 2011-08-02 rsc break
2662 26b4bea8 2011-08-02 rsc if e.reason == "AccountDeleted":
2663 26b4bea8 2011-08-02 rsc print >>sys.stderr, "The user account has been deleted."
2664 26b4bea8 2011-08-02 rsc break
2665 26b4bea8 2011-08-02 rsc if e.reason == "AccountDisabled":
2666 26b4bea8 2011-08-02 rsc print >>sys.stderr, "The user account has been disabled."
2667 26b4bea8 2011-08-02 rsc break
2668 26b4bea8 2011-08-02 rsc if e.reason == "ServiceDisabled":
2669 26b4bea8 2011-08-02 rsc print >>sys.stderr, "The user's access to the service has been disabled."
2670 26b4bea8 2011-08-02 rsc break
2671 26b4bea8 2011-08-02 rsc if e.reason == "ServiceUnavailable":
2672 26b4bea8 2011-08-02 rsc print >>sys.stderr, "The service is not available; try again later."
2673 26b4bea8 2011-08-02 rsc break
2674 26b4bea8 2011-08-02 rsc raise
2675 26b4bea8 2011-08-02 rsc self._GetAuthCookie(auth_token)
2676 26b4bea8 2011-08-02 rsc return
2677 ef27bfa4 2010-01-12 rsc
2678 26b4bea8 2011-08-02 rsc def Send(self, request_path, payload=None,
2679 26b4bea8 2011-08-02 rsc content_type="application/octet-stream",
2680 26b4bea8 2011-08-02 rsc timeout=None,
2681 26b4bea8 2011-08-02 rsc **kwargs):
2682 26b4bea8 2011-08-02 rsc """Sends an RPC and returns the response.
2683 ef27bfa4 2010-01-12 rsc
2684 26b4bea8 2011-08-02 rsc Args:
2685 26b4bea8 2011-08-02 rsc request_path: The path to send the request to, eg /api/appversion/create.
2686 26b4bea8 2011-08-02 rsc payload: The body of the request, or None to send an empty request.
2687 26b4bea8 2011-08-02 rsc content_type: The Content-Type header to use.
2688 26b4bea8 2011-08-02 rsc timeout: timeout in seconds; default None i.e. no timeout.
2689 26b4bea8 2011-08-02 rsc (Note: for large requests on OS X, the timeout doesn't work right.)
2690 26b4bea8 2011-08-02 rsc kwargs: Any keyword arguments are converted into query string parameters.
2691 ef27bfa4 2010-01-12 rsc
2692 26b4bea8 2011-08-02 rsc Returns:
2693 26b4bea8 2011-08-02 rsc The response body, as a string.
2694 26b4bea8 2011-08-02 rsc """
2695 26b4bea8 2011-08-02 rsc # TODO: Don't require authentication. Let the server say
2696 26b4bea8 2011-08-02 rsc # whether it is necessary.
2697 26b4bea8 2011-08-02 rsc if not self.authenticated:
2698 26b4bea8 2011-08-02 rsc self._Authenticate()
2699 ef27bfa4 2010-01-12 rsc
2700 26b4bea8 2011-08-02 rsc old_timeout = socket.getdefaulttimeout()
2701 26b4bea8 2011-08-02 rsc socket.setdefaulttimeout(timeout)
2702 26b4bea8 2011-08-02 rsc try:
2703 26b4bea8 2011-08-02 rsc tries = 0
2704 26b4bea8 2011-08-02 rsc while True:
2705 26b4bea8 2011-08-02 rsc tries += 1
2706 26b4bea8 2011-08-02 rsc args = dict(kwargs)
2707 26b4bea8 2011-08-02 rsc url = "http://%s%s" % (self.host, request_path)
2708 26b4bea8 2011-08-02 rsc if args:
2709 26b4bea8 2011-08-02 rsc url += "?" + urllib.urlencode(args)
2710 26b4bea8 2011-08-02 rsc req = self._CreateRequest(url=url, data=payload)
2711 26b4bea8 2011-08-02 rsc req.add_header("Content-Type", content_type)
2712 26b4bea8 2011-08-02 rsc try:
2713 26b4bea8 2011-08-02 rsc f = self.opener.open(req)
2714 26b4bea8 2011-08-02 rsc response = f.read()
2715 26b4bea8 2011-08-02 rsc f.close()
2716 26b4bea8 2011-08-02 rsc return response
2717 26b4bea8 2011-08-02 rsc except urllib2.HTTPError, e:
2718 26b4bea8 2011-08-02 rsc if tries > 3:
2719 26b4bea8 2011-08-02 rsc raise
2720 26b4bea8 2011-08-02 rsc elif e.code == 401 or e.code == 302:
2721 26b4bea8 2011-08-02 rsc self._Authenticate()
2722 26b4bea8 2011-08-02 rsc else:
2723 26b4bea8 2011-08-02 rsc raise
2724 26b4bea8 2011-08-02 rsc finally:
2725 26b4bea8 2011-08-02 rsc socket.setdefaulttimeout(old_timeout)
2726 ef27bfa4 2010-01-12 rsc
2727 ef27bfa4 2010-01-12 rsc
2728 ef27bfa4 2010-01-12 rsc class HttpRpcServer(AbstractRpcServer):
2729 26b4bea8 2011-08-02 rsc """Provides a simplified RPC-style interface for HTTP requests."""
2730 ef27bfa4 2010-01-12 rsc
2731 26b4bea8 2011-08-02 rsc def _Authenticate(self):
2732 26b4bea8 2011-08-02 rsc """Save the cookie jar after authentication."""
2733 26b4bea8 2011-08-02 rsc super(HttpRpcServer, self)._Authenticate()
2734 26b4bea8 2011-08-02 rsc if self.save_cookies:
2735 26b4bea8 2011-08-02 rsc StatusUpdate("Saving authentication cookies to %s" % self.cookie_file)
2736 26b4bea8 2011-08-02 rsc self.cookie_jar.save()
2737 ef27bfa4 2010-01-12 rsc
2738 26b4bea8 2011-08-02 rsc def _GetOpener(self):
2739 26b4bea8 2011-08-02 rsc """Returns an OpenerDirector that supports cookies and ignores redirects.
2740 ef27bfa4 2010-01-12 rsc
2741 26b4bea8 2011-08-02 rsc Returns:
2742 26b4bea8 2011-08-02 rsc A urllib2.OpenerDirector object.
2743 26b4bea8 2011-08-02 rsc """
2744 26b4bea8 2011-08-02 rsc opener = urllib2.OpenerDirector()
2745 26b4bea8 2011-08-02 rsc opener.add_handler(urllib2.ProxyHandler())
2746 26b4bea8 2011-08-02 rsc opener.add_handler(urllib2.UnknownHandler())
2747 26b4bea8 2011-08-02 rsc opener.add_handler(urllib2.HTTPHandler())
2748 26b4bea8 2011-08-02 rsc opener.add_handler(urllib2.HTTPDefaultErrorHandler())
2749 26b4bea8 2011-08-02 rsc opener.add_handler(urllib2.HTTPSHandler())
2750 26b4bea8 2011-08-02 rsc opener.add_handler(urllib2.HTTPErrorProcessor())
2751 26b4bea8 2011-08-02 rsc if self.save_cookies:
2752 26b4bea8 2011-08-02 rsc self.cookie_file = os.path.expanduser("~/.codereview_upload_cookies_" + server)
2753 26b4bea8 2011-08-02 rsc self.cookie_jar = cookielib.MozillaCookieJar(self.cookie_file)
2754 26b4bea8 2011-08-02 rsc if os.path.exists(self.cookie_file):
2755 26b4bea8 2011-08-02 rsc try:
2756 26b4bea8 2011-08-02 rsc self.cookie_jar.load()
2757 26b4bea8 2011-08-02 rsc self.authenticated = True
2758 26b4bea8 2011-08-02 rsc StatusUpdate("Loaded authentication cookies from %s" % self.cookie_file)
2759 26b4bea8 2011-08-02 rsc except (cookielib.LoadError, IOError):
2760 26b4bea8 2011-08-02 rsc # Failed to load cookies - just ignore them.
2761 26b4bea8 2011-08-02 rsc pass
2762 26b4bea8 2011-08-02 rsc else:
2763 26b4bea8 2011-08-02 rsc # Create an empty cookie file with mode 600
2764 26b4bea8 2011-08-02 rsc fd = os.open(self.cookie_file, os.O_CREAT, 0600)
2765 26b4bea8 2011-08-02 rsc os.close(fd)
2766 26b4bea8 2011-08-02 rsc # Always chmod the cookie file
2767 26b4bea8 2011-08-02 rsc os.chmod(self.cookie_file, 0600)
2768 26b4bea8 2011-08-02 rsc else:
2769 26b4bea8 2011-08-02 rsc # Don't save cookies across runs of update.py.
2770 26b4bea8 2011-08-02 rsc self.cookie_jar = cookielib.CookieJar()
2771 26b4bea8 2011-08-02 rsc opener.add_handler(urllib2.HTTPCookieProcessor(self.cookie_jar))
2772 26b4bea8 2011-08-02 rsc return opener
2773 ef27bfa4 2010-01-12 rsc
2774 ef27bfa4 2010-01-12 rsc
2775 26b4bea8 2011-08-02 rsc def GetRpcServer(options):
2776 26b4bea8 2011-08-02 rsc """Returns an instance of an AbstractRpcServer.
2777 ef27bfa4 2010-01-12 rsc
2778 26b4bea8 2011-08-02 rsc Returns:
2779 26b4bea8 2011-08-02 rsc A new AbstractRpcServer, on which RPC calls can be made.
2780 26b4bea8 2011-08-02 rsc """
2781 ef27bfa4 2010-01-12 rsc
2782 26b4bea8 2011-08-02 rsc rpc_server_class = HttpRpcServer
2783 ef27bfa4 2010-01-12 rsc
2784 26b4bea8 2011-08-02 rsc def GetUserCredentials():
2785 26b4bea8 2011-08-02 rsc """Prompts the user for a username and password."""
2786 26b4bea8 2011-08-02 rsc # Disable status prints so they don't obscure the password prompt.
2787 26b4bea8 2011-08-02 rsc global global_status
2788 26b4bea8 2011-08-02 rsc st = global_status
2789 26b4bea8 2011-08-02 rsc global_status = None
2790 ef27bfa4 2010-01-12 rsc
2791 26b4bea8 2011-08-02 rsc email = options.email
2792 26b4bea8 2011-08-02 rsc if email is None:
2793 26b4bea8 2011-08-02 rsc email = GetEmail("Email (login for uploading to %s)" % options.server)
2794 26b4bea8 2011-08-02 rsc password = getpass.getpass("Password for %s: " % email)
2795 ef27bfa4 2010-01-12 rsc
2796 26b4bea8 2011-08-02 rsc # Put status back.
2797 26b4bea8 2011-08-02 rsc global_status = st
2798 26b4bea8 2011-08-02 rsc return (email, password)
2799 ef27bfa4 2010-01-12 rsc
2800 26b4bea8 2011-08-02 rsc # If this is the dev_appserver, use fake authentication.
2801 26b4bea8 2011-08-02 rsc host = (options.host or options.server).lower()
2802 26b4bea8 2011-08-02 rsc if host == "localhost" or host.startswith("localhost:"):
2803 26b4bea8 2011-08-02 rsc email = options.email
2804 26b4bea8 2011-08-02 rsc if email is None:
2805 26b4bea8 2011-08-02 rsc email = "test@example.com"
2806 26b4bea8 2011-08-02 rsc logging.info("Using debug user %s. Override with --email" % email)
2807 26b4bea8 2011-08-02 rsc server = rpc_server_class(
2808 26b4bea8 2011-08-02 rsc options.server,
2809 26b4bea8 2011-08-02 rsc lambda: (email, "password"),
2810 26b4bea8 2011-08-02 rsc host_override=options.host,
2811 26b4bea8 2011-08-02 rsc extra_headers={"Cookie": 'dev_appserver_login="%s:False"' % email},
2812 26b4bea8 2011-08-02 rsc save_cookies=options.save_cookies)
2813 26b4bea8 2011-08-02 rsc # Don't try to talk to ClientLogin.
2814 26b4bea8 2011-08-02 rsc server.authenticated = True
2815 26b4bea8 2011-08-02 rsc return server
2816 ef27bfa4 2010-01-12 rsc
2817 26b4bea8 2011-08-02 rsc return rpc_server_class(options.server, GetUserCredentials,
2818 26b4bea8 2011-08-02 rsc host_override=options.host, save_cookies=options.save_cookies)
2819 ef27bfa4 2010-01-12 rsc
2820 ef27bfa4 2010-01-12 rsc
2821 ef27bfa4 2010-01-12 rsc def EncodeMultipartFormData(fields, files):
2822 26b4bea8 2011-08-02 rsc """Encode form fields for multipart/form-data.
2823 ef27bfa4 2010-01-12 rsc
2824 26b4bea8 2011-08-02 rsc Args:
2825 26b4bea8 2011-08-02 rsc fields: A sequence of (name, value) elements for regular form fields.
2826 26b4bea8 2011-08-02 rsc files: A sequence of (name, filename, value) elements for data to be
2827 26b4bea8 2011-08-02 rsc uploaded as files.
2828 26b4bea8 2011-08-02 rsc Returns:
2829 26b4bea8 2011-08-02 rsc (content_type, body) ready for httplib.HTTP instance.
2830 ef27bfa4 2010-01-12 rsc
2831 26b4bea8 2011-08-02 rsc Source:
2832 26b4bea8 2011-08-02 rsc http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306
2833 26b4bea8 2011-08-02 rsc """
2834 26b4bea8 2011-08-02 rsc BOUNDARY = '-M-A-G-I-C---B-O-U-N-D-A-R-Y-'
2835 26b4bea8 2011-08-02 rsc CRLF = '\r\n'
2836 26b4bea8 2011-08-02 rsc lines = []
2837 26b4bea8 2011-08-02 rsc for (key, value) in fields:
2838 26b4bea8 2011-08-02 rsc typecheck(key, str)
2839 26b4bea8 2011-08-02 rsc typecheck(value, str)
2840 26b4bea8 2011-08-02 rsc lines.append('--' + BOUNDARY)
2841 26b4bea8 2011-08-02 rsc lines.append('Content-Disposition: form-data; name="%s"' % key)
2842 26b4bea8 2011-08-02 rsc lines.append('')
2843 26b4bea8 2011-08-02 rsc lines.append(value)
2844 26b4bea8 2011-08-02 rsc for (key, filename, value) in files:
2845 26b4bea8 2011-08-02 rsc typecheck(key, str)
2846 26b4bea8 2011-08-02 rsc typecheck(filename, str)
2847 26b4bea8 2011-08-02 rsc typecheck(value, str)
2848 26b4bea8 2011-08-02 rsc lines.append('--' + BOUNDARY)
2849 26b4bea8 2011-08-02 rsc lines.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
2850 26b4bea8 2011-08-02 rsc lines.append('Content-Type: %s' % GetContentType(filename))
2851 26b4bea8 2011-08-02 rsc lines.append('')
2852 26b4bea8 2011-08-02 rsc lines.append(value)
2853 26b4bea8 2011-08-02 rsc lines.append('--' + BOUNDARY + '--')
2854 26b4bea8 2011-08-02 rsc lines.append('')
2855 26b4bea8 2011-08-02 rsc body = CRLF.join(lines)
2856 26b4bea8 2011-08-02 rsc content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
2857 26b4bea8 2011-08-02 rsc return content_type, body
2858 ef27bfa4 2010-01-12 rsc
2859 ef27bfa4 2010-01-12 rsc
2860 ef27bfa4 2010-01-12 rsc def GetContentType(filename):
2861 26b4bea8 2011-08-02 rsc """Helper to guess the content-type from the filename."""
2862 26b4bea8 2011-08-02 rsc return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
2863 ef27bfa4 2010-01-12 rsc
2864 ef27bfa4 2010-01-12 rsc
2865 ef27bfa4 2010-01-12 rsc # Use a shell for subcommands on Windows to get a PATH search.
2866 ef27bfa4 2010-01-12 rsc use_shell = sys.platform.startswith("win")
2867 ef27bfa4 2010-01-12 rsc
2868 ef27bfa4 2010-01-12 rsc def RunShellWithReturnCode(command, print_output=False,
2869 26b4bea8 2011-08-02 rsc universal_newlines=True, env=os.environ):
2870 26b4bea8 2011-08-02 rsc """Executes a command and returns the output from stdout and the return code.
2871 ef27bfa4 2010-01-12 rsc
2872 26b4bea8 2011-08-02 rsc Args:
2873 26b4bea8 2011-08-02 rsc command: Command to execute.
2874 26b4bea8 2011-08-02 rsc print_output: If True, the output is printed to stdout.
2875 26b4bea8 2011-08-02 rsc If False, both stdout and stderr are ignored.
2876 26b4bea8 2011-08-02 rsc universal_newlines: Use universal_newlines flag (default: True).
2877 ef27bfa4 2010-01-12 rsc
2878 26b4bea8 2011-08-02 rsc Returns:
2879 26b4bea8 2011-08-02 rsc Tuple (output, return code)
2880 26b4bea8 2011-08-02 rsc """
2881 26b4bea8 2011-08-02 rsc logging.info("Running %s", command)
2882 26b4bea8 2011-08-02 rsc p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
2883 26b4bea8 2011-08-02 rsc shell=use_shell, universal_newlines=universal_newlines, env=env)
2884 26b4bea8 2011-08-02 rsc if print_output:
2885 26b4bea8 2011-08-02 rsc output_array = []
2886 26b4bea8 2011-08-02 rsc while True:
2887 26b4bea8 2011-08-02 rsc line = p.stdout.readline()
2888 26b4bea8 2011-08-02 rsc if not line:
2889 26b4bea8 2011-08-02 rsc break
2890 26b4bea8 2011-08-02 rsc print line.strip("\n")
2891 26b4bea8 2011-08-02 rsc output_array.append(line)
2892 26b4bea8 2011-08-02 rsc output = "".join(output_array)
2893 26b4bea8 2011-08-02 rsc else:
2894 26b4bea8 2011-08-02 rsc output = p.stdout.read()
2895 26b4bea8 2011-08-02 rsc p.wait()
2896 26b4bea8 2011-08-02 rsc errout = p.stderr.read()
2897 26b4bea8 2011-08-02 rsc if print_output and errout:
2898 26b4bea8 2011-08-02 rsc print >>sys.stderr, errout
2899 26b4bea8 2011-08-02 rsc p.stdout.close()
2900 26b4bea8 2011-08-02 rsc p.stderr.close()
2901 26b4bea8 2011-08-02 rsc return output, p.returncode
2902 ef27bfa4 2010-01-12 rsc
2903 ef27bfa4 2010-01-12 rsc
2904 26b4bea8 2011-08-02 rsc def RunShell(command, silent_ok=False, universal_newlines=True,
2905 26b4bea8 2011-08-02 rsc print_output=False, env=os.environ):
2906 26b4bea8 2011-08-02 rsc data, retcode = RunShellWithReturnCode(command, print_output, universal_newlines, env)
2907 26b4bea8 2011-08-02 rsc if retcode:
2908 26b4bea8 2011-08-02 rsc ErrorExit("Got error status from %s:\n%s" % (command, data))
2909 26b4bea8 2011-08-02 rsc if not silent_ok and not data:
2910 26b4bea8 2011-08-02 rsc ErrorExit("No output from %s" % command)
2911 26b4bea8 2011-08-02 rsc return data
2912 ef27bfa4 2010-01-12 rsc
2913 ef27bfa4 2010-01-12 rsc
2914 26b4bea8 2011-08-02 rsc class VersionControlSystem(object):
2915 26b4bea8 2011-08-02 rsc """Abstract base class providing an interface to the VCS."""
2916 ef27bfa4 2010-01-12 rsc
2917 26b4bea8 2011-08-02 rsc def __init__(self, options):
2918 26b4bea8 2011-08-02 rsc """Constructor.
2919 ef27bfa4 2010-01-12 rsc
2920 26b4bea8 2011-08-02 rsc Args:
2921 26b4bea8 2011-08-02 rsc options: Command line options.
2922 26b4bea8 2011-08-02 rsc """
2923 26b4bea8 2011-08-02 rsc self.options = options
2924 ef27bfa4 2010-01-12 rsc
2925 26b4bea8 2011-08-02 rsc def GenerateDiff(self, args):
2926 26b4bea8 2011-08-02 rsc """Return the current diff as a string.
2927 ef27bfa4 2010-01-12 rsc
2928 26b4bea8 2011-08-02 rsc Args:
2929 26b4bea8 2011-08-02 rsc args: Extra arguments to pass to the diff command.
2930 26b4bea8 2011-08-02 rsc """
2931 26b4bea8 2011-08-02 rsc raise NotImplementedError(
2932 26b4bea8 2011-08-02 rsc "abstract method -- subclass %s must override" % self.__class__)
2933 ef27bfa4 2010-01-12 rsc
2934 26b4bea8 2011-08-02 rsc def GetUnknownFiles(self):
2935 26b4bea8 2011-08-02 rsc """Return a list of files unknown to the VCS."""
2936 26b4bea8 2011-08-02 rsc raise NotImplementedError(
2937 26b4bea8 2011-08-02 rsc "abstract method -- subclass %s must override" % self.__class__)
2938 ef27bfa4 2010-01-12 rsc
2939 26b4bea8 2011-08-02 rsc def CheckForUnknownFiles(self):
2940 26b4bea8 2011-08-02 rsc """Show an "are you sure?" prompt if there are unknown files."""
2941 26b4bea8 2011-08-02 rsc unknown_files = self.GetUnknownFiles()
2942 26b4bea8 2011-08-02 rsc if unknown_files:
2943 26b4bea8 2011-08-02 rsc print "The following files are not added to version control:"
2944 26b4bea8 2011-08-02 rsc for line in unknown_files:
2945 26b4bea8 2011-08-02 rsc print line
2946 26b4bea8 2011-08-02 rsc prompt = "Are you sure to continue?(y/N) "
2947 26b4bea8 2011-08-02 rsc answer = raw_input(prompt).strip()
2948 26b4bea8 2011-08-02 rsc if answer != "y":
2949 26b4bea8 2011-08-02 rsc ErrorExit("User aborted")
2950 ef27bfa4 2010-01-12 rsc
2951 26b4bea8 2011-08-02 rsc def GetBaseFile(self, filename):
2952 26b4bea8 2011-08-02 rsc """Get the content of the upstream version of a file.
2953 ef27bfa4 2010-01-12 rsc
2954 26b4bea8 2011-08-02 rsc Returns:
2955 26b4bea8 2011-08-02 rsc A tuple (base_content, new_content, is_binary, status)
2956 26b4bea8 2011-08-02 rsc base_content: The contents of the base file.
2957 26b4bea8 2011-08-02 rsc new_content: For text files, this is empty. For binary files, this is
2958 26b4bea8 2011-08-02 rsc the contents of the new file, since the diff output won't contain
2959 26b4bea8 2011-08-02 rsc information to reconstruct the current file.
2960 26b4bea8 2011-08-02 rsc is_binary: True iff the file is binary.
2961 26b4bea8 2011-08-02 rsc status: The status of the file.
2962 26b4bea8 2011-08-02 rsc """
2963 ef27bfa4 2010-01-12 rsc
2964 26b4bea8 2011-08-02 rsc raise NotImplementedError(
2965 26b4bea8 2011-08-02 rsc "abstract method -- subclass %s must override" % self.__class__)
2966 ef27bfa4 2010-01-12 rsc
2967 ef27bfa4 2010-01-12 rsc
2968 26b4bea8 2011-08-02 rsc def GetBaseFiles(self, diff):
2969 26b4bea8 2011-08-02 rsc """Helper that calls GetBase file for each file in the patch.
2970 ef27bfa4 2010-01-12 rsc
2971 26b4bea8 2011-08-02 rsc Returns:
2972 26b4bea8 2011-08-02 rsc A dictionary that maps from filename to GetBaseFile's tuple. Filenames
2973 26b4bea8 2011-08-02 rsc are retrieved based on lines that start with "Index:" or
2974 26b4bea8 2011-08-02 rsc "Property changes on:".
2975 26b4bea8 2011-08-02 rsc """
2976 26b4bea8 2011-08-02 rsc files = {}
2977 26b4bea8 2011-08-02 rsc for line in diff.splitlines(True):
2978 26b4bea8 2011-08-02 rsc if line.startswith('Index:') or line.startswith('Property changes on:'):
2979 26b4bea8 2011-08-02 rsc unused, filename = line.split(':', 1)
2980 26b4bea8 2011-08-02 rsc # On Windows if a file has property changes its filename uses '\'
2981 26b4bea8 2011-08-02 rsc # instead of '/'.
2982 26b4bea8 2011-08-02 rsc filename = filename.strip().replace('\\', '/')
2983 26b4bea8 2011-08-02 rsc files[filename] = self.GetBaseFile(filename)
2984 26b4bea8 2011-08-02 rsc return files
2985 ef27bfa4 2010-01-12 rsc
2986 ef27bfa4 2010-01-12 rsc
2987 26b4bea8 2011-08-02 rsc def UploadBaseFiles(self, issue, rpc_server, patch_list, patchset, options,
2988 26b4bea8 2011-08-02 rsc files):
2989 26b4bea8 2011-08-02 rsc """Uploads the base files (and if necessary, the current ones as well)."""
2990 ef27bfa4 2010-01-12 rsc
2991 26b4bea8 2011-08-02 rsc def UploadFile(filename, file_id, content, is_binary, status, is_base):
2992 26b4bea8 2011-08-02 rsc """Uploads a file to the server."""
2993 26b4bea8 2011-08-02 rsc set_status("uploading " + filename)
2994 26b4bea8 2011-08-02 rsc file_too_large = False
2995 26b4bea8 2011-08-02 rsc if is_base:
2996 26b4bea8 2011-08-02 rsc type = "base"
2997 26b4bea8 2011-08-02 rsc else:
2998 26b4bea8 2011-08-02 rsc type = "current"
2999 26b4bea8 2011-08-02 rsc if len(content) > MAX_UPLOAD_SIZE:
3000 26b4bea8 2011-08-02 rsc print ("Not uploading the %s file for %s because it's too large." %
3001 26b4bea8 2011-08-02 rsc (type, filename))
3002 26b4bea8 2011-08-02 rsc file_too_large = True
3003 26b4bea8 2011-08-02 rsc content = ""
3004 26b4bea8 2011-08-02 rsc checksum = md5(content).hexdigest()
3005 26b4bea8 2011-08-02 rsc if options.verbose > 0 and not file_too_large:
3006 26b4bea8 2011-08-02 rsc print "Uploading %s file for %s" % (type, filename)
3007 26b4bea8 2011-08-02 rsc url = "/%d/upload_content/%d/%d" % (int(issue), int(patchset), file_id)
3008 26b4bea8 2011-08-02 rsc form_fields = [
3009 26b4bea8 2011-08-02 rsc ("filename", filename),
3010 26b4bea8 2011-08-02 rsc ("status", status),
3011 26b4bea8 2011-08-02 rsc ("checksum", checksum),
3012 26b4bea8 2011-08-02 rsc ("is_binary", str(is_binary)),
3013 26b4bea8 2011-08-02 rsc ("is_current", str(not is_base)),
3014 26b4bea8 2011-08-02 rsc ]
3015 26b4bea8 2011-08-02 rsc if file_too_large:
3016 26b4bea8 2011-08-02 rsc form_fields.append(("file_too_large", "1"))
3017 26b4bea8 2011-08-02 rsc if options.email:
3018 26b4bea8 2011-08-02 rsc form_fields.append(("user", options.email))
3019 26b4bea8 2011-08-02 rsc ctype, body = EncodeMultipartFormData(form_fields, [("data", filename, content)])
3020 26b4bea8 2011-08-02 rsc response_body = rpc_server.Send(url, body, content_type=ctype)
3021 26b4bea8 2011-08-02 rsc if not response_body.startswith("OK"):
3022 26b4bea8 2011-08-02 rsc StatusUpdate(" --> %s" % response_body)
3023 26b4bea8 2011-08-02 rsc sys.exit(1)
3024 ef27bfa4 2010-01-12 rsc
3025 26b4bea8 2011-08-02 rsc # Don't want to spawn too many threads, nor do we want to
3026 26b4bea8 2011-08-02 rsc # hit Rietveld too hard, or it will start serving 500 errors.
3027 26b4bea8 2011-08-02 rsc # When 8 works, it's no better than 4, and sometimes 8 is
3028 26b4bea8 2011-08-02 rsc # too many for Rietveld to handle.
3029 26b4bea8 2011-08-02 rsc MAX_PARALLEL_UPLOADS = 4
3030 ef27bfa4 2010-01-12 rsc
3031 26b4bea8 2011-08-02 rsc sema = threading.BoundedSemaphore(MAX_PARALLEL_UPLOADS)
3032 26b4bea8 2011-08-02 rsc upload_threads = []
3033 26b4bea8 2011-08-02 rsc finished_upload_threads = []
3034 26b4bea8 2011-08-02 rsc
3035 26b4bea8 2011-08-02 rsc class UploadFileThread(threading.Thread):
3036 26b4bea8 2011-08-02 rsc def __init__(self, args):
3037 26b4bea8 2011-08-02 rsc threading.Thread.__init__(self)
3038 26b4bea8 2011-08-02 rsc self.args = args
3039 26b4bea8 2011-08-02 rsc def run(self):
3040 26b4bea8 2011-08-02 rsc UploadFile(*self.args)
3041 26b4bea8 2011-08-02 rsc finished_upload_threads.append(self)
3042 26b4bea8 2011-08-02 rsc sema.release()
3043 ef27bfa4 2010-01-12 rsc
3044 26b4bea8 2011-08-02 rsc def StartUploadFile(*args):
3045 26b4bea8 2011-08-02 rsc sema.acquire()
3046 26b4bea8 2011-08-02 rsc while len(finished_upload_threads) > 0:
3047 26b4bea8 2011-08-02 rsc t = finished_upload_threads.pop()
3048 26b4bea8 2011-08-02 rsc upload_threads.remove(t)
3049 26b4bea8 2011-08-02 rsc t.join()
3050 26b4bea8 2011-08-02 rsc t = UploadFileThread(args)
3051 26b4bea8 2011-08-02 rsc upload_threads.append(t)
3052 26b4bea8 2011-08-02 rsc t.start()
3053 ef27bfa4 2010-01-12 rsc
3054 26b4bea8 2011-08-02 rsc def WaitForUploads():
3055 26b4bea8 2011-08-02 rsc for t in upload_threads:
3056 26b4bea8 2011-08-02 rsc t.join()
3057 ef27bfa4 2010-01-12 rsc
3058 26b4bea8 2011-08-02 rsc patches = dict()
3059 26b4bea8 2011-08-02 rsc [patches.setdefault(v, k) for k, v in patch_list]
3060 26b4bea8 2011-08-02 rsc for filename in patches.keys():
3061 26b4bea8 2011-08-02 rsc base_content, new_content, is_binary, status = files[filename]
3062 26b4bea8 2011-08-02 rsc file_id_str = patches.get(filename)
3063 26b4bea8 2011-08-02 rsc if file_id_str.find("nobase") != -1:
3064 26b4bea8 2011-08-02 rsc base_content = None
3065 26b4bea8 2011-08-02 rsc file_id_str = file_id_str[file_id_str.rfind("_") + 1:]
3066 26b4bea8 2011-08-02 rsc file_id = int(file_id_str)
3067 26b4bea8 2011-08-02 rsc if base_content != None:
3068 26b4bea8 2011-08-02 rsc StartUploadFile(filename, file_id, base_content, is_binary, status, True)
3069 26b4bea8 2011-08-02 rsc if new_content != None:
3070 26b4bea8 2011-08-02 rsc StartUploadFile(filename, file_id, new_content, is_binary, status, False)
3071 26b4bea8 2011-08-02 rsc WaitForUploads()
3072 ef27bfa4 2010-01-12 rsc
3073 26b4bea8 2011-08-02 rsc def IsImage(self, filename):
3074 26b4bea8 2011-08-02 rsc """Returns true if the filename has an image extension."""
3075 26b4bea8 2011-08-02 rsc mimetype = mimetypes.guess_type(filename)[0]
3076 26b4bea8 2011-08-02 rsc if not mimetype:
3077 26b4bea8 2011-08-02 rsc return False
3078 26b4bea8 2011-08-02 rsc return mimetype.startswith("image/")
3079 ef27bfa4 2010-01-12 rsc
3080 26b4bea8 2011-08-02 rsc def IsBinary(self, filename):
3081 26b4bea8 2011-08-02 rsc """Returns true if the guessed mimetyped isnt't in text group."""
3082 26b4bea8 2011-08-02 rsc mimetype = mimetypes.guess_type(filename)[0]
3083 26b4bea8 2011-08-02 rsc if not mimetype:
3084 26b4bea8 2011-08-02 rsc return False # e.g. README, "real" binaries usually have an extension
3085 26b4bea8 2011-08-02 rsc # special case for text files which don't start with text/
3086 26b4bea8 2011-08-02 rsc if mimetype in TEXT_MIMETYPES:
3087 26b4bea8 2011-08-02 rsc return False
3088 26b4bea8 2011-08-02 rsc return not mimetype.startswith("text/")
3089 ef27bfa4 2010-01-12 rsc
3090 26b4bea8 2011-08-02 rsc class FakeMercurialUI(object):
3091 26b4bea8 2011-08-02 rsc def __init__(self):
3092 26b4bea8 2011-08-02 rsc self.quiet = True
3093 26b4bea8 2011-08-02 rsc self.output = ''
3094 26b4bea8 2011-08-02 rsc
3095 26b4bea8 2011-08-02 rsc def write(self, *args, **opts):
3096 26b4bea8 2011-08-02 rsc self.output += ' '.join(args)
3097 ef27bfa4 2010-01-12 rsc
3098 26b4bea8 2011-08-02 rsc use_hg_shell = False # set to True to shell out to hg always; slower
3099 ef27bfa4 2010-01-12 rsc
3100 26b4bea8 2011-08-02 rsc class MercurialVCS(VersionControlSystem):
3101 26b4bea8 2011-08-02 rsc """Implementation of the VersionControlSystem interface for Mercurial."""
3102 ef27bfa4 2010-01-12 rsc
3103 26b4bea8 2011-08-02 rsc def __init__(self, options, ui, repo):
3104 26b4bea8 2011-08-02 rsc super(MercurialVCS, self).__init__(options)
3105 26b4bea8 2011-08-02 rsc self.ui = ui
3106 26b4bea8 2011-08-02 rsc self.repo = repo
3107 26b4bea8 2011-08-02 rsc # Absolute path to repository (we can be in a subdir)
3108 26b4bea8 2011-08-02 rsc self.repo_dir = os.path.normpath(repo.root)
3109 26b4bea8 2011-08-02 rsc # Compute the subdir
3110 26b4bea8 2011-08-02 rsc cwd = os.path.normpath(os.getcwd())
3111 26b4bea8 2011-08-02 rsc assert cwd.startswith(self.repo_dir)
3112 26b4bea8 2011-08-02 rsc self.subdir = cwd[len(self.repo_dir):].lstrip(r"\/")
3113 26b4bea8 2011-08-02 rsc if self.options.revision:
3114 26b4bea8 2011-08-02 rsc self.base_rev = self.options.revision
3115 26b4bea8 2011-08-02 rsc else:
3116 26b4bea8 2011-08-02 rsc mqparent, err = RunShellWithReturnCode(['hg', 'log', '--rev', 'qparent', '--template={node}'])
3117 26b4bea8 2011-08-02 rsc if not err and mqparent != "":
3118 26b4bea8 2011-08-02 rsc self.base_rev = mqparent
3119 26b4bea8 2011-08-02 rsc else:
3120 26b4bea8 2011-08-02 rsc self.base_rev = RunShell(["hg", "parents", "-q"]).split(':')[1].strip()
3121 26b4bea8 2011-08-02 rsc def _GetRelPath(self, filename):
3122 26b4bea8 2011-08-02 rsc """Get relative path of a file according to the current directory,
3123 26b4bea8 2011-08-02 rsc given its logical path in the repo."""
3124 26b4bea8 2011-08-02 rsc assert filename.startswith(self.subdir), (filename, self.subdir)
3125 26b4bea8 2011-08-02 rsc return filename[len(self.subdir):].lstrip(r"\/")
3126 ef27bfa4 2010-01-12 rsc
3127 26b4bea8 2011-08-02 rsc def GenerateDiff(self, extra_args):
3128 26b4bea8 2011-08-02 rsc # If no file specified, restrict to the current subdir
3129 26b4bea8 2011-08-02 rsc extra_args = extra_args or ["."]
3130 26b4bea8 2011-08-02 rsc cmd = ["hg", "diff", "--git", "-r", self.base_rev] + extra_args
3131 26b4bea8 2011-08-02 rsc data = RunShell(cmd, silent_ok=True)
3132 26b4bea8 2011-08-02 rsc svndiff = []
3133 26b4bea8 2011-08-02 rsc filecount = 0
3134 26b4bea8 2011-08-02 rsc for line in data.splitlines():
3135 26b4bea8 2011-08-02 rsc m = re.match("diff --git a/(\S+) b/(\S+)", line)
3136 26b4bea8 2011-08-02 rsc if m:
3137 26b4bea8 2011-08-02 rsc # Modify line to make it look like as it comes from svn diff.
3138 26b4bea8 2011-08-02 rsc # With this modification no changes on the server side are required
3139 26b4bea8 2011-08-02 rsc # to make upload.py work with Mercurial repos.
3140 26b4bea8 2011-08-02 rsc # NOTE: for proper handling of moved/copied files, we have to use
3141 26b4bea8 2011-08-02 rsc # the second filename.
3142 26b4bea8 2011-08-02 rsc filename = m.group(2)
3143 26b4bea8 2011-08-02 rsc svndiff.append("Index: %s" % filename)
3144 26b4bea8 2011-08-02 rsc svndiff.append("=" * 67)
3145 26b4bea8 2011-08-02 rsc filecount += 1
3146 26b4bea8 2011-08-02 rsc logging.info(line)
3147 26b4bea8 2011-08-02 rsc else:
3148 26b4bea8 2011-08-02 rsc svndiff.append(line)
3149 26b4bea8 2011-08-02 rsc if not filecount:
3150 26b4bea8 2011-08-02 rsc ErrorExit("No valid patches found in output from hg diff")
3151 26b4bea8 2011-08-02 rsc return "\n".join(svndiff) + "\n"
3152 ef27bfa4 2010-01-12 rsc
3153 26b4bea8 2011-08-02 rsc def GetUnknownFiles(self):
3154 26b4bea8 2011-08-02 rsc """Return a list of files unknown to the VCS."""
3155 26b4bea8 2011-08-02 rsc args = []
3156 26b4bea8 2011-08-02 rsc status = RunShell(["hg", "status", "--rev", self.base_rev, "-u", "."],
3157 26b4bea8 2011-08-02 rsc silent_ok=True)
3158 26b4bea8 2011-08-02 rsc unknown_files = []
3159 26b4bea8 2011-08-02 rsc for line in status.splitlines():
3160 26b4bea8 2011-08-02 rsc st, fn = line.split(" ", 1)
3161 26b4bea8 2011-08-02 rsc if st == "?":
3162 26b4bea8 2011-08-02 rsc unknown_files.append(fn)
3163 26b4bea8 2011-08-02 rsc return unknown_files
3164 ef27bfa4 2010-01-12 rsc
3165 26b4bea8 2011-08-02 rsc def GetBaseFile(self, filename):
3166 26b4bea8 2011-08-02 rsc set_status("inspecting " + filename)
3167 26b4bea8 2011-08-02 rsc # "hg status" and "hg cat" both take a path relative to the current subdir
3168 26b4bea8 2011-08-02 rsc # rather than to the repo root, but "hg diff" has given us the full path
3169 26b4bea8 2011-08-02 rsc # to the repo root.
3170 26b4bea8 2011-08-02 rsc base_content = ""
3171 26b4bea8 2011-08-02 rsc new_content = None
3172 26b4bea8 2011-08-02 rsc is_binary = False
3173 26b4bea8 2011-08-02 rsc oldrelpath = relpath = self._GetRelPath(filename)
3174 26b4bea8 2011-08-02 rsc # "hg status -C" returns two lines for moved/copied files, one otherwise
3175 26b4bea8 2011-08-02 rsc if use_hg_shell:
3176 26b4bea8 2011-08-02 rsc out = RunShell(["hg", "status", "-C", "--rev", self.base_rev, relpath])
3177 26b4bea8 2011-08-02 rsc else:
3178 26b4bea8 2011-08-02 rsc fui = FakeMercurialUI()
3179 26b4bea8 2011-08-02 rsc ret = commands.status(fui, self.repo, *[relpath], **{'rev': [self.base_rev], 'copies': True})
3180 26b4bea8 2011-08-02 rsc if ret:
3181 26b4bea8 2011-08-02 rsc raise util.Abort(ret)
3182 26b4bea8 2011-08-02 rsc out = fui.output
3183 26b4bea8 2011-08-02 rsc out = out.splitlines()
3184 26b4bea8 2011-08-02 rsc # HACK: strip error message about missing file/directory if it isn't in
3185 26b4bea8 2011-08-02 rsc # the working copy
3186 26b4bea8 2011-08-02 rsc if out[0].startswith('%s: ' % relpath):
3187 26b4bea8 2011-08-02 rsc out = out[1:]
3188 26b4bea8 2011-08-02 rsc status, what = out[0].split(' ', 1)
3189 26b4bea8 2011-08-02 rsc if len(out) > 1 and status == "A" and what == relpath:
3190 26b4bea8 2011-08-02 rsc oldrelpath = out[1].strip()
3191 26b4bea8 2011-08-02 rsc status = "M"
3192 26b4bea8 2011-08-02 rsc if ":" in self.base_rev:
3193 26b4bea8 2011-08-02 rsc base_rev = self.base_rev.split(":", 1)[0]
3194 26b4bea8 2011-08-02 rsc else:
3195 26b4bea8 2011-08-02 rsc base_rev = self.base_rev
3196 26b4bea8 2011-08-02 rsc if status != "A":
3197 26b4bea8 2011-08-02 rsc if use_hg_shell:
3198 26b4bea8 2011-08-02 rsc base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath], silent_ok=True)
3199 26b4bea8 2011-08-02 rsc else:
3200 26b4bea8 2011-08-02 rsc base_content = str(self.repo[base_rev][oldrelpath].data())
3201 26b4bea8 2011-08-02 rsc is_binary = "\0" in base_content # Mercurial's heuristic
3202 26b4bea8 2011-08-02 rsc if status != "R":
3203 26b4bea8 2011-08-02 rsc new_content = open(relpath, "rb").read()
3204 26b4bea8 2011-08-02 rsc is_binary = is_binary or "\0" in new_content
3205 26b4bea8 2011-08-02 rsc if is_binary and base_content and use_hg_shell:
3206 26b4bea8 2011-08-02 rsc # Fetch again without converting newlines
3207 26b4bea8 2011-08-02 rsc base_content = RunShell(["hg", "cat", "-r", base_rev, oldrelpath],
3208 26b4bea8 2011-08-02 rsc silent_ok=True, universal_newlines=False)
3209 26b4bea8 2011-08-02 rsc if not is_binary or not self.IsImage(relpath):
3210 26b4bea8 2011-08-02 rsc new_content = None
3211 26b4bea8 2011-08-02 rsc return base_content, new_content, is_binary, status
3212 ef27bfa4 2010-01-12 rsc
3213 ef27bfa4 2010-01-12 rsc
3214 ef27bfa4 2010-01-12 rsc # NOTE: The SplitPatch function is duplicated in engine.py, keep them in sync.
3215 ef27bfa4 2010-01-12 rsc def SplitPatch(data):
3216 26b4bea8 2011-08-02 rsc """Splits a patch into separate pieces for each file.
3217 ef27bfa4 2010-01-12 rsc
3218 26b4bea8 2011-08-02 rsc Args:
3219 26b4bea8 2011-08-02 rsc data: A string containing the output of svn diff.
3220 26b4bea8 2011-08-02 rsc
3221 26b4bea8 2011-08-02 rsc Returns:
3222 26b4bea8 2011-08-02 rsc A list of 2-tuple (filename, text) where text is the svn diff output
3223 26b4bea8 2011-08-02 rsc pertaining to filename.
3224 26b4bea8 2011-08-02 rsc """
3225 26b4bea8 2011-08-02 rsc patches = []
3226 26b4bea8 2011-08-02 rsc filename = None
3227 26b4bea8 2011-08-02 rsc diff = []
3228 26b4bea8 2011-08-02 rsc for line in data.splitlines(True):
3229 26b4bea8 2011-08-02 rsc new_filename = None
3230 26b4bea8 2011-08-02 rsc if line.startswith('Index:'):
3231 26b4bea8 2011-08-02 rsc unused, new_filename = line.split(':', 1)
3232 26b4bea8 2011-08-02 rsc new_filename = new_filename.strip()
3233 26b4bea8 2011-08-02 rsc elif line.startswith('Property changes on:'):
3234 26b4bea8 2011-08-02 rsc unused, temp_filename = line.split(':', 1)
3235 26b4bea8 2011-08-02 rsc # When a file is modified, paths use '/' between directories, however
3236 26b4bea8 2011-08-02 rsc # when a property is modified '\' is used on Windows. Make them the same
3237 26b4bea8 2011-08-02 rsc # otherwise the file shows up twice.
3238 26b4bea8 2011-08-02 rsc temp_filename = temp_filename.strip().replace('\\', '/')
3239 26b4bea8 2011-08-02 rsc if temp_filename != filename:
3240 26b4bea8 2011-08-02 rsc # File has property changes but no modifications, create a new diff.
3241 26b4bea8 2011-08-02 rsc new_filename = temp_filename
3242 26b4bea8 2011-08-02 rsc if new_filename:
3243 26b4bea8 2011-08-02 rsc if filename and diff:
3244 26b4bea8 2011-08-02 rsc patches.append((filename, ''.join(diff)))
3245 26b4bea8 2011-08-02 rsc filename = new_filename
3246 26b4bea8 2011-08-02 rsc diff = [line]
3247 26b4bea8 2011-08-02 rsc continue
3248 26b4bea8 2011-08-02 rsc if diff is not None:
3249 26b4bea8 2011-08-02 rsc diff.append(line)
3250 26b4bea8 2011-08-02 rsc if filename and diff:
3251 26b4bea8 2011-08-02 rsc patches.append((filename, ''.join(diff)))
3252 26b4bea8 2011-08-02 rsc return patches
3253 ef27bfa4 2010-01-12 rsc
3254 ef27bfa4 2010-01-12 rsc
3255 ef27bfa4 2010-01-12 rsc def UploadSeparatePatches(issue, rpc_server, patchset, data, options):
3256 26b4bea8 2011-08-02 rsc """Uploads a separate patch for each file in the diff output.
3257 ef27bfa4 2010-01-12 rsc
3258 26b4bea8 2011-08-02 rsc Returns a list of [patch_key, filename] for each file.
3259 26b4bea8 2011-08-02 rsc """
3260 26b4bea8 2011-08-02 rsc patches = SplitPatch(data)
3261 26b4bea8 2011-08-02 rsc rv = []
3262 26b4bea8 2011-08-02 rsc for patch in patches:
3263 26b4bea8 2011-08-02 rsc set_status("uploading patch for " + patch[0])
3264 26b4bea8 2011-08-02 rsc if len(patch[1]) > MAX_UPLOAD_SIZE:
3265 26b4bea8 2011-08-02 rsc print ("Not uploading the patch for " + patch[0] +
3266 26b4bea8 2011-08-02 rsc " because the file is too large.")
3267 26b4bea8 2011-08-02 rsc continue
3268 26b4bea8 2011-08-02 rsc form_fields = [("filename", patch[0])]
3269 26b4bea8 2011-08-02 rsc if not options.download_base:
3270 26b4bea8 2011-08-02 rsc form_fields.append(("content_upload", "1"))
3271 26b4bea8 2011-08-02 rsc files = [("data", "data.diff", patch[1])]
3272 26b4bea8 2011-08-02 rsc ctype, body = EncodeMultipartFormData(form_fields, files)
3273 26b4bea8 2011-08-02 rsc url = "/%d/upload_patch/%d" % (int(issue), int(patchset))
3274 26b4bea8 2011-08-02 rsc print "Uploading patch for " + patch[0]
3275 26b4bea8 2011-08-02 rsc response_body = rpc_server.Send(url, body, content_type=ctype)
3276 26b4bea8 2011-08-02 rsc lines = response_body.splitlines()
3277 26b4bea8 2011-08-02 rsc if not lines or lines[0] != "OK":
3278 26b4bea8 2011-08-02 rsc StatusUpdate(" --> %s" % response_body)
3279 26b4bea8 2011-08-02 rsc sys.exit(1)
3280 26b4bea8 2011-08-02 rsc rv.append([lines[1], patch[0]])
3281 26b4bea8 2011-08-02 rsc return rv