comparison contrib/hg-relink @ 4244:7663780b55a7

Add hg-relink script to contrib
author Brendan Cully <brendan@kublai.com>
date Mon, 19 Mar 2007 09:36:06 -0700
parents
children 29eb88bd5c8d
comparison
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)