I think you should actually read the link and check it had what you
expected.
+ except TransportNotPossible:
+ # This transport doesn't do hardlinks
+ return
+
def test_list_dir(self):
# TODO: Test list_dir, just try once, and if it throws, stop testing
t = self.get_transport()
+ def link(self, source, link_name):
+ """Create a hardlink pointing to source named link_name.
+
+ Return True if successful.
+ """
+ raise errors.TransportNotPossible("Hard links are not supported on %s" % self)
+
+ def symlink(self, source, link_name):
+ """Create a symlink pointing to source named link_name.
+
+ Return True if successful.
+ """
+ raise errors.TransportNotPossible("Symlinks are not supported on %s" % self)
+
def listable(self):
"""Return True if this store supports listing."""
raise NotImplementedError(self.listable)
This is in LocalTransport.stat, obviously changing it to behave more
like lstat that stat. Since transports never previously expected to
see symlinks perhaps it will not break anything, but on the other hand
perhaps it is a bit surprising that t.stat behaves like lstat. If we
ever actually wanted to follow symlinks, we'd need to add another
method for that.
Perhaps if that behaviour is changed it should be tested.
Alternatively you could add a t.lstat.
I posted an rfc. On the whole I think what you have is fine.
I think this is pretty close; thanks for posting it. Just a few comments:
=== modified file 'bzrlib/ tests/per_ transport. py' tests/per_ transport. py 2010-02-23 07:43:11 +0000 tests/per_ transport. py 2010-02-26 04:50:30 +0000
subdir. stat('. /file')
subdir. stat('. ')
--- bzrlib/
+++ bzrlib/
@@ -1083,6 +1083,52 @@
+ def test_link(self): transport( ) tree([source_ name], transport=t) (link_result)
+ from stat import ST_NLINK
+
+ t = self.get_
+
+ source_name = "original_target"
+ link_name = "target_link"
+
+ self.build_
+
+ try:
+ link_result = t.link(source_name, link_name)
+ self.failUnless
Returning True for success is a bit strange in Python. Why not just
rely on exceptions to indicate errors?
+ (t.has( source_ name)) (t.has( link_name) ) Equal(st[ ST_NLINK] , 2)
+ self.failUnless
+ self.failUnless
+
+ st = t.stat(link_name)
+ self.failUnless
Good; I think there might be a TestCase method to check they're linked
to each other.
+ except TransportNotPos sible:
+ # This transport doesn't do hardlinks
+ return
Probably better to indicate it was skipped: raise TestSkipped(...).
+ transport( ) tree([source_ name], transport=t) source_ name, link_name) (link_result)
+ def test_symlink(self):
+ from stat import S_ISLNK
+
+ t = self.get_
+
+ source_name = "original_target"
+ link_name = "target_link"
+
+ self.build_
+
+ try:
+ link_result = t.symlink(
+ self.failUnless
+
likewise
+ self.failUnless (t.has( source_ name)) (t.has( link_name) ) (S_ISLNK( st.st_mode) )
+ self.failUnless
+
+ st = t.stat(link_name)
+ self.failUnless
I think you should actually read the link and check it had what you
expected.
+ except TransportNotPos sible: dir(self) : transport( )
+ # This transport doesn't do hardlinks
+ return
+
def test_list_
# TODO: Test list_dir, just try once, and if it throws, stop testing
t = self.get_
=== modified file 'bzrlib/ transport/ __init_ _.py' transport/ __init_ _.py 2010-02-23 07:43:11 +0000 transport/ __init_ _.py 2010-02-26 04:50:30 +0000 over(relpaths, gather, pb, 'stat', expand=False)
--- bzrlib/
+++ bzrlib/
@@ -1202,6 +1202,20 @@
count = self._iterate_
return stats
+ def link(self, source, link_name): TransportNotPos sible(" Hard links are not supported on %s" % self) TransportNotPos sible(" Symlinks are not supported on %s" % self) rror(self. listable)
+ """Create a hardlink pointing to source named link_name.
+
+ Return True if successful.
+ """
+ raise errors.
+
+ def symlink(self, source, link_name):
+ """Create a symlink pointing to source named link_name.
+
+ Return True if successful.
+ """
+ raise errors.
+
def listable(self):
"""Return True if this store supports listing."""
raise NotImplementedE
=== modified file 'bzrlib/ transport/ local.py' transport/ local.py 2010-02-23 07:43:11 +0000 transport/ local.py 2010-02-26 04:50:30 +0000 relpath)
self. _translate_ error(e, path)
--- bzrlib/
+++ bzrlib/
@@ -481,7 +481,7 @@
path = relpath
try:
path = self._abspath(
- return os.stat(path)
+ return os.lstat(path)
except (IOError, OSError),e:
This is in LocalTransport. stat, obviously changing it to behave more
like lstat that stat. Since transports never previously expected to
see symlinks perhaps it will not break anything, but on the other hand
perhaps it is a bit surprising that t.stat behaves like lstat. If we
ever actually wanted to follow symlinks, we'd need to add another
method for that.
Perhaps if that behaviour is changed it should be tested.
Alternatively you could add a t.lstat.
I posted an rfc. On the whole I think what you have is fine.
@@ -515,6 +515,32 @@
self. _translate_ error(e, path)
except (IOError, OSError),e:
+ if osutils. hardlinks_ good(): self._abspath( source) , link_name) ) _error( e, source)
+ def link(self, source, link_name):
+ """See Transport.link."""
+ try:
+ os.link(
+ self._abspath(
+ return True
+ except (IOError, OSError), e:
+ self._translate
+
Similarly to what gzlist said, I think here you should do
try: sible(. ..)
link = os.link
except AttributeError, ...
raise TransportNotPos
link(...)
+ def symlink(self, source, link_name): symlink. """ dirname( self.abspath( link_name) ) file_relpath( strip_trailing_ slash(abs_ link_dirpath) , strip_trailing_ slash(self. abspath( source) ) source_ rel, self._abspath( link_name) ) TransportNotPos sible(" Symlinks are not supported on %s" % self) _error( e, source_rel) unix_modebits( self):
+ """See Transport.
+ abs_link_dirpath = urlutils.
+ source_rel = urlutils.
+ urlutils.
+ urlutils.
+ )
+
+ try:
+ os.symlink(
+ return True
+ except AttributeError:
+ raise errors.
+ except (IOError, OSError), e:
+ self._translate
+
def _can_roundtrip_
if sys.platform == 'win32':
# anyone else?
=== modified file 'bzrlib/ transport/ sftp.py' transport/ sftp.py 2010-02-23 07:43:11 +0000 transport/ sftp.py 2010-02-26 04:50:30 +0000
SFTP_ FLAG_EXCL, SFTP_FLAG_TRUNC,
--- bzrlib/
+++ bzrlib/
@@ -82,7 +82,7 @@
else:
from paramiko.sftp import (SFTP_FLAG_WRITE, SFTP_FLAG_CREATE,
- CMD_HANDLE, CMD_OPEN)
+ SFTP_OK, CMD_HANDLE, CMD_OPEN)
from paramiko.sftp_attr import SFTPAttributes
from paramiko.sftp_file import SFTPFile
@@ -810,10 +810,20 @@ path(relpath) sftp(). stat(path) sftp(). lstat(path) SSHException) , e:
self. _translate_ io_exception( e, path, ': unable to stat')
"""Return the stat information for a file."""
path = self._remote_
try:
- return self._get_
+ return self._get_
except (IOError, paramiko.
+ def symlink(self, source, link_name): symlink. """ source, link_name) SSHException) , e: _io_exception( e, link_name,
+ """See Transport.
+ try:
+ conn = self._get_sftp()
+ retval = conn.symlink(
+ return (SFTP_OK == retval)
+ except (IOError, paramiko.
+ self._translate
+ ': unable to create symlink to %r' % (source))
+
def lock_read(self, relpath):
"""
Lock the given file for shared (read) access.