[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):
|
||||
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):
|
||||
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 parse_wcinfotime
|
||||
from py.__.path.svn import svncommon
|
||||
from py.__.conftest import option
|
||||
|
||||
if py.path.local.sysfind('svn') is None:
|
||||
py.test.skip("cannot test py.path.svn, 'svn' binary not found")
|
||||
|
@ -141,6 +142,78 @@ class TestWCSvnCommandPath(CommonSvnTests):
|
|||
finally:
|
||||
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):
|
||||
p = self.root / 'anotherfile'
|
||||
out = p.diff(rev=2)
|
||||
|
|
|
@ -9,6 +9,8 @@ svn-Command based Implementation of a Subversion WorkingCopy Path.
|
|||
"""
|
||||
|
||||
import os, sys, time, re, calendar
|
||||
from xml.dom import minidom
|
||||
from xml.parsers.expat import ExpatError
|
||||
import py
|
||||
from py.__.path import common
|
||||
from py.__.path.svn import cache
|
||||
|
@ -255,9 +257,17 @@ class SvnWCCommandPath(common.FSPathBase):
|
|||
else:
|
||||
updates = ''
|
||||
|
||||
cmd = 'status -v %s %s %s' % (updates, rec, externals)
|
||||
out = self._authsvn(cmd)
|
||||
rootstatus = WCStatus(self).fromstring(out, self)
|
||||
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)
|
||||
rootstatus = WCStatus(self).fromstring(out, self)
|
||||
else:
|
||||
rootstatus = XMLWCStatus(self).fromstring(out, self)
|
||||
return rootstatus
|
||||
|
||||
def diff(self, rev=None):
|
||||
|
@ -528,7 +538,6 @@ class WCStatus:
|
|||
# seem to be a more solid approach :(
|
||||
_rex_status = re.compile(r'\s+(\d+|-)\s+(\S+)\s+(.+?)\s{2,}(.*)')
|
||||
|
||||
@staticmethod
|
||||
def fromstring(data, rootwcpath, rev=None, modrev=None, author=None):
|
||||
""" return a new WCStatus object from data 's'
|
||||
"""
|
||||
|
@ -573,6 +582,13 @@ class WCStatus:
|
|||
if line.lower().find('against revision:')!=-1:
|
||||
update_rev = int(rest.split(':')[1].strip())
|
||||
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
|
||||
raise ValueError, "could not parse line %r" % line
|
||||
else:
|
||||
|
@ -615,6 +631,106 @@ class WCStatus:
|
|||
rootstatus.update_rev = update_rev
|
||||
continue
|
||||
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:
|
||||
def __init__(self, output):
|
||||
|
|
Loading…
Reference in New Issue