Index: gcl.py |
=================================================================== |
--- gcl.py (revision 14731) |
+++ gcl.py (working copy) |
@@ -16,6 +16,7 @@ |
import tempfile |
import upload |
import urllib2 |
+import xml.dom.minidom |
__version__ = '1.0' |
@@ -47,6 +48,32 @@ |
read_gcl_info = False |
+### Simplified XML processing functions. |
+ |
+def ParseXML(output): |
+ try: |
+ return xml.dom.minidom.parseString(output) |
+ except xml.parsers.expat.ExpatError: |
+ return None |
+ |
+def GetNamedNodeText(node, node_name): |
+ child_nodes = node.getElementsByTagName(node_name) |
+ if not child_nodes: |
+ return None |
+ assert len(child_nodes) == 1 and child_nodes[0].childNodes.length == 1 |
+ return child_nodes[0].firstChild.nodeValue |
+ |
+ |
+def GetNodeNamedAttributeText(node, node_name, attribute_name): |
+ child_nodes = node.getElementsByTagName(node_name) |
+ if not child_nodes: |
+ return None |
+ assert len(child_nodes) == 1 |
+ return child_nodes[0].getAttribute(attribute_name) |
+ |
+ |
+### SVN Functions |
+ |
def IsSVNMoved(filename): |
"""Determine if a file has been added through svn mv""" |
info = GetSVNFileInfo(filename) |
@@ -57,13 +84,21 @@ |
def GetSVNFileInfo(file): |
"""Returns a dictionary from the svn info output for the given file.""" |
- output = RunShell(["svn", "info", file]) |
+ dom = ParseXML(RunShell(["svn", "info", "--xml", file])) |
result = {} |
- re_key_value_pair = re.compile('^(.*)\: (.*)$') |
- for line in output.splitlines(): |
- key_value_pair = re_key_value_pair.match(line) |
- if key_value_pair: |
- result[key_value_pair.group(1)] = key_value_pair.group(2) |
+ if dom: |
+ # /info/entry/ |
+ # url |
+ # reposityory/(root|uuid) |
+ # wc-info/(schedule|depth) |
+ # commit/(author|date) |
+ result['Node Kind'] = GetNodeNamedAttributeText(dom, 'entry', 'kind') |
+ result['Repository Root'] = GetNamedNodeText(dom, 'root') |
+ result['Schedule'] = GetNamedNodeText(dom, 'schedule') |
+ result['URL'] = GetNamedNodeText(dom, 'url') |
+ result['Path'] = GetNodeNamedAttributeText(dom, 'entry', 'path') |
+ result['Copied From URL'] = GetNamedNodeText(dom, 'copy-from-url') |
+ result['Copied From Rev'] = GetNamedNodeText(dom, 'copy-from-rev') |
return result |
@@ -87,6 +122,77 @@ |
return output |
+def GetSVNStatus(file): |
+ """Returns the svn 1.5 svn status emulated output. |
+ |
+ @file can be a string (one file) or a list of files.""" |
+ command = ["svn", "status", "--xml"] |
+ if file is None: |
+ pass |
+ elif isinstance(file, basestring): |
+ command.append(file) |
+ else: |
+ command.extend(file) |
+ |
+ status_letter = { |
+ '': ' ', |
+ 'unversioned': '?', |
+ 'modified': 'M', |
+ 'added': 'A', |
+ 'conflicted': 'C', |
+ 'deleted': 'D', |
+ 'ignored': 'I', |
+ 'replaced': 'R', |
+ # TODO(maruel): Find the corresponding strings for X, !, ~ |
+ } |
+ dom = ParseXML(RunShell(command)) |
+ results = [] |
+ if dom: |
+ # /status/target/entry/(wc-status|commit|author|date) |
+ for target in dom.getElementsByTagName('target'): |
+ base_path = target.getAttribute('path') |
+ for entry in target.getElementsByTagName('entry'): |
+ file = entry.getAttribute('path') |
+ wc_status = entry.getElementsByTagName('wc-status') |
+ assert len(wc_status) == 1 |
+ # Emulate svn 1.5 status ouput... |
+ statuses = [' ' for i in range(7)] |
+ # Col 0 |
+ xml_item_status = wc_status[0].getAttribute('item') |
+ if xml_item_status in status_letter: |
+ statuses[0] = status_letter[xml_item_status] |
+ else: |
+ raise Exception('Unknown item status "%s"; please implement me!' % |
+ xml_item_status) |
+ # Col 1 |
+ xml_props_status = wc_status[0].getAttribute('props') |
+ if xml_props_status == 'modified': |
+ statuses[1] = 'M' |
+ elif xml_props_status == 'conflicted': |
+ statuses[1] = 'C' |
+ elif (not xml_props_status or xml_props_status == 'none' or |
+ xml_props_status == 'normal'): |
+ pass |
+ else: |
+ raise Exception('Unknown props status "%s"; please implement me!' % |
+ xml_props_status) |
+ # Col 3 |
+ if wc_status[0].getAttribute('copied') == 'true': |
+ statuses[3] = '+' |
+ item = (''.join(statuses), file) |
+ results.append(item) |
+ return results |
+ |
+ |
+def UnknownFiles(extra_args): |
+ """Runs svn status and prints unknown files. |
+ |
+ Any args in |extra_args| are passed to the tool to support giving alternate |
+ code locations. |
+ """ |
+ return [item[1] for item in GetSVNStatus(extra_args) if item[0][0] == '?'] |
+ |
+ |
def GetRepositoryRoot(): |
"""Returns the top level directory of the current repository. |
@@ -418,8 +524,9 @@ |
if update_status: |
for file in files: |
filename = os.path.join(GetRepositoryRoot(), file[1]) |
- status = RunShell(["svn", "status", filename])[:7] |
- if not status: # File has been reverted. |
+ status_result = GetSVNStatus(filename) |
+ if not status_result or not status_result[0][0]: |
+ # File has been reverted. |
save = True |
files.remove(file) |
elif status != file[0]: |
@@ -474,12 +581,12 @@ |
files_in_cl[filename] = change_info.name |
# Get all the modified files. |
- status = RunShell(["svn", "status"]) |
- for line in status.splitlines(): |
- if not len(line) or line[0] == "?": |
+ status_result = GetSVNStatus(None) |
+ for line in status_result: |
+ status = line[0] |
+ filename = line[1] |
+ if status[0] == "?": |
continue |
- status = line[:7] |
- filename = line[7:].strip() |
if dir_prefix: |
filename = os.path.join(dir_prefix, filename) |
change_list_name = "" |
@@ -530,30 +637,6 @@ |
return SendToRietveld("/" + issue + "/description") |
-def UnknownFiles(extra_args): |
- """Runs svn status and prints unknown files. |
- |
- Any args in |extra_args| are passed to the tool to support giving alternate |
- code locations. |
- """ |
- args = ["svn", "status"] |
- args += extra_args |
- p = subprocess.Popen(args, stdout = subprocess.PIPE, |
- stderr = subprocess.STDOUT, shell = use_shell) |
- while 1: |
- line = p.stdout.readline() |
- if not line: |
- break |
- if line[0] != '?': |
- continue # Not an unknown file to svn. |
- # The lines look like this: |
- # "? foo.txt" |
- # and we want just "foo.txt" |
- print line[7:].strip() |
- p.wait() |
- p.stdout.close() |
- |
- |
def Opened(): |
"""Prints a list of modified files in the current directory down.""" |
files = GetModifiedFiles() |