##// END OF EJS Templates
relink/progress: correctly handle the no file to relink case
Benoit Boissinot -
r11052:d6094402 stable
parent child Browse files
Show More
@@ -1,159 +1,159
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 cmdutil, 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 cmdutil.remoteui(repo, opts),
43 43 ui.expandpath(origin or 'default-relink', origin or 'default'))
44 44 if not src.local():
45 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 locallock = repo.lock()
48 48 try:
49 49 remotelock = src.lock()
50 50 try:
51 51 candidates = sorted(collect(src.store.path, ui))
52 52 targets = prune(candidates, src.store.path, repo.store.path, ui)
53 53 do_relink(src.store.path, repo.store.path, targets, ui)
54 54 finally:
55 55 remotelock.release()
56 56 finally:
57 57 locallock.release()
58 58
59 59 def collect(src, ui):
60 60 seplen = len(os.path.sep)
61 61 candidates = []
62 62 for dirpath, dirnames, filenames in os.walk(src):
63 63 relpath = dirpath[len(src) + seplen:]
64 64 for filename in filenames:
65 65 if not filename[-2:] in ('.d', '.i'):
66 66 continue
67 67 st = os.stat(os.path.join(dirpath, filename))
68 68 if not stat.S_ISREG(st.st_mode):
69 69 continue
70 70 candidates.append((os.path.join(relpath, filename), st))
71 71
72 72 ui.status(_('collected %d candidate storage files\n') % len(candidates))
73 73 return candidates
74 74
75 75 def prune(candidates, src, dst, ui):
76 76 def linkfilter(src, dst, st):
77 77 try:
78 78 ts = os.stat(dst)
79 79 except OSError:
80 80 # Destination doesn't have this file?
81 81 return False
82 82 if util.samefile(src, dst):
83 83 return False
84 84 if not util.samedevice(src, dst):
85 85 # No point in continuing
86 86 raise util.Abort(
87 87 _('source and destination are on different devices'))
88 88 if st.st_size != ts.st_size:
89 89 return False
90 90 return st
91 91
92 92 targets = []
93 93 for fn, st in candidates:
94 94 srcpath = os.path.join(src, fn)
95 95 tgt = os.path.join(dst, fn)
96 96 ts = linkfilter(srcpath, tgt, st)
97 97 if not ts:
98 98 ui.debug(_('not linkable: %s\n') % fn)
99 99 continue
100 100 targets.append((fn, ts.st_size))
101 101
102 102 ui.status(_('pruned down to %d probably relinkable files\n') % len(targets))
103 103 return targets
104 104
105 105 def do_relink(src, dst, files, ui):
106 106 def relinkfile(src, dst):
107 107 bak = dst + '.bak'
108 108 os.rename(dst, bak)
109 109 try:
110 110 util.os_link(src, dst)
111 111 except OSError:
112 112 os.rename(bak, dst)
113 113 raise
114 114 os.remove(bak)
115 115
116 116 CHUNKLEN = 65536
117 117 relinked = 0
118 118 savedbytes = 0
119 119
120 120 pos = 0
121 121 total = len(files)
122 122 for f, sz in files:
123 123 pos += 1
124 124 source = os.path.join(src, f)
125 125 tgt = os.path.join(dst, f)
126 126 # Binary mode, so that read() works correctly, especially on Windows
127 127 sfp = file(source, 'rb')
128 128 dfp = file(tgt, 'rb')
129 129 sin = sfp.read(CHUNKLEN)
130 130 while sin:
131 131 din = dfp.read(CHUNKLEN)
132 132 if sin != din:
133 133 break
134 134 sin = sfp.read(CHUNKLEN)
135 135 sfp.close()
136 136 dfp.close()
137 137 if sin:
138 138 ui.debug(_('not linkable: %s\n') % f)
139 139 continue
140 140 try:
141 141 relinkfile(source, tgt)
142 142 ui.progress(_('relinking'), pos, f, _(' files'), total)
143 143 relinked += 1
144 144 savedbytes += sz
145 145 except OSError, inst:
146 146 ui.warn('%s: %s\n' % (tgt, str(inst)))
147 147
148 ui.progress(_('relinking'), None, f, _(' files'), total)
148 ui.progress(_('relinking'), None, unit=_(' files'), total=total)
149 149
150 150 ui.status(_('relinked %d files (%d bytes reclaimed)\n') %
151 151 (relinked, savedbytes))
152 152
153 153 cmdtable = {
154 154 'relink': (
155 155 relink,
156 156 [],
157 157 _('[ORIGIN]')
158 158 )
159 159 }
General Comments 0
You need to be logged in to leave comments. Login now