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