hey, Here's a patch that adds symlink support (and some test cases to go with it). I'd appreciate testing, and suggestions for fixing the BrokenSymLinkToFile test are welcome.
-- dann frazier | HP Open Source and Linux Organization
------------------------------------------------------------------------ r39 | dannf | 2007-04-04 16:43:14 -0600 (Wed, 04 Apr 2007) | 4 lines Add support for symlinks. Still doesn't support the case where a symlink gets replaced by a regular file - looks like that will require an extra commit pass. The BrokenSymLinkToFile test is expected to fail due to this. ------------------------------------------------------------------------ Index: svn-load =================================================================== --- svn-load (revision 38) +++ svn-load (revision 39) @@ -191,9 +191,19 @@ % (counterpath, fullpath)) return False needs_add = False - if not os.path.exists(counterpath): + if not os.path.lexists(counterpath): needs_add = True - shutil.copy(fullpath, counterpath) + + # shutil.copy follows symlinks, so we need to handle them + # separately + if os.path.lexists(counterpath) and \ + (os.path.islink(counterpath) or os.path.islink(fullpath)): + os.unlink(counterpath) + + if os.path.islink(fullpath): + os.symlink(os.readlink(fullpath), counterpath) + else: + shutil.copy(fullpath, counterpath) if needs_add: client.add(counterpath) # We have to use a counter instead of something like 'for d in dirs' @@ -206,7 +216,7 @@ counterpath = os.path.join(workingdir, relpath) if not os.path.exists(counterpath): - shutil.copytree(fullpath, counterpath) + shutil.copytree(fullpath, counterpath, symlinks=True) client.add(counterpath) dirs.pop(i) continue @@ -305,7 +315,9 @@ ## treats u+x as the canonical decider for svn:executable ## should probably see if svn import does it differently.. def is_executable(f): - s = os.stat(f) + if os.path.islink(f): + return False + s = os.lstat(f) return s[stat.ST_MODE] & 0500 == 0500 def svn_is_executable(file): Index: test.py =================================================================== --- test.py (revision 38) +++ test.py (revision 39) @@ -94,9 +94,27 @@ else: return False + ## This should allow us to accept cases that dircmp considers "funny". + ## The only known case so far is where both trees have the same identical + ## symlink + def funny_check(self, a, b, funny): + haha = True + strange = False + for f in funny: + funnyA = os.path.join(a, f) + funnyB = os.path.join(b, f) + + if os.path.islink(funnyA) and os.path.islink(funnyB): + if os.readlink(funnyA) == os.readlink(funnyB): + continue + else: + return strange + return haha + def compare_dirs(self, a, b): c = filecmp.dircmp(a, b) - if c.left_only or c.right_only or c.diff_files or c.common_funny: + funny_ok = self.funny_check(a, b, c.common_funny) + if c.left_only or c.right_only or c.diff_files or not funny_ok: return False else: return c.common_dirs @@ -134,6 +152,16 @@ f.write("baz\n") f.close() +class EmptyToSymLink(BaseLoadCase): + def loadDirsSetUp(self): + BaseLoadCase.loadDirsSetUp(self) + os.symlink('baz', os.path.join(self.loaddirs[0], 'foo')) + +class EmptyToSingleFileAndSymLink(EmptyToSingleFile): + def loadDirsSetUp(self): + EmptyToSingleFile.loadDirsSetUp(self) + os.symlink('foo', os.path.join(self.loaddirs[0], 'baz')) + class SingleFileContentChange(BaseLoadCase): def importDirSetUp(self): BaseLoadCase.importDirSetUp(self) @@ -160,7 +188,39 @@ f.write("baz\n") f.close() +class SingleFileToBrokenSymLink(BaseLoadCase): + def importDirSetUp(self): + BaseLoadCase.importDirSetUp(self) + f = open(os.path.join(self.importdir, 'foo'), 'w') + f.write("bar\n") + f.close() + + def loadDirsSetUp(self): + BaseLoadCase.loadDirsSetUp(self) + os.symlink('broken', os.path.join(self.loaddirs[0], 'foo')) + +class SingleFileToBrokenSymLink(BaseLoadCase): + def importDirSetUp(self): + BaseLoadCase.importDirSetUp(self) + f = open(os.path.join(self.importdir, 'foo'), 'w') + f.write("bar\n") + f.close() + + def loadDirsSetUp(self): + BaseLoadCase.loadDirsSetUp(self) + os.symlink('broken', os.path.join(self.loaddirs[0], 'foo')) + +class BrokenSymLinkToFile(BaseLoadCase): + def importDirSetUp(self): + BaseLoadCase.importDirSetUp(self) + os.symlink('broken', os.path.join(self.importdir, 'foo')) + + def loadDirsSetUp(self): + BaseLoadCase.loadDirsSetUp(self) + f = open(os.path.join(self.loaddirs[0], 'foo'), 'w') + f.write("bar\n") + f.close() + + if __name__ == '__main__': unittest.main() - -