Blame


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