--- 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