scmutil: handle conflicting files and dirs in origbackuppath
authorMark Thomas <mbthomas@fb.com>
Mon, 02 Oct 2017 14:05:30 -0700
changeset 34543 6fad8059a970
parent 34542 153e4e05e9b3
child 34544 34c8080d12ac
scmutil: handle conflicting files and dirs in origbackuppath When ui.origbackuppath is set, .orig files are stored outside of the working copy. However conflicts can occur when files or directories end up having the same name. These conflicts cause Mercurial to abort, even if they've been created as a result of different backups. Make sure we always replace files or directories in the origbackuppath if they conflict with another file or directory. Test Plan: Add new unit test for conflicting paths. Differential Revision: https://phab.mercurial-scm.org/D680
mercurial/scmutil.py
tests/test-origbackup-conflict.t
--- a/mercurial/scmutil.py	Sun Oct 01 12:21:50 2017 +0100
+++ b/mercurial/scmutil.py	Mon Oct 02 14:05:30 2017 -0700
@@ -38,6 +38,7 @@
     similar,
     url,
     util,
+    vfs,
 )
 
 if pycompat.osname == 'nt':
@@ -573,18 +574,34 @@
     Fall back to default (filepath with .orig suffix) if not specified
     '''
     origbackuppath = ui.config('ui', 'origbackuppath')
-    if origbackuppath is None:
+    if not origbackuppath:
         return filepath + ".orig"
 
-    filepathfromroot = os.path.relpath(filepath, start=repo.root)
-    fullorigpath = repo.wjoin(origbackuppath, filepathfromroot)
+    # Convert filepath from an absolute path into a path inside the repo.
+    filepathfromroot = util.normpath(os.path.relpath(filepath,
+                                                     start=repo.root))
+
+    origvfs = vfs.vfs(repo.wjoin(origbackuppath))
+    origbackupdir = origvfs.dirname(filepathfromroot)
+    if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
+        ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
 
-    origbackupdir = repo.vfs.dirname(fullorigpath)
-    if not repo.vfs.exists(origbackupdir):
-        ui.note(_('creating directory: %s\n') % origbackupdir)
-        util.makedirs(origbackupdir)
+        # Remove any files that conflict with the backup file's path
+        for f in reversed(list(util.finddirs(filepathfromroot))):
+            if origvfs.isfileorlink(f):
+                ui.note(_('removing conflicting file: %s\n')
+                        % origvfs.join(f))
+                origvfs.unlink(f)
+                break
 
-    return fullorigpath
+        origvfs.makedirs(origbackupdir)
+
+    if origvfs.isdir(filepathfromroot):
+        ui.note(_('removing conflicting directory: %s\n')
+                % origvfs.join(filepathfromroot))
+        origvfs.rmtree(filepathfromroot, forcibly=True)
+
+    return origvfs.join(filepathfromroot)
 
 class _containsnode(object):
     """proxy __contains__(node) to container.__contains__ which accepts revs"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-origbackup-conflict.t	Mon Oct 02 14:05:30 2017 -0700
@@ -0,0 +1,118 @@
+Set up repo
+
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > origbackuppath=.hg/origbackups
+  > [merge]
+  > checkunknown=warn
+  > EOF
+  $ hg init repo
+  $ cd repo
+  $ echo base > base
+  $ hg add base
+  $ hg commit -m "base"
+
+Make a dir named b that contains a file
+
+  $ mkdir -p b
+  $ echo c1 > b/c
+  $ hg add b/c
+  $ hg commit -m "c1"
+  $ hg bookmark c1
+
+Peform an update that causes b/c to be backed up
+
+  $ hg up -q 0
+  $ mkdir -p b
+  $ echo c2 > b/c
+  $ hg up --verbose c1
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/origbackups/b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark c1)
+  $ test -f .hg/origbackups/b/c
+
+Make a file named b
+
+  $ hg up -q 0
+  $ echo b1 > b
+  $ hg add b
+  $ hg commit -m b1
+  created new head
+  $ hg bookmark b1
+
+Perform an update that causes b to be backed up - it should replace the backup b dir
+
+  $ hg up -q 0
+  $ echo b2 > b
+  $ hg up --verbose b1
+  resolving manifests
+  b: replacing untracked file
+  getting b
+  removing conflicting directory: $TESTTMP/repo/.hg/origbackups/b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark b1)
+  $ test -f .hg/origbackups/b
+
+Perform an update the causes b/c to be backed up again - it should replace the backup b file
+
+  $ hg up -q 0
+  $ mkdir b
+  $ echo c3 > b/c
+  $ hg up --verbose c1
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/origbackups/b
+  removing conflicting file: $TESTTMP/repo/.hg/origbackups/b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark c1)
+  $ test -d .hg/origbackups/b
+
+Cause a symlink to be backed up that points to a valid location from the backup dir
+
+  $ hg up -q 0
+  $ mkdir ../sym-link-target
+  $ ln -s ../../../sym-link-target b
+  $ hg up b1
+  b: replacing untracked file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark b1)
+  $ readlink .hg/origbackups/b
+  ../../../sym-link-target
+
+Perform an update that causes b/c to be backed up again - it should not go into the target dir
+
+  $ hg up -q 0
+  $ mkdir b
+  $ echo c4 > b/c
+  $ hg up --verbose c1
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/origbackups/b
+  removing conflicting file: $TESTTMP/repo/.hg/origbackups/b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark c1)
+  $ cat .hg/origbackups/b/c
+  c4
+  $ ls ../sym-link-target
+
+Incorrectly configure origbackuppath to be under a file
+
+  $ echo data > .hg/badorigbackups
+  $ hg up -q 0
+  $ mkdir b
+  $ echo c5 > b/c
+  $ hg up --verbose c1 --config ui.origbackuppath=.hg/badorigbackups
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/badorigbackups/b
+  abort: Not a directory: '$TESTTMP/repo/.hg/badorigbackups/b'
+  [255]
+  $ cat .hg/badorigbackups
+  data
+