Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(96)

Side by Side Diff: scm.py

Issue 389020: Split scm-specific functions out of gclient_scm.py to scm.py. (Closed)
Patch Set: 80 cols Created 11 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « gclient_scm.py ('k') | tests/gclient_scm_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # 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
5 """SCM-specific functions."""
6
7 import os
8 import re
9 import subprocess
10 import sys
11 import xml.dom.minidom
12
13 import gclient_utils
14
15
16 SVN_COMMAND = "svn"
17 GIT_COMMAND = "git"
18
19 # -----------------------------------------------------------------------------
20 # Git utils:
21
22
23 def CaptureGit(args, in_directory=None, print_error=True):
24 """Runs git, capturing output sent to stdout as a string.
25
26 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.
29
30 Returns:
31 The output sent to stdout as a string.
32 """
33 c = [GIT_COMMAND]
34 c.extend(args)
35
36 # *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]
48
49
50 def CaptureGitStatus(files, upstream_branch='origin'):
51 """Returns git status.
52
53 @files can be a string (one file) or a list of files.
54
55 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)
63
64 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
73
74
75 # -----------------------------------------------------------------------------
76 # SVN utils:
77
78
79 def RunSVN(args, in_directory):
80 """Runs svn, sending output to stdout.
81
82 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.
85
86 Raises:
87 Error: An error occurred while running the svn command.
88 """
89 c = [SVN_COMMAND]
90 c.extend(args)
91
92 gclient_utils.SubprocessCall(c, in_directory)
93
94
95 def CaptureSVN(args, in_directory=None, print_error=True):
96 """Runs svn, capturing output sent to stdout as a string.
97
98 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.
101
102 Returns:
103 The output sent to stdout as a string.
104 """
105 c = [SVN_COMMAND]
106 c.extend(args)
107
108 # *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]
120
121
122 def RunSVNAndGetFileList(options, args, in_directory, file_list):
123 """Runs svn checkout, update, or status, output to stdout.
124
125 The first item in args must be either "checkout", "update", or "status".
126
127 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.
130
131 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.
135
136 Raises:
137 Error: An error occurred while running the svn command.
138 """
139 command = [SVN_COMMAND]
140 command.extend(args)
141
142 # 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 = '^... (.*)$'
146
147 # 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 = '^...... (.*)$'
152
153 # 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]]
162
163 compiled_pattern = re.compile(pattern)
164
165 def CaptureMatchingLines(line):
166 match = compiled_pattern.search(line)
167 if match:
168 file_list.append(match.group(1))
169
170 RunSVNAndFilterOutput(args,
171 in_directory,
172 options.verbose,
173 True,
174 CaptureMatchingLines)
175
176 def 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.
183
184 The first item in args must be either "checkout", "update",
185 "status", or "diff".
186
187 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.
189
190 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.
199
200 Raises:
201 Error: An error occurred while running the svn command.
202 """
203 command = [SVN_COMMAND]
204 command.extend(args)
205
206 gclient_utils.SubprocessCallAndFilter(command,
207 in_directory,
208 print_messages,
209 print_stdout,
210 filter=filter)
211
212 def CaptureSVNInfo(relpath, in_directory=None, print_error=True):
213 """Returns a dictionary from the svn info output for the given file.
214
215 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
249
250
251 def CaptureSVNHeadRevision(url):
252 """Get the head revision of a SVN repository.
253
254 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')
260
261
262 def 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
OLDNEW
« no previous file with comments | « gclient_scm.py ('k') | tests/gclient_scm_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698