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) |