[PATCH] convert: svn-sink: apply property changes after adding dirs/files

Maxim Dounin mdounin at mdounin.ru
Wed Dec 26 18:28:29 CST 2007


Hello!

On Sat, 15 Dec 2007, Maxim Dounin wrote:

> When subversion used as a conversion sink, current code tries to set 
> svn:executable property in putfile() hook. This may happen before apropriate 
> file or dir was added to subversion, and so conversion will crash.
>
> Attached patch fixes it by moving actual work into putcommit().

Updated version of the patch. It also addresses similar issue with copy to 
new directory.

Note: test-convert-svn-sink is expected to fail unless patch-rename.txt 
from thread "[PATCH] convert: svn-sink: test if execute bit was actually 
stored" also applied.

Maxim Dounin

# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1198714486 -10800
# Node ID a7da468d57897e7685c8c6ecb1b80bd20c0c151e
# Parent  1ce185f3c640a0b7210004e3d2145ef64e853a9c
convert: svn-sink: copy and set properties after adding dirs/files

We can't store properties for files we haven't added to repo. Similarly,
we can't copy file to directory we haven't added to svn yet. Remember
needed changes and apply them in putcommit().

diff -r 1ce185f3c640 -r a7da468d5789 hgext/convert/subversion.py
--- a/hgext/convert/subversion.py	Wed Dec 26 23:03:32 2007 +0100
+++ b/hgext/convert/subversion.py	Thu Dec 27 03:14:46 2007 +0300
@@ -725,6 +725,9 @@ class svn_sink(converter_sink, commandli
          converter_sink.__init__(self, ui, path)
          commandline.__init__(self, ui, 'svn')
          self.delete = []
+        self.setexec = []
+        self.delexec = []
+        self.copies = []
          self.wc = None
          self.cwd = os.getcwd()

@@ -792,15 +795,18 @@ class svn_sink(converter_sink, commandli
              util.set_exec(self.wjoin(filename), 'x' in flags)
              if was_exec:
                  if 'x' not in flags:
-                    self.run0('propdel', 'svn:executable', filename)
+                    self.delexec.append(filename)
              else:
                  if 'x' in flags:
-                    self.run0('propset', 'svn:executable', '*', filename)
- 
+                    self.setexec.append(filename)
+
      def delfile(self, name):
          self.delete.append(name)

      def copyfile(self, source, dest):
+        self.copies.append([source, dest])
+
+    def _copyfile(self, source, dest):
          # SVN's copy command pukes if the destination file exists, but
          # our copyfile method expects to record a copy that has
          # already occurred.  Cross the semantic gap.
@@ -831,15 +837,18 @@ class svn_sink(converter_sink, commandli
                  dirs.add(f[:i])
          return dirs

-    def add_files(self, files):
+    def add_dirs(self, files):
          add_dirs = [d for d in self.dirs_of(files)
                      if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
          if add_dirs:
              add_dirs.sort()
              self.run('add', non_recursive=True, quiet=True, *add_dirs)
+        return add_dirs
+
+    def add_files(self, files):
          if files:
              self.run('add', quiet=True, *files)
-        return files.union(add_dirs)
+        return files

      def tidy_dirs(self, names):
          dirs = list(self.dirs_of(names))
@@ -857,7 +866,7 @@ class svn_sink(converter_sink, commandli

      def revid(self, rev):
          return u"svn:%s@%s" % (self.uuid, rev)
- 
+
      def putcommit(self, files, parents, commit):
          for parent in parents:
              try:
@@ -865,12 +874,24 @@ class svn_sink(converter_sink, commandli
              except KeyError:
                  pass
          entries = set(self.delete)
+        files = util.frozenset(files)
+        entries.update(self.add_dirs(files.difference(entries)))
+        if self.copies:
+            for s, d in self.copies:
+                self._copyfile(s, d)
+            self.copies = []
          if self.delete:
              self.run0('delete', *self.delete)
              self.delete = []
-        files = util.frozenset(files)
          entries.update(self.add_files(files.difference(entries)))
          entries.update(self.tidy_dirs(entries))
+        if self.delexec:
+            self.run0('propdel', 'svn:executable', *self.delexec)
+            self.delexec = []
+        if self.setexec:
+            self.run0('propset', 'svn:executable', '*', *self.setexec)
+            self.setexec = []
+
          fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
          fp = os.fdopen(fd, 'w')
          fp.write(commit.desc)
diff -r 1ce185f3c640 -r a7da468d5789 tests/test-convert-svn-sink
--- a/tests/test-convert-svn-sink	Wed Dec 26 23:03:32 2007 +0100
+++ b/tests/test-convert-svn-sink	Thu Dec 27 03:14:46 2007 +0300
@@ -59,6 +59,29 @@ hg convert -d svn a
  (cd a-hg-wc; svn up; svn st -v; svn log --xml -v --limit=1 | sed 's,<date>.*,<date/>,')
  test -x a-hg-wc/c && echo executable || echo not executable

+echo % executable in new directory
+
+rm -rf a a-hg a-hg-wc
+hg init a
+
+mkdir a/d1
+echo a > a/d1/a
+chmod +x a/d1/a
+hg --cwd a ci -d '0 0' -A -m 'add executable file in new directory'
+
+hg convert -d svn a
+(cd a-hg-wc; svn up; svn st -v; svn log --xml -v --limit=1 | sed 's,<date>.*,<date/>,')
+test -x a-hg-wc/d1/a && echo executable || echo not executable
+
+echo % copy to new directory
+
+mkdir a/d2
+hg --cwd a cp d1/a d2/a
+hg --cwd a ci -d '1 0' -A -m 'copy file to new directory'
+
+hg convert -d svn a
+(cd a-hg-wc; svn up; svn st -v; svn log --xml -v --limit=1 | sed 's,<date>.*,<date/>,')
+
  echo % branchy history

  hg init b
diff -r 1ce185f3c640 -r a7da468d5789 tests/test-convert-svn-sink.out
--- a/tests/test-convert-svn-sink.out	Wed Dec 26 23:03:32 2007 +0100
+++ b/tests/test-convert-svn-sink.out	Thu Dec 27 03:14:46 2007 +0300
@@ -195,6 +195,65 @@ At revision 5.
  </logentry>
  </log>
  executable
+% executable in new directory
+adding d1/a
+assuming destination a-hg
+initializing svn repo 'a-hg'
+initializing svn wc 'a-hg-wc'
+scanning source...
+sorting...
+converting...
+0 add executable file in new directory
+At revision 1.
+                1        1 test         .
+                1        1 test         d1
+                1        1 test         d1/a
+<?xml version="1.0"?>
+<log>
+<logentry
+   revision="1">
+<author>test</author>
+<date/>
+<paths>
+<path
+   action="A">/d1</path>
+<path
+   action="A">/d1/a</path>
+</paths>
+<msg>add executable file in new directory</msg>
+</logentry>
+</log>
+executable
+% copy to new directory
+assuming destination a-hg
+initializing svn wc 'a-hg-wc'
+scanning source...
+sorting...
+converting...
+0 copy file to new directory
+At revision 2.
+                2        2 test         .
+                2        1 test         d1
+                2        1 test         d1/a
+                2        2 test         d2
+                2        2 test         d2/a
+<?xml version="1.0"?>
+<log>
+<logentry
+   revision="2">
+<author>test</author>
+<date/>
+<paths>
+<path
+   action="A">/d2</path>
+<path
+   copyfrom-path="/d1/a"
+   copyfrom-rev="1"
+   action="A">/d2/a</path>
+</paths>
+<msg>copy file to new directory</msg>
+</logentry>
+</log>
  % branchy history
  adding b
  adding left-1


More information about the Mercurial-devel mailing list