In parallel to the other discussion on import order of svn.fs.FileDiff, I'd like some comments on the attached patch. Mostly, this is to address the failing tests on Windows [0], since there isn't a 'diff' executable necessarily available. The attached patch uses the internal Subversion diff implementation (which IMHO is actually better all around anyway), unless the 'diffoptions' constructor argument is provided. If diffoptions are given, then the old behavior of shelling out to 'diff' is still used.
I'm asking for comments because the actual diff output given when 'diffoptions' is None, is changed. Currently a no argument call to 'diff' is used, which results in a 'normal' diff output. Using svn_diff_file_output_unified4(), as I have in the patch, results in a unified diff output. This output is more consistent with other subversion diff output, but it *is* different than what svn.fs.FileDiff.get_pipe() currently returns. Another option would be to default diffoptions to [ '--normal' ], so that it takes an explicit argument of 'None' or '[]' to get the new behavior, which we could use in the test. Thoughts? Troy 0: https://ci.apache.org/builders/svn-windows-ra/builds/1998
Index: subversion/bindings/swig/python/svn/fs.py =================================================================== --- subversion/bindings/swig/python/svn/fs.py (revision 1823802) +++ subversion/bindings/swig/python/svn/fs.py (working copy) @@ -42,6 +42,7 @@ # Python >=3.0 import builtins import svn.core as _svncore +import svn.diff as _svndiff def entries(root, path, pool=None): @@ -58,6 +59,7 @@ self.tempfile1 = None self.tempfile2 = None + self.difftemp = None self.root1 = root1 self.path1 = path1 @@ -110,27 +112,49 @@ def get_pipe(self): self.get_files() - # use an array for the command to avoid the shell and potential - # security exposures - cmd = ["diff"] \ - + self.diffoptions \ - + [self.tempfile1, self.tempfile2] + # If diffoptions were provided, then the diff command needs to be + # called in preference to using the internal Subversion diff. + if self.diffoptions: + # use an array for the command to avoid the shell and potential + # security exposures + cmd = ["diff"] \ + + self.diffoptions \ + + [self.tempfile1, self.tempfile2] - # open the pipe, and return the file object for reading from the child. - p = _subprocess.Popen(cmd, stdout=_subprocess.PIPE, bufsize=-1, - close_fds=_sys.platform != "win32") - return p.stdout + # open the pipe, and return the file object for reading from the child. + p = _subprocess.Popen(cmd, stdout=_subprocess.PIPE, bufsize=-1, + close_fds=_sys.platform != "win32") + return p.stdout + else: + if self.difftemp is None: + self.difftemp = _tempfile.mktemp() + + with builtins.open(self.difftemp, "wb") as fp: + diffopt = _svndiff.file_options_create() + diffobj = _svndiff.file_diff_2(self.tempfile1, + self.tempfile2, + diffopt) + + _svndiff.file_output_unified4(fp, + diffobj, + self.tempfile1, + self.tempfile2, + None, None, + "utf8", + None, + diffopt.show_c_function, + diffopt.context_size, + None, None) + + return builtins.open(self.difftemp, "r") + def __del__(self): # it seems that sometimes the files are deleted, so just ignore any # failures trying to remove them - if self.tempfile1 is not None: - try: - _os.remove(self.tempfile1) - except OSError: - pass - if self.tempfile2 is not None: - try: - _os.remove(self.tempfile2) - except OSError: - pass + for tmpfile in [self.tempfile1, self.tempfile2, self.difftemp]: + if tmpfile is not None: + try: + _os.remove(tmpfile) + except OSError: + pass Index: subversion/bindings/swig/python/tests/fs.py =================================================================== --- subversion/bindings/swig/python/tests/fs.py (revision 1823802) +++ subversion/bindings/swig/python/tests/fs.py (working copy) @@ -19,7 +19,7 @@ # under the License. # # -import os, unittest +import os, unittest, sys from tempfile import mkstemp try: # Python >=3.0 @@ -83,6 +83,7 @@ clientctx) self.assertEqual(commitinfo.revision, self.rev + 1) + # Test standard internal diff fdiff = fs.FileDiff(fs.revision_root(self.fs, commitinfo.revision), "/trunk/UniTest.txt", None, None) @@ -89,8 +90,19 @@ diffp = fdiff.get_pipe() diffoutput = diffp.read().decode('utf8') - self.assertTrue(diffoutput.find(u'< ⊙_ʘ') > 0) + self.assertTrue(diffoutput.find(u'-⊙_ʘ') > 0) + # Test passing diffoptions to an external 'diff' executable. + # It is unusual to have the 'diff' tool on Windows, so do not + # try the test there. + if sys.platform != "win32": + fdiff = fs.FileDiff(fs.revision_root(self.fs, commitinfo.revision), "/trunk/UniTest.txt", + None, None, None, ['--normal']) + diffp = fdiff.get_pipe() + diffoutput = diffp.read().decode('utf8') + + self.assertTrue(diffoutput.find(u'< ⊙_ʘ') > 0) + def suite(): return unittest.defaultTestLoader.loadTestsFromTestCase( SubversionFSTestCase)