##// END OF EJS Templates
relink: mark abort message for translation
Martin Geisler -
r13658:6cc30609 stable
parent child Browse files
Show More
@@ -1,184 +1,184 b''
1 1 # Mercurial extension to provide 'hg relink' command
2 2 #
3 3 # Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 """recreates hardlinks between repository clones"""
9 9
10 10 from mercurial import hg, util
11 11 from mercurial.i18n import _
12 12 import os, stat
13 13
14 14 def relink(ui, repo, origin=None, **opts):
15 15 """recreate hardlinks between two repositories
16 16
17 17 When repositories are cloned locally, their data files will be
18 18 hardlinked so that they only use the space of a single repository.
19 19
20 20 Unfortunately, subsequent pulls into either repository will break
21 21 hardlinks for any files touched by the new changesets, even if
22 22 both repositories end up pulling the same changes.
23 23
24 24 Similarly, passing --rev to "hg clone" will fail to use any
25 25 hardlinks, falling back to a complete copy of the source
26 26 repository.
27 27
28 28 This command lets you recreate those hardlinks and reclaim that
29 29 wasted space.
30 30
31 31 This repository will be relinked to share space with ORIGIN, which
32 32 must be on the same local disk. If ORIGIN is omitted, looks for
33 33 "default-relink", then "default", in [paths].
34 34
35 35 Do not attempt any read operations on this repository while the
36 36 command is running. (Both repositories will be locked against
37 37 writes.)
38 38 """
39 39 if not hasattr(util, 'samefile') or not hasattr(util, 'samedevice'):
40 40 raise util.Abort(_('hardlinks are not supported on this system'))
41 41 src = hg.repository(
42 42 hg.remoteui(repo, opts),
43 43 ui.expandpath(origin or 'default-relink', origin or 'default'))
44 44 if not src.local():
45 raise util.Abort('must specify local origin repository')
45 raise util.Abort(_('must specify local origin repository'))
46 46 ui.status(_('relinking %s to %s\n') % (src.store.path, repo.store.path))
47 47 if repo.root == src.root:
48 48 ui.status(_('there is nothing to relink\n'))
49 49 return
50 50
51 51 locallock = repo.lock()
52 52 try:
53 53 remotelock = src.lock()
54 54 try:
55 55 candidates = sorted(collect(src, ui))
56 56 targets = prune(candidates, src.store.path, repo.store.path, ui)
57 57 do_relink(src.store.path, repo.store.path, targets, ui)
58 58 finally:
59 59 remotelock.release()
60 60 finally:
61 61 locallock.release()
62 62
63 63 def collect(src, ui):
64 64 seplen = len(os.path.sep)
65 65 candidates = []
66 66 live = len(src['tip'].manifest())
67 67 # Your average repository has some files which were deleted before
68 68 # the tip revision. We account for that by assuming that there are
69 69 # 3 tracked files for every 2 live files as of the tip version of
70 70 # the repository.
71 71 #
72 72 # mozilla-central as of 2010-06-10 had a ratio of just over 7:5.
73 73 total = live * 3 // 2
74 74 src = src.store.path
75 75 pos = 0
76 76 ui.status(_("tip has %d files, estimated total number of files: %s\n")
77 77 % (live, total))
78 78 for dirpath, dirnames, filenames in os.walk(src):
79 79 dirnames.sort()
80 80 relpath = dirpath[len(src) + seplen:]
81 81 for filename in sorted(filenames):
82 82 if not filename[-2:] in ('.d', '.i'):
83 83 continue
84 84 st = os.stat(os.path.join(dirpath, filename))
85 85 if not stat.S_ISREG(st.st_mode):
86 86 continue
87 87 pos += 1
88 88 candidates.append((os.path.join(relpath, filename), st))
89 89 ui.progress(_('collecting'), pos, filename, _('files'), total)
90 90
91 91 ui.progress(_('collecting'), None)
92 92 ui.status(_('collected %d candidate storage files\n') % len(candidates))
93 93 return candidates
94 94
95 95 def prune(candidates, src, dst, ui):
96 96 def linkfilter(src, dst, st):
97 97 try:
98 98 ts = os.stat(dst)
99 99 except OSError:
100 100 # Destination doesn't have this file?
101 101 return False
102 102 if util.samefile(src, dst):
103 103 return False
104 104 if not util.samedevice(src, dst):
105 105 # No point in continuing
106 106 raise util.Abort(
107 107 _('source and destination are on different devices'))
108 108 if st.st_size != ts.st_size:
109 109 return False
110 110 return st
111 111
112 112 targets = []
113 113 total = len(candidates)
114 114 pos = 0
115 115 for fn, st in candidates:
116 116 pos += 1
117 117 srcpath = os.path.join(src, fn)
118 118 tgt = os.path.join(dst, fn)
119 119 ts = linkfilter(srcpath, tgt, st)
120 120 if not ts:
121 121 ui.debug(_('not linkable: %s\n') % fn)
122 122 continue
123 123 targets.append((fn, ts.st_size))
124 124 ui.progress(_('pruning'), pos, fn, _('files'), total)
125 125
126 126 ui.progress(_('pruning'), None)
127 127 ui.status(_('pruned down to %d probably relinkable files\n') % len(targets))
128 128 return targets
129 129
130 130 def do_relink(src, dst, files, ui):
131 131 def relinkfile(src, dst):
132 132 bak = dst + '.bak'
133 133 os.rename(dst, bak)
134 134 try:
135 135 util.os_link(src, dst)
136 136 except OSError:
137 137 os.rename(bak, dst)
138 138 raise
139 139 os.remove(bak)
140 140
141 141 CHUNKLEN = 65536
142 142 relinked = 0
143 143 savedbytes = 0
144 144
145 145 pos = 0
146 146 total = len(files)
147 147 for f, sz in files:
148 148 pos += 1
149 149 source = os.path.join(src, f)
150 150 tgt = os.path.join(dst, f)
151 151 # Binary mode, so that read() works correctly, especially on Windows
152 152 sfp = file(source, 'rb')
153 153 dfp = file(tgt, 'rb')
154 154 sin = sfp.read(CHUNKLEN)
155 155 while sin:
156 156 din = dfp.read(CHUNKLEN)
157 157 if sin != din:
158 158 break
159 159 sin = sfp.read(CHUNKLEN)
160 160 sfp.close()
161 161 dfp.close()
162 162 if sin:
163 163 ui.debug(_('not linkable: %s\n') % f)
164 164 continue
165 165 try:
166 166 relinkfile(source, tgt)
167 167 ui.progress(_('relinking'), pos, f, _('files'), total)
168 168 relinked += 1
169 169 savedbytes += sz
170 170 except OSError, inst:
171 171 ui.warn('%s: %s\n' % (tgt, str(inst)))
172 172
173 173 ui.progress(_('relinking'), None)
174 174
175 175 ui.status(_('relinked %d files (%d bytes reclaimed)\n') %
176 176 (relinked, savedbytes))
177 177
178 178 cmdtable = {
179 179 'relink': (
180 180 relink,
181 181 [],
182 182 _('[ORIGIN]')
183 183 )
184 184 }
General Comments 0
You need to be logged in to leave comments. Login now