1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
import os
import re
import sys
import codecs
from distutils import log
import xml.dom.pulldom
try:
import urlparse
except ImportError:
import urllib.parse as urlparse
#requires python >= 2.4
from subprocess import Popen as _Popen, PIPE as _PIPE
#NOTE: Use of the command line options
# require SVN 1.3 or newer (December 2005)
# and SVN 1.3 hsan't been supported by the
# developers since mid 2008.
#svnversion return values (previous implementations return max revision)
# 4123:4168 mixed revision working copy
# 4168M modified working copy
# 4123S switched working copy
# 4123:4168MS mixed revision, modified, switched working copy
_SVN_VER_RE = re.compile(r'(?:(\d+):)?(\d+)([a-z]*)\s*$', re.I)
#subprocess is called several times with shell=(sys.platform=='win32')
#see the follow for more information:
# http://bugs.python.org/issue8557
# http://stackoverflow.com/questions/5658622/
# python-subprocess-popen-environment-path
def _run_command(args, stdout=_PIPE, stderr=_PIPE):
#regarding the shell argument, see: http://bugs.python.org/issue8557
proc = _Popen(args, stdout=stdout, stderr=stderr,
shell=(sys.platform == 'win32'))
data = proc.communicate()[0]
#TODO: this is probably NOT always utf-8
try:
data = unicode(data, encoding='utf-8')
except NameError:
data = str(data, encoding='utf-8')
#communciate calls wait()
return proc.returncode, data
def _get_entry_name(entry):
return entry.getAttribute('path')
def _get_entry_schedule(entry):
schedule = entry.getElementsByTagName('schedule')[0]
return "".join([t.nodeValue
for t in schedule.childNodes
if t.nodeType == t.TEXT_NODE])
def parse_revision(path):
code, data = _run_command(['svnversion', path])
if code:
log.warn("svnversion failed")
return []
parsed = _SVN_VER_RE.match(data)
if parsed:
try:
#No max needed this command summarizes working copy since 1.0
return int(parsed.group(2))
except ValueError:
#This should only happen if the revision is WAY too big.
pass
return 0
def parse_dir_entries(path):
code, data = _run_command(['svn', 'info',
'--depth', 'immediates', '--xml', path])
if code:
log.warn("svn info failed")
return []
doc = xml.dom.pulldom.parseString(data)
entries = list()
for event, node in doc:
if event == 'START_ELEMENT' and node.nodeName == 'entry':
doc.expandNode(node)
entries.append(node)
if entries:
return [
_get_entry_name(element)
for element in entries[1:]
if _get_entry_schedule(element).lower() != 'deleted'
]
else:
return []
#--xml wasn't supported until 1.5.x
#-R without --xml parses a bit funny
def parse_externals(path):
try:
code, lines = _run_command(['svn',
'propget', 'svn:externals', path])
if code:
log.warn("svn propget failed")
return []
lines = [line for line in lines.splitlines() if line]
except ValueError:
lines = []
externals = []
for line in lines:
line = line.split()
if not line:
continue
if urlparse.urlsplit(line[-1])[0]:
externals.append(line[0])
else:
externals.append(line[-1])
return externals
def get_svn_tool_version():
_, data = _run_command(['svn', '--version', '--quiet'])
if data:
return data.strip()
else:
return ''
|