Merge with crew
authorMatt Mackall <mpm@selenic.com>
Sat, 24 Mar 2007 02:57:27 -0500
changeset 4276 cb6107f78b92
parent 4269 73c918c71300 (current diff)
parent 4275 81402b2b294d (diff)
child 4277 0ce23256e454
Merge with crew
mercurial/commands.py
mercurial/localrepo.py
--- a/contrib/hg-relink	Sat Mar 24 02:47:33 2007 -0500
+++ b/contrib/hg-relink	Sat Mar 24 02:57:27 2007 -0500
@@ -23,20 +23,13 @@
             if not os.path.exists(os.path.join(d, '.hg')):
                 raise ConfigError("%s: not a mercurial repository" % d)
 
-try:
-    cfg = Config(sys.argv)
-except ConfigError, inst:
-    print str(inst)
-    usage()
-    sys.exit(1)
-
 def collect(src):
     seplen = len(os.path.sep)
     candidates = []
     for dirpath, dirnames, filenames in os.walk(src):
         relpath = dirpath[len(src) + seplen:]
         for filename in filenames:
-            if not (filename.endswith('.i') or filename.endswith('.d')):
+            if not filename.endswith('.i'):
                 continue
             st = os.stat(os.path.join(dirpath, filename))
             candidates.append((os.path.join(relpath, filename), st))
@@ -44,25 +37,56 @@
     return candidates
 
 def prune(candidates, dst):
+    def getdatafile(path):
+        if not path.endswith('.i'):
+            return None, None
+        df = path[:-1] + 'd'
+        try:
+            st = os.stat(df)
+        except OSError:
+            return None, None
+        return df, st
+
+    def linkfilter(dst, st):
+        try:
+            ts = os.stat(dst)
+        except OSError:
+            # Destination doesn't have this file?
+            return False
+        if st.st_ino == ts.st_ino:
+            return False
+        if st.st_dev != ts.st_dev:
+            # No point in continuing
+            raise Exception('Source and destination are on different devices')
+        if st.st_size != ts.st_size:
+            # TODO: compare revlog heads
+            return False
+        return st
+
     targets = []
     for fn, st in candidates:
         tgt = os.path.join(dst, fn)
-        try:
-            ts = os.stat(tgt)
-        except OSError:
-            # Destination doesn't have this file?
-            continue
-        if st.st_ino == ts.st_ino:
-            continue
-        if st.st_dev != ts.st_dev:
-            raise Exception('Source and destination are on different devices')
-        if st.st_size != ts.st_size:
+        ts = linkfilter(tgt, st)
+        if not ts:
             continue
         targets.append((fn, ts.st_size))
+        df, ts = getdatafile(tgt)
+        if df:
+            targets.append((fn[:-1] + 'd', ts.st_size))
 
     return targets
 
 def relink(src, dst, files):
+    def relinkfile(src, dst):
+        bak = dst + '.bak'
+        os.rename(dst, bak)
+        try:
+            os.link(src, dst)
+        except OSError:
+            os.rename(bak, dst)
+            raise
+        os.remove(bak)
+
     CHUNKLEN = 65536
     relinked = 0
     savedbytes = 0
@@ -81,21 +105,22 @@
         if sin:
             continue
         try:
-            os.rename(tgt, tgt + '.bak')
-            try:
-                os.link(source, tgt)
-            except OSError:
-                os.rename(tgt + '.bak', tgt)
-                raise
+            relinkfile(source, tgt)
             print 'Relinked %s' % f
             relinked += 1
             savedbytes += sz
-            os.remove(tgt + '.bak')
         except OSError, inst:
             print '%s: %s' % (tgt, str(inst))
 
     print 'Relinked %d files (%d bytes reclaimed)' % (relinked, savedbytes)
 
+try:
+    cfg = Config(sys.argv)
+except ConfigError, inst:
+    print str(inst)
+    usage()
+    sys.exit(1)
+
 src = os.path.join(cfg.src, '.hg')
 dst = os.path.join(cfg.dst, '.hg')
 candidates = collect(src)
--- a/mercurial/commands.py	Sat Mar 24 02:47:33 2007 -0500
+++ b/mercurial/commands.py	Sat Mar 24 02:57:27 2007 -0500
@@ -2255,7 +2255,8 @@
         def handle(xlist, dobackup):
             xlist[0].append(abs)
             update[abs] = 1
-            if dobackup and not opts['no_backup'] and os.path.exists(rel):
+            if (dobackup and not opts['no_backup'] and
+                (os.path.islink(rel) or os.path.exists(rel))):
                 bakname = "%s.orig" % rel
                 ui.note(_('saving current version of %s as %s\n') %
                         (rel, bakname))
--- a/mercurial/localrepo.py	Sat Mar 24 02:47:33 2007 -0500
+++ b/mercurial/localrepo.py	Sat Mar 24 02:57:27 2007 -0500
@@ -102,11 +102,6 @@
         self.filterpats = {}
         self.transhandle = None
 
-        self._link = lambda x: False
-        if util.checklink(self.root):
-            r = self.root # avoid circular reference in lambda
-            self._link = lambda x: util.is_link(os.path.join(r, x))
-
         self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
 
     def url(self):
@@ -505,6 +500,9 @@
     def wfile(self, f, mode='r'):
         return self.wopener(f, mode)
 
+    def _link(self, f):
+        return os.path.islink(self.wjoin(f))
+
     def _filter(self, filter, filename, data):
         if filter not in self.filterpats:
             l = []
@@ -1052,10 +1050,11 @@
 
     def copy(self, source, dest, wlock=None):
         p = self.wjoin(dest)
-        if not os.path.exists(p):
+        if not (os.path.exists(p) or os.path.islink(p)):
             self.ui.warn(_("%s does not exist!\n") % dest)
-        elif not os.path.isfile(p):
-            self.ui.warn(_("copy failed: %s is not a file\n") % dest)
+        elif not (os.path.isfile(p) or os.path.islink(p)):
+            self.ui.warn(_("copy failed: %s is not a file or a "
+                           "symbolic link\n") % dest)
         else:
             if not wlock:
                 wlock = self.wlock()
--- a/mercurial/util.py	Sat Mar 24 02:47:33 2007 -0500
+++ b/mercurial/util.py	Sat Mar 24 02:57:27 2007 -0500
@@ -614,11 +614,18 @@
 
 def copyfile(src, dest):
     "copy a file, preserving mode"
-    try:
-        shutil.copyfile(src, dest)
-        shutil.copymode(src, dest)
-    except shutil.Error, inst:
-        raise Abort(str(inst))
+    if os.path.islink(src):
+        try:
+            os.unlink(dest)
+        except:
+            pass
+        os.symlink(os.readlink(src), dest)
+    else:
+        try:
+            shutil.copyfile(src, dest)
+            shutil.copymode(src, dest)
+        except shutil.Error, inst:
+            raise Abort(str(inst))
 
 def copyfiles(src, dst, hardlink=None):
     """Copy a directory tree using hardlinks if possible"""
@@ -785,7 +792,7 @@
 def linkfunc(path, fallback):
     '''return an is_link() function with default to fallback'''
     if checklink(path):
-        return lambda x: is_link(os.path.join(path, x))
+        return lambda x: os.path.islink(os.path.join(path, x))
     return fallback
 
 # Platform specific variants
@@ -961,10 +968,6 @@
         else:
             os.chmod(f, s & 0666)
 
-    def is_link(f):
-        """check whether a file is a symlink"""
-        return (os.lstat(f).st_mode & 0120000 == 0120000)
-
     def set_link(f, mode):
         """make a file a symbolic link/regular file
 
@@ -972,7 +975,7 @@
         if a link is changed to a file, its link data become its contents
         """
 
-        m = is_link(f)
+        m = os.path.islink(f)
         if m == bool(mode):
             return
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-symlink-basic	Sat Mar 24 02:57:27 2007 -0500
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+cat >> readlink.py <<EOF
+import os
+import sys
+
+for f in sys.argv[1:]:
+    print f, '->', os.readlink(f)
+EOF
+
+hg init a
+cd a
+ln -s nothing dangling
+hg add dangling
+hg commit -m 'add symlink' -d '0 0'
+
+hg tip -v
+hg manifest --debug
+echo '% rev 0:'
+python ../readlink.py dangling
+
+rm dangling
+ln -s void dangling
+hg commit -m 'change symlink'
+echo '% rev 1:'
+python ../readlink.py dangling
+
+echo '% modifying link'
+rm dangling
+ln -s empty dangling
+python ../readlink.py dangling
+
+echo '% reverting to rev 0:'
+hg revert -r 0 -a
+python ../readlink.py dangling
+
+echo '% backups:'
+python ../readlink.py *.orig
+
+rm *.orig
+hg up -C
+echo '% copies'
+hg cp -v dangling dangling2
+hg st -Cmard
+python ../readlink.py dangling dangling2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-symlink-basic.out	Sat Mar 24 02:57:27 2007 -0500
@@ -0,0 +1,28 @@
+changeset:   0:cabd88b706fc
+tag:         tip
+user:        test
+date:        Thu Jan 01 00:00:00 1970 +0000
+files:       dangling
+description:
+add symlink
+
+
+2564acbe54bbbedfbf608479340b359f04597f80 644 dangling
+% rev 0:
+dangling -> nothing
+% rev 1:
+dangling -> void
+% modifying link
+dangling -> empty
+% reverting to rev 0:
+reverting dangling
+dangling -> nothing
+% backups:
+dangling.orig -> empty
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% copies
+copying dangling to dangling2
+A dangling2
+  dangling
+dangling -> void
+dangling2 -> void