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