contrib/hg-relink
changeset 4260 29eb88bd5c8d
parent 4244 7663780b55a7
equal deleted inserted replaced
4259:bda63383d529 4260:29eb88bd5c8d
    21         self.dst = os.path.abspath(args[2])
    21         self.dst = os.path.abspath(args[2])
    22         for d in (self.src, self.dst):
    22         for d in (self.src, self.dst):
    23             if not os.path.exists(os.path.join(d, '.hg')):
    23             if not os.path.exists(os.path.join(d, '.hg')):
    24                 raise ConfigError("%s: not a mercurial repository" % d)
    24                 raise ConfigError("%s: not a mercurial repository" % d)
    25 
    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):
    26 def collect(src):
    34     seplen = len(os.path.sep)
    27     seplen = len(os.path.sep)
    35     candidates = []
    28     candidates = []
    36     for dirpath, dirnames, filenames in os.walk(src):
    29     for dirpath, dirnames, filenames in os.walk(src):
    37         relpath = dirpath[len(src) + seplen:]
    30         relpath = dirpath[len(src) + seplen:]
    38         for filename in filenames:
    31         for filename in filenames:
    39             if not (filename.endswith('.i') or filename.endswith('.d')):
    32             if not filename.endswith('.i'):
    40                 continue
    33                 continue
    41             st = os.stat(os.path.join(dirpath, filename))
    34             st = os.stat(os.path.join(dirpath, filename))
    42             candidates.append((os.path.join(relpath, filename), st))
    35             candidates.append((os.path.join(relpath, filename), st))
    43 
    36 
    44     return candidates
    37     return candidates
    45 
    38 
    46 def prune(candidates, dst):
    39 def prune(candidates, dst):
       
    40     def getdatafile(path):
       
    41         if not path.endswith('.i'):
       
    42             return None, None
       
    43         df = path[:-1] + 'd'
       
    44         try:
       
    45             st = os.stat(df)
       
    46         except OSError:
       
    47             return None, None
       
    48         return df, st
       
    49 
       
    50     def linkfilter(dst, st):
       
    51         try:
       
    52             ts = os.stat(dst)
       
    53         except OSError:
       
    54             # Destination doesn't have this file?
       
    55             return False
       
    56         if st.st_ino == ts.st_ino:
       
    57             return False
       
    58         if st.st_dev != ts.st_dev:
       
    59             # No point in continuing
       
    60             raise Exception('Source and destination are on different devices')
       
    61         if st.st_size != ts.st_size:
       
    62             # TODO: compare revlog heads
       
    63             return False
       
    64         return st
       
    65 
    47     targets = []
    66     targets = []
    48     for fn, st in candidates:
    67     for fn, st in candidates:
    49         tgt = os.path.join(dst, fn)
    68         tgt = os.path.join(dst, fn)
    50         try:
    69         ts = linkfilter(tgt, st)
    51             ts = os.stat(tgt)
    70         if not ts:
    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
    71             continue
    61         targets.append((fn, ts.st_size))
    72         targets.append((fn, ts.st_size))
       
    73         df, ts = getdatafile(tgt)
       
    74         if df:
       
    75             targets.append((fn[:-1] + 'd', ts.st_size))
    62 
    76 
    63     return targets
    77     return targets
    64 
    78 
    65 def relink(src, dst, files):
    79 def relink(src, dst, files):
       
    80     def relinkfile(src, dst):
       
    81         bak = dst + '.bak'
       
    82         os.rename(dst, bak)
       
    83         try:
       
    84             os.link(src, dst)
       
    85         except OSError:
       
    86             os.rename(bak, dst)
       
    87             raise
       
    88         os.remove(bak)
       
    89 
    66     CHUNKLEN = 65536
    90     CHUNKLEN = 65536
    67     relinked = 0
    91     relinked = 0
    68     savedbytes = 0
    92     savedbytes = 0
    69 
    93 
    70     for f, sz in files:
    94     for f, sz in files:
    79                 break
   103                 break
    80             sin = sfp.read(CHUNKLEN)
   104             sin = sfp.read(CHUNKLEN)
    81         if sin:
   105         if sin:
    82             continue
   106             continue
    83         try:
   107         try:
    84             os.rename(tgt, tgt + '.bak')
   108             relinkfile(source, tgt)
    85             try:
       
    86                 os.link(source, tgt)
       
    87             except OSError:
       
    88                 os.rename(tgt + '.bak', tgt)
       
    89                 raise
       
    90             print 'Relinked %s' % f
   109             print 'Relinked %s' % f
    91             relinked += 1
   110             relinked += 1
    92             savedbytes += sz
   111             savedbytes += sz
    93             os.remove(tgt + '.bak')
       
    94         except OSError, inst:
   112         except OSError, inst:
    95             print '%s: %s' % (tgt, str(inst))
   113             print '%s: %s' % (tgt, str(inst))
    96 
   114 
    97     print 'Relinked %d files (%d bytes reclaimed)' % (relinked, savedbytes)
   115     print 'Relinked %d files (%d bytes reclaimed)' % (relinked, savedbytes)
       
   116 
       
   117 try:
       
   118     cfg = Config(sys.argv)
       
   119 except ConfigError, inst:
       
   120     print str(inst)
       
   121     usage()
       
   122     sys.exit(1)
    98 
   123 
    99 src = os.path.join(cfg.src, '.hg')
   124 src = os.path.join(cfg.src, '.hg')
   100 dst = os.path.join(cfg.dst, '.hg')
   125 dst = os.path.join(cfg.dst, '.hg')
   101 candidates = collect(src)
   126 candidates = collect(src)
   102 targets = prune(candidates, dst)
   127 targets = prune(candidates, dst)