[PATCH] merge: detect conflict between path of added file and a local file (issue29)

Evgeniy Makeev evgeniym at fb.com
Tue Oct 9 16:18:45 CDT 2012


# HG changeset patch
# User Evgeniy Makeev <evgeniym at fb.com>
# Date 1349754802 25200
# Node ID 227411b21482b7ee80a85628989957716eb3659b
# Parent  072243d45be07be438736f125baa6a2381dd53c4
merge: detect conflict between path of added file and a local file (issue29)

The fix checks for conflicts between local files and directories being
merged/added, if there is a conflict, user is prompted to abbort or
skip merging/adding all files wich have the conflicting directories
in their paths. The check is done by building a directory set off all
partial paths of added files.

diff -r 072243d45be0 -r 227411b21482 mercurial/merge.py
--- a/mercurial/merge.py	Mon Oct 08 16:30:50 2012 -0700
+++ b/mercurial/merge.py	Mon Oct 08 20:53:22 2012 -0700
@@ -259,6 +259,7 @@
             else:
                 act("other deleted", "r", f)
 
+    newdirset = set()
     for f, n in m2.iteritems():
         if partial and not partial(f):
             continue
@@ -286,11 +287,13 @@
                 if os.path.isdir(dirpath):
                 # check if the added remote file collides with existing dir
                     if f in p1.dirs(): # existing non-empty dir
-                        if not repo.ui.promptchoice(
+                        if repo.ui.promptchoice(
                             _("cannot merge file %s, existing directory %s is"
                               " in the way\nwould you like to (A)bort operation"
                               " or (S)kip writing the file? [a]")
                               % (f, dirpath), (_("&Abort"), _("&Skip")), 0):
+                            continue
+                        else:
                             raise util.Abort(
                                 _("cannot write file %s conflicting with local"
                                   " directory %s") % (f, repo.wjoin(f)))
@@ -301,18 +304,25 @@
                               "the empty directory or (A)bort operation? [r]")
                               % (dirpath, f), (_("&Remove"), _("&Abort")), 0):
                             raise util.Abort(
-                                _("cannot write file %s, remove conflicting empty "
-                                  "directory %s or rename the file")
+                                _("cannot write file %s, remove conflicting "
+                                  "empty directory %s or rename the file")
                                   % (f, dirpath))
                         else:
                             os.rmdir(dirpath)
-                            act("remote created", "g", f, m2.flags(f))
                     else:
                         raise util.Abort(
                             _("file %s conflicts with local directory %s")
                             % (f, dirpath))
-                else:
-                    act("remote created", "g", f, m2.flags(f))
+
+                act("remote created", "g", f, m2.flags(f))
+                # find all dirs from f's path, OK to reuse f string here
+                pathpos = f.rfind('/')
+                while pathpos != -1:
+                    f = f[:pathpos]
+                    if f in newdirset:
+                        break # newdirset already contains this and above
+                    newdirset.add(f)
+                    pathpos = f.rfind('/')
 
         elif n != ma[f]:
             if repo.ui.promptchoice(
@@ -320,7 +330,33 @@
                   "use (c)hanged version or leave (d)eleted?") % f,
                 (_("&Changed"), _("&Deleted")), 0) == 0:
                 act("prompt recreating", "g", f, m2.flags(f))
-
+    for mdir in newdirset:
+        # verify if a remote dirs conflicts with an existing file
+        # everything inside the following if block only executes on error
+        if mdir in m1 and os.path.isfile(repo.wjoin(mdir)):
+            # collission - expand error message/recovery option
+            confiles = [fn for fn in p2 if fn.find(mdir+'/') == 0]
+            confilesstr = ', '.join(confiles)
+            if len(confiles) <= 1:
+                promptmsg = _("cannot write %s, local file %s conflicts with"
+                            " its path\nwould you like to (A)bort operation or"
+                            " (S)kip file %s? [a]")
+                abortmsg = _("local file %s conflicts with paths of files %s"
+                           " to be written")
+            else:
+                promptmsg = _("cannot write %s\nlocal file %s conflicts with"
+                            " their paths\nwould you like to (A)bort operation"
+                            " or\n(S)kip files %s? [a]")
+                abortmsg = _("local file %s conflicts with paths of files %s"
+                            " to be written")
+            if repo.ui.promptchoice(promptmsg
+                    % (confilesstr, repo.wjoin(mdir), confilesstr),
+                    (_("&Abort"), _("&Skip")), 0):
+                # remove conflicting file additions from action
+                action = [a for a in action
+                          if a[1] != 'g' or a[0].find(mdir) != 0]
+            else:
+                raise util.Abort(abortmsg % (repo.wjoin(mdir), confilesstr))
     return action
 
 def actionkey(a):
diff -r 072243d45be0 -r 227411b21482 tests/test-merge8.t
--- a/tests/test-merge8.t	Mon Oct 08 16:30:50 2012 -0700
+++ b/tests/test-merge8.t	Mon Oct 08 20:53:22 2012 -0700
@@ -45,14 +45,15 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../a
   $ mkdir emptydir
-  $ mkdir dir
-  $ echo afile > dir/afile
+  $ mkdir dirorfile
+  $ mkdir dirorfile/cdir
+  $ echo cfile > dirorfile/cdir/cfile
   $ hg ci -Am m
-  adding dir/afile
+  adding dirorfile/cdir/cfile
   $ cd ../b
-  $ touch dir
+  $ touch dirorfile
   $ hg ci -Am m
-  adding dir
+  adding dirorfile
   $ hg pull ../a
   pulling from ../a
   searching for changes
@@ -62,7 +63,9 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
-  abort: Not a directory: $TESTTMP/b/dir/afile
+  cannot write dirorfile/cdir/cfile, local file $TESTTMP/b/dirorfile conflicts with its path
+  would you like to (A)bort operation or (S)kip file dirorfile/cdir/cfile? [a] a
+  abort: local file $TESTTMP/b/dirorfile conflicts with paths of files dirorfile/cdir/cfile to be written
   [255]
   $ cd ../a
   $ hg pull ../b
@@ -74,15 +77,15 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
-  cannot merge file dir, existing directory $TESTTMP/a/dir is in the way
+  cannot merge file dirorfile, existing directory $TESTTMP/a/dirorfile is in the way
   would you like to (A)bort operation or (S)kip writing the file? [a] a
-  abort: cannot write file dir conflicting with local directory $TESTTMP/a/dir
+  abort: cannot write file dirorfile conflicting with local directory $TESTTMP/a/dirorfile
   [255]
   $ cd ../b
-  $ rm dir
+  $ rm dirorfile
   $ touch emptydir
   $ hg ci -Am m
-  removing dir
+  removing dirorfile
   adding emptydir
   $ cd ../a
   $ hg pull ../b


More information about the Mercurial-devel mailing list