[svn r57449] Merging https://codespeak.net/svn/py/branch/guido-svnwc-xml-status with trunk
(revisions 56843:57448). --HG-- branch : trunk
This commit is contained in:
parent
8cefb88d9c
commit
623ad564ed
|
@ -73,7 +73,7 @@ class CommonSvnTests(CommonFSTests):
|
||||||
|
|
||||||
def setup_method(self, meth):
|
def setup_method(self, meth):
|
||||||
bn = meth.func_name
|
bn = meth.func_name
|
||||||
for x in 'test_remove', 'test_move':
|
for x in 'test_remove', 'test_move', 'test_status_deleted':
|
||||||
if bn.startswith(x):
|
if bn.startswith(x):
|
||||||
self._savedrepowc = save_repowc()
|
self._savedrepowc = save_repowc()
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ from py.__.path.svn.testing.svntestbase import CommonSvnTests, getrepowc
|
||||||
from py.__.path.svn.wccommand import InfoSvnWCCommand
|
from py.__.path.svn.wccommand import InfoSvnWCCommand
|
||||||
from py.__.path.svn.wccommand import parse_wcinfotime
|
from py.__.path.svn.wccommand import parse_wcinfotime
|
||||||
from py.__.path.svn import svncommon
|
from py.__.path.svn import svncommon
|
||||||
|
from py.__.conftest import option
|
||||||
|
|
||||||
if py.path.local.sysfind('svn') is None:
|
if py.path.local.sysfind('svn') is None:
|
||||||
py.test.skip("cannot test py.path.svn, 'svn' binary not found")
|
py.test.skip("cannot test py.path.svn, 'svn' binary not found")
|
||||||
|
@ -141,6 +142,78 @@ class TestWCSvnCommandPath(CommonSvnTests):
|
||||||
finally:
|
finally:
|
||||||
self.root.revert(rec=1)
|
self.root.revert(rec=1)
|
||||||
|
|
||||||
|
def test_status_ignored(self):
|
||||||
|
try:
|
||||||
|
d = self.root.join('sampledir')
|
||||||
|
p = py.path.local(d).join('ignoredfile')
|
||||||
|
p.ensure(file=True)
|
||||||
|
s = d.status()
|
||||||
|
assert [x.basename for x in s.unknown] == ['ignoredfile']
|
||||||
|
assert [x.basename for x in s.ignored] == []
|
||||||
|
d.propset('svn:ignore', 'ignoredfile')
|
||||||
|
s = d.status()
|
||||||
|
assert [x.basename for x in s.unknown] == []
|
||||||
|
assert [x.basename for x in s.ignored] == ['ignoredfile']
|
||||||
|
finally:
|
||||||
|
self.root.revert(rec=1)
|
||||||
|
|
||||||
|
def test_status_conflict(self):
|
||||||
|
if not option.runslowtests:
|
||||||
|
py.test.skip('skipping slow unit tests - use --runslowtests '
|
||||||
|
'to override')
|
||||||
|
wc = self.root
|
||||||
|
wccopy = py.path.svnwc(
|
||||||
|
py.test.ensuretemp('test_status_conflict_wccopy'))
|
||||||
|
wccopy.checkout(wc.url)
|
||||||
|
p = wc.ensure('conflictsamplefile', file=1)
|
||||||
|
p.write('foo')
|
||||||
|
wc.commit('added conflictsamplefile')
|
||||||
|
wccopy.update()
|
||||||
|
assert wccopy.join('conflictsamplefile').check()
|
||||||
|
p.write('bar')
|
||||||
|
wc.commit('wrote some data')
|
||||||
|
wccopy.join('conflictsamplefile').write('baz')
|
||||||
|
wccopy.update()
|
||||||
|
s = wccopy.status()
|
||||||
|
assert [x.basename for x in s.conflict] == ['conflictsamplefile']
|
||||||
|
|
||||||
|
def test_status_external(self):
|
||||||
|
if not option.runslowtests:
|
||||||
|
py.test.skip('skipping slow unit tests - use --runslowtests '
|
||||||
|
'to override')
|
||||||
|
otherrepo, otherwc = getrepowc('externalrepo', 'externalwc')
|
||||||
|
d = self.root.ensure('sampledir', dir=1)
|
||||||
|
try:
|
||||||
|
d.remove()
|
||||||
|
d.add()
|
||||||
|
d.update()
|
||||||
|
d.propset('svn:externals', 'otherwc %s' % (otherwc.url,))
|
||||||
|
d.update()
|
||||||
|
s = d.status()
|
||||||
|
assert [x.basename for x in s.external] == ['otherwc']
|
||||||
|
assert 'otherwc' not in [x.basename for x in s.unchanged]
|
||||||
|
s = d.status(rec=1)
|
||||||
|
assert [x.basename for x in s.external] == ['otherwc']
|
||||||
|
assert 'otherwc' in [x.basename for x in s.unchanged]
|
||||||
|
finally:
|
||||||
|
self.root.revert(rec=1)
|
||||||
|
|
||||||
|
def test_status_deleted(self):
|
||||||
|
d = self.root.ensure('sampledir', dir=1)
|
||||||
|
d.remove()
|
||||||
|
d.add()
|
||||||
|
self.root.commit()
|
||||||
|
d.ensure('deletefile', dir=0)
|
||||||
|
d.commit()
|
||||||
|
s = d.status()
|
||||||
|
assert 'deletefile' in [x.basename for x in s.unchanged]
|
||||||
|
assert not s.deleted
|
||||||
|
p = d.join('deletefile')
|
||||||
|
p.remove()
|
||||||
|
s = d.status()
|
||||||
|
assert 'deletefile' not in s.unchanged
|
||||||
|
assert [x.basename for x in s.deleted] == ['deletefile']
|
||||||
|
|
||||||
def test_diff(self):
|
def test_diff(self):
|
||||||
p = self.root / 'anotherfile'
|
p = self.root / 'anotherfile'
|
||||||
out = p.diff(rev=2)
|
out = p.diff(rev=2)
|
||||||
|
|
|
@ -9,6 +9,8 @@ svn-Command based Implementation of a Subversion WorkingCopy Path.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os, sys, time, re, calendar
|
import os, sys, time, re, calendar
|
||||||
|
from xml.dom import minidom
|
||||||
|
from xml.parsers.expat import ExpatError
|
||||||
import py
|
import py
|
||||||
from py.__.path import common
|
from py.__.path import common
|
||||||
from py.__.path.svn import cache
|
from py.__.path.svn import cache
|
||||||
|
@ -255,9 +257,17 @@ class SvnWCCommandPath(common.FSPathBase):
|
||||||
else:
|
else:
|
||||||
updates = ''
|
updates = ''
|
||||||
|
|
||||||
cmd = 'status -v %s %s %s' % (updates, rec, externals)
|
try:
|
||||||
|
cmd = 'status -v --xml --no-ignore %s %s %s' % (
|
||||||
|
updates, rec, externals)
|
||||||
|
out = self._authsvn(cmd)
|
||||||
|
except py.process.cmdexec.Error:
|
||||||
|
cmd = 'status -v --no-ignore %s %s %s' % (
|
||||||
|
updates, rec, externals)
|
||||||
out = self._authsvn(cmd)
|
out = self._authsvn(cmd)
|
||||||
rootstatus = WCStatus(self).fromstring(out, self)
|
rootstatus = WCStatus(self).fromstring(out, self)
|
||||||
|
else:
|
||||||
|
rootstatus = XMLWCStatus(self).fromstring(out, self)
|
||||||
return rootstatus
|
return rootstatus
|
||||||
|
|
||||||
def diff(self, rev=None):
|
def diff(self, rev=None):
|
||||||
|
@ -528,7 +538,6 @@ class WCStatus:
|
||||||
# seem to be a more solid approach :(
|
# seem to be a more solid approach :(
|
||||||
_rex_status = re.compile(r'\s+(\d+|-)\s+(\S+)\s+(.+?)\s{2,}(.*)')
|
_rex_status = re.compile(r'\s+(\d+|-)\s+(\S+)\s+(.+?)\s{2,}(.*)')
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def fromstring(data, rootwcpath, rev=None, modrev=None, author=None):
|
def fromstring(data, rootwcpath, rev=None, modrev=None, author=None):
|
||||||
""" return a new WCStatus object from data 's'
|
""" return a new WCStatus object from data 's'
|
||||||
"""
|
"""
|
||||||
|
@ -573,6 +582,13 @@ class WCStatus:
|
||||||
if line.lower().find('against revision:')!=-1:
|
if line.lower().find('against revision:')!=-1:
|
||||||
update_rev = int(rest.split(':')[1].strip())
|
update_rev = int(rest.split(':')[1].strip())
|
||||||
continue
|
continue
|
||||||
|
if line.lower().find('status on external') > -1:
|
||||||
|
# XXX not sure what to do here... perhaps we want to
|
||||||
|
# store some state instead of just continuing, as right
|
||||||
|
# now it makes the top-level external get added twice
|
||||||
|
# (once as external, once as 'normal' unchanged item)
|
||||||
|
# because of the way SVN presents external items
|
||||||
|
continue
|
||||||
# keep trying
|
# keep trying
|
||||||
raise ValueError, "could not parse line %r" % line
|
raise ValueError, "could not parse line %r" % line
|
||||||
else:
|
else:
|
||||||
|
@ -615,6 +631,106 @@ class WCStatus:
|
||||||
rootstatus.update_rev = update_rev
|
rootstatus.update_rev = update_rev
|
||||||
continue
|
continue
|
||||||
return rootstatus
|
return rootstatus
|
||||||
|
fromstring = staticmethod(fromstring)
|
||||||
|
|
||||||
|
class XMLWCStatus(WCStatus):
|
||||||
|
def fromstring(data, rootwcpath, rev=None, modrev=None, author=None):
|
||||||
|
""" parse 'data' (XML string as outputted by svn st) into a status obj
|
||||||
|
"""
|
||||||
|
# XXX for externals, the path is shown twice: once
|
||||||
|
# with external information, and once with full info as if
|
||||||
|
# the item was a normal non-external... the current way of
|
||||||
|
# dealing with this issue is by ignoring it - this does make
|
||||||
|
# externals appear as external items as well as 'normal',
|
||||||
|
# unchanged ones in the status object so this is far from ideal
|
||||||
|
rootstatus = WCStatus(rootwcpath, rev, modrev, author)
|
||||||
|
update_rev = None
|
||||||
|
try:
|
||||||
|
doc = minidom.parseString(data)
|
||||||
|
except ExpatError, e:
|
||||||
|
raise ValueError(str(e))
|
||||||
|
urevels = doc.getElementsByTagName('against')
|
||||||
|
if urevels:
|
||||||
|
rootstatus.update_rev = urevels[-1].getAttribute('revision')
|
||||||
|
for entryel in doc.getElementsByTagName('entry'):
|
||||||
|
path = entryel.getAttribute('path')
|
||||||
|
statusel = entryel.getElementsByTagName('wc-status')[0]
|
||||||
|
itemstatus = statusel.getAttribute('item')
|
||||||
|
|
||||||
|
if itemstatus == 'unversioned':
|
||||||
|
wcpath = rootwcpath.join(path, abs=1)
|
||||||
|
rootstatus.unknown.append(wcpath)
|
||||||
|
continue
|
||||||
|
elif itemstatus == 'external':
|
||||||
|
wcpath = rootwcpath.__class__(
|
||||||
|
rootwcpath.localpath.join(path, abs=1),
|
||||||
|
auth=rootwcpath.auth)
|
||||||
|
rootstatus.external.append(wcpath)
|
||||||
|
continue
|
||||||
|
elif itemstatus == 'ignored':
|
||||||
|
wcpath = rootwcpath.join(path, abs=1)
|
||||||
|
rootstatus.ignored.append(wcpath)
|
||||||
|
continue
|
||||||
|
|
||||||
|
rev = statusel.getAttribute('revision')
|
||||||
|
if itemstatus == 'added' or itemstatus == 'none':
|
||||||
|
rev = '0'
|
||||||
|
modrev = '?'
|
||||||
|
author = '?'
|
||||||
|
date = ''
|
||||||
|
else:
|
||||||
|
print entryel.toxml()
|
||||||
|
commitel = entryel.getElementsByTagName('commit')[0]
|
||||||
|
if commitel:
|
||||||
|
modrev = commitel.getAttribute('revision')
|
||||||
|
author = ''
|
||||||
|
for c in commitel.getElementsByTagName('author')[0]\
|
||||||
|
.childNodes:
|
||||||
|
author += c.nodeValue
|
||||||
|
date = ''
|
||||||
|
for c in commitel.getElementsByTagName('date')[0]\
|
||||||
|
.childNodes:
|
||||||
|
date += c.nodeValue
|
||||||
|
|
||||||
|
wcpath = rootwcpath.join(path, abs=1)
|
||||||
|
|
||||||
|
assert itemstatus != 'modified' or wcpath.check(file=1), (
|
||||||
|
'did\'t expect a directory with changed content here')
|
||||||
|
|
||||||
|
itemattrname = {
|
||||||
|
'normal': 'unchanged',
|
||||||
|
'unversioned': 'unknown',
|
||||||
|
'conflicted': 'conflict',
|
||||||
|
'none': 'added',
|
||||||
|
}.get(itemstatus, itemstatus)
|
||||||
|
|
||||||
|
attr = getattr(rootstatus, itemattrname)
|
||||||
|
attr.append(wcpath)
|
||||||
|
|
||||||
|
propsstatus = statusel.getAttribute('props')
|
||||||
|
if propsstatus not in ('none', 'normal'):
|
||||||
|
rootstatus.prop_modified.append(wcpath)
|
||||||
|
|
||||||
|
if wcpath == rootwcpath:
|
||||||
|
rootstatus.rev = rev
|
||||||
|
rootstatus.modrev = modrev
|
||||||
|
rootstatus.author = author
|
||||||
|
rootstatus.date = date
|
||||||
|
|
||||||
|
# handle repos-status element (remote info)
|
||||||
|
rstatusels = entryel.getElementsByTagName('repos-status')
|
||||||
|
if rstatusels:
|
||||||
|
rstatusel = rstatusels[0]
|
||||||
|
ritemstatus = rstatusel.getAttribute('item')
|
||||||
|
if ritemstatus in ('added', 'modified'):
|
||||||
|
rootstatus.update_available.append(wcpath)
|
||||||
|
|
||||||
|
lockels = entryel.getElementsByTagName('lock')
|
||||||
|
if len(lockels):
|
||||||
|
rootstatus.locked.append(wcpath)
|
||||||
|
|
||||||
|
return rootstatus
|
||||||
|
fromstring = staticmethod(fromstring)
|
||||||
|
|
||||||
class InfoSvnWCCommand:
|
class InfoSvnWCCommand:
|
||||||
def __init__(self, output):
|
def __init__(self, output):
|
||||||
|
|
Loading…
Reference in New Issue