blob: 12848152abcde2efa2de76c51085450f98d30ebc [file] [log] [blame]
[email protected]d5800f12009-11-12 20:03:431# Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
[email protected]261eeb52009-11-16 18:25:455"""SCM-specific functions."""
[email protected]d5800f12009-11-12 20:03:436
7import os
8import re
9import subprocess
10import sys
11import xml.dom.minidom
12
13import gclient_utils
14
15
[email protected]261eeb52009-11-16 18:25:4516SVN_COMMAND = "svn"
17GIT_COMMAND = "git"
[email protected]d5800f12009-11-12 20:03:4318
[email protected]261eeb52009-11-16 18:25:4519# -----------------------------------------------------------------------------
20# Git utils:
[email protected]d5800f12009-11-12 20:03:4321
22
[email protected]261eeb52009-11-16 18:25:4523def CaptureGit(args, in_directory=None, print_error=True):
24 """Runs git, capturing output sent to stdout as a string.
[email protected]d5800f12009-11-12 20:03:4325
[email protected]261eeb52009-11-16 18:25:4526 Args:
27 args: A sequence of command line parameters to be passed to git.
28 in_directory: The directory where git is to be run.
[email protected]d5800f12009-11-12 20:03:4329
[email protected]261eeb52009-11-16 18:25:4530 Returns:
31 The output sent to stdout as a string.
32 """
33 c = [GIT_COMMAND]
34 c.extend(args)
[email protected]d5800f12009-11-12 20:03:4335
[email protected]261eeb52009-11-16 18:25:4536 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for
37 # the git.exe executable, but shell=True makes subprocess on Linux fail
38 # when it's called with a list because it only tries to execute the
39 # first string ("git").
40 stderr = None
41 if not print_error:
42 stderr = subprocess.PIPE
43 return subprocess.Popen(c,
44 cwd=in_directory,
45 shell=sys.platform.startswith('win'),
46 stdout=subprocess.PIPE,
47 stderr=stderr).communicate()[0]
[email protected]d5800f12009-11-12 20:03:4348
49
[email protected]261eeb52009-11-16 18:25:4550def CaptureGitStatus(files, upstream_branch='origin'):
51 """Returns git status.
[email protected]d5800f12009-11-12 20:03:4352
[email protected]261eeb52009-11-16 18:25:4553 @files can be a string (one file) or a list of files.
[email protected]d5800f12009-11-12 20:03:4354
[email protected]261eeb52009-11-16 18:25:4555 Returns an array of (status, file) tuples."""
56 command = ["diff", "--name-status", "-r", "%s.." % upstream_branch]
57 if not files:
58 pass
59 elif isinstance(files, basestring):
60 command.append(files)
61 else:
62 command.extend(files)
[email protected]d5800f12009-11-12 20:03:4363
[email protected]261eeb52009-11-16 18:25:4564 status = CaptureGit(command).rstrip()
65 results = []
66 if status:
67 for statusline in status.split('\n'):
68 m = re.match('^(\w)\t(.+)$', statusline)
69 if not m:
70 raise Exception("status currently unsupported: %s" % statusline)
71 results.append(('%s ' % m.group(1), m.group(2)))
72 return results
[email protected]d5800f12009-11-12 20:03:4373
74
[email protected]261eeb52009-11-16 18:25:4575# -----------------------------------------------------------------------------
76# SVN utils:
[email protected]d5800f12009-11-12 20:03:4377
78
[email protected]261eeb52009-11-16 18:25:4579def RunSVN(args, in_directory):
80 """Runs svn, sending output to stdout.
[email protected]d5800f12009-11-12 20:03:4381
[email protected]261eeb52009-11-16 18:25:4582 Args:
83 args: A sequence of command line parameters to be passed to svn.
84 in_directory: The directory where svn is to be run.
[email protected]d5800f12009-11-12 20:03:4385
[email protected]261eeb52009-11-16 18:25:4586 Raises:
87 Error: An error occurred while running the svn command.
88 """
89 c = [SVN_COMMAND]
90 c.extend(args)
[email protected]d5800f12009-11-12 20:03:4391
[email protected]261eeb52009-11-16 18:25:4592 gclient_utils.SubprocessCall(c, in_directory)
[email protected]d5800f12009-11-12 20:03:4393
94
[email protected]261eeb52009-11-16 18:25:4595def CaptureSVN(args, in_directory=None, print_error=True):
96 """Runs svn, capturing output sent to stdout as a string.
[email protected]d5800f12009-11-12 20:03:4397
[email protected]261eeb52009-11-16 18:25:4598 Args:
99 args: A sequence of command line parameters to be passed to svn.
100 in_directory: The directory where svn is to be run.
[email protected]d5800f12009-11-12 20:03:43101
[email protected]261eeb52009-11-16 18:25:45102 Returns:
103 The output sent to stdout as a string.
104 """
105 c = [SVN_COMMAND]
106 c.extend(args)
[email protected]d5800f12009-11-12 20:03:43107
[email protected]261eeb52009-11-16 18:25:45108 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for
109 # the svn.exe executable, but shell=True makes subprocess on Linux fail
110 # when it's called with a list because it only tries to execute the
111 # first string ("svn").
112 stderr = None
113 if not print_error:
114 stderr = subprocess.PIPE
115 return subprocess.Popen(c,
116 cwd=in_directory,
117 shell=(sys.platform == 'win32'),
118 stdout=subprocess.PIPE,
119 stderr=stderr).communicate()[0]
[email protected]d5800f12009-11-12 20:03:43120
121
[email protected]261eeb52009-11-16 18:25:45122def RunSVNAndGetFileList(options, args, in_directory, file_list):
123 """Runs svn checkout, update, or status, output to stdout.
[email protected]d5800f12009-11-12 20:03:43124
[email protected]261eeb52009-11-16 18:25:45125 The first item in args must be either "checkout", "update", or "status".
[email protected]d5800f12009-11-12 20:03:43126
[email protected]261eeb52009-11-16 18:25:45127 svn's stdout is parsed to collect a list of files checked out or updated.
128 These files are appended to file_list. svn's stdout is also printed to
129 sys.stdout as in RunSVN.
[email protected]d5800f12009-11-12 20:03:43130
[email protected]261eeb52009-11-16 18:25:45131 Args:
132 options: command line options to gclient
133 args: A sequence of command line parameters to be passed to svn.
134 in_directory: The directory where svn is to be run.
[email protected]d5800f12009-11-12 20:03:43135
[email protected]261eeb52009-11-16 18:25:45136 Raises:
137 Error: An error occurred while running the svn command.
138 """
139 command = [SVN_COMMAND]
140 command.extend(args)
[email protected]d5800f12009-11-12 20:03:43141
[email protected]261eeb52009-11-16 18:25:45142 # svn update and svn checkout use the same pattern: the first three columns
143 # are for file status, property status, and lock status. This is followed
144 # by two spaces, and then the path to the file.
145 update_pattern = '^... (.*)$'
[email protected]d5800f12009-11-12 20:03:43146
[email protected]261eeb52009-11-16 18:25:45147 # The first three columns of svn status are the same as for svn update and
148 # svn checkout. The next three columns indicate addition-with-history,
149 # switch, and remote lock status. This is followed by one space, and then
150 # the path to the file.
151 status_pattern = '^...... (.*)$'
[email protected]d5800f12009-11-12 20:03:43152
[email protected]261eeb52009-11-16 18:25:45153 # args[0] must be a supported command. This will blow up if it's something
154 # else, which is good. Note that the patterns are only effective when
155 # these commands are used in their ordinary forms, the patterns are invalid
156 # for "svn status --show-updates", for example.
157 pattern = {
158 'checkout': update_pattern,
159 'status': status_pattern,
160 'update': update_pattern,
161 }[args[0]]
[email protected]d5800f12009-11-12 20:03:43162
[email protected]261eeb52009-11-16 18:25:45163 compiled_pattern = re.compile(pattern)
[email protected]d5800f12009-11-12 20:03:43164
[email protected]261eeb52009-11-16 18:25:45165 def CaptureMatchingLines(line):
166 match = compiled_pattern.search(line)
167 if match:
168 file_list.append(match.group(1))
[email protected]d5800f12009-11-12 20:03:43169
[email protected]261eeb52009-11-16 18:25:45170 RunSVNAndFilterOutput(args,
171 in_directory,
172 options.verbose,
173 True,
174 CaptureMatchingLines)
[email protected]d5800f12009-11-12 20:03:43175
[email protected]261eeb52009-11-16 18:25:45176def RunSVNAndFilterOutput(args,
177 in_directory,
178 print_messages,
179 print_stdout,
180 filter):
181 """Runs svn checkout, update, status, or diff, optionally outputting
182 to stdout.
[email protected]d5800f12009-11-12 20:03:43183
[email protected]261eeb52009-11-16 18:25:45184 The first item in args must be either "checkout", "update",
185 "status", or "diff".
[email protected]d5800f12009-11-12 20:03:43186
[email protected]261eeb52009-11-16 18:25:45187 svn's stdout is passed line-by-line to the given filter function. If
188 print_stdout is true, it is also printed to sys.stdout as in RunSVN.
[email protected]d5800f12009-11-12 20:03:43189
[email protected]261eeb52009-11-16 18:25:45190 Args:
191 args: A sequence of command line parameters to be passed to svn.
192 in_directory: The directory where svn is to be run.
193 print_messages: Whether to print status messages to stdout about
194 which Subversion commands are being run.
195 print_stdout: Whether to forward Subversion's output to stdout.
196 filter: A function taking one argument (a string) which will be
197 passed each line (with the ending newline character removed) of
198 Subversion's output for filtering.
[email protected]d5800f12009-11-12 20:03:43199
[email protected]261eeb52009-11-16 18:25:45200 Raises:
201 Error: An error occurred while running the svn command.
202 """
203 command = [SVN_COMMAND]
204 command.extend(args)
[email protected]d5800f12009-11-12 20:03:43205
[email protected]261eeb52009-11-16 18:25:45206 gclient_utils.SubprocessCallAndFilter(command,
207 in_directory,
208 print_messages,
209 print_stdout,
210 filter=filter)
[email protected]d5800f12009-11-12 20:03:43211
[email protected]261eeb52009-11-16 18:25:45212def CaptureSVNInfo(relpath, in_directory=None, print_error=True):
213 """Returns a dictionary from the svn info output for the given file.
[email protected]d5800f12009-11-12 20:03:43214
[email protected]261eeb52009-11-16 18:25:45215 Args:
216 relpath: The directory where the working copy resides relative to
217 the directory given by in_directory.
218 in_directory: The directory where svn is to be run.
219 """
220 output = CaptureSVN(["info", "--xml", relpath], in_directory, print_error)
221 dom = gclient_utils.ParseXML(output)
222 result = {}
223 if dom:
224 GetNamedNodeText = gclient_utils.GetNamedNodeText
225 GetNodeNamedAttributeText = gclient_utils.GetNodeNamedAttributeText
226 def C(item, f):
227 if item is not None: return f(item)
228 # /info/entry/
229 # url
230 # reposityory/(root|uuid)
231 # wc-info/(schedule|depth)
232 # commit/(author|date)
233 # str() the results because they may be returned as Unicode, which
234 # interferes with the higher layers matching up things in the deps
235 # dictionary.
236 # TODO(maruel): Fix at higher level instead (!)
237 result['Repository Root'] = C(GetNamedNodeText(dom, 'root'), str)
238 result['URL'] = C(GetNamedNodeText(dom, 'url'), str)
239 result['UUID'] = C(GetNamedNodeText(dom, 'uuid'), str)
240 result['Revision'] = C(GetNodeNamedAttributeText(dom, 'entry', 'revision'),
241 int)
242 result['Node Kind'] = C(GetNodeNamedAttributeText(dom, 'entry', 'kind'),
243 str)
244 result['Schedule'] = C(GetNamedNodeText(dom, 'schedule'), str)
245 result['Path'] = C(GetNodeNamedAttributeText(dom, 'entry', 'path'), str)
246 result['Copied From URL'] = C(GetNamedNodeText(dom, 'copy-from-url'), str)
247 result['Copied From Rev'] = C(GetNamedNodeText(dom, 'copy-from-rev'), str)
248 return result
[email protected]d5800f12009-11-12 20:03:43249
250
[email protected]261eeb52009-11-16 18:25:45251def CaptureSVNHeadRevision(url):
252 """Get the head revision of a SVN repository.
[email protected]d5800f12009-11-12 20:03:43253
[email protected]261eeb52009-11-16 18:25:45254 Returns:
255 Int head revision
256 """
257 info = CaptureSVN(["info", "--xml", url], os.getcwd())
258 dom = xml.dom.minidom.parseString(info)
259 return dom.getElementsByTagName('entry')[0].getAttribute('revision')
[email protected]d5800f12009-11-12 20:03:43260
[email protected]261eeb52009-11-16 18:25:45261
262def CaptureSVNStatus(files):
263 """Returns the svn 1.5 svn status emulated output.
264
265 @files can be a string (one file) or a list of files.
266
267 Returns an array of (status, file) tuples."""
268 command = ["status", "--xml"]
269 if not files:
270 pass
271 elif isinstance(files, basestring):
272 command.append(files)
273 else:
274 command.extend(files)
275
276 status_letter = {
277 None: ' ',
278 '': ' ',
279 'added': 'A',
280 'conflicted': 'C',
281 'deleted': 'D',
282 'external': 'X',
283 'ignored': 'I',
284 'incomplete': '!',
285 'merged': 'G',
286 'missing': '!',
287 'modified': 'M',
288 'none': ' ',
289 'normal': ' ',
290 'obstructed': '~',
291 'replaced': 'R',
292 'unversioned': '?',
293 }
294 dom = gclient_utils.ParseXML(CaptureSVN(command))
295 results = []
296 if dom:
297 # /status/target/entry/(wc-status|commit|author|date)
298 for target in dom.getElementsByTagName('target'):
299 for entry in target.getElementsByTagName('entry'):
300 file_path = entry.getAttribute('path')
301 wc_status = entry.getElementsByTagName('wc-status')
302 assert len(wc_status) == 1
303 # Emulate svn 1.5 status ouput...
304 statuses = [' '] * 7
305 # Col 0
306 xml_item_status = wc_status[0].getAttribute('item')
307 if xml_item_status in status_letter:
308 statuses[0] = status_letter[xml_item_status]
309 else:
310 raise Exception('Unknown item status "%s"; please implement me!' %
311 xml_item_status)
312 # Col 1
313 xml_props_status = wc_status[0].getAttribute('props')
314 if xml_props_status == 'modified':
315 statuses[1] = 'M'
316 elif xml_props_status == 'conflicted':
317 statuses[1] = 'C'
318 elif (not xml_props_status or xml_props_status == 'none' or
319 xml_props_status == 'normal'):
320 pass
321 else:
322 raise Exception('Unknown props status "%s"; please implement me!' %
323 xml_props_status)
324 # Col 2
325 if wc_status[0].getAttribute('wc-locked') == 'true':
326 statuses[2] = 'L'
327 # Col 3
328 if wc_status[0].getAttribute('copied') == 'true':
329 statuses[3] = '+'
330 # Col 4
331 if wc_status[0].getAttribute('switched') == 'true':
332 statuses[4] = 'S'
333 # TODO(maruel): Col 5 and 6
334 item = (''.join(statuses), file_path)
335 results.append(item)
336 return results