##// END OF EJS Templates
largefiles: update lfdirstate for unchanged largefiles during linear merging...
FUJIWARA Katsunori -
r22197:f72d7393 default
parent child Browse files
Show More
@@ -1,559 +1,573 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, errno
11 import os, errno
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 cmdutil, scmutil, commands
15 cmdutil, scmutil, commands
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 from mercurial.lock import release
17 from mercurial.lock import release
18
18
19 import lfutil
19 import lfutil
20 import basestore
20 import basestore
21
21
22 # -- Commands ----------------------------------------------------------
22 # -- Commands ----------------------------------------------------------
23
23
24 cmdtable = {}
24 cmdtable = {}
25 command = cmdutil.command(cmdtable)
25 command = cmdutil.command(cmdtable)
26
26
27 @command('lfconvert',
27 @command('lfconvert',
28 [('s', 'size', '',
28 [('s', 'size', '',
29 _('minimum size (MB) for files to be converted as largefiles'), 'SIZE'),
29 _('minimum size (MB) for files to be converted as largefiles'), 'SIZE'),
30 ('', 'to-normal', False,
30 ('', 'to-normal', False,
31 _('convert from a largefiles repo to a normal repo')),
31 _('convert from a largefiles repo to a normal repo')),
32 ],
32 ],
33 _('hg lfconvert SOURCE DEST [FILE ...]'),
33 _('hg lfconvert SOURCE DEST [FILE ...]'),
34 norepo=True,
34 norepo=True,
35 inferrepo=True)
35 inferrepo=True)
36 def lfconvert(ui, src, dest, *pats, **opts):
36 def lfconvert(ui, src, dest, *pats, **opts):
37 '''convert a normal repository to a largefiles repository
37 '''convert a normal repository to a largefiles repository
38
38
39 Convert repository SOURCE to a new repository DEST, identical to
39 Convert repository SOURCE to a new repository DEST, identical to
40 SOURCE except that certain files will be converted as largefiles:
40 SOURCE except that certain files will be converted as largefiles:
41 specifically, any file that matches any PATTERN *or* whose size is
41 specifically, any file that matches any PATTERN *or* whose size is
42 above the minimum size threshold is converted as a largefile. The
42 above the minimum size threshold is converted as a largefile. The
43 size used to determine whether or not to track a file as a
43 size used to determine whether or not to track a file as a
44 largefile is the size of the first version of the file. The
44 largefile is the size of the first version of the file. The
45 minimum size can be specified either with --size or in
45 minimum size can be specified either with --size or in
46 configuration as ``largefiles.size``.
46 configuration as ``largefiles.size``.
47
47
48 After running this command you will need to make sure that
48 After running this command you will need to make sure that
49 largefiles is enabled anywhere you intend to push the new
49 largefiles is enabled anywhere you intend to push the new
50 repository.
50 repository.
51
51
52 Use --to-normal to convert largefiles back to normal files; after
52 Use --to-normal to convert largefiles back to normal files; after
53 this, the DEST repository can be used without largefiles at all.'''
53 this, the DEST repository can be used without largefiles at all.'''
54
54
55 if opts['to_normal']:
55 if opts['to_normal']:
56 tolfile = False
56 tolfile = False
57 else:
57 else:
58 tolfile = True
58 tolfile = True
59 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
59 size = lfutil.getminsize(ui, True, opts.get('size'), default=None)
60
60
61 if not hg.islocal(src):
61 if not hg.islocal(src):
62 raise util.Abort(_('%s is not a local Mercurial repo') % src)
62 raise util.Abort(_('%s is not a local Mercurial repo') % src)
63 if not hg.islocal(dest):
63 if not hg.islocal(dest):
64 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
64 raise util.Abort(_('%s is not a local Mercurial repo') % dest)
65
65
66 rsrc = hg.repository(ui, src)
66 rsrc = hg.repository(ui, src)
67 ui.status(_('initializing destination %s\n') % dest)
67 ui.status(_('initializing destination %s\n') % dest)
68 rdst = hg.repository(ui, dest, create=True)
68 rdst = hg.repository(ui, dest, create=True)
69
69
70 success = False
70 success = False
71 dstwlock = dstlock = None
71 dstwlock = dstlock = None
72 try:
72 try:
73 # Lock destination to prevent modification while it is converted to.
73 # Lock destination to prevent modification while it is converted to.
74 # Don't need to lock src because we are just reading from its history
74 # Don't need to lock src because we are just reading from its history
75 # which can't change.
75 # which can't change.
76 dstwlock = rdst.wlock()
76 dstwlock = rdst.wlock()
77 dstlock = rdst.lock()
77 dstlock = rdst.lock()
78
78
79 # Get a list of all changesets in the source. The easy way to do this
79 # Get a list of all changesets in the source. The easy way to do this
80 # is to simply walk the changelog, using changelog.nodesbetween().
80 # is to simply walk the changelog, using changelog.nodesbetween().
81 # Take a look at mercurial/revlog.py:639 for more details.
81 # Take a look at mercurial/revlog.py:639 for more details.
82 # Use a generator instead of a list to decrease memory usage
82 # Use a generator instead of a list to decrease memory usage
83 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
83 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None,
84 rsrc.heads())[0])
84 rsrc.heads())[0])
85 revmap = {node.nullid: node.nullid}
85 revmap = {node.nullid: node.nullid}
86 if tolfile:
86 if tolfile:
87 lfiles = set()
87 lfiles = set()
88 normalfiles = set()
88 normalfiles = set()
89 if not pats:
89 if not pats:
90 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
90 pats = ui.configlist(lfutil.longname, 'patterns', default=[])
91 if pats:
91 if pats:
92 matcher = match_.match(rsrc.root, '', list(pats))
92 matcher = match_.match(rsrc.root, '', list(pats))
93 else:
93 else:
94 matcher = None
94 matcher = None
95
95
96 lfiletohash = {}
96 lfiletohash = {}
97 for ctx in ctxs:
97 for ctx in ctxs:
98 ui.progress(_('converting revisions'), ctx.rev(),
98 ui.progress(_('converting revisions'), ctx.rev(),
99 unit=_('revision'), total=rsrc['tip'].rev())
99 unit=_('revision'), total=rsrc['tip'].rev())
100 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
100 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
101 lfiles, normalfiles, matcher, size, lfiletohash)
101 lfiles, normalfiles, matcher, size, lfiletohash)
102 ui.progress(_('converting revisions'), None)
102 ui.progress(_('converting revisions'), None)
103
103
104 if os.path.exists(rdst.wjoin(lfutil.shortname)):
104 if os.path.exists(rdst.wjoin(lfutil.shortname)):
105 shutil.rmtree(rdst.wjoin(lfutil.shortname))
105 shutil.rmtree(rdst.wjoin(lfutil.shortname))
106
106
107 for f in lfiletohash.keys():
107 for f in lfiletohash.keys():
108 if os.path.isfile(rdst.wjoin(f)):
108 if os.path.isfile(rdst.wjoin(f)):
109 os.unlink(rdst.wjoin(f))
109 os.unlink(rdst.wjoin(f))
110 try:
110 try:
111 os.removedirs(os.path.dirname(rdst.wjoin(f)))
111 os.removedirs(os.path.dirname(rdst.wjoin(f)))
112 except OSError:
112 except OSError:
113 pass
113 pass
114
114
115 # If there were any files converted to largefiles, add largefiles
115 # If there were any files converted to largefiles, add largefiles
116 # to the destination repository's requirements.
116 # to the destination repository's requirements.
117 if lfiles:
117 if lfiles:
118 rdst.requirements.add('largefiles')
118 rdst.requirements.add('largefiles')
119 rdst._writerequirements()
119 rdst._writerequirements()
120 else:
120 else:
121 for ctx in ctxs:
121 for ctx in ctxs:
122 ui.progress(_('converting revisions'), ctx.rev(),
122 ui.progress(_('converting revisions'), ctx.rev(),
123 unit=_('revision'), total=rsrc['tip'].rev())
123 unit=_('revision'), total=rsrc['tip'].rev())
124 _addchangeset(ui, rsrc, rdst, ctx, revmap)
124 _addchangeset(ui, rsrc, rdst, ctx, revmap)
125
125
126 ui.progress(_('converting revisions'), None)
126 ui.progress(_('converting revisions'), None)
127 success = True
127 success = True
128 finally:
128 finally:
129 rdst.dirstate.clear()
129 rdst.dirstate.clear()
130 release(dstlock, dstwlock)
130 release(dstlock, dstwlock)
131 if not success:
131 if not success:
132 # we failed, remove the new directory
132 # we failed, remove the new directory
133 shutil.rmtree(rdst.root)
133 shutil.rmtree(rdst.root)
134
134
135 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
135 def _addchangeset(ui, rsrc, rdst, ctx, revmap):
136 # Convert src parents to dst parents
136 # Convert src parents to dst parents
137 parents = _convertparents(ctx, revmap)
137 parents = _convertparents(ctx, revmap)
138
138
139 # Generate list of changed files
139 # Generate list of changed files
140 files = _getchangedfiles(ctx, parents)
140 files = _getchangedfiles(ctx, parents)
141
141
142 def getfilectx(repo, memctx, f):
142 def getfilectx(repo, memctx, f):
143 if lfutil.standin(f) in files:
143 if lfutil.standin(f) in files:
144 # if the file isn't in the manifest then it was removed
144 # if the file isn't in the manifest then it was removed
145 # or renamed, raise IOError to indicate this
145 # or renamed, raise IOError to indicate this
146 try:
146 try:
147 fctx = ctx.filectx(lfutil.standin(f))
147 fctx = ctx.filectx(lfutil.standin(f))
148 except error.LookupError:
148 except error.LookupError:
149 raise IOError
149 raise IOError
150 renamed = fctx.renamed()
150 renamed = fctx.renamed()
151 if renamed:
151 if renamed:
152 renamed = lfutil.splitstandin(renamed[0])
152 renamed = lfutil.splitstandin(renamed[0])
153
153
154 hash = fctx.data().strip()
154 hash = fctx.data().strip()
155 path = lfutil.findfile(rsrc, hash)
155 path = lfutil.findfile(rsrc, hash)
156
156
157 # If one file is missing, likely all files from this rev are
157 # If one file is missing, likely all files from this rev are
158 if path is None:
158 if path is None:
159 cachelfiles(ui, rsrc, ctx.node())
159 cachelfiles(ui, rsrc, ctx.node())
160 path = lfutil.findfile(rsrc, hash)
160 path = lfutil.findfile(rsrc, hash)
161
161
162 if path is None:
162 if path is None:
163 raise util.Abort(
163 raise util.Abort(
164 _("missing largefile \'%s\' from revision %s")
164 _("missing largefile \'%s\' from revision %s")
165 % (f, node.hex(ctx.node())))
165 % (f, node.hex(ctx.node())))
166
166
167 data = ''
167 data = ''
168 fd = None
168 fd = None
169 try:
169 try:
170 fd = open(path, 'rb')
170 fd = open(path, 'rb')
171 data = fd.read()
171 data = fd.read()
172 finally:
172 finally:
173 if fd:
173 if fd:
174 fd.close()
174 fd.close()
175 return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
175 return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
176 'x' in fctx.flags(), renamed)
176 'x' in fctx.flags(), renamed)
177 else:
177 else:
178 return _getnormalcontext(repo, ctx, f, revmap)
178 return _getnormalcontext(repo, ctx, f, revmap)
179
179
180 dstfiles = []
180 dstfiles = []
181 for file in files:
181 for file in files:
182 if lfutil.isstandin(file):
182 if lfutil.isstandin(file):
183 dstfiles.append(lfutil.splitstandin(file))
183 dstfiles.append(lfutil.splitstandin(file))
184 else:
184 else:
185 dstfiles.append(file)
185 dstfiles.append(file)
186 # Commit
186 # Commit
187 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
187 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
188
188
189 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
189 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles,
190 matcher, size, lfiletohash):
190 matcher, size, lfiletohash):
191 # Convert src parents to dst parents
191 # Convert src parents to dst parents
192 parents = _convertparents(ctx, revmap)
192 parents = _convertparents(ctx, revmap)
193
193
194 # Generate list of changed files
194 # Generate list of changed files
195 files = _getchangedfiles(ctx, parents)
195 files = _getchangedfiles(ctx, parents)
196
196
197 dstfiles = []
197 dstfiles = []
198 for f in files:
198 for f in files:
199 if f not in lfiles and f not in normalfiles:
199 if f not in lfiles and f not in normalfiles:
200 islfile = _islfile(f, ctx, matcher, size)
200 islfile = _islfile(f, ctx, matcher, size)
201 # If this file was renamed or copied then copy
201 # If this file was renamed or copied then copy
202 # the largefile-ness of its predecessor
202 # the largefile-ness of its predecessor
203 if f in ctx.manifest():
203 if f in ctx.manifest():
204 fctx = ctx.filectx(f)
204 fctx = ctx.filectx(f)
205 renamed = fctx.renamed()
205 renamed = fctx.renamed()
206 renamedlfile = renamed and renamed[0] in lfiles
206 renamedlfile = renamed and renamed[0] in lfiles
207 islfile |= renamedlfile
207 islfile |= renamedlfile
208 if 'l' in fctx.flags():
208 if 'l' in fctx.flags():
209 if renamedlfile:
209 if renamedlfile:
210 raise util.Abort(
210 raise util.Abort(
211 _('renamed/copied largefile %s becomes symlink')
211 _('renamed/copied largefile %s becomes symlink')
212 % f)
212 % f)
213 islfile = False
213 islfile = False
214 if islfile:
214 if islfile:
215 lfiles.add(f)
215 lfiles.add(f)
216 else:
216 else:
217 normalfiles.add(f)
217 normalfiles.add(f)
218
218
219 if f in lfiles:
219 if f in lfiles:
220 dstfiles.append(lfutil.standin(f))
220 dstfiles.append(lfutil.standin(f))
221 # largefile in manifest if it has not been removed/renamed
221 # largefile in manifest if it has not been removed/renamed
222 if f in ctx.manifest():
222 if f in ctx.manifest():
223 fctx = ctx.filectx(f)
223 fctx = ctx.filectx(f)
224 if 'l' in fctx.flags():
224 if 'l' in fctx.flags():
225 renamed = fctx.renamed()
225 renamed = fctx.renamed()
226 if renamed and renamed[0] in lfiles:
226 if renamed and renamed[0] in lfiles:
227 raise util.Abort(_('largefile %s becomes symlink') % f)
227 raise util.Abort(_('largefile %s becomes symlink') % f)
228
228
229 # largefile was modified, update standins
229 # largefile was modified, update standins
230 m = util.sha1('')
230 m = util.sha1('')
231 m.update(ctx[f].data())
231 m.update(ctx[f].data())
232 hash = m.hexdigest()
232 hash = m.hexdigest()
233 if f not in lfiletohash or lfiletohash[f] != hash:
233 if f not in lfiletohash or lfiletohash[f] != hash:
234 rdst.wwrite(f, ctx[f].data(), ctx[f].flags())
234 rdst.wwrite(f, ctx[f].data(), ctx[f].flags())
235 executable = 'x' in ctx[f].flags()
235 executable = 'x' in ctx[f].flags()
236 lfutil.writestandin(rdst, lfutil.standin(f), hash,
236 lfutil.writestandin(rdst, lfutil.standin(f), hash,
237 executable)
237 executable)
238 lfiletohash[f] = hash
238 lfiletohash[f] = hash
239 else:
239 else:
240 # normal file
240 # normal file
241 dstfiles.append(f)
241 dstfiles.append(f)
242
242
243 def getfilectx(repo, memctx, f):
243 def getfilectx(repo, memctx, f):
244 if lfutil.isstandin(f):
244 if lfutil.isstandin(f):
245 # if the file isn't in the manifest then it was removed
245 # if the file isn't in the manifest then it was removed
246 # or renamed, raise IOError to indicate this
246 # or renamed, raise IOError to indicate this
247 srcfname = lfutil.splitstandin(f)
247 srcfname = lfutil.splitstandin(f)
248 try:
248 try:
249 fctx = ctx.filectx(srcfname)
249 fctx = ctx.filectx(srcfname)
250 except error.LookupError:
250 except error.LookupError:
251 raise IOError
251 raise IOError
252 renamed = fctx.renamed()
252 renamed = fctx.renamed()
253 if renamed:
253 if renamed:
254 # standin is always a largefile because largefile-ness
254 # standin is always a largefile because largefile-ness
255 # doesn't change after rename or copy
255 # doesn't change after rename or copy
256 renamed = lfutil.standin(renamed[0])
256 renamed = lfutil.standin(renamed[0])
257
257
258 return context.memfilectx(repo, f, lfiletohash[srcfname] + '\n',
258 return context.memfilectx(repo, f, lfiletohash[srcfname] + '\n',
259 'l' in fctx.flags(), 'x' in fctx.flags(),
259 'l' in fctx.flags(), 'x' in fctx.flags(),
260 renamed)
260 renamed)
261 else:
261 else:
262 return _getnormalcontext(repo, ctx, f, revmap)
262 return _getnormalcontext(repo, ctx, f, revmap)
263
263
264 # Commit
264 # Commit
265 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
265 _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap)
266
266
267 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
267 def _commitcontext(rdst, parents, ctx, dstfiles, getfilectx, revmap):
268 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
268 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles,
269 getfilectx, ctx.user(), ctx.date(), ctx.extra())
269 getfilectx, ctx.user(), ctx.date(), ctx.extra())
270 ret = rdst.commitctx(mctx)
270 ret = rdst.commitctx(mctx)
271 rdst.setparents(ret)
271 rdst.setparents(ret)
272 revmap[ctx.node()] = rdst.changelog.tip()
272 revmap[ctx.node()] = rdst.changelog.tip()
273
273
274 # Generate list of changed files
274 # Generate list of changed files
275 def _getchangedfiles(ctx, parents):
275 def _getchangedfiles(ctx, parents):
276 files = set(ctx.files())
276 files = set(ctx.files())
277 if node.nullid not in parents:
277 if node.nullid not in parents:
278 mc = ctx.manifest()
278 mc = ctx.manifest()
279 mp1 = ctx.parents()[0].manifest()
279 mp1 = ctx.parents()[0].manifest()
280 mp2 = ctx.parents()[1].manifest()
280 mp2 = ctx.parents()[1].manifest()
281 files |= (set(mp1) | set(mp2)) - set(mc)
281 files |= (set(mp1) | set(mp2)) - set(mc)
282 for f in mc:
282 for f in mc:
283 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
283 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
284 files.add(f)
284 files.add(f)
285 return files
285 return files
286
286
287 # Convert src parents to dst parents
287 # Convert src parents to dst parents
288 def _convertparents(ctx, revmap):
288 def _convertparents(ctx, revmap):
289 parents = []
289 parents = []
290 for p in ctx.parents():
290 for p in ctx.parents():
291 parents.append(revmap[p.node()])
291 parents.append(revmap[p.node()])
292 while len(parents) < 2:
292 while len(parents) < 2:
293 parents.append(node.nullid)
293 parents.append(node.nullid)
294 return parents
294 return parents
295
295
296 # Get memfilectx for a normal file
296 # Get memfilectx for a normal file
297 def _getnormalcontext(repo, ctx, f, revmap):
297 def _getnormalcontext(repo, ctx, f, revmap):
298 try:
298 try:
299 fctx = ctx.filectx(f)
299 fctx = ctx.filectx(f)
300 except error.LookupError:
300 except error.LookupError:
301 raise IOError
301 raise IOError
302 renamed = fctx.renamed()
302 renamed = fctx.renamed()
303 if renamed:
303 if renamed:
304 renamed = renamed[0]
304 renamed = renamed[0]
305
305
306 data = fctx.data()
306 data = fctx.data()
307 if f == '.hgtags':
307 if f == '.hgtags':
308 data = _converttags (repo.ui, revmap, data)
308 data = _converttags (repo.ui, revmap, data)
309 return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
309 return context.memfilectx(repo, f, data, 'l' in fctx.flags(),
310 'x' in fctx.flags(), renamed)
310 'x' in fctx.flags(), renamed)
311
311
312 # Remap tag data using a revision map
312 # Remap tag data using a revision map
313 def _converttags(ui, revmap, data):
313 def _converttags(ui, revmap, data):
314 newdata = []
314 newdata = []
315 for line in data.splitlines():
315 for line in data.splitlines():
316 try:
316 try:
317 id, name = line.split(' ', 1)
317 id, name = line.split(' ', 1)
318 except ValueError:
318 except ValueError:
319 ui.warn(_('skipping incorrectly formatted tag %s\n')
319 ui.warn(_('skipping incorrectly formatted tag %s\n')
320 % line)
320 % line)
321 continue
321 continue
322 try:
322 try:
323 newid = node.bin(id)
323 newid = node.bin(id)
324 except TypeError:
324 except TypeError:
325 ui.warn(_('skipping incorrectly formatted id %s\n')
325 ui.warn(_('skipping incorrectly formatted id %s\n')
326 % id)
326 % id)
327 continue
327 continue
328 try:
328 try:
329 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
329 newdata.append('%s %s\n' % (node.hex(revmap[newid]),
330 name))
330 name))
331 except KeyError:
331 except KeyError:
332 ui.warn(_('no mapping for id %s\n') % id)
332 ui.warn(_('no mapping for id %s\n') % id)
333 continue
333 continue
334 return ''.join(newdata)
334 return ''.join(newdata)
335
335
336 def _islfile(file, ctx, matcher, size):
336 def _islfile(file, ctx, matcher, size):
337 '''Return true if file should be considered a largefile, i.e.
337 '''Return true if file should be considered a largefile, i.e.
338 matcher matches it or it is larger than size.'''
338 matcher matches it or it is larger than size.'''
339 # never store special .hg* files as largefiles
339 # never store special .hg* files as largefiles
340 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
340 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs':
341 return False
341 return False
342 if matcher and matcher(file):
342 if matcher and matcher(file):
343 return True
343 return True
344 try:
344 try:
345 return ctx.filectx(file).size() >= size * 1024 * 1024
345 return ctx.filectx(file).size() >= size * 1024 * 1024
346 except error.LookupError:
346 except error.LookupError:
347 return False
347 return False
348
348
349 def uploadlfiles(ui, rsrc, rdst, files):
349 def uploadlfiles(ui, rsrc, rdst, files):
350 '''upload largefiles to the central store'''
350 '''upload largefiles to the central store'''
351
351
352 if not files:
352 if not files:
353 return
353 return
354
354
355 store = basestore._openstore(rsrc, rdst, put=True)
355 store = basestore._openstore(rsrc, rdst, put=True)
356
356
357 at = 0
357 at = 0
358 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
358 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
359 retval = store.exists(files)
359 retval = store.exists(files)
360 files = filter(lambda h: not retval[h], files)
360 files = filter(lambda h: not retval[h], files)
361 ui.debug("%d largefiles need to be uploaded\n" % len(files))
361 ui.debug("%d largefiles need to be uploaded\n" % len(files))
362
362
363 for hash in files:
363 for hash in files:
364 ui.progress(_('uploading largefiles'), at, unit='largefile',
364 ui.progress(_('uploading largefiles'), at, unit='largefile',
365 total=len(files))
365 total=len(files))
366 source = lfutil.findfile(rsrc, hash)
366 source = lfutil.findfile(rsrc, hash)
367 if not source:
367 if not source:
368 raise util.Abort(_('largefile %s missing from store'
368 raise util.Abort(_('largefile %s missing from store'
369 ' (needs to be uploaded)') % hash)
369 ' (needs to be uploaded)') % hash)
370 # XXX check for errors here
370 # XXX check for errors here
371 store.put(source, hash)
371 store.put(source, hash)
372 at += 1
372 at += 1
373 ui.progress(_('uploading largefiles'), None)
373 ui.progress(_('uploading largefiles'), None)
374
374
375 def verifylfiles(ui, repo, all=False, contents=False):
375 def verifylfiles(ui, repo, all=False, contents=False):
376 '''Verify that every largefile revision in the current changeset
376 '''Verify that every largefile revision in the current changeset
377 exists in the central store. With --contents, also verify that
377 exists in the central store. With --contents, also verify that
378 the contents of each local largefile file revision are correct (SHA-1 hash
378 the contents of each local largefile file revision are correct (SHA-1 hash
379 matches the revision ID). With --all, check every changeset in
379 matches the revision ID). With --all, check every changeset in
380 this repository.'''
380 this repository.'''
381 if all:
381 if all:
382 # Pass a list to the function rather than an iterator because we know a
382 # Pass a list to the function rather than an iterator because we know a
383 # list will work.
383 # list will work.
384 revs = range(len(repo))
384 revs = range(len(repo))
385 else:
385 else:
386 revs = ['.']
386 revs = ['.']
387
387
388 store = basestore._openstore(repo)
388 store = basestore._openstore(repo)
389 return store.verify(revs, contents=contents)
389 return store.verify(revs, contents=contents)
390
390
391 def cachelfiles(ui, repo, node, filelist=None):
391 def cachelfiles(ui, repo, node, filelist=None):
392 '''cachelfiles ensures that all largefiles needed by the specified revision
392 '''cachelfiles ensures that all largefiles needed by the specified revision
393 are present in the repository's largefile cache.
393 are present in the repository's largefile cache.
394
394
395 returns a tuple (cached, missing). cached is the list of files downloaded
395 returns a tuple (cached, missing). cached is the list of files downloaded
396 by this operation; missing is the list of files that were needed but could
396 by this operation; missing is the list of files that were needed but could
397 not be found.'''
397 not be found.'''
398 lfiles = lfutil.listlfiles(repo, node)
398 lfiles = lfutil.listlfiles(repo, node)
399 if filelist:
399 if filelist:
400 lfiles = set(lfiles) & set(filelist)
400 lfiles = set(lfiles) & set(filelist)
401 toget = []
401 toget = []
402
402
403 for lfile in lfiles:
403 for lfile in lfiles:
404 try:
404 try:
405 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
405 expectedhash = repo[node][lfutil.standin(lfile)].data().strip()
406 except IOError, err:
406 except IOError, err:
407 if err.errno == errno.ENOENT:
407 if err.errno == errno.ENOENT:
408 continue # node must be None and standin wasn't found in wctx
408 continue # node must be None and standin wasn't found in wctx
409 raise
409 raise
410 if not lfutil.findfile(repo, expectedhash):
410 if not lfutil.findfile(repo, expectedhash):
411 toget.append((lfile, expectedhash))
411 toget.append((lfile, expectedhash))
412
412
413 if toget:
413 if toget:
414 store = basestore._openstore(repo)
414 store = basestore._openstore(repo)
415 ret = store.get(toget)
415 ret = store.get(toget)
416 return ret
416 return ret
417
417
418 return ([], [])
418 return ([], [])
419
419
420 def downloadlfiles(ui, repo, rev=None):
420 def downloadlfiles(ui, repo, rev=None):
421 matchfn = scmutil.match(repo[None],
421 matchfn = scmutil.match(repo[None],
422 [repo.wjoin(lfutil.shortname)], {})
422 [repo.wjoin(lfutil.shortname)], {})
423 def prepare(ctx, fns):
423 def prepare(ctx, fns):
424 pass
424 pass
425 totalsuccess = 0
425 totalsuccess = 0
426 totalmissing = 0
426 totalmissing = 0
427 if rev != []: # walkchangerevs on empty list would return all revs
427 if rev != []: # walkchangerevs on empty list would return all revs
428 for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev' : rev},
428 for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev' : rev},
429 prepare):
429 prepare):
430 success, missing = cachelfiles(ui, repo, ctx.node())
430 success, missing = cachelfiles(ui, repo, ctx.node())
431 totalsuccess += len(success)
431 totalsuccess += len(success)
432 totalmissing += len(missing)
432 totalmissing += len(missing)
433 ui.status(_("%d additional largefiles cached\n") % totalsuccess)
433 ui.status(_("%d additional largefiles cached\n") % totalsuccess)
434 if totalmissing > 0:
434 if totalmissing > 0:
435 ui.status(_("%d largefiles failed to download\n") % totalmissing)
435 ui.status(_("%d largefiles failed to download\n") % totalmissing)
436 return totalsuccess, totalmissing
436 return totalsuccess, totalmissing
437
437
438 def updatelfiles(ui, repo, filelist=None, printmessage=True,
438 def updatelfiles(ui, repo, filelist=None, printmessage=True,
439 normallookup=False):
439 normallookup=False):
440 wlock = repo.wlock()
440 wlock = repo.wlock()
441 try:
441 try:
442 lfdirstate = lfutil.openlfdirstate(ui, repo)
442 lfdirstate = lfutil.openlfdirstate(ui, repo)
443 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
443 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
444
444
445 if filelist is not None:
445 if filelist is not None:
446 filelist = set(filelist)
446 lfiles = [f for f in lfiles if f in filelist]
447 lfiles = [f for f in lfiles if f in filelist]
447
448
448 update = {}
449 update = {}
449 updated, removed = 0, 0
450 updated, removed = 0, 0
450 for lfile in lfiles:
451 for lfile in lfiles:
451 abslfile = repo.wjoin(lfile)
452 abslfile = repo.wjoin(lfile)
452 absstandin = repo.wjoin(lfutil.standin(lfile))
453 absstandin = repo.wjoin(lfutil.standin(lfile))
453 if os.path.exists(absstandin):
454 if os.path.exists(absstandin):
454 if (os.path.exists(absstandin + '.orig') and
455 if (os.path.exists(absstandin + '.orig') and
455 os.path.exists(abslfile)):
456 os.path.exists(abslfile)):
456 shutil.copyfile(abslfile, abslfile + '.orig')
457 shutil.copyfile(abslfile, abslfile + '.orig')
457 util.unlinkpath(absstandin + '.orig')
458 util.unlinkpath(absstandin + '.orig')
458 expecthash = lfutil.readstandin(repo, lfile)
459 expecthash = lfutil.readstandin(repo, lfile)
459 if (expecthash != '' and
460 if (expecthash != '' and
460 (not os.path.exists(abslfile) or
461 (not os.path.exists(abslfile) or
461 expecthash != lfutil.hashfile(abslfile))):
462 expecthash != lfutil.hashfile(abslfile))):
462 if lfile not in repo[None]: # not switched to normal file
463 if lfile not in repo[None]: # not switched to normal file
463 util.unlinkpath(abslfile, ignoremissing=True)
464 util.unlinkpath(abslfile, ignoremissing=True)
464 # use normallookup() to allocate entry in largefiles
465 # use normallookup() to allocate entry in largefiles
465 # dirstate, because lack of it misleads
466 # dirstate, because lack of it misleads
466 # lfilesrepo.status() into recognition that such cache
467 # lfilesrepo.status() into recognition that such cache
467 # missing files are REMOVED.
468 # missing files are REMOVED.
468 lfdirstate.normallookup(lfile)
469 lfdirstate.normallookup(lfile)
469 update[lfile] = expecthash
470 update[lfile] = expecthash
470 else:
471 else:
471 # Remove lfiles for which the standin is deleted, unless the
472 # Remove lfiles for which the standin is deleted, unless the
472 # lfile is added to the repository again. This happens when a
473 # lfile is added to the repository again. This happens when a
473 # largefile is converted back to a normal file: the standin
474 # largefile is converted back to a normal file: the standin
474 # disappears, but a new (normal) file appears as the lfile.
475 # disappears, but a new (normal) file appears as the lfile.
475 if (os.path.exists(abslfile) and
476 if (os.path.exists(abslfile) and
476 repo.dirstate.normalize(lfile) not in repo[None]):
477 repo.dirstate.normalize(lfile) not in repo[None]):
477 util.unlinkpath(abslfile)
478 util.unlinkpath(abslfile)
478 removed += 1
479 removed += 1
479
480
480 # largefile processing might be slow and be interrupted - be prepared
481 # largefile processing might be slow and be interrupted - be prepared
481 lfdirstate.write()
482 lfdirstate.write()
482
483
483 if lfiles:
484 if lfiles:
484 if printmessage:
485 if printmessage:
485 ui.status(_('getting changed largefiles\n'))
486 ui.status(_('getting changed largefiles\n'))
486 cachelfiles(ui, repo, None, lfiles)
487 cachelfiles(ui, repo, None, lfiles)
487
488
488 for lfile in lfiles:
489 for lfile in lfiles:
489 update1 = 0
490 update1 = 0
490
491
491 expecthash = update.get(lfile)
492 expecthash = update.get(lfile)
492 if expecthash:
493 if expecthash:
493 if not lfutil.copyfromcache(repo, expecthash, lfile):
494 if not lfutil.copyfromcache(repo, expecthash, lfile):
494 # failed ... but already removed and set to normallookup
495 # failed ... but already removed and set to normallookup
495 continue
496 continue
496 # Synchronize largefile dirstate to the last modified
497 # Synchronize largefile dirstate to the last modified
497 # time of the file
498 # time of the file
498 lfdirstate.normal(lfile)
499 lfdirstate.normal(lfile)
499 update1 = 1
500 update1 = 1
500
501
501 # copy the state of largefile standin from the repository's
502 # copy the state of largefile standin from the repository's
502 # dirstate to its state in the lfdirstate.
503 # dirstate to its state in the lfdirstate.
503 abslfile = repo.wjoin(lfile)
504 abslfile = repo.wjoin(lfile)
504 absstandin = repo.wjoin(lfutil.standin(lfile))
505 absstandin = repo.wjoin(lfutil.standin(lfile))
505 if os.path.exists(absstandin):
506 if os.path.exists(absstandin):
506 mode = os.stat(absstandin).st_mode
507 mode = os.stat(absstandin).st_mode
507 if mode != os.stat(abslfile).st_mode:
508 if mode != os.stat(abslfile).st_mode:
508 os.chmod(abslfile, mode)
509 os.chmod(abslfile, mode)
509 update1 = 1
510 update1 = 1
510
511
511 updated += update1
512 updated += update1
512
513
513 lfutil.synclfdirstate(repo, lfdirstate, lfile, normallookup)
514 lfutil.synclfdirstate(repo, lfdirstate, lfile, normallookup)
514
515
516 if filelist is not None:
517 # If "local largefile" is chosen at file merging, it is
518 # not listed in "filelist" (= dirstate syncing is
519 # omitted), because the standin file is not changed before and
520 # after merging.
521 # But the status of such files may have to be changed by
522 # merging. For example, locally modified ("M") largefile
523 # has to become re-added("A"), if it is "normal" file in
524 # the target revision of linear-merging.
525 for lfile in lfdirstate:
526 if lfile not in filelist:
527 lfutil.synclfdirstate(repo, lfdirstate, lfile, True)
528
515 lfdirstate.write()
529 lfdirstate.write()
516 if printmessage and lfiles:
530 if printmessage and lfiles:
517 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
531 ui.status(_('%d largefiles updated, %d removed\n') % (updated,
518 removed))
532 removed))
519 finally:
533 finally:
520 wlock.release()
534 wlock.release()
521
535
522 @command('lfpull',
536 @command('lfpull',
523 [('r', 'rev', [], _('pull largefiles for these revisions'))
537 [('r', 'rev', [], _('pull largefiles for these revisions'))
524 ] + commands.remoteopts,
538 ] + commands.remoteopts,
525 _('-r REV... [-e CMD] [--remotecmd CMD] [SOURCE]'))
539 _('-r REV... [-e CMD] [--remotecmd CMD] [SOURCE]'))
526 def lfpull(ui, repo, source="default", **opts):
540 def lfpull(ui, repo, source="default", **opts):
527 """pull largefiles for the specified revisions from the specified source
541 """pull largefiles for the specified revisions from the specified source
528
542
529 Pull largefiles that are referenced from local changesets but missing
543 Pull largefiles that are referenced from local changesets but missing
530 locally, pulling from a remote repository to the local cache.
544 locally, pulling from a remote repository to the local cache.
531
545
532 If SOURCE is omitted, the 'default' path will be used.
546 If SOURCE is omitted, the 'default' path will be used.
533 See :hg:`help urls` for more information.
547 See :hg:`help urls` for more information.
534
548
535 .. container:: verbose
549 .. container:: verbose
536
550
537 Some examples:
551 Some examples:
538
552
539 - pull largefiles for all branch heads::
553 - pull largefiles for all branch heads::
540
554
541 hg lfpull -r "head() and not closed()"
555 hg lfpull -r "head() and not closed()"
542
556
543 - pull largefiles on the default branch::
557 - pull largefiles on the default branch::
544
558
545 hg lfpull -r "branch(default)"
559 hg lfpull -r "branch(default)"
546 """
560 """
547 repo.lfpullsource = source
561 repo.lfpullsource = source
548
562
549 revs = opts.get('rev', [])
563 revs = opts.get('rev', [])
550 if not revs:
564 if not revs:
551 raise util.Abort(_('no revisions specified'))
565 raise util.Abort(_('no revisions specified'))
552 revs = scmutil.revrange(repo, revs)
566 revs = scmutil.revrange(repo, revs)
553
567
554 numcached = 0
568 numcached = 0
555 for rev in revs:
569 for rev in revs:
556 ui.note(_('pulling largefiles for revision %s\n') % rev)
570 ui.note(_('pulling largefiles for revision %s\n') % rev)
557 (cached, missing) = cachelfiles(ui, repo, rev)
571 (cached, missing) = cachelfiles(ui, repo, rev)
558 numcached += len(cached)
572 numcached += len(cached)
559 ui.status(_("%d largefiles cached\n") % numcached)
573 ui.status(_("%d largefiles cached\n") % numcached)
@@ -1,267 +1,271 b''
1 This file focuses mainly on updating largefiles in the working
1 This file focuses mainly on updating largefiles in the working
2 directory (and ".hg/largefiles/dirstate")
2 directory (and ".hg/largefiles/dirstate")
3
3
4 $ cat >> $HGRCPATH <<EOF
4 $ cat >> $HGRCPATH <<EOF
5 > [ui]
5 > [ui]
6 > merge = internal:fail
6 > merge = internal:fail
7 > [extensions]
7 > [extensions]
8 > largefiles =
8 > largefiles =
9 > EOF
9 > EOF
10
10
11 $ hg init repo
11 $ hg init repo
12 $ cd repo
12 $ cd repo
13
13
14 $ echo large1 > large1
14 $ echo large1 > large1
15 $ echo large2 > large2
15 $ echo large2 > large2
16 $ hg add --large large1 large2
16 $ hg add --large large1 large2
17 $ echo normal1 > normal1
17 $ echo normal1 > normal1
18 $ hg add normal1
18 $ hg add normal1
19 $ hg commit -m '#0'
19 $ hg commit -m '#0'
20 $ echo 'large1 in #1' > large1
20 $ echo 'large1 in #1' > large1
21 $ echo 'normal1 in #1' > normal1
21 $ echo 'normal1 in #1' > normal1
22 $ hg commit -m '#1'
22 $ hg commit -m '#1'
23 $ hg update -q -C 0
23 $ hg update -q -C 0
24 $ echo 'large2 in #2' > large2
24 $ echo 'large2 in #2' > large2
25 $ hg commit -m '#2'
25 $ hg commit -m '#2'
26 created new head
26 created new head
27
27
28 Test that "hg merge" updates largefiles from "other" correctly
28 Test that "hg merge" updates largefiles from "other" correctly
29
29
30 (getting largefiles from "other" normally)
30 (getting largefiles from "other" normally)
31
31
32 $ hg status -A large1
32 $ hg status -A large1
33 C large1
33 C large1
34 $ cat large1
34 $ cat large1
35 large1
35 large1
36 $ cat .hglf/large1
36 $ cat .hglf/large1
37 4669e532d5b2c093a78eca010077e708a071bb64
37 4669e532d5b2c093a78eca010077e708a071bb64
38 $ hg merge --config debug.dirstate.delaywrite=2
38 $ hg merge --config debug.dirstate.delaywrite=2
39 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
39 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 (branch merge, don't forget to commit)
40 (branch merge, don't forget to commit)
41 getting changed largefiles
41 getting changed largefiles
42 1 largefiles updated, 0 removed
42 1 largefiles updated, 0 removed
43 $ hg status -A large1
43 $ hg status -A large1
44 M large1
44 M large1
45 $ cat large1
45 $ cat large1
46 large1 in #1
46 large1 in #1
47 $ cat .hglf/large1
47 $ cat .hglf/large1
48 58e24f733a964da346e2407a2bee99d9001184f5
48 58e24f733a964da346e2407a2bee99d9001184f5
49 $ hg diff -c 1 --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
49 $ hg diff -c 1 --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
50 -4669e532d5b2c093a78eca010077e708a071bb64
50 -4669e532d5b2c093a78eca010077e708a071bb64
51 +58e24f733a964da346e2407a2bee99d9001184f5
51 +58e24f733a964da346e2407a2bee99d9001184f5
52
52
53 (getting largefiles from "other" via conflict prompt)
53 (getting largefiles from "other" via conflict prompt)
54
54
55 $ hg update -q -C 2
55 $ hg update -q -C 2
56 $ echo 'large1 in #3' > large1
56 $ echo 'large1 in #3' > large1
57 $ echo 'normal1 in #3' > normal1
57 $ echo 'normal1 in #3' > normal1
58 $ hg commit -m '#3'
58 $ hg commit -m '#3'
59 $ cat .hglf/large1
59 $ cat .hglf/large1
60 e5bb990443d6a92aaf7223813720f7566c9dd05b
60 e5bb990443d6a92aaf7223813720f7566c9dd05b
61 $ hg merge --config debug.dirstate.delaywrite=2 --config ui.interactive=True <<EOF
61 $ hg merge --config debug.dirstate.delaywrite=2 --config ui.interactive=True <<EOF
62 > o
62 > o
63 > EOF
63 > EOF
64 largefile large1 has a merge conflict
64 largefile large1 has a merge conflict
65 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
65 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
66 keep (l)ocal e5bb990443d6a92aaf7223813720f7566c9dd05b or
66 keep (l)ocal e5bb990443d6a92aaf7223813720f7566c9dd05b or
67 take (o)ther 58e24f733a964da346e2407a2bee99d9001184f5? merging normal1
67 take (o)ther 58e24f733a964da346e2407a2bee99d9001184f5? merging normal1
68 warning: conflicts during merge.
68 warning: conflicts during merge.
69 merging normal1 incomplete! (edit conflicts, then use 'hg resolve --mark')
69 merging normal1 incomplete! (edit conflicts, then use 'hg resolve --mark')
70 0 files updated, 1 files merged, 0 files removed, 1 files unresolved
70 0 files updated, 1 files merged, 0 files removed, 1 files unresolved
71 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
71 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
72 getting changed largefiles
72 getting changed largefiles
73 1 largefiles updated, 0 removed
73 1 largefiles updated, 0 removed
74 [1]
74 [1]
75 $ hg status -A large1
75 $ hg status -A large1
76 M large1
76 M large1
77 $ cat large1
77 $ cat large1
78 large1 in #1
78 large1 in #1
79 $ cat .hglf/large1
79 $ cat .hglf/large1
80 58e24f733a964da346e2407a2bee99d9001184f5
80 58e24f733a964da346e2407a2bee99d9001184f5
81
81
82 Test that "hg revert -r REV" updates largefiles from "REV" correctly
82 Test that "hg revert -r REV" updates largefiles from "REV" correctly
83
83
84 $ hg update -q -C 3
84 $ hg update -q -C 3
85 $ hg status -A large1
85 $ hg status -A large1
86 C large1
86 C large1
87 $ cat large1
87 $ cat large1
88 large1 in #3
88 large1 in #3
89 $ cat .hglf/large1
89 $ cat .hglf/large1
90 e5bb990443d6a92aaf7223813720f7566c9dd05b
90 e5bb990443d6a92aaf7223813720f7566c9dd05b
91 $ hg diff -c 1 --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
91 $ hg diff -c 1 --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
92 -4669e532d5b2c093a78eca010077e708a071bb64
92 -4669e532d5b2c093a78eca010077e708a071bb64
93 +58e24f733a964da346e2407a2bee99d9001184f5
93 +58e24f733a964da346e2407a2bee99d9001184f5
94 $ hg revert --no-backup -r 1 --config debug.dirstate.delaywrite=2 large1
94 $ hg revert --no-backup -r 1 --config debug.dirstate.delaywrite=2 large1
95 $ hg status -A large1
95 $ hg status -A large1
96 M large1
96 M large1
97 $ cat large1
97 $ cat large1
98 large1 in #1
98 large1 in #1
99 $ cat .hglf/large1
99 $ cat .hglf/large1
100 58e24f733a964da346e2407a2bee99d9001184f5
100 58e24f733a964da346e2407a2bee99d9001184f5
101
101
102 Test that "hg rollback" restores status of largefiles correctly
102 Test that "hg rollback" restores status of largefiles correctly
103
103
104 $ hg update -C -q
104 $ hg update -C -q
105 $ hg remove large1
105 $ hg remove large1
106 $ hg forget large2
106 $ hg forget large2
107 $ echo largeX > largeX
107 $ echo largeX > largeX
108 $ hg add --large largeX
108 $ hg add --large largeX
109 $ hg commit -m 'will be rollback-ed soon'
109 $ hg commit -m 'will be rollback-ed soon'
110 $ echo largeY > largeY
110 $ echo largeY > largeY
111 $ hg add --large largeY
111 $ hg add --large largeY
112 $ hg status -A large1
112 $ hg status -A large1
113 large1: No such file or directory
113 large1: No such file or directory
114 $ hg status -A large2
114 $ hg status -A large2
115 ? large2
115 ? large2
116 $ hg status -A largeX
116 $ hg status -A largeX
117 C largeX
117 C largeX
118 $ hg status -A largeY
118 $ hg status -A largeY
119 A largeY
119 A largeY
120 $ hg rollback
120 $ hg rollback
121 repository tip rolled back to revision 3 (undo commit)
121 repository tip rolled back to revision 3 (undo commit)
122 working directory now based on revision 3
122 working directory now based on revision 3
123 $ hg status -A large1
123 $ hg status -A large1
124 R large1
124 R large1
125 $ hg status -A large2
125 $ hg status -A large2
126 R large2
126 R large2
127 $ hg status -A largeX
127 $ hg status -A largeX
128 A largeX
128 A largeX
129 $ hg status -A largeY
129 $ hg status -A largeY
130 ? largeY
130 ? largeY
131
131
132 Test that "hg status" shows status of largefiles correctly just after
132 Test that "hg status" shows status of largefiles correctly just after
133 automated commit like rebase/transplant
133 automated commit like rebase/transplant
134
134
135 $ cat >> .hg/hgrc <<EOF
135 $ cat >> .hg/hgrc <<EOF
136 > [extensions]
136 > [extensions]
137 > rebase =
137 > rebase =
138 > strip =
138 > strip =
139 > transplant =
139 > transplant =
140 > EOF
140 > EOF
141 $ hg update -q -C 1
141 $ hg update -q -C 1
142 $ hg remove large1
142 $ hg remove large1
143 $ echo largeX > largeX
143 $ echo largeX > largeX
144 $ hg add --large largeX
144 $ hg add --large largeX
145 $ hg commit -m '#4'
145 $ hg commit -m '#4'
146
146
147 $ hg rebase -s 1 -d 2 --keep
147 $ hg rebase -s 1 -d 2 --keep
148 $ hg status -A large1
148 $ hg status -A large1
149 large1: No such file or directory
149 large1: No such file or directory
150 $ hg status -A largeX
150 $ hg status -A largeX
151 C largeX
151 C largeX
152 $ hg strip -q 5
152 $ hg strip -q 5
153
153
154 $ hg update -q -C 2
154 $ hg update -q -C 2
155 $ hg transplant -q 1 4
155 $ hg transplant -q 1 4
156 $ hg status -A large1
156 $ hg status -A large1
157 large1: No such file or directory
157 large1: No such file or directory
158 $ hg status -A largeX
158 $ hg status -A largeX
159 C largeX
159 C largeX
160 $ hg strip -q 5
160 $ hg strip -q 5
161
161
162 $ hg update -q -C 2
162 $ hg update -q -C 2
163 $ hg transplant -q --merge 1 --merge 4
163 $ hg transplant -q --merge 1 --merge 4
164 $ hg status -A large1
164 $ hg status -A large1
165 large1: No such file or directory
165 large1: No such file or directory
166 $ hg status -A largeX
166 $ hg status -A largeX
167 C largeX
167 C largeX
168 $ hg strip -q 5
168 $ hg strip -q 5
169
169
170 Test that linear merge can detect modification (and conflict) correctly
170 Test that linear merge can detect modification (and conflict) correctly
171
171
172 (linear merge without conflict)
172 (linear merge without conflict)
173
173
174 $ echo 'large2 for linear merge (no conflict)' > large2
174 $ echo 'large2 for linear merge (no conflict)' > large2
175 $ hg update 3 --config debug.dirstate.delaywrite=2
175 $ hg update 3 --config debug.dirstate.delaywrite=2
176 getting changed largefiles
176 getting changed largefiles
177 1 largefiles updated, 0 removed
177 1 largefiles updated, 0 removed
178 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
178 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
179 $ hg status -A large2
179 $ hg status -A large2
180 M large2
180 M large2
181 $ cat large2
181 $ cat large2
182 large2 for linear merge (no conflict)
182 large2 for linear merge (no conflict)
183 $ cat .hglf/large2
183 $ cat .hglf/large2
184 9c4bf8f1b33536d6e5f89447e10620cfe52ea710
184 9c4bf8f1b33536d6e5f89447e10620cfe52ea710
185
185
186 (linear merge with conflict, choosing "other")
186 (linear merge with conflict, choosing "other")
187
187
188 $ hg update -q -C 2
188 $ hg update -q -C 2
189 $ echo 'large1 for linear merge (conflict)' > large1
189 $ echo 'large1 for linear merge (conflict)' > large1
190 $ hg update 3 --config ui.interactive=True <<EOF
190 $ hg update 3 --config ui.interactive=True <<EOF
191 > o
191 > o
192 > EOF
192 > EOF
193 largefile large1 has a merge conflict
193 largefile large1 has a merge conflict
194 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
194 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
195 keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
195 keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
196 take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? getting changed largefiles
196 take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? getting changed largefiles
197 1 largefiles updated, 0 removed
197 1 largefiles updated, 0 removed
198 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
198 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
199 $ hg status -A large1
199 $ hg status -A large1
200 C large1
200 C large1
201 $ cat large1
201 $ cat large1
202 large1 in #3
202 large1 in #3
203 $ cat .hglf/large1
203 $ cat .hglf/large1
204 e5bb990443d6a92aaf7223813720f7566c9dd05b
204 e5bb990443d6a92aaf7223813720f7566c9dd05b
205
205
206 (linear merge with conflict, choosing "local")
206 (linear merge with conflict, choosing "local")
207
207
208 $ hg update -q -C 2
208 $ hg update -q -C 2
209 $ echo 'large1 for linear merge (conflict)' > large1
209 $ echo 'large1 for linear merge (conflict)' > large1
210 $ hg update 3 --config debug.dirstate.delaywrite=2
210 $ hg update 3 --config debug.dirstate.delaywrite=2
211 largefile large1 has a merge conflict
211 largefile large1 has a merge conflict
212 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
212 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
213 keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
213 keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
214 take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? l
214 take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? l
215 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
215 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
216 $ hg status -A large1
216 $ hg status -A large1
217 M large1
217 M large1
218 $ cat large1
218 $ cat large1
219 large1 for linear merge (conflict)
219 large1 for linear merge (conflict)
220 $ cat .hglf/large1
220 $ cat .hglf/large1
221 ba94c2efe5b7c5e0af8d189295ce00553b0612b7
221 ba94c2efe5b7c5e0af8d189295ce00553b0612b7
222
222
223 Test a linear merge to a revision containing same-name normal file
223 Test a linear merge to a revision containing same-name normal file
224
224
225 $ hg update -q -C 3
225 $ hg update -q -C 3
226 $ hg remove large2
226 $ hg remove large2
227 $ echo 'large2 as normal file' > large2
227 $ echo 'large2 as normal file' > large2
228 $ hg add large2
228 $ hg add large2
229 $ echo 'large3 as normal file' > large3
229 $ echo 'large3 as normal file' > large3
230 $ hg add large3
230 $ hg add large3
231 $ hg commit -m '#5'
231 $ hg commit -m '#5'
232 $ hg manifest
232 $ hg manifest
233 .hglf/large1
233 .hglf/large1
234 large2
234 large2
235 large3
235 large3
236 normal1
236 normal1
237
237
238 (modified largefile is already switched to normal)
238 (modified largefile is already switched to normal)
239
239
240 $ hg update -q -C 2
240 $ hg update -q -C 2
241 $ echo 'modified large2 for linear merge' > large2
241 $ echo 'modified large2 for linear merge' > large2
242 $ hg update -q 5
242 $ hg update -q 5
243 local changed .hglf/large2 which remote deleted
243 local changed .hglf/large2 which remote deleted
244 use (c)hanged version or (d)elete? c
244 use (c)hanged version or (d)elete? c
245 remote turned local largefile large2 into a normal file
245 remote turned local largefile large2 into a normal file
246 keep (l)argefile or use (n)ormal file? l
246 keep (l)argefile or use (n)ormal file? l
247 $ hg debugdirstate --nodates | grep large2
247 $ hg debugdirstate --nodates | grep large2
248 a 0 -1 .hglf/large2
248 a 0 -1 .hglf/large2
249 r 0 0 large2
249 r 0 0 large2
250 $ hg status -A large2
251 A large2
250 $ cat large2
252 $ cat large2
251 modified large2 for linear merge
253 modified large2 for linear merge
252
254
253 (added largefile is already committed as normal)
255 (added largefile is already committed as normal)
254
256
255 $ hg update -q -C 2
257 $ hg update -q -C 2
256 $ echo 'large3 as large file for linear merge' > large3
258 $ echo 'large3 as large file for linear merge' > large3
257 $ hg add --large large3
259 $ hg add --large large3
258 $ hg update -q 5
260 $ hg update -q 5
259 remote turned local largefile large3 into a normal file
261 remote turned local largefile large3 into a normal file
260 keep (l)argefile or use (n)ormal file? l
262 keep (l)argefile or use (n)ormal file? l
261 $ hg debugdirstate --nodates | grep large3
263 $ hg debugdirstate --nodates | grep large3
262 a 0 -1 .hglf/large3
264 a 0 -1 .hglf/large3
263 r 0 0 large3
265 r 0 0 large3
266 $ hg status -A large3
267 A large3
264 $ cat large3
268 $ cat large3
265 large3 as large file for linear merge
269 large3 as large file for linear merge
266
270
267 $ cd ..
271 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now