Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. |
|
Dirk Pranke
2009/11/13 22:42:54
Is there real value in keeping these in one file?
M-A Ruel
2009/11/14 01:15:00
Yes that's probably what I'm going to do but not n
| |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """SCM-specific functions.""" | 5 """SCM-specific utility classes.""" |
| 6 | 6 |
| 7 import os | 7 import os |
| 8 import re | 8 import re |
| 9 import subprocess | 9 import subprocess |
| 10 import sys | 10 import sys |
| 11 import tempfile | |
| 11 import xml.dom.minidom | 12 import xml.dom.minidom |
| 12 | 13 |
| 13 import gclient_utils | 14 import gclient_utils |
| 14 | 15 |
| 15 | 16 |
| 16 SVN_COMMAND = "svn" | 17 class GIT(object): |
| 17 GIT_COMMAND = "git" | 18 COMMAND = "git" |
| 18 | 19 |
| 19 # ----------------------------------------------------------------------------- | 20 @staticmethod |
| 20 # Git utils: | 21 def Capture(args, in_directory=None, print_error=True): |
| 21 | 22 """Runs git, capturing output sent to stdout as a string. |
| 22 | 23 |
| 23 def CaptureGit(args, in_directory=None, print_error=True): | 24 Args: |
| 24 """Runs git, capturing output sent to stdout as a string. | 25 args: A sequence of command line parameters to be passed to git. |
| 25 | 26 in_directory: The directory where git is to be run. |
| 26 Args: | 27 |
| 27 args: A sequence of command line parameters to be passed to git. | 28 Returns: |
| 28 in_directory: The directory where git is to be run. | 29 The output sent to stdout as a string. |
| 29 | 30 """ |
| 30 Returns: | 31 c = [GIT.COMMAND] |
| 31 The output sent to stdout as a string. | 32 c.extend(args) |
| 32 """ | 33 |
| 33 c = [GIT_COMMAND] | 34 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for |
| 34 c.extend(args) | 35 # the git.exe executable, but shell=True makes subprocess on Linux fail |
| 35 | 36 # when it's called with a list because it only tries to execute the |
| 36 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for | 37 # first string ("git"). |
| 37 # the git.exe executable, but shell=True makes subprocess on Linux fail | 38 stderr = None |
| 38 # when it's called with a list because it only tries to execute the | 39 if not print_error: |
| 39 # first string ("git"). | 40 stderr = subprocess.PIPE |
| 40 stderr = None | 41 return subprocess.Popen(c, |
| 41 if not print_error: | 42 cwd=in_directory, |
| 42 stderr = subprocess.PIPE | 43 shell=sys.platform.startswith('win'), |
| 43 return subprocess.Popen(c, | 44 stdout=subprocess.PIPE, |
| 44 cwd=in_directory, | 45 stderr=stderr).communicate()[0] |
| 45 shell=sys.platform.startswith('win'), | 46 |
| 46 stdout=subprocess.PIPE, | 47 |
| 47 stderr=stderr).communicate()[0] | 48 @staticmethod |
| 48 | 49 def CaptureStatus(files, upstream_branch='origin'): |
| 49 | 50 """Returns git status. |
| 50 def CaptureGitStatus(files, upstream_branch='origin'): | 51 |
| 51 """Returns git status. | 52 @files can be a string (one file) or a list of files. |
| 52 | 53 |
| 53 @files can be a string (one file) or a list of files. | 54 Returns an array of (status, file) tuples.""" |
| 54 | 55 command = ["diff", "--name-status", "-r", "%s.." % upstream_branch] |
| 55 Returns an array of (status, file) tuples.""" | 56 if not files: |
| 56 command = ["diff", "--name-status", "-r", "%s.." % upstream_branch] | 57 pass |
| 57 if not files: | 58 elif isinstance(files, basestring): |
| 58 pass | 59 command.append(files) |
| 59 elif isinstance(files, basestring): | 60 else: |
| 60 command.append(files) | 61 command.extend(files) |
| 61 else: | 62 |
| 62 command.extend(files) | 63 status = GIT.Capture(command).rstrip() |
| 63 | 64 results = [] |
| 64 status = CaptureGit(command).rstrip() | 65 if status: |
| 65 results = [] | 66 for statusline in status.split('\n'): |
| 66 if status: | 67 m = re.match('^(\w)\t(.+)$', statusline) |
| 67 for statusline in status.split('\n'): | 68 if not m: |
| 68 m = re.match('^(\w)\t(.+)$', statusline) | 69 raise Exception("status currently unsupported: %s" % statusline) |
| 69 if not m: | 70 results.append(('%s ' % m.group(1), m.group(2))) |
| 70 raise Exception("status currently unsupported: %s" % statusline) | 71 return results |
| 71 results.append(('%s ' % m.group(1), m.group(2))) | 72 |
| 72 return results | 73 |
| 73 | 74 class SVN(object): |
| 74 | 75 COMMAND = "svn" |
| 75 # ----------------------------------------------------------------------------- | 76 |
| 76 # SVN utils: | 77 @staticmethod |
| 77 | 78 def Run(args, in_directory): |
| 78 | 79 """Runs svn, sending output to stdout. |
| 79 def RunSVN(args, in_directory): | 80 |
| 80 """Runs svn, sending output to stdout. | 81 Args: |
| 81 | 82 args: A sequence of command line parameters to be passed to svn. |
| 82 Args: | 83 in_directory: The directory where svn is to be run. |
| 83 args: A sequence of command line parameters to be passed to svn. | 84 |
| 84 in_directory: The directory where svn is to be run. | 85 Raises: |
| 85 | 86 Error: An error occurred while running the svn command. |
| 86 Raises: | 87 """ |
| 87 Error: An error occurred while running the svn command. | 88 c = [SVN.COMMAND] |
| 88 """ | 89 c.extend(args) |
| 89 c = [SVN_COMMAND] | 90 |
| 90 c.extend(args) | 91 gclient_utils.SubprocessCall(c, in_directory) |
| 91 | 92 |
| 92 gclient_utils.SubprocessCall(c, in_directory) | 93 @staticmethod |
| 93 | 94 def Capture(args, in_directory=None, print_error=True): |
| 94 | 95 """Runs svn, capturing output sent to stdout as a string. |
| 95 def CaptureSVN(args, in_directory=None, print_error=True): | 96 |
| 96 """Runs svn, capturing output sent to stdout as a string. | 97 Args: |
| 97 | 98 args: A sequence of command line parameters to be passed to svn. |
| 98 Args: | 99 in_directory: The directory where svn is to be run. |
| 99 args: A sequence of command line parameters to be passed to svn. | 100 |
| 100 in_directory: The directory where svn is to be run. | 101 Returns: |
| 101 | 102 The output sent to stdout as a string. |
| 102 Returns: | 103 """ |
| 103 The output sent to stdout as a string. | 104 c = [SVN.COMMAND] |
| 104 """ | 105 c.extend(args) |
| 105 c = [SVN_COMMAND] | 106 |
| 106 c.extend(args) | 107 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for |
| 107 | 108 # the svn.exe executable, but shell=True makes subprocess on Linux fail |
| 108 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for | 109 # when it's called with a list because it only tries to execute the |
| 109 # the svn.exe executable, but shell=True makes subprocess on Linux fail | 110 # first string ("svn"). |
| 110 # when it's called with a list because it only tries to execute the | 111 stderr = None |
| 111 # first string ("svn"). | 112 if not print_error: |
| 112 stderr = None | 113 stderr = subprocess.PIPE |
| 113 if not print_error: | 114 return subprocess.Popen(c, |
| 114 stderr = subprocess.PIPE | 115 cwd=in_directory, |
| 115 return subprocess.Popen(c, | 116 shell=(sys.platform == 'win32'), |
| 116 cwd=in_directory, | 117 stdout=subprocess.PIPE, |
| 117 shell=(sys.platform == 'win32'), | 118 stderr=stderr).communicate()[0] |
| 118 stdout=subprocess.PIPE, | 119 |
| 119 stderr=stderr).communicate()[0] | 120 @staticmethod |
| 120 | 121 def RunAndGetFileList(options, args, in_directory, file_list): |
| 121 | 122 """Runs svn checkout, update, or status, output to stdout. |
| 122 def RunSVNAndGetFileList(options, args, in_directory, file_list): | 123 |
| 123 """Runs svn checkout, update, or status, output to stdout. | 124 The first item in args must be either "checkout", "update", or "status". |
| 124 | 125 |
| 125 The first item in args must be either "checkout", "update", or "status". | 126 svn's stdout is parsed to collect a list of files checked out or updated. |
| 126 | 127 These files are appended to file_list. svn's stdout is also printed to |
| 127 svn's stdout is parsed to collect a list of files checked out or updated. | 128 sys.stdout as in Run. |
| 128 These files are appended to file_list. svn's stdout is also printed to | 129 |
| 129 sys.stdout as in RunSVN. | 130 Args: |
| 130 | 131 options: command line options to gclient |
| 131 Args: | 132 args: A sequence of command line parameters to be passed to svn. |
| 132 options: command line options to gclient | 133 in_directory: The directory where svn is to be run. |
| 133 args: A sequence of command line parameters to be passed to svn. | 134 |
| 134 in_directory: The directory where svn is to be run. | 135 Raises: |
| 135 | 136 Error: An error occurred while running the svn command. |
| 136 Raises: | 137 """ |
| 137 Error: An error occurred while running the svn command. | 138 command = [SVN.COMMAND] |
| 138 """ | 139 command.extend(args) |
| 139 command = [SVN_COMMAND] | 140 |
| 140 command.extend(args) | 141 # svn update and svn checkout use the same pattern: the first three columns |
| 141 | 142 # are for file status, property status, and lock status. This is followed |
| 142 # svn update and svn checkout use the same pattern: the first three columns | 143 # by two spaces, and then the path to the file. |
| 143 # are for file status, property status, and lock status. This is followed | 144 update_pattern = '^... (.*)$' |
| 144 # by two spaces, and then the path to the file. | 145 |
| 145 update_pattern = '^... (.*)$' | 146 # The first three columns of svn status are the same as for svn update and |
| 146 | 147 # svn checkout. The next three columns indicate addition-with-history, |
| 147 # The first three columns of svn status are the same as for svn update and | 148 # switch, and remote lock status. This is followed by one space, and then |
| 148 # svn checkout. The next three columns indicate addition-with-history, | 149 # the path to the file. |
| 149 # switch, and remote lock status. This is followed by one space, and then | 150 status_pattern = '^...... (.*)$' |
| 150 # the path to the file. | 151 |
| 151 status_pattern = '^...... (.*)$' | 152 # args[0] must be a supported command. This will blow up if it's something |
| 152 | 153 # else, which is good. Note that the patterns are only effective when |
| 153 # args[0] must be a supported command. This will blow up if it's something | 154 # these commands are used in their ordinary forms, the patterns are invalid |
| 154 # else, which is good. Note that the patterns are only effective when | 155 # for "svn status --show-updates", for example. |
| 155 # these commands are used in their ordinary forms, the patterns are invalid | 156 pattern = { |
| 156 # for "svn status --show-updates", for example. | 157 'checkout': update_pattern, |
| 157 pattern = { | 158 'status': status_pattern, |
| 158 'checkout': update_pattern, | 159 'update': update_pattern, |
| 159 'status': status_pattern, | 160 }[args[0]] |
| 160 'update': update_pattern, | 161 |
| 161 }[args[0]] | 162 compiled_pattern = re.compile(pattern) |
| 162 | 163 |
| 163 compiled_pattern = re.compile(pattern) | 164 def CaptureMatchingLines(line): |
| 164 | 165 match = compiled_pattern.search(line) |
| 165 def CaptureMatchingLines(line): | 166 if match: |
| 166 match = compiled_pattern.search(line) | 167 file_list.append(match.group(1)) |
| 167 if match: | 168 |
| 168 file_list.append(match.group(1)) | 169 SVN.RunAndFilterOutput(args, |
| 169 | 170 in_directory, |
| 170 RunSVNAndFilterOutput(args, | 171 options.verbose, |
| 171 in_directory, | 172 True, |
| 172 options.verbose, | 173 CaptureMatchingLines) |
| 173 True, | 174 |
| 174 CaptureMatchingLines) | 175 @staticmethod |
| 175 | 176 def RunAndFilterOutput(args, |
| 176 def RunSVNAndFilterOutput(args, | 177 in_directory, |
| 177 in_directory, | 178 print_messages, |
| 178 print_messages, | 179 print_stdout, |
| 179 print_stdout, | 180 filter): |
| 180 filter): | 181 """Runs svn checkout, update, status, or diff, optionally outputting |
| 181 """Runs svn checkout, update, status, or diff, optionally outputting | 182 to stdout. |
| 182 to stdout. | 183 |
| 183 | 184 The first item in args must be either "checkout", "update", |
| 184 The first item in args must be either "checkout", "update", | 185 "status", or "diff". |
| 185 "status", or "diff". | 186 |
| 186 | 187 svn's stdout is passed line-by-line to the given filter function. If |
| 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 Run. |
| 188 print_stdout is true, it is also printed to sys.stdout as in RunSVN. | 189 |
| 189 | 190 Args: |
| 190 Args: | 191 args: A sequence of command line parameters to be passed to svn. |
| 191 args: A sequence of command line parameters to be passed to svn. | 192 in_directory: The directory where svn is to be run. |
| 192 in_directory: The directory where svn is to be run. | 193 print_messages: Whether to print status messages to stdout about |
| 193 print_messages: Whether to print status messages to stdout about | 194 which Subversion commands are being run. |
| 194 which Subversion commands are being run. | 195 print_stdout: Whether to forward Subversion's output to stdout. |
| 195 print_stdout: Whether to forward Subversion's output to stdout. | 196 filter: A function taking one argument (a string) which will be |
| 196 filter: A function taking one argument (a string) which will be | 197 passed each line (with the ending newline character removed) of |
| 197 passed each line (with the ending newline character removed) of | 198 Subversion's output for filtering. |
| 198 Subversion's output for filtering. | 199 |
| 199 | 200 Raises: |
| 200 Raises: | 201 Error: An error occurred while running the svn command. |
| 201 Error: An error occurred while running the svn command. | 202 """ |
| 202 """ | 203 command = [SVN.COMMAND] |
| 203 command = [SVN_COMMAND] | 204 command.extend(args) |
| 204 command.extend(args) | 205 |
| 205 | 206 gclient_utils.SubprocessCallAndFilter(command, |
| 206 gclient_utils.SubprocessCallAndFilter(command, | 207 in_directory, |
| 207 in_directory, | 208 print_messages, |
| 208 print_messages, | 209 print_stdout, |
| 209 print_stdout, | 210 filter=filter) |
| 210 filter=filter) | 211 |
| 211 | 212 @staticmethod |
| 212 def CaptureSVNInfo(relpath, in_directory=None, print_error=True): | 213 def CaptureInfo(relpath, in_directory=None, print_error=True): |
| 213 """Returns a dictionary from the svn info output for the given file. | 214 """Returns a dictionary from the svn info output for the given file. |
| 214 | 215 |
| 215 Args: | 216 Args: |
| 216 relpath: The directory where the working copy resides relative to | 217 relpath: The directory where the working copy resides relative to |
| 217 the directory given by in_directory. | 218 the directory given by in_directory. |
| 218 in_directory: The directory where svn is to be run. | 219 in_directory: The directory where svn is to be run. |
| 219 """ | 220 """ |
| 220 output = CaptureSVN(["info", "--xml", relpath], in_directory, print_error) | 221 output = SVN.Capture(["info", "--xml", relpath], in_directory, print_error) |
| 221 dom = gclient_utils.ParseXML(output) | 222 dom = gclient_utils.ParseXML(output) |
| 222 result = {} | 223 result = {} |
| 223 if dom: | 224 if dom: |
| 224 GetNamedNodeText = gclient_utils.GetNamedNodeText | 225 GetNamedNodeText = gclient_utils.GetNamedNodeText |
| 225 GetNodeNamedAttributeText = gclient_utils.GetNodeNamedAttributeText | 226 GetNodeNamedAttributeText = gclient_utils.GetNodeNamedAttributeText |
| 226 def C(item, f): | 227 def C(item, f): |
| 227 if item is not None: return f(item) | 228 if item is not None: return f(item) |
| 228 # /info/entry/ | 229 # /info/entry/ |
| 229 # url | 230 # url |
| 230 # reposityory/(root|uuid) | 231 # reposityory/(root|uuid) |
| 231 # wc-info/(schedule|depth) | 232 # wc-info/(schedule|depth) |
| 232 # commit/(author|date) | 233 # commit/(author|date) |
| 233 # str() the results because they may be returned as Unicode, which | 234 # str() the results because they may be returned as Unicode, which |
| 234 # interferes with the higher layers matching up things in the deps | 235 # interferes with the higher layers matching up things in the deps |
| 235 # dictionary. | 236 # dictionary. |
| 236 # TODO(maruel): Fix at higher level instead (!) | 237 # TODO(maruel): Fix at higher level instead (!) |
| 237 result['Repository Root'] = C(GetNamedNodeText(dom, 'root'), str) | 238 result['Repository Root'] = C(GetNamedNodeText(dom, 'root'), str) |
| 238 result['URL'] = C(GetNamedNodeText(dom, 'url'), str) | 239 result['URL'] = C(GetNamedNodeText(dom, 'url'), str) |
| 239 result['UUID'] = C(GetNamedNodeText(dom, 'uuid'), str) | 240 result['UUID'] = C(GetNamedNodeText(dom, 'uuid'), str) |
| 240 result['Revision'] = C(GetNodeNamedAttributeText(dom, 'entry', 'revision'), | 241 result['Revision'] = C(GetNodeNamedAttributeText(dom, 'entry', |
| 241 int) | 242 'revision'), |
| 242 result['Node Kind'] = C(GetNodeNamedAttributeText(dom, 'entry', 'kind'), | 243 int) |
| 243 str) | 244 result['Node Kind'] = C(GetNodeNamedAttributeText(dom, 'entry', 'kind'), |
| 244 result['Schedule'] = C(GetNamedNodeText(dom, 'schedule'), str) | 245 str) |
| 245 result['Path'] = C(GetNodeNamedAttributeText(dom, 'entry', 'path'), str) | 246 # Differs across versions. |
| 246 result['Copied From URL'] = C(GetNamedNodeText(dom, 'copy-from-url'), str) | 247 if result['Node Kind'] == 'dir': |
|
M-A Ruel
2009/11/13 21:41:07
This is new and simplify the expectations.
| |
| 247 result['Copied From Rev'] = C(GetNamedNodeText(dom, 'copy-from-rev'), str) | 248 result['Node Kind'] = 'directory' |
| 248 return result | 249 result['Schedule'] = C(GetNamedNodeText(dom, 'schedule'), str) |
| 249 | 250 result['Path'] = C(GetNodeNamedAttributeText(dom, 'entry', 'path'), str) |
| 250 | 251 result['Copied From URL'] = C(GetNamedNodeText(dom, 'copy-from-url'), str) |
| 251 def CaptureSVNHeadRevision(url): | 252 result['Copied From Rev'] = C(GetNamedNodeText(dom, 'copy-from-rev'), str) |
| 252 """Get the head revision of a SVN repository. | 253 return result |
| 253 | 254 |
| 254 Returns: | 255 @staticmethod |
| 255 Int head revision | 256 def CaptureHeadRevision(url): |
| 256 """ | 257 """Get the head revision of a SVN repository. |
| 257 info = CaptureSVN(["info", "--xml", url], os.getcwd()) | 258 |
| 258 dom = xml.dom.minidom.parseString(info) | 259 Returns: |
| 259 return dom.getElementsByTagName('entry')[0].getAttribute('revision') | 260 Int head revision |
| 260 | 261 """ |
| 261 | 262 info = SVN.Capture(["info", "--xml", url], os.getcwd()) |
| 262 def CaptureSVNStatus(files): | 263 dom = xml.dom.minidom.parseString(info) |
| 263 """Returns the svn 1.5 svn status emulated output. | 264 return dom.getElementsByTagName('entry')[0].getAttribute('revision') |
| 264 | 265 |
| 265 @files can be a string (one file) or a list of files. | 266 @staticmethod |
| 266 | 267 def CaptureStatus(files): |
| 267 Returns an array of (status, file) tuples.""" | 268 """Returns the svn 1.5 svn status emulated output. |
| 268 command = ["status", "--xml"] | 269 |
| 269 if not files: | 270 @files can be a string (one file) or a list of files. |
| 270 pass | 271 |
| 271 elif isinstance(files, basestring): | 272 Returns an array of (status, file) tuples.""" |
| 272 command.append(files) | 273 command = ["status", "--xml"] |
| 273 else: | 274 if not files: |
| 274 command.extend(files) | 275 pass |
| 275 | 276 elif isinstance(files, basestring): |
| 276 status_letter = { | 277 command.append(files) |
| 277 None: ' ', | 278 else: |
| 278 '': ' ', | 279 command.extend(files) |
| 279 'added': 'A', | 280 |
| 280 'conflicted': 'C', | 281 status_letter = { |
| 281 'deleted': 'D', | 282 None: ' ', |
| 282 'external': 'X', | 283 '': ' ', |
| 283 'ignored': 'I', | 284 'added': 'A', |
| 284 'incomplete': '!', | 285 'conflicted': 'C', |
| 285 'merged': 'G', | 286 'deleted': 'D', |
| 286 'missing': '!', | 287 'external': 'X', |
| 287 'modified': 'M', | 288 'ignored': 'I', |
| 288 'none': ' ', | 289 'incomplete': '!', |
| 289 'normal': ' ', | 290 'merged': 'G', |
| 290 'obstructed': '~', | 291 'missing': '!', |
| 291 'replaced': 'R', | 292 'modified': 'M', |
| 292 'unversioned': '?', | 293 'none': ' ', |
| 293 } | 294 'normal': ' ', |
| 294 dom = gclient_utils.ParseXML(CaptureSVN(command)) | 295 'obstructed': '~', |
| 295 results = [] | 296 'replaced': 'R', |
| 296 if dom: | 297 'unversioned': '?', |
| 297 # /status/target/entry/(wc-status|commit|author|date) | 298 } |
| 298 for target in dom.getElementsByTagName('target'): | 299 dom = gclient_utils.ParseXML(SVN.Capture(command)) |
| 299 for entry in target.getElementsByTagName('entry'): | 300 results = [] |
| 300 file_path = entry.getAttribute('path') | 301 if dom: |
| 301 wc_status = entry.getElementsByTagName('wc-status') | 302 # /status/target/entry/(wc-status|commit|author|date) |
| 302 assert len(wc_status) == 1 | 303 for target in dom.getElementsByTagName('target'): |
| 303 # Emulate svn 1.5 status ouput... | 304 #base_path = target.getAttribute('path') |
| 304 statuses = [' '] * 7 | 305 for entry in target.getElementsByTagName('entry'): |
| 305 # Col 0 | 306 file_path = entry.getAttribute('path') |
| 306 xml_item_status = wc_status[0].getAttribute('item') | 307 wc_status = entry.getElementsByTagName('wc-status') |
| 307 if xml_item_status in status_letter: | 308 assert len(wc_status) == 1 |
| 308 statuses[0] = status_letter[xml_item_status] | 309 # Emulate svn 1.5 status ouput... |
| 309 else: | 310 statuses = [' '] * 7 |
| 310 raise Exception('Unknown item status "%s"; please implement me!' % | 311 # Col 0 |
| 311 xml_item_status) | 312 xml_item_status = wc_status[0].getAttribute('item') |
| 312 # Col 1 | 313 if xml_item_status in status_letter: |
| 313 xml_props_status = wc_status[0].getAttribute('props') | 314 statuses[0] = status_letter[xml_item_status] |
| 314 if xml_props_status == 'modified': | 315 else: |
| 315 statuses[1] = 'M' | 316 raise Exception('Unknown item status "%s"; please implement me!' % |
| 316 elif xml_props_status == 'conflicted': | 317 xml_item_status) |
| 317 statuses[1] = 'C' | 318 # Col 1 |
| 318 elif (not xml_props_status or xml_props_status == 'none' or | 319 xml_props_status = wc_status[0].getAttribute('props') |
| 319 xml_props_status == 'normal'): | 320 if xml_props_status == 'modified': |
| 320 pass | 321 statuses[1] = 'M' |
| 321 else: | 322 elif xml_props_status == 'conflicted': |
| 322 raise Exception('Unknown props status "%s"; please implement me!' % | 323 statuses[1] = 'C' |
| 323 xml_props_status) | 324 elif (not xml_props_status or xml_props_status == 'none' or |
| 324 # Col 2 | 325 xml_props_status == 'normal'): |
| 325 if wc_status[0].getAttribute('wc-locked') == 'true': | 326 pass |
| 326 statuses[2] = 'L' | 327 else: |
| 327 # Col 3 | 328 raise Exception('Unknown props status "%s"; please implement me!' % |
| 328 if wc_status[0].getAttribute('copied') == 'true': | 329 xml_props_status) |
| 329 statuses[3] = '+' | 330 # Col 2 |
| 330 # Col 4 | 331 if wc_status[0].getAttribute('wc-locked') == 'true': |
| 331 if wc_status[0].getAttribute('switched') == 'true': | 332 statuses[2] = 'L' |
| 332 statuses[4] = 'S' | 333 # Col 3 |
| 333 # TODO(maruel): Col 5 and 6 | 334 if wc_status[0].getAttribute('copied') == 'true': |
| 334 item = (''.join(statuses), file_path) | 335 statuses[3] = '+' |
| 335 results.append(item) | 336 # Col 4 |
| 336 return results | 337 if wc_status[0].getAttribute('switched') == 'true': |
| 338 statuses[4] = 'S' | |
| 339 # TODO(maruel): Col 5 and 6 | |
| 340 item = (''.join(statuses), file_path) | |
| 341 results.append(item) | |
| 342 return results | |
| 343 | |
| 344 @staticmethod | |
| 345 def IsMoved(filename): | |
| 346 """Determine if a file has been added through svn mv""" | |
| 347 info = SVN.CaptureInfo(filename) | |
| 348 return (info.get('Copied From URL') and | |
| 349 info.get('Copied From Rev') and | |
| 350 info.get('Schedule') == 'add') | |
| 351 | |
| 352 @staticmethod | |
| 353 def GetFileProperty(file, property_name): | |
| 354 """Returns the value of an SVN property for the given file. | |
| 355 | |
| 356 Args: | |
| 357 file: The file to check | |
| 358 property_name: The name of the SVN property, e.g. "svn:mime-type" | |
| 359 | |
| 360 Returns: | |
| 361 The value of the property, which will be the empty string if the property | |
| 362 is not set on the file. If the file is not under version control, the | |
| 363 empty string is also returned. | |
| 364 """ | |
| 365 output = SVN.Run(["propget", property_name, file], None) | |
| 366 if (output.startswith("svn: ") and | |
| 367 output.endswith("is not under version control")): | |
| 368 return "" | |
| 369 else: | |
| 370 return output | |
| 371 | |
| 372 @staticmethod | |
| 373 def DiffItem(filename): | |
|
M-A Ruel
2009/11/13 21:41:07
Extracted from trychange.py
| |
| 374 """Diff a single file""" | |
| 375 # Use svn info output instead of os.path.isdir because the latter fails | |
| 376 # when the file is deleted. | |
| 377 if SVN.CaptureInfo(filename).get("Node Kind") == "directory": | |
| 378 return None | |
| 379 # If the user specified a custom diff command in their svn config file, | |
| 380 # then it'll be used when we do svn diff, which we don't want to happen | |
| 381 # since we want the unified diff. Using --diff-cmd=diff doesn't always | |
| 382 # work, since they can have another diff executable in their path that | |
| 383 # gives different line endings. So we use a bogus temp directory as the | |
| 384 # config directory, which gets around these problems. | |
| 385 if sys.platform.startswith("win"): | |
| 386 parent_dir = tempfile.gettempdir() | |
| 387 else: | |
| 388 parent_dir = sys.path[0] # tempdir is not secure. | |
| 389 bogus_dir = os.path.join(parent_dir, "temp_svn_config") | |
| 390 if not os.path.exists(bogus_dir): | |
| 391 os.mkdir(bogus_dir) | |
| 392 # Grabs the diff data. | |
| 393 data = SVN.Capture(["diff", "--config-dir", bogus_dir, filename], None) | |
| 394 | |
| 395 # We know the diff will be incorrectly formatted. Fix it. | |
| 396 if SVN.IsMoved(filename): | |
| 397 # The file is "new" in the patch sense. Generate a homebrew diff. | |
| 398 # We can't use ReadFile() since it's not using binary mode. | |
| 399 file_handle = open(filename, 'rb') | |
| 400 file_content = file_handle.read() | |
| 401 file_handle.close() | |
| 402 # Prepend '+' to every lines. | |
| 403 file_content = ['+' + i for i in file_content.splitlines(True)] | |
| 404 nb_lines = len(file_content) | |
| 405 # We need to use / since patch on unix will fail otherwise. | |
| 406 filename = filename.replace('\\', '/') | |
| 407 data = "Index: %s\n" % filename | |
| 408 data += ("=============================================================" | |
| 409 "======\n") | |
| 410 # Note: Should we use /dev/null instead? | |
| 411 data += "--- %s\n" % filename | |
| 412 data += "+++ %s\n" % filename | |
| 413 data += "@@ -0,0 +1,%d @@\n" % nb_lines | |
| 414 data += ''.join(file_content) | |
| 415 return data | |
| OLD | NEW |