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