copy/rename to a removed destination file
authorRobin Farine <robin.farine@terminus.org>
Tue, 28 Feb 2006 23:47:40 -0800
changeset 1822 64df4220b349
parent 1815 3e2a2f230296
child 1823 adef8661c8f9
copy/rename to a removed destination file When the destination of a copy or rename operation has been marked for removal, we need to restore it before we overwrite it with the new content. This also handles the case of idempotent renames, i.e. hg rename "a" "b" hg rename "b" "a"
mercurial/commands.py
tests/test-rename
tests/test-rename.out
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -825,7 +825,8 @@ def commit(ui, repo, *pats, **opts):
     except ValueError, inst:
         raise util.Abort(str(inst))
 
-def docopy(ui, repo, pats, opts):
+def docopy(ui, repo, pats, opts, wlock):
+    # called with the repo lock held
     cwd = repo.getcwd()
     errors = 0
     copied = []
@@ -871,8 +872,16 @@ def docopy(ui, repo, pats, opts):
             if not os.path.isdir(targetdir):
                 os.makedirs(targetdir)
             try:
-                shutil.copyfile(relsrc, reltarget)
-                shutil.copymode(relsrc, reltarget)
+                restore = repo.dirstate.state(abstarget) == 'r'
+                if restore:
+                    repo.undelete([abstarget], wlock)
+                try:
+                    shutil.copyfile(relsrc, reltarget)
+                    shutil.copymode(relsrc, reltarget)
+                    restore = False
+                finally:
+                    if restore:
+                        repo.remove([abstarget], wlock)
             except shutil.Error, inst:
                 raise util.Abort(str(inst))
             except IOError, inst:
@@ -886,7 +895,8 @@ def docopy(ui, repo, pats, opts):
         if ui.verbose or not exact:
             ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
         targets[abstarget] = abssrc
-        repo.copy(origsrc, abstarget)
+        if abstarget != origsrc:
+            repo.copy(origsrc, abstarget, wlock)
         copied.append((abssrc, relsrc, exact))
 
     def targetpathfn(pat, dest, srcs):
@@ -994,7 +1004,12 @@ def copy(ui, repo, *pats, **opts):
     should properly record copied files, this information is not yet
     fully used by merge, nor fully reported by log.
     """
-    errs, copied = docopy(ui, repo, pats, opts)
+    try:
+        wlock = repo.wlock(0)
+        errs, copied = docopy(ui, repo, pats, opts, wlock)
+    except lock.LockHeld, inst:
+        ui.warn(_("repository lock held by %s\n") % inst.args[0])
+        errs = 1
     return errs
 
 def debugancestor(ui, index, rev1, rev2):
@@ -1953,13 +1968,18 @@ def rename(ui, repo, *pats, **opts):
     should properly record rename files, this information is not yet
     fully used by merge, nor fully reported by log.
     """
-    errs, copied = docopy(ui, repo, pats, opts)
-    names = []
-    for abs, rel, exact in copied:
-        if ui.verbose or not exact:
-            ui.status(_('removing %s\n') % rel)
-        names.append(abs)
-    repo.remove(names, unlink=True)
+    try:
+        wlock = repo.wlock(0)
+        errs, copied = docopy(ui, repo, pats, opts, wlock)
+        names = []
+        for abs, rel, exact in copied:
+            if ui.verbose or not exact:
+                ui.status(_('removing %s\n') % rel)
+            names.append(abs)
+        repo.remove(names, True, wlock)
+    except lock.LockHeld, inst:
+        ui.warn(_("repository lock held by %s\n") % inst.args[0])
+        errs = 1
     return errs
 
 def revert(ui, repo, *pats, **opts):
--- a/tests/test-rename
+++ b/tests/test-rename
@@ -171,3 +171,11 @@ mv d1/bb d1/bc
 hg rename --after d1/bb d1/bc
 hg status
 hg update -C
+
+echo "# idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)"
+hg rename d1/b d1/bb
+echo "some stuff added to d1/bb" >> d1/bb
+hg rename d1/bb d1/b
+hg status
+hg debugstate | grep copy
+hg update -C
--- a/tests/test-rename.out
+++ b/tests/test-rename.out
@@ -252,3 +252,5 @@ R d1/b
 # transitive rename --after
 A d1/bc
 R d1/b
+# idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)
+M d1/b