# HG changeset patch # User Patrick Mezard # Date 1262993156 -3600 # Node ID 489b0caf21edefb6ca0993a83440a37201ea3b99 # Parent 843f6ee6d14bf7ce2004cf3583ca67c75308f4bc# Parent 500d09be7ace66663af9e1bd150b414578a4b8d6 Merge with crew-stable diff -r 843f6ee6d14b -r 489b0caf21ed hgext/relink.py --- a/hgext/relink.py Thu Jan 07 16:06:36 2010 +0100 +++ b/hgext/relink.py Sat Jan 09 00:25:56 2010 +0100 @@ -36,6 +36,8 @@ command is running. (Both repositories will be locked against writes.) """ + if not hasattr(util, 'samefile') or not hasattr(util, 'samedevice'): + raise util.Abort(_('hardlinks are not supported on this system')) src = hg.repository( cmdutil.remoteui(repo, opts), ui.expandpath(origin or 'default-relink', origin or 'default')) @@ -47,7 +49,7 @@ remotelock = src.lock() try: candidates = collect(src.store.path, ui) - targets = prune(candidates, repo.store.path, ui) + targets = prune(candidates, src.store.path, repo.store.path, ui) do_relink(src.store.path, repo.store.path, targets, ui) finally: remotelock.release() @@ -70,16 +72,16 @@ ui.status(_('collected %d candidate storage files\n') % len(candidates)) return candidates -def prune(candidates, dst, ui): - def linkfilter(dst, st): +def prune(candidates, src, dst, ui): + def linkfilter(src, dst, st): try: ts = os.stat(dst) except OSError: # Destination doesn't have this file? return False - if st.st_ino == ts.st_ino: + if util.samefile(src, dst): return False - if st.st_dev != ts.st_dev: + if not util.samedevice(src, dst): # No point in continuing raise util.Abort( _('source and destination are on different devices')) @@ -89,8 +91,9 @@ targets = [] for fn, st in candidates: + srcpath = os.path.join(src, fn) tgt = os.path.join(dst, fn) - ts = linkfilter(tgt, st) + ts = linkfilter(srcpath, tgt, st) if not ts: ui.debug(_('not linkable: %s\n') % fn) continue @@ -104,7 +107,7 @@ bak = dst + '.bak' os.rename(dst, bak) try: - os.link(src, dst) + util.os_link(src, dst) except OSError: os.rename(bak, dst) raise @@ -120,14 +123,17 @@ pos += 1 source = os.path.join(src, f) tgt = os.path.join(dst, f) - sfp = file(source) - dfp = file(tgt) + # Binary mode, so that read() works correctly, especially on Windows + sfp = file(source, 'rb') + dfp = file(tgt, 'rb') sin = sfp.read(CHUNKLEN) while sin: din = dfp.read(CHUNKLEN) if sin != din: break sin = sfp.read(CHUNKLEN) + sfp.close() + dfp.close() if sin: ui.debug(_('not linkable: %s\n') % f) continue diff -r 843f6ee6d14b -r 489b0caf21ed mercurial/posix.py --- a/mercurial/posix.py Thu Jan 07 16:06:36 2010 +0100 +++ b/mercurial/posix.py Sat Jan 09 00:25:56 2010 +0100 @@ -105,6 +105,18 @@ def localpath(path): return path +def samefile(fpath1, fpath2): + """Returns whether path1 and path2 refer to the same file. This is only + guaranteed to work for files, not directories.""" + return os.path.samefile(fpath1, fpath2) + +def samedevice(fpath1, fpath2): + """Returns whether fpath1 and fpath2 are on the same device. This is only + guaranteed to work for files, not directories.""" + st1 = os.lstat(fpath1) + st2 = os.lstat(fpath2) + return st1.st_dev == st2.st_dev + if sys.platform == 'darwin': def realpath(path): ''' diff -r 843f6ee6d14b -r 489b0caf21ed mercurial/ui.py --- a/mercurial/ui.py Thu Jan 07 16:06:36 2010 +0100 +++ b/mercurial/ui.py Sat Jan 09 00:25:56 2010 +0100 @@ -389,7 +389,7 @@ if total: pct = 100.0 * pos / total - self.debug('%s:%s %s/%s%s (%4.2g%%)\n' + self.debug('%s:%s %s/%s%s (%4.2f%%)\n' % (topic, item, pos, total, unit, pct)) else: self.debug('%s:%s %s%s\n' % (topic, item, pos, unit)) diff -r 843f6ee6d14b -r 489b0caf21ed mercurial/win32.py --- a/mercurial/win32.py Thu Jan 07 16:06:36 2010 +0100 +++ b/mercurial/win32.py Sat Jan 09 00:25:56 2010 +0100 @@ -37,18 +37,48 @@ except NotImplementedError: # Another fake error win Win98 raise OSError(errno.EINVAL, 'Hardlinking not supported') -def nlinks(pathname): +def _getfileinfo(pathname): """Return number of hardlinks for the given file.""" try: fh = win32file.CreateFile(pathname, win32file.GENERIC_READ, win32file.FILE_SHARE_READ, None, win32file.OPEN_EXISTING, 0, None) - res = win32file.GetFileInformationByHandle(fh) - fh.Close() + try: + return win32file.GetFileInformationByHandle(fh) + finally: + fh.Close() + except pywintypes.error: + return None + +def nlinks(pathname): + """Return number of hardlinks for the given file.""" + res = _getfileinfo(pathname) + if res is not None: return res[7] - except pywintypes.error: + else: return os.lstat(pathname).st_nlink +def samefile(fpath1, fpath2): + """Returns whether fpath1 and fpath2 refer to the same file. This is only + guaranteed to work for files, not directories.""" + res1 = _getfileinfo(fpath1) + res2 = _getfileinfo(fpath2) + if res1 is not None and res2 is not None: + # Index 4 is the volume serial number, and 8 and 9 contain the file ID + return res1[4] == res2[4] and res1[8] == res2[8] and res1[9] == res2[9] + else: + return False + +def samedevice(fpath1, fpath2): + """Returns whether fpath1 and fpath2 are on the same device. This is only + guaranteed to work for files, not directories.""" + res1 = _getfileinfo(fpath1) + res2 = _getfileinfo(fpath2) + if res1 is not None and res2 is not None: + return res1[4] == res2[4] + else: + return False + def testpid(pid): '''return True if pid is still running or unable to determine, False otherwise''' diff -r 843f6ee6d14b -r 489b0caf21ed tests/test-relink --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-relink Sat Jan 09 00:25:56 2010 +0100 @@ -0,0 +1,54 @@ +#!/bin/sh + +echo "[extensions]" >> $HGRCPATH +echo "relink=" >> $HGRCPATH + +fix_path() +{ + tr '\\' / +} + +cat > arelinked.py < .hg/hgrc +echo 'username= A. Foo ' >> .hg/hgrc +echo a > a +echo b > b +hg ci -Am addfile +echo a >> a +echo a >> b +hg ci -Am changefiles +# Test files are read in binary mode +python -c "file('.hg/store/data/dummy.i', 'wb').write('a\r\nb\n')" +cd .. + +echo '% clone and pull to break links' +hg clone --pull -r0 repo clone +cd clone +echo '[ui]' >> .hg/hgrc +echo 'username= A. Baz ' >> .hg/hgrc +hg pull -q +echo b >> b +hg ci -m changeb +python -c "file('.hg/store/data/dummy.i', 'wb').write('a\nb\r\n')" + +echo '% relink' +hg relink --debug | sed 's:relinking.*store:relinking .hg/store:g' \ + | fix_path +cd .. + +echo '% check hardlinks' +python arelinked.py repo/.hg/store/data/a.i clone/.hg/store/data/a.i +python arelinked.py repo/.hg/store/data/b.i clone/.hg/store/data/b.i + diff -r 843f6ee6d14b -r 489b0caf21ed tests/test-relink.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-relink.out Sat Jan 09 00:25:56 2010 +0100 @@ -0,0 +1,25 @@ +% create source repository +adding a +adding b +% clone and pull to break links +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 2 changes to 2 files +updating to branch default +2 files updated, 0 files merged, 0 files removed, 0 files unresolved +created new head +% relink +relinking .hg/store +collected 5 candidate storage files +not linkable: 00changelog.i +not linkable: 00manifest.i +not linkable: data/b.i +pruned down to 2 probably relinkable files +relink: data/a.i 1/2 files (50.00%) +not linkable: data/dummy.i +relinked 1 files (136 bytes reclaimed) +% check hardlinks +repo/.hg/store/data/a.i == clone/.hg/store/data/a.i +repo/.hg/store/data/b.i != clone/.hg/store/data/b.i