Mercurial > hg > mercurial-crew-with-dirclash
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) |