##// END OF EJS Templates
largefiles: use "ui.configlist()" to get largefiles.patterns configuration...
FUJIWARA Katsunori -
r15579:6c5e6ebe stable
parent child Browse files
Show More
@@ -1,477 +1,475 b''
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''High-level command function for lfconvert, plus the cmdtable.'''
9 '''High-level command function for lfconvert, plus the cmdtable.'''
10
10
11 import os
11 import os
12 import shutil
12 import shutil
13
13
14 from mercurial import util, match as match_, hg, node, context, error
14 from mercurial import util, match as match_, hg, node, context, error
15 from mercurial.i18n import _
15 from mercurial.i18n import _
16
16
17 import lfutil
17 import lfutil
18 import basestore
18 import basestore
19
19
20 # -- Commands ----------------------------------------------------------
20 # -- Commands ----------------------------------------------------------
21
21
22 def lfconvert(ui, src, dest, *pats, **opts):
22 def lfconvert(ui, src, dest, *pats, **opts):
23 '''convert a normal repository to a largefiles repository
23 '''convert a normal repository to a largefiles repository
24
24
25 Convert repository SOURCE to a new repository DEST, identical to
25 Convert repository SOURCE to a new repository DEST, identical to
26 SOURCE except that certain files will be converted as largefiles:
26 SOURCE except that certain files will be converted as largefiles:
27 specifically, any file that matches any PATTERN *or* whose size is
27 specifically, any file that matches any PATTERN *or* whose size is
28 above the minimum size threshold is converted as a largefile. The
28 above the minimum size threshold is converted as a largefile. The
29 size used to determine whether or not to track a file as a
29 size used to determine whether or not to track a file as a
30 largefile is the size of the first version of the file. The
30 largefile is the size of the first version of the file. The
31 minimum size can be specified either with --size or in
31 minimum size can be specified either with --size or in
32 configuration as ``largefiles.size``.
32 configuration as ``largefiles.size``.
33
33
34 After running this command you will need to make sure that
34 After running this command you will need to make sure that
35 largefiles is enabled anywhere you intend to push the new
35 largefiles is enabled anywhere you intend to push the new
36 repository.
36 repository.
37
37
38 Use --to-normal to convert largefiles back to normal files; after
38 Use --to-normal to convert largefiles back to normal files; after
39 this, the DEST repository can be used without largefiles at all.'''
39 this, the DEST repository can be used without largefiles at all.'''
40
40
41 if opts['to_normal']:
41 if opts['to_normal']:
42 tolfile = False
42 tolfile = False
43 else:
43 else:
44 tolfile = True
44 tolfile = True
45 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
45 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
46
46
47 if not hg.islocal(src):
47 if not hg.islocal(src):
48 raise util.Abort(_('%s is not a local Mercurial repo') % src)
48 raise util.Abort(_('%s is not a local Mercurial repo') % src)
49 if not hg.islocal(dest):
49 if not hg.islocal(dest):
50 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
50 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
51
51
52 rsrc = hg.repository(ui, src)
52 rsrc = hg.repository(ui, src)
53 ui.status(_('initializing destination %s\n') % dest)
53 ui.status(_('initializing destination %s\n') % dest)
54 rdst = hg.repository(ui, dest, create=True)
54 rdst = hg.repository(ui, dest, create=True)
55
55
56 success = False
56 success = False
57 try:
57 try:
58 # Lock destination to prevent modification while it is converted to.
58 # Lock destination to prevent modification while it is converted to.
59 # Don't need to lock src because we are just reading from its history
59 # Don't need to lock src because we are just reading from its history
60 # which can't change.
60 # which can't change.
61 dst_lock = rdst.lock()
61 dst_lock = rdst.lock()
62
62
63 # Get a list of all changesets in the source. The easy way to do this
63 # Get a list of all changesets in the source. The easy way to do this
64 # is to simply walk the changelog, using changelog.nodesbewteen().
64 # is to simply walk the changelog, using changelog.nodesbewteen().
65 # Take a look at mercurial/revlog.py:639 for more details.
65 # Take a look at mercurial/revlog.py:639 for more details.
66 # Use a generator instead of a list to decrease memory usage
66 # Use a generator instead of a list to decrease memory usage
67 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
67 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
68 rsrc.heads())[0])
68 rsrc.heads())[0])
69 revmap = {node.nullid: node.nullid}
69 revmap = {node.nullid: node.nullid}
70 if tolfile:
70 if tolfile:
71 lfiles = set()
71 lfiles = set()
72 normalfiles = set()
72 normalfiles = set()
73 if not pats:
73 if not pats:
74 pats = ui.config(lfutil.longname, 'patterns', default=())
74 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
75 if pats:
76 pats = pats.split(' ')
77 if pats:
75 if pats:
78 matcher = match_.match(rsrc.root, '', list(pats))
76 matcher = match_.match(rsrc.root, '', list(pats))
79 else:
77 else:
80 matcher = None
78 matcher = None
81
79
82 lfiletohash = {}
80 lfiletohash = {}
83 for ctx in ctxs:
81 for ctx in ctxs:
84 ui.progress(_('converting revisions'), ctx.rev(),
82 ui.progress(_('converting revisions'), ctx.rev(),
85 unit=_('revision'), total=rsrc['tip'].rev())
83 unit=_('revision'), total=rsrc['tip'].rev())
86 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
84 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
87 lfiles, normalfiles, matcher, size, lfiletohash)
85 lfiles, normalfiles, matcher, size, lfiletohash)
88 ui.progress(_('converting revisions'), None)
86 ui.progress(_('converting revisions'), None)
89
87
90 if os.path.exists(rdst.wjoin(lfutil.shortname)):
88 if os.path.exists(rdst.wjoin(lfutil.shortname)):
91 shutil.rmtree(rdst.wjoin(lfutil.shortname))
89 shutil.rmtree(rdst.wjoin(lfutil.shortname))
92
90
93 for f in lfiletohash.keys():
91 for f in lfiletohash.keys():
94 if os.path.isfile(rdst.wjoin(f)):
92 if os.path.isfile(rdst.wjoin(f)):
95 os.unlink(rdst.wjoin(f))
93 os.unlink(rdst.wjoin(f))
96 try:
94 try:
97 os.removedirs(os.path.dirname(rdst.wjoin(f)))
95 os.removedirs(os.path.dirname(rdst.wjoin(f)))
98 except OSError:
96 except OSError:
99 pass
97 pass
100
98
101 # If there were any files converted to largefiles, add largefiles
99 # If there were any files converted to largefiles, add largefiles
102 # to the destination repository's requirements.
100 # to the destination repository's requirements.
103 if lfiles:
101 if lfiles:
104 rdst.requirements.add('largefiles')
102 rdst.requirements.add('largefiles')
105 rdst._writerequirements()
103 rdst._writerequirements()
106 else:
104 else:
107 for ctx in ctxs:
105 for ctx in ctxs:
108 ui.progress(_('converting revisions'), ctx.rev(),
106 ui.progress(_('converting revisions'), ctx.rev(),
109 unit=_('revision'), total=rsrc['tip'].rev())
107 unit=_('revision'), total=rsrc['tip'].rev())
110 _addchangeset(ui, rsrc, rdst, ctx, revmap)
108 _addchangeset(ui, rsrc, rdst, ctx, revmap)
111
109
112 ui.progress(_('converting revisions'), None)
110 ui.progress(_('converting revisions'), None)
113 success = True
111 success = True
114 finally:
112 finally:
115 if not success:
113 if not success:
116 # we failed, remove the new directory
114 # we failed, remove the new directory
117 shutil.rmtree(rdst.root)
115 shutil.rmtree(rdst.root)
118 dst_lock.release()
116 dst_lock.release()
119
117
120 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
118 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
121 # Convert src parents to dst parents
119 # Convert src parents to dst parents
122 parents = []
120 parents = []
123 for p in ctx.parents():
121 for p in ctx.parents():
124 parents.append(revmap[p.node()])
122 parents.append(revmap[p.node()])
125 while len(parents) < 2:
123 while len(parents) < 2:
126 parents.append(node.nullid)
124 parents.append(node.nullid)
127
125
128 # Generate list of changed files
126 # Generate list of changed files
129 files = set(ctx.files())
127 files = set(ctx.files())
130 if node.nullid not in parents:
128 if node.nullid not in parents:
131 mc = ctx.manifest()
129 mc = ctx.manifest()
132 mp1 = ctx.parents()[0].manifest()
130 mp1 = ctx.parents()[0].manifest()
133 mp2 = ctx.parents()[1].manifest()
131 mp2 = ctx.parents()[1].manifest()
134 files |= (set(mp1) | set(mp2)) - set(mc)
132 files |= (set(mp1) | set(mp2)) - set(mc)
135 for f in mc:
133 for f in mc:
136 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
134 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
137 files.add(f)
135 files.add(f)
138
136
139 def getfilectx(repo, memctx, f):
137 def getfilectx(repo, memctx, f):
140 if lfutil.standin(f) in files:
138 if lfutil.standin(f) in files:
141 # if the file isn't in the manifest then it was removed
139 # if the file isn't in the manifest then it was removed
142 # or renamed, raise IOError to indicate this
140 # or renamed, raise IOError to indicate this
143 try:
141 try:
144 fctx = ctx.filectx(lfutil.standin(f))
142 fctx = ctx.filectx(lfutil.standin(f))
145 except error.LookupError:
143 except error.LookupError:
146 raise IOError()
144 raise IOError()
147 renamed = fctx.renamed()
145 renamed = fctx.renamed()
148 if renamed:
146 if renamed:
149 renamed = lfutil.splitstandin(renamed[0])
147 renamed = lfutil.splitstandin(renamed[0])
150
148
151 hash = fctx.data().strip()
149 hash = fctx.data().strip()
152 path = lfutil.findfile(rsrc, hash)
150 path = lfutil.findfile(rsrc, hash)
153 ### TODO: What if the file is not cached?
151 ### TODO: What if the file is not cached?
154 data = ''
152 data = ''
155 fd = None
153 fd = None
156 try:
154 try:
157 fd = open(path, 'rb')
155 fd = open(path, 'rb')
158 data = fd.read()
156 data = fd.read()
159 finally:
157 finally:
160 if fd:
158 if fd:
161 fd.close()
159 fd.close()
162 return context.memfilectx(f, data, 'l' in fctx.flags(),
160 return context.memfilectx(f, data, 'l' in fctx.flags(),
163 'x' in fctx.flags(), renamed)
161 'x' in fctx.flags(), renamed)
164 else:
162 else:
165 try:
163 try:
166 fctx = ctx.filectx(f)
164 fctx = ctx.filectx(f)
167 except error.LookupError:
165 except error.LookupError:
168 raise IOError()
166 raise IOError()
169 renamed = fctx.renamed()
167 renamed = fctx.renamed()
170 if renamed:
168 if renamed:
171 renamed = renamed[0]
169 renamed = renamed[0]
172 data = fctx.data()
170 data = fctx.data()
173 if f == '.hgtags':
171 if f == '.hgtags':
174 newdata = []
172 newdata = []
175 for line in data.splitlines():
173 for line in data.splitlines():
176 id, name = line.split(' ', 1)
174 id, name = line.split(' ', 1)
177 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
175 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
178 name))
176 name))
179 data = ''.join(newdata)
177 data = ''.join(newdata)
180 return context.memfilectx(f, data, 'l' in fctx.flags(),
178 return context.memfilectx(f, data, 'l' in fctx.flags(),
181 'x' in fctx.flags(), renamed)
179 'x' in fctx.flags(), renamed)
182
180
183 dstfiles = []
181 dstfiles = []
184 for file in files:
182 for file in files:
185 if lfutil.isstandin(file):
183 if lfutil.isstandin(file):
186 dstfiles.append(lfutil.splitstandin(file))
184 dstfiles.append(lfutil.splitstandin(file))
187 else:
185 else:
188 dstfiles.append(file)
186 dstfiles.append(file)
189 # Commit
187 # Commit
190 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
188 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
191 getfilectx, ctx.user(), ctx.date(), ctx.extra())
189 getfilectx, ctx.user(), ctx.date(), ctx.extra())
192 ret = rdst.commitctx(mctx)
190 ret = rdst.commitctx(mctx)
193 rdst.dirstate.setparents(ret)
191 rdst.dirstate.setparents(ret)
194 revmap[ctx.node()] = rdst.changelog.tip()
192 revmap[ctx.node()] = rdst.changelog.tip()
195
193
196 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
194 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
197 matcher, size, lfiletohash):
195 matcher, size, lfiletohash):
198 # Convert src parents to dst parents
196 # Convert src parents to dst parents
199 parents = []
197 parents = []
200 for p in ctx.parents():
198 for p in ctx.parents():
201 parents.append(revmap[p.node()])
199 parents.append(revmap[p.node()])
202 while len(parents) < 2:
200 while len(parents) < 2:
203 parents.append(node.nullid)
201 parents.append(node.nullid)
204
202
205 # Generate list of changed files
203 # Generate list of changed files
206 files = set(ctx.files())
204 files = set(ctx.files())
207 if node.nullid not in parents:
205 if node.nullid not in parents:
208 mc = ctx.manifest()
206 mc = ctx.manifest()
209 mp1 = ctx.parents()[0].manifest()
207 mp1 = ctx.parents()[0].manifest()
210 mp2 = ctx.parents()[1].manifest()
208 mp2 = ctx.parents()[1].manifest()
211 files |= (set(mp1) | set(mp2)) - set(mc)
209 files |= (set(mp1) | set(mp2)) - set(mc)
212 for f in mc:
210 for f in mc:
213 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
211 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
214 files.add(f)
212 files.add(f)
215
213
216 dstfiles = []
214 dstfiles = []
217 for f in files:
215 for f in files:
218 if f not in lfiles and f not in normalfiles:
216 if f not in lfiles and f not in normalfiles:
219 islfile = _islfile(f, ctx, matcher, size)
217 islfile = _islfile(f, ctx, matcher, size)
220 # If this file was renamed or copied then copy
218 # If this file was renamed or copied then copy
221 # the lfileness of its predecessor
219 # the lfileness of its predecessor
222 if f in ctx.manifest():
220 if f in ctx.manifest():
223 fctx = ctx.filectx(f)
221 fctx = ctx.filectx(f)
224 renamed = fctx.renamed()
222 renamed = fctx.renamed()
225 renamedlfile = renamed and renamed[0] in lfiles
223 renamedlfile = renamed and renamed[0] in lfiles
226 islfile |= renamedlfile
224 islfile |= renamedlfile
227 if 'l' in fctx.flags():
225 if 'l' in fctx.flags():
228 if renamedlfile:
226 if renamedlfile:
229 raise util.Abort(
227 raise util.Abort(
230 _('renamed/copied largefile %s becomes symlink')
228 _('renamed/copied largefile %s becomes symlink')
231 % f)
229 % f)
232 islfile = False
230 islfile = False
233 if islfile:
231 if islfile:
234 lfiles.add(f)
232 lfiles.add(f)
235 else:
233 else:
236 normalfiles.add(f)
234 normalfiles.add(f)
237
235
238 if f in lfiles:
236 if f in lfiles:
239 dstfiles.append(lfutil.standin(f))
237 dstfiles.append(lfutil.standin(f))
240 # largefile in manifest if it has not been removed/renamed
238 # largefile in manifest if it has not been removed/renamed
241 if f in ctx.manifest():
239 if f in ctx.manifest():
242 if 'l' in ctx.filectx(f).flags():
240 if 'l' in ctx.filectx(f).flags():
243 if renamed and renamed[0] in lfiles:
241 if renamed and renamed[0] in lfiles:
244 raise util.Abort(_('largefile %s becomes symlink') % f)
242 raise util.Abort(_('largefile %s becomes symlink') % f)
245
243
246 # largefile was modified, update standins
244 # largefile was modified, update standins
247 fullpath = rdst.wjoin(f)
245 fullpath = rdst.wjoin(f)
248 util.makedirs(os.path.dirname(fullpath))
246 util.makedirs(os.path.dirname(fullpath))
249 m = util.sha1('')
247 m = util.sha1('')
250 m.update(ctx[f].data())
248 m.update(ctx[f].data())
251 hash = m.hexdigest()
249 hash = m.hexdigest()
252 if f not in lfiletohash or lfiletohash[f] != hash:
250 if f not in lfiletohash or lfiletohash[f] != hash:
253 try:
251 try:
254 fd = open(fullpath, 'wb')
252 fd = open(fullpath, 'wb')
255 fd.write(ctx[f].data())
253 fd.write(ctx[f].data())
256 finally:
254 finally:
257 if fd:
255 if fd:
258 fd.close()
256 fd.close()
259 executable = 'x' in ctx[f].flags()
257 executable = 'x' in ctx[f].flags()
260 os.chmod(fullpath, lfutil.getmode(executable))
258 os.chmod(fullpath, lfutil.getmode(executable))
261 lfutil.writestandin(rdst, lfutil.standin(f), hash,
259 lfutil.writestandin(rdst, lfutil.standin(f), hash,
262 executable)
260 executable)
263 lfiletohash[f] = hash
261 lfiletohash[f] = hash
264 else:
262 else:
265 # normal file
263 # normal file
266 dstfiles.append(f)
264 dstfiles.append(f)
267
265
268 def getfilectx(repo, memctx, f):
266 def getfilectx(repo, memctx, f):
269 if lfutil.isstandin(f):
267 if lfutil.isstandin(f):
270 # if the file isn't in the manifest then it was removed
268 # if the file isn't in the manifest then it was removed
271 # or renamed, raise IOError to indicate this
269 # or renamed, raise IOError to indicate this
272 srcfname = lfutil.splitstandin(f)
270 srcfname = lfutil.splitstandin(f)
273 try:
271 try:
274 fctx = ctx.filectx(srcfname)
272 fctx = ctx.filectx(srcfname)
275 except error.LookupError:
273 except error.LookupError:
276 raise IOError()
274 raise IOError()
277 renamed = fctx.renamed()
275 renamed = fctx.renamed()
278 if renamed:
276 if renamed:
279 # standin is always a largefile because largefile-ness
277 # standin is always a largefile because largefile-ness
280 # doesn't change after rename or copy
278 # doesn't change after rename or copy
281 renamed = lfutil.standin(renamed[0])
279 renamed = lfutil.standin(renamed[0])
282
280
283 return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in
281 return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in
284 fctx.flags(), 'x' in fctx.flags(), renamed)
282 fctx.flags(), 'x' in fctx.flags(), renamed)
285 else:
283 else:
286 try:
284 try:
287 fctx = ctx.filectx(f)
285 fctx = ctx.filectx(f)
288 except error.LookupError:
286 except error.LookupError:
289 raise IOError()
287 raise IOError()
290 renamed = fctx.renamed()
288 renamed = fctx.renamed()
291 if renamed:
289 if renamed:
292 renamed = renamed[0]
290 renamed = renamed[0]
293
291
294 data = fctx.data()
292 data = fctx.data()
295 if f == '.hgtags':
293 if f == '.hgtags':
296 newdata = []
294 newdata = []
297 for line in data.splitlines():
295 for line in data.splitlines():
298 id, name = line.split(' ', 1)
296 id, name = line.split(' ', 1)
299 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
297 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]),
300 name))
298 name))
301 data = ''.join(newdata)
299 data = ''.join(newdata)
302 return context.memfilectx(f, data, 'l' in fctx.flags(),
300 return context.memfilectx(f, data, 'l' in fctx.flags(),
303 'x' in fctx.flags(), renamed)
301 'x' in fctx.flags(), renamed)
304
302
305 # Commit
303 # Commit
306 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
304 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
307 getfilectx, ctx.user(), ctx.date(), ctx.extra())
305 getfilectx, ctx.user(), ctx.date(), ctx.extra())
308 ret = rdst.commitctx(mctx)
306 ret = rdst.commitctx(mctx)
309 rdst.dirstate.setparents(ret)
307 rdst.dirstate.setparents(ret)
310 revmap[ctx.node()] = rdst.changelog.tip()
308 revmap[ctx.node()] = rdst.changelog.tip()
311
309
312 def _islfile(file, ctx, matcher, size):
310 def _islfile(file, ctx, matcher, size):
313 '''Return true if file should be considered a largefile, i.e.
311 '''Return true if file should be considered a largefile, i.e.
314 matcher matches it or it is larger than size.'''
312 matcher matches it or it is larger than size.'''
315 # never store special .hg* files as largefiles
313 # never store special .hg* files as largefiles
316 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
314 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
317 return False
315 return False
318 if matcher and matcher(file):
316 if matcher and matcher(file):
319 return True
317 return True
320 try:
318 try:
321 return ctx.filectx(file).size() >= size * 1024 * 1024
319 return ctx.filectx(file).size() >= size * 1024 * 1024
322 except error.LookupError:
320 except error.LookupError:
323 return False
321 return False
324
322
325 def uploadlfiles(ui, rsrc, rdst, files):
323 def uploadlfiles(ui, rsrc, rdst, files):
326 '''upload largefiles to the central store'''
324 '''upload largefiles to the central store'''
327
325
328 if not files:
326 if not files:
329 return
327 return
330
328
331 store = basestore._openstore(rsrc, rdst, put=True)
329 store = basestore._openstore(rsrc, rdst, put=True)
332
330
333 at = 0
331 at = 0
334 files = filter(lambda h: not store.exists(h), files)
332 files = filter(lambda h: not store.exists(h), files)
335 for hash in files:
333 for hash in files:
336 ui.progress(_('uploading largefiles'), at, unit='largefile',
334 ui.progress(_('uploading largefiles'), at, unit='largefile',
337 total=len(files))
335 total=len(files))
338 source = lfutil.findfile(rsrc, hash)
336 source = lfutil.findfile(rsrc, hash)
339 if not source:
337 if not source:
340 raise util.Abort(_('largefile %s missing from store'
338 raise util.Abort(_('largefile %s missing from store'
341 ' (needs to be uploaded)') % hash)
339 ' (needs to be uploaded)') % hash)
342 # XXX check for errors here
340 # XXX check for errors here
343 store.put(source, hash)
341 store.put(source, hash)
344 at += 1
342 at += 1
345 ui.progress(_('uploading largefiles'), None)
343 ui.progress(_('uploading largefiles'), None)
346
344
347 def verifylfiles(ui, repo, all=False, contents=False):
345 def verifylfiles(ui, repo, all=False, contents=False):
348 '''Verify that every big file revision in the current changeset
346 '''Verify that every big file revision in the current changeset
349 exists in the central store. With --contents, also verify that
347 exists in the central store. With --contents, also verify that
350 the contents of each big file revision are correct (SHA-1 hash
348 the contents of each big file revision are correct (SHA-1 hash
351 matches the revision ID). With --all, check every changeset in
349 matches the revision ID). With --all, check every changeset in
352 this repository.'''
350 this repository.'''
353 if all:
351 if all:
354 # Pass a list to the function rather than an iterator because we know a
352 # Pass a list to the function rather than an iterator because we know a
355 # list will work.
353 # list will work.
356 revs = range(len(repo))
354 revs = range(len(repo))
357 else:
355 else:
358 revs = ['.']
356 revs = ['.']
359
357
360 store = basestore._openstore(repo)
358 store = basestore._openstore(repo)
361 return store.verify(revs, contents=contents)
359 return store.verify(revs, contents=contents)
362
360
363 def cachelfiles(ui, repo, node):
361 def cachelfiles(ui, repo, node):
364 '''cachelfiles ensures that all largefiles needed by the specified revision
362 '''cachelfiles ensures that all largefiles needed by the specified revision
365 are present in the repository's largefile cache.
363 are present in the repository's largefile cache.
366
364
367 returns a tuple (cached, missing). cached is the list of files downloaded
365 returns a tuple (cached, missing). cached is the list of files downloaded
368 by this operation; missing is the list of files that were needed but could
366 by this operation; missing is the list of files that were needed but could
369 not be found.'''
367 not be found.'''
370 lfiles = lfutil.listlfiles(repo, node)
368 lfiles = lfutil.listlfiles(repo, node)
371 toget = []
369 toget = []
372
370
373 for lfile in lfiles:
371 for lfile in lfiles:
374 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
372 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
375 # if it exists and its hash matches, it might have been locally
373 # if it exists and its hash matches, it might have been locally
376 # modified before updating and the user chose 'local'. in this case,
374 # modified before updating and the user chose 'local'. in this case,
377 # it will not be in any store, so don't look for it.
375 # it will not be in any store, so don't look for it.
378 if ((not os.path.exists(repo.wjoin(lfile)) or
376 if ((not os.path.exists(repo.wjoin(lfile)) or
379 expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and
377 expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and
380 not lfutil.findfile(repo, expectedhash)):
378 not lfutil.findfile(repo, expectedhash)):
381 toget.append((lfile, expectedhash))
379 toget.append((lfile, expectedhash))
382
380
383 if toget:
381 if toget:
384 store = basestore._openstore(repo)
382 store = basestore._openstore(repo)
385 ret = store.get(toget)
383 ret = store.get(toget)
386 return ret
384 return ret
387
385
388 return ([], [])
386 return ([], [])
389
387
390 def updatelfiles(ui, repo, filelist=None, printmessage=True):
388 def updatelfiles(ui, repo, filelist=None, printmessage=True):
391 wlock = repo.wlock()
389 wlock = repo.wlock()
392 try:
390 try:
393 lfdirstate = lfutil.openlfdirstate(ui, repo)
391 lfdirstate = lfutil.openlfdirstate(ui, repo)
394 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
392 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
395
393
396 if filelist is not None:
394 if filelist is not None:
397 lfiles = [f for f in lfiles if f in filelist]
395 lfiles = [f for f in lfiles if f in filelist]
398
396
399 printed = False
397 printed = False
400 if printmessage and lfiles:
398 if printmessage and lfiles:
401 ui.status(_('getting changed largefiles\n'))
399 ui.status(_('getting changed largefiles\n'))
402 printed = True
400 printed = True
403 cachelfiles(ui, repo, '.')
401 cachelfiles(ui, repo, '.')
404
402
405 updated, removed = 0, 0
403 updated, removed = 0, 0
406 for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles):
404 for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles):
407 # increment the appropriate counter according to _updatelfile's
405 # increment the appropriate counter according to _updatelfile's
408 # return value
406 # return value
409 updated += i > 0 and i or 0
407 updated += i > 0 and i or 0
410 removed -= i < 0 and i or 0
408 removed -= i < 0 and i or 0
411 if printmessage and (removed or updated) and not printed:
409 if printmessage and (removed or updated) and not printed:
412 ui.status(_('getting changed largefiles\n'))
410 ui.status(_('getting changed largefiles\n'))
413 printed = True
411 printed = True
414
412
415 lfdirstate.write()
413 lfdirstate.write()
416 if printed and printmessage:
414 if printed and printmessage:
417 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
415 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
418 removed))
416 removed))
419 finally:
417 finally:
420 wlock.release()
418 wlock.release()
421
419
422 def _updatelfile(repo, lfdirstate, lfile):
420 def _updatelfile(repo, lfdirstate, lfile):
423 '''updates a single largefile and copies the state of its standin from
421 '''updates a single largefile and copies the state of its standin from
424 the repository's dirstate to its state in the lfdirstate.
422 the repository's dirstate to its state in the lfdirstate.
425
423
426 returns 1 if the file was modified, -1 if the file was removed, 0 if the
424 returns 1 if the file was modified, -1 if the file was removed, 0 if the
427 file was unchanged, and None if the needed largefile was missing from the
425 file was unchanged, and None if the needed largefile was missing from the
428 cache.'''
426 cache.'''
429 ret = 0
427 ret = 0
430 abslfile = repo.wjoin(lfile)
428 abslfile = repo.wjoin(lfile)
431 absstandin = repo.wjoin(lfutil.standin(lfile))
429 absstandin = repo.wjoin(lfutil.standin(lfile))
432 if os.path.exists(absstandin):
430 if os.path.exists(absstandin):
433 if os.path.exists(absstandin+'.orig'):
431 if os.path.exists(absstandin+'.orig'):
434 shutil.copyfile(abslfile, abslfile+'.orig')
432 shutil.copyfile(abslfile, abslfile+'.orig')
435 expecthash = lfutil.readstandin(repo, lfile)
433 expecthash = lfutil.readstandin(repo, lfile)
436 if (expecthash != '' and
434 if (expecthash != '' and
437 (not os.path.exists(abslfile) or
435 (not os.path.exists(abslfile) or
438 expecthash != lfutil.hashfile(abslfile))):
436 expecthash != lfutil.hashfile(abslfile))):
439 if not lfutil.copyfromcache(repo, expecthash, lfile):
437 if not lfutil.copyfromcache(repo, expecthash, lfile):
440 # use normallookup() to allocate entry in largefiles dirstate,
438 # use normallookup() to allocate entry in largefiles dirstate,
441 # because lack of it misleads lfiles_repo.status() into
439 # because lack of it misleads lfiles_repo.status() into
442 # recognition that such cache missing files are REMOVED.
440 # recognition that such cache missing files are REMOVED.
443 lfdirstate.normallookup(lfile)
441 lfdirstate.normallookup(lfile)
444 return None # don't try to set the mode
442 return None # don't try to set the mode
445 ret = 1
443 ret = 1
446 mode = os.stat(absstandin).st_mode
444 mode = os.stat(absstandin).st_mode
447 if mode != os.stat(abslfile).st_mode:
445 if mode != os.stat(abslfile).st_mode:
448 os.chmod(abslfile, mode)
446 os.chmod(abslfile, mode)
449 ret = 1
447 ret = 1
450 else:
448 else:
451 if os.path.exists(abslfile):
449 if os.path.exists(abslfile):
452 os.unlink(abslfile)
450 os.unlink(abslfile)
453 ret = -1
451 ret = -1
454 state = repo.dirstate[lfutil.standin(lfile)]
452 state = repo.dirstate[lfutil.standin(lfile)]
455 if state == 'n':
453 if state == 'n':
456 lfdirstate.normal(lfile)
454 lfdirstate.normal(lfile)
457 elif state == 'r':
455 elif state == 'r':
458 lfdirstate.remove(lfile)
456 lfdirstate.remove(lfile)
459 elif state == 'a':
457 elif state == 'a':
460 lfdirstate.add(lfile)
458 lfdirstate.add(lfile)
461 elif state == '?':
459 elif state == '?':
462 lfdirstate.drop(lfile)
460 lfdirstate.drop(lfile)
463 return ret
461 return ret
464
462
465 # -- hg commands declarations ------------------------------------------------
463 # -- hg commands declarations ------------------------------------------------
466
464
467 cmdtable = {
465 cmdtable = {
468 'lfconvert': (lfconvert,
466 'lfconvert': (lfconvert,
469 [('s', 'size', '',
467 [('s', 'size', '',
470 _('minimum size (MB) for files to be converted '
468 _('minimum size (MB) for files to be converted '
471 'as largefiles'),
469 'as largefiles'),
472 'SIZE'),
470 'SIZE'),
473 ('', 'to-normal', False,
471 ('', 'to-normal', False,
474 _('convert from a largefiles repo to a normal repo')),
472 _('convert from a largefiles repo to a normal repo')),
475 ],
473 ],
476 _('hg lfconvert SOURCE DEST [FILE ...]')),
474 _('hg lfconvert SOURCE DEST [FILE ...]')),
477 }
475 }
@@ -1,232 +1,233 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > largefiles =
3 > largefiles =
4 > share =
4 > share =
5 > graphlog =
5 > graphlog =
6 > [largefiles]
6 > [largefiles]
7 > minsize = 0.5
7 > minsize = 0.5
8 > patterns = **.dat
8 > patterns = **.other
9 > **.dat
9 > EOF
10 > EOF
10
11
11 "lfconvert" works
12 "lfconvert" works
12 $ hg init bigfile-repo
13 $ hg init bigfile-repo
13 $ cd bigfile-repo
14 $ cd bigfile-repo
14 $ cat >> .hg/hgrc <<EOF
15 $ cat >> .hg/hgrc <<EOF
15 > [extensions]
16 > [extensions]
16 > largefiles = !
17 > largefiles = !
17 > EOF
18 > EOF
18 $ mkdir sub
19 $ mkdir sub
19 $ dd if=/dev/zero bs=1k count=256 > large 2> /dev/null
20 $ dd if=/dev/zero bs=1k count=256 > large 2> /dev/null
20 $ echo normal > normal1
21 $ echo normal > normal1
21 $ echo alsonormal > sub/normal2
22 $ echo alsonormal > sub/normal2
22 $ dd if=/dev/zero bs=1k count=10 > sub/maybelarge.dat 2> /dev/null
23 $ dd if=/dev/zero bs=1k count=10 > sub/maybelarge.dat 2> /dev/null
23 $ hg addremove
24 $ hg addremove
24 adding large
25 adding large
25 adding normal1
26 adding normal1
26 adding sub/maybelarge.dat
27 adding sub/maybelarge.dat
27 adding sub/normal2
28 adding sub/normal2
28 $ hg commit -m"add large, normal1" large normal1
29 $ hg commit -m"add large, normal1" large normal1
29 $ hg commit -m"add sub/*" sub
30 $ hg commit -m"add sub/*" sub
30 $ [ -d .hg/largefiles ] && echo fail || echo pass
31 $ [ -d .hg/largefiles ] && echo fail || echo pass
31 pass
32 pass
32 $ cd ..
33 $ cd ..
33 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
34 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
34 initializing destination largefiles-repo
35 initializing destination largefiles-repo
35
36
36 "lfconvert" converts content correctly
37 "lfconvert" converts content correctly
37 $ cd largefiles-repo
38 $ cd largefiles-repo
38 $ hg up
39 $ hg up
39 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 getting changed largefiles
41 getting changed largefiles
41 2 largefiles updated, 0 removed
42 2 largefiles updated, 0 removed
42 $ hg locate
43 $ hg locate
43 .hglf/large
44 .hglf/large
44 .hglf/sub/maybelarge.dat
45 .hglf/sub/maybelarge.dat
45 normal1
46 normal1
46 sub/normal2
47 sub/normal2
47 $ cat normal1
48 $ cat normal1
48 normal
49 normal
49 $ cat sub/normal2
50 $ cat sub/normal2
50 alsonormal
51 alsonormal
51 $ "$TESTDIR/md5sum.py" large sub/maybelarge.dat
52 $ "$TESTDIR/md5sum.py" large sub/maybelarge.dat
52 ec87a838931d4d5d2e94a04644788a55 large
53 ec87a838931d4d5d2e94a04644788a55 large
53 1276481102f218c981e0324180bafd9f sub/maybelarge.dat
54 1276481102f218c981e0324180bafd9f sub/maybelarge.dat
54
55
55 "lfconvert" adds 'largefiles' to .hg/requires.
56 "lfconvert" adds 'largefiles' to .hg/requires.
56 $ cat .hg/requires
57 $ cat .hg/requires
57 largefiles
58 largefiles
58 revlogv1
59 revlogv1
59 fncache
60 fncache
60 store
61 store
61 dotencode
62 dotencode
62
63
63 "lfconvert" includes a newline at the end of the standin files.
64 "lfconvert" includes a newline at the end of the standin files.
64 $ cat .hglf/large .hglf/sub/maybelarge.dat
65 $ cat .hglf/large .hglf/sub/maybelarge.dat
65 2e000fa7e85759c7f4c254d4d9c33ef481e459a7
66 2e000fa7e85759c7f4c254d4d9c33ef481e459a7
66 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c
67 34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c
67 $ cd ..
68 $ cd ..
68
69
69 add some changesets to rename/remove/merge
70 add some changesets to rename/remove/merge
70 $ cd bigfile-repo
71 $ cd bigfile-repo
71 $ hg mv -q sub stuff
72 $ hg mv -q sub stuff
72 $ hg commit -m"rename sub/ to stuff/"
73 $ hg commit -m"rename sub/ to stuff/"
73 $ hg update -q 1
74 $ hg update -q 1
74 $ echo blah >> normal3
75 $ echo blah >> normal3
75 $ echo blah >> sub/normal2
76 $ echo blah >> sub/normal2
76 $ echo blah >> sub/maybelarge.dat
77 $ echo blah >> sub/maybelarge.dat
77 $ "$TESTDIR/md5sum.py" sub/maybelarge.dat
78 $ "$TESTDIR/md5sum.py" sub/maybelarge.dat
78 1dd0b99ff80e19cff409702a1d3f5e15 sub/maybelarge.dat
79 1dd0b99ff80e19cff409702a1d3f5e15 sub/maybelarge.dat
79 $ hg commit -A -m"add normal3, modify sub/*"
80 $ hg commit -A -m"add normal3, modify sub/*"
80 adding normal3
81 adding normal3
81 created new head
82 created new head
82 $ hg rm large normal3
83 $ hg rm large normal3
83 $ hg commit -q -m"remove large, normal3"
84 $ hg commit -q -m"remove large, normal3"
84 $ hg merge
85 $ hg merge
85 merging sub/maybelarge.dat and stuff/maybelarge.dat to stuff/maybelarge.dat
86 merging sub/maybelarge.dat and stuff/maybelarge.dat to stuff/maybelarge.dat
86 warning: $TESTTMP/bigfile-repo/stuff/maybelarge.dat looks like a binary file.
87 warning: $TESTTMP/bigfile-repo/stuff/maybelarge.dat looks like a binary file.
87 merging stuff/maybelarge.dat failed!
88 merging stuff/maybelarge.dat failed!
88 merging sub/normal2 and stuff/normal2 to stuff/normal2
89 merging sub/normal2 and stuff/normal2 to stuff/normal2
89 0 files updated, 1 files merged, 0 files removed, 1 files unresolved
90 0 files updated, 1 files merged, 0 files removed, 1 files unresolved
90 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
91 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
91 [1]
92 [1]
92 $ hg cat -r . sub/maybelarge.dat > stuff/maybelarge.dat
93 $ hg cat -r . sub/maybelarge.dat > stuff/maybelarge.dat
93 $ hg resolve -m stuff/maybelarge.dat
94 $ hg resolve -m stuff/maybelarge.dat
94 $ hg commit -m"merge"
95 $ hg commit -m"merge"
95 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
96 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
96 @ 5:4884f215abda merge
97 @ 5:4884f215abda merge
97 |\
98 |\
98 | o 4:7285f817b77e remove large, normal3
99 | o 4:7285f817b77e remove large, normal3
99 | |
100 | |
100 | o 3:67e3892e3534 add normal3, modify sub/*
101 | o 3:67e3892e3534 add normal3, modify sub/*
101 | |
102 | |
102 o | 2:c96c8beb5d56 rename sub/ to stuff/
103 o | 2:c96c8beb5d56 rename sub/ to stuff/
103 |/
104 |/
104 o 1:020c65d24e11 add sub/*
105 o 1:020c65d24e11 add sub/*
105 |
106 |
106 o 0:117b8328f97a add large, normal1
107 o 0:117b8328f97a add large, normal1
107
108
108 $ cd ..
109 $ cd ..
109
110
110 lfconvert with rename, merge, and remove
111 lfconvert with rename, merge, and remove
111 $ rm -rf largefiles-repo
112 $ rm -rf largefiles-repo
112 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
113 $ hg lfconvert --size 0.2 bigfile-repo largefiles-repo
113 initializing destination largefiles-repo
114 initializing destination largefiles-repo
114 $ cd largefiles-repo
115 $ cd largefiles-repo
115 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
116 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
116 o 5:8e05f5f2b77e merge
117 o 5:8e05f5f2b77e merge
117 |\
118 |\
118 | o 4:a5a02de7a8e4 remove large, normal3
119 | o 4:a5a02de7a8e4 remove large, normal3
119 | |
120 | |
120 | o 3:55759520c76f add normal3, modify sub/*
121 | o 3:55759520c76f add normal3, modify sub/*
121 | |
122 | |
122 o | 2:261ad3f3f037 rename sub/ to stuff/
123 o | 2:261ad3f3f037 rename sub/ to stuff/
123 |/
124 |/
124 o 1:334e5237836d add sub/*
125 o 1:334e5237836d add sub/*
125 |
126 |
126 o 0:d4892ec57ce2 add large, normal1
127 o 0:d4892ec57ce2 add large, normal1
127
128
128 $ hg locate -r 2
129 $ hg locate -r 2
129 .hglf/large
130 .hglf/large
130 .hglf/stuff/maybelarge.dat
131 .hglf/stuff/maybelarge.dat
131 normal1
132 normal1
132 stuff/normal2
133 stuff/normal2
133 $ hg locate -r 3
134 $ hg locate -r 3
134 .hglf/large
135 .hglf/large
135 .hglf/sub/maybelarge.dat
136 .hglf/sub/maybelarge.dat
136 normal1
137 normal1
137 normal3
138 normal3
138 sub/normal2
139 sub/normal2
139 $ hg locate -r 4
140 $ hg locate -r 4
140 .hglf/sub/maybelarge.dat
141 .hglf/sub/maybelarge.dat
141 normal1
142 normal1
142 sub/normal2
143 sub/normal2
143 $ hg locate -r 5
144 $ hg locate -r 5
144 .hglf/stuff/maybelarge.dat
145 .hglf/stuff/maybelarge.dat
145 normal1
146 normal1
146 stuff/normal2
147 stuff/normal2
147 $ hg update
148 $ hg update
148 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 getting changed largefiles
150 getting changed largefiles
150 1 largefiles updated, 0 removed
151 1 largefiles updated, 0 removed
151 $ cat stuff/normal2
152 $ cat stuff/normal2
152 alsonormal
153 alsonormal
153 blah
154 blah
154 $ "$TESTDIR/md5sum.py" stuff/maybelarge.dat
155 $ "$TESTDIR/md5sum.py" stuff/maybelarge.dat
155 1dd0b99ff80e19cff409702a1d3f5e15 stuff/maybelarge.dat
156 1dd0b99ff80e19cff409702a1d3f5e15 stuff/maybelarge.dat
156 $ cat .hglf/stuff/maybelarge.dat
157 $ cat .hglf/stuff/maybelarge.dat
157 76236b6a2c6102826c61af4297dd738fb3b1de38
158 76236b6a2c6102826c61af4297dd738fb3b1de38
158 $ cd ..
159 $ cd ..
159
160
160 "lfconvert" error cases
161 "lfconvert" error cases
161 $ hg lfconvert http://localhost/foo foo
162 $ hg lfconvert http://localhost/foo foo
162 abort: http://localhost/foo is not a local Mercurial repo
163 abort: http://localhost/foo is not a local Mercurial repo
163 [255]
164 [255]
164 $ hg lfconvert foo ssh://localhost/foo
165 $ hg lfconvert foo ssh://localhost/foo
165 abort: ssh://localhost/foo is not a local Mercurial repo
166 abort: ssh://localhost/foo is not a local Mercurial repo
166 [255]
167 [255]
167 $ hg lfconvert nosuchrepo foo
168 $ hg lfconvert nosuchrepo foo
168 abort: repository nosuchrepo not found!
169 abort: repository nosuchrepo not found!
169 [255]
170 [255]
170 $ hg share -q -U bigfile-repo shared
171 $ hg share -q -U bigfile-repo shared
171 $ printf 'bogus' > shared/.hg/sharedpath
172 $ printf 'bogus' > shared/.hg/sharedpath
172 $ hg lfconvert shared foo
173 $ hg lfconvert shared foo
173 abort: .hg/sharedpath points to nonexistent directory $TESTTMP/bogus!
174 abort: .hg/sharedpath points to nonexistent directory $TESTTMP/bogus!
174 [255]
175 [255]
175 $ hg lfconvert bigfile-repo largefiles-repo
176 $ hg lfconvert bigfile-repo largefiles-repo
176 initializing destination largefiles-repo
177 initializing destination largefiles-repo
177 abort: repository largefiles-repo already exists!
178 abort: repository largefiles-repo already exists!
178 [255]
179 [255]
179
180
180 add another largefile to the new largefiles repo
181 add another largefile to the new largefiles repo
181 $ cd largefiles-repo
182 $ cd largefiles-repo
182 $ dd if=/dev/zero bs=1k count=1k > anotherlarge 2> /dev/null
183 $ dd if=/dev/zero bs=1k count=1k > anotherlarge 2> /dev/null
183 $ hg add --lfsize=1 anotherlarge
184 $ hg add --lfsize=1 anotherlarge
184 $ hg commit -m "add anotherlarge (should be a largefile)"
185 $ hg commit -m "add anotherlarge (should be a largefile)"
185 $ cat .hglf/anotherlarge
186 $ cat .hglf/anotherlarge
186 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3
187 3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3
187 $ cd ..
188 $ cd ..
188
189
189 round-trip: converting back to a normal (non-largefiles) repo with
190 round-trip: converting back to a normal (non-largefiles) repo with
190 "lfconvert --to-normal" should give the same as ../bigfile-repo
191 "lfconvert --to-normal" should give the same as ../bigfile-repo
191 $ cd largefiles-repo
192 $ cd largefiles-repo
192 $ hg lfconvert --to-normal . ../normal-repo
193 $ hg lfconvert --to-normal . ../normal-repo
193 initializing destination ../normal-repo
194 initializing destination ../normal-repo
194 $ cd ../normal-repo
195 $ cd ../normal-repo
195 $ cat >> .hg/hgrc <<EOF
196 $ cat >> .hg/hgrc <<EOF
196 > [extensions]
197 > [extensions]
197 > largefiles = !
198 > largefiles = !
198 > EOF
199 > EOF
199
200
200 # Hmmm: the changeset ID for rev 5 is different from the original
201 # Hmmm: the changeset ID for rev 5 is different from the original
201 # normal repo (../bigfile-repo), because the changelog filelist
202 # normal repo (../bigfile-repo), because the changelog filelist
202 # differs between the two incarnations of rev 5: this repo includes
203 # differs between the two incarnations of rev 5: this repo includes
203 # 'large' in the list, but ../bigfile-repo does not. Since rev 5
204 # 'large' in the list, but ../bigfile-repo does not. Since rev 5
204 # removes 'large' relative to the first parent in both repos, it seems
205 # removes 'large' relative to the first parent in both repos, it seems
205 # to me that lfconvert is doing a *better* job than
206 # to me that lfconvert is doing a *better* job than
206 # "hg remove" + "hg merge" + "hg commit".
207 # "hg remove" + "hg merge" + "hg commit".
207 # $ hg -R ../bigfile-repo debugdata -c 5
208 # $ hg -R ../bigfile-repo debugdata -c 5
208 # $ hg debugdata -c 5
209 # $ hg debugdata -c 5
209 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
210 $ hg glog --template "{rev}:{node|short} {desc|firstline}\n"
210 o 6:1635824e6f59 add anotherlarge (should be a largefile)
211 o 6:1635824e6f59 add anotherlarge (should be a largefile)
211 |
212 |
212 o 5:7215f8deeaaf merge
213 o 5:7215f8deeaaf merge
213 |\
214 |\
214 | o 4:7285f817b77e remove large, normal3
215 | o 4:7285f817b77e remove large, normal3
215 | |
216 | |
216 | o 3:67e3892e3534 add normal3, modify sub/*
217 | o 3:67e3892e3534 add normal3, modify sub/*
217 | |
218 | |
218 o | 2:c96c8beb5d56 rename sub/ to stuff/
219 o | 2:c96c8beb5d56 rename sub/ to stuff/
219 |/
220 |/
220 o 1:020c65d24e11 add sub/*
221 o 1:020c65d24e11 add sub/*
221 |
222 |
222 o 0:117b8328f97a add large, normal1
223 o 0:117b8328f97a add large, normal1
223
224
224 $ hg update
225 $ hg update
225 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
226 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
226 $ hg locate
227 $ hg locate
227 anotherlarge
228 anotherlarge
228 normal1
229 normal1
229 stuff/maybelarge.dat
230 stuff/maybelarge.dat
230 stuff/normal2
231 stuff/normal2
231 $ [ -d .hg/largefiles ] && echo fail || echo pass
232 $ [ -d .hg/largefiles ] && echo fail || echo pass
232 pass
233 pass
General Comments 0
You need to be logged in to leave comments. Login now