contrib/hg-relink
changeset 4244 7663780b55a7
child 4260 29eb88bd5c8d
equal deleted inserted replaced
4243:815ad65cfca9 4244:7663780b55a7
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms
       
     6 # of the GNU General Public License, incorporated herein by reference.
       
     7 
       
     8 import os, sys
       
     9 
       
    10 class ConfigError(Exception): pass
       
    11 
       
    12 def usage():
       
    13     print """relink <source> <destination>
       
    14     Recreate hard links between source and destination repositories"""
       
    15 
       
    16 class Config:
       
    17     def __init__(self, args):
       
    18         if len(args) != 3:
       
    19             raise ConfigError("wrong number of arguments")
       
    20         self.src = os.path.abspath(args[1])
       
    21         self.dst = os.path.abspath(args[2])
       
    22         for d in (self.src, self.dst):
       
    23             if not os.path.exists(os.path.join(d, '.hg')):
       
    24                 raise ConfigError("%s: not a mercurial repository" % d)
       
    25 
       
    26 try:
       
    27     cfg = Config(sys.argv)
       
    28 except ConfigError, inst:
       
    29     print str(inst)
       
    30     usage()
       
    31     sys.exit(1)
       
    32 
       
    33 def collect(src):
       
    34     seplen = len(os.path.sep)
       
    35     candidates = []
       
    36     for dirpath, dirnames, filenames in os.walk(src):
       
    37         relpath = dirpath[len(src) + seplen:]
       
    38         for filename in filenames:
       
    39             if not (filename.endswith('.i') or filename.endswith('.d')):
       
    40                 continue
       
    41             st = os.stat(os.path.join(dirpath, filename))
       
    42             candidates.append((os.path.join(relpath, filename), st))
       
    43 
       
    44     return candidates
       
    45 
       
    46 def prune(candidates, dst):
       
    47     targets = []
       
    48     for fn, st in candidates:
       
    49         tgt = os.path.join(dst, fn)
       
    50         try:
       
    51             ts = os.stat(tgt)
       
    52         except OSError:
       
    53             # Destination doesn't have this file?
       
    54             continue
       
    55         if st.st_ino == ts.st_ino:
       
    56             continue
       
    57         if st.st_dev != ts.st_dev:
       
    58             raise Exception('Source and destination are on different devices')
       
    59         if st.st_size != ts.st_size:
       
    60             continue
       
    61         targets.append((fn, ts.st_size))
       
    62 
       
    63     return targets
       
    64 
       
    65 def relink(src, dst, files):
       
    66     CHUNKLEN = 65536
       
    67     relinked = 0
       
    68     savedbytes = 0
       
    69 
       
    70     for f, sz in files:
       
    71         source = os.path.join(src, f)
       
    72         tgt = os.path.join(dst, f)
       
    73         sfp = file(source)
       
    74         dfp = file(tgt)
       
    75         sin = sfp.read(CHUNKLEN)
       
    76         while sin:
       
    77             din = dfp.read(CHUNKLEN)
       
    78             if sin != din:
       
    79                 break
       
    80             sin = sfp.read(CHUNKLEN)
       
    81         if sin:
       
    82             continue
       
    83         try:
       
    84             os.rename(tgt, tgt + '.bak')
       
    85             try:
       
    86                 os.link(source, tgt)
       
    87             except OSError:
       
    88                 os.rename(tgt + '.bak', tgt)
       
    89                 raise
       
    90             print 'Relinked %s' % f
       
    91             relinked += 1
       
    92             savedbytes += sz
       
    93             os.remove(tgt + '.bak')
       
    94         except OSError, inst:
       
    95             print '%s: %s' % (tgt, str(inst))
       
    96 
       
    97     print 'Relinked %d files (%d bytes reclaimed)' % (relinked, savedbytes)
       
    98 
       
    99 src = os.path.join(cfg.src, '.hg')
       
   100 dst = os.path.join(cfg.dst, '.hg')
       
   101 candidates = collect(src)
       
   102 targets = prune(candidates, dst)
       
   103 relink(src, dst, targets)