Mercurial > public > mercurial-scm > hg
annotate hgext/largefiles/lfcommands.py @ 15793:3ef07ecdb0d5
largefiles: correctly handle dirstate status when rebasing
When rebasing, we need to trust that the standins are always correct. The
rebase operation updates the standins according to the changeset it is
rebasing. We need to make the largefiles in the working copy match. If we
don't make them match, then they get accidentally reverted, either during
the rebase or during the next commit after the rebase.
This worked previously only becuase we were relying on the behavior that
largefiles with a changed standin, but unchanged contents, never showed up in
the list of modified largefiles. Unfortunately, pre-commit hooks can get
an incorrect status this way, and it also results in extra execution of code.
The solution is to simply trust the standins when we are about to commit a
rebased changeset, and politely ask updatelfiles() to pull the new contents
down. In this case, updatelfiles() will also mark any files it has pulled
down as dirty in the lfdirstate so that pre-commit hooks will get correct
status output.
author | Na'Tosha Bard <natosha@unity3d.com> |
---|---|
date | Sat, 07 Jan 2012 18:43:34 +0100 |
parents | 9036c7d106bf |
children | 62098aeb1e15 |
rev | line source |
---|---|
15168 | 1 # Copyright 2009-2010 Gregory P. Ward |
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated | |
3 # Copyright 2010-2011 Fog Creek Software | |
4 # Copyright 2010-2011 Unity Technologies | |
5 # | |
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. | |
8 | |
15252
6e809bb4f969
largefiles: improve comments, internal docstrings
Greg Ward <greg@gerg.ca>
parents:
15230
diff
changeset
|
9 '''High-level command function for lfconvert, plus the cmdtable.''' |
15168 | 10 |
11 import os | |
12 import shutil | |
13 | |
14 from mercurial import util, match as match_, hg, node, context, error | |
15 from mercurial.i18n import _ | |
16 | |
17 import lfutil | |
18 import basestore | |
19 | |
20 # -- Commands ---------------------------------------------------------- | |
21 | |
22 def lfconvert(ui, src, dest, *pats, **opts): | |
15230 | 23 '''convert a normal repository to a largefiles repository |
15168 | 24 |
15230 | 25 Convert repository SOURCE to a new repository DEST, identical to |
26 SOURCE except that certain files will be converted as largefiles: | |
27 specifically, any file that matches any PATTERN *or* whose size is | |
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 | |
30 largefile is the size of the first version of the file. The | |
31 minimum size can be specified either with --size or in | |
32 configuration as ``largefiles.size``. | |
33 | |
34 After running this command you will need to make sure that | |
35 largefiles is enabled anywhere you intend to push the new | |
36 repository. | |
37 | |
15332
0db47b8d025f
largefiles: rename lfconvert --tonormal option to --to-normal
Greg Ward <greg@gerg.ca>
parents:
15317
diff
changeset
|
38 Use --to-normal to convert largefiles back to normal files; after |
15230 | 39 this, the DEST repository can be used without largefiles at all.''' |
15168 | 40 |
15332
0db47b8d025f
largefiles: rename lfconvert --tonormal option to --to-normal
Greg Ward <greg@gerg.ca>
parents:
15317
diff
changeset
|
41 if opts['to_normal']: |
15168 | 42 tolfile = False |
43 else: | |
44 tolfile = True | |
15227
a7686abf73a6
largefiles: factor out lfutil.getminsize()
Greg Ward <greg@gerg.ca>
parents:
15224
diff
changeset
|
45 size = lfutil.getminsize(ui, True, opts.get('size'), default=None) |
15340
0e58513cc59a
largefiles: rearrange how lfconvert detects non-local repos
Greg Ward <greg@gerg.ca>
parents:
15339
diff
changeset
|
46 |
0e58513cc59a
largefiles: rearrange how lfconvert detects non-local repos
Greg Ward <greg@gerg.ca>
parents:
15339
diff
changeset
|
47 if not hg.islocal(src): |
0e58513cc59a
largefiles: rearrange how lfconvert detects non-local repos
Greg Ward <greg@gerg.ca>
parents:
15339
diff
changeset
|
48 raise util.Abort(_('%s is not a local Mercurial repo') % src) |
0e58513cc59a
largefiles: rearrange how lfconvert detects non-local repos
Greg Ward <greg@gerg.ca>
parents:
15339
diff
changeset
|
49 if not hg.islocal(dest): |
0e58513cc59a
largefiles: rearrange how lfconvert detects non-local repos
Greg Ward <greg@gerg.ca>
parents:
15339
diff
changeset
|
50 raise util.Abort(_('%s is not a local Mercurial repo') % dest) |
0e58513cc59a
largefiles: rearrange how lfconvert detects non-local repos
Greg Ward <greg@gerg.ca>
parents:
15339
diff
changeset
|
51 |
15339
be1377d19018
largefiles: test lfconvert error handling; remove redundant code
Greg Ward <greg@gerg.ca>
parents:
15332
diff
changeset
|
52 rsrc = hg.repository(ui, src) |
be1377d19018
largefiles: test lfconvert error handling; remove redundant code
Greg Ward <greg@gerg.ca>
parents:
15332
diff
changeset
|
53 ui.status(_('initializing destination %s\n') % dest) |
be1377d19018
largefiles: test lfconvert error handling; remove redundant code
Greg Ward <greg@gerg.ca>
parents:
15332
diff
changeset
|
54 rdst = hg.repository(ui, dest, create=True) |
15168 | 55 |
15171
547da6115d1d
largefiles: eliminate naked exceptions
Matt Mackall <mpm@selenic.com>
parents:
15170
diff
changeset
|
56 success = False |
15168 | 57 try: |
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 | |
60 # which can't change. | |
61 dst_lock = rdst.lock() | |
62 | |
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(). | |
65 # Take a look at mercurial/revlog.py:639 for more details. | |
66 # Use a generator instead of a list to decrease memory usage | |
67 ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None, | |
68 rsrc.heads())[0]) | |
69 revmap = {node.nullid: node.nullid} | |
70 if tolfile: | |
71 lfiles = set() | |
72 normalfiles = set() | |
73 if not pats: | |
15579
6c5e6ebe0812
largefiles: use "ui.configlist()" to get largefiles.patterns configuration
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
15472
diff
changeset
|
74 pats = ui.configlist(lfutil.longname, 'patterns', default=[]) |
15168 | 75 if pats: |
76 matcher = match_.match(rsrc.root, '', list(pats)) | |
77 else: | |
78 matcher = None | |
79 | |
80 lfiletohash = {} | |
81 for ctx in ctxs: | |
82 ui.progress(_('converting revisions'), ctx.rev(), | |
83 unit=_('revision'), total=rsrc['tip'].rev()) | |
84 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, | |
85 lfiles, normalfiles, matcher, size, lfiletohash) | |
86 ui.progress(_('converting revisions'), None) | |
87 | |
88 if os.path.exists(rdst.wjoin(lfutil.shortname)): | |
89 shutil.rmtree(rdst.wjoin(lfutil.shortname)) | |
90 | |
91 for f in lfiletohash.keys(): | |
92 if os.path.isfile(rdst.wjoin(f)): | |
93 os.unlink(rdst.wjoin(f)) | |
94 try: | |
95 os.removedirs(os.path.dirname(rdst.wjoin(f))) | |
15171
547da6115d1d
largefiles: eliminate naked exceptions
Matt Mackall <mpm@selenic.com>
parents:
15170
diff
changeset
|
96 except OSError: |
15168 | 97 pass |
98 | |
15303
07811b3b119b
largefiles: include 'largefiles' in converted repository requirements
Eli Carter <eli.carter@tektronix.com>
parents:
15255
diff
changeset
|
99 # If there were any files converted to largefiles, add largefiles |
07811b3b119b
largefiles: include 'largefiles' in converted repository requirements
Eli Carter <eli.carter@tektronix.com>
parents:
15255
diff
changeset
|
100 # to the destination repository's requirements. |
07811b3b119b
largefiles: include 'largefiles' in converted repository requirements
Eli Carter <eli.carter@tektronix.com>
parents:
15255
diff
changeset
|
101 if lfiles: |
07811b3b119b
largefiles: include 'largefiles' in converted repository requirements
Eli Carter <eli.carter@tektronix.com>
parents:
15255
diff
changeset
|
102 rdst.requirements.add('largefiles') |
07811b3b119b
largefiles: include 'largefiles' in converted repository requirements
Eli Carter <eli.carter@tektronix.com>
parents:
15255
diff
changeset
|
103 rdst._writerequirements() |
15168 | 104 else: |
105 for ctx in ctxs: | |
106 ui.progress(_('converting revisions'), ctx.rev(), | |
107 unit=_('revision'), total=rsrc['tip'].rev()) | |
108 _addchangeset(ui, rsrc, rdst, ctx, revmap) | |
109 | |
110 ui.progress(_('converting revisions'), None) | |
15171
547da6115d1d
largefiles: eliminate naked exceptions
Matt Mackall <mpm@selenic.com>
parents:
15170
diff
changeset
|
111 success = True |
15168 | 112 finally: |
15171
547da6115d1d
largefiles: eliminate naked exceptions
Matt Mackall <mpm@selenic.com>
parents:
15170
diff
changeset
|
113 if not success: |
547da6115d1d
largefiles: eliminate naked exceptions
Matt Mackall <mpm@selenic.com>
parents:
15170
diff
changeset
|
114 # we failed, remove the new directory |
547da6115d1d
largefiles: eliminate naked exceptions
Matt Mackall <mpm@selenic.com>
parents:
15170
diff
changeset
|
115 shutil.rmtree(rdst.root) |
15168 | 116 dst_lock.release() |
117 | |
118 def _addchangeset(ui, rsrc, rdst, ctx, revmap): | |
119 # Convert src parents to dst parents | |
120 parents = [] | |
121 for p in ctx.parents(): | |
122 parents.append(revmap[p.node()]) | |
123 while len(parents) < 2: | |
124 parents.append(node.nullid) | |
125 | |
126 # Generate list of changed files | |
127 files = set(ctx.files()) | |
128 if node.nullid not in parents: | |
129 mc = ctx.manifest() | |
130 mp1 = ctx.parents()[0].manifest() | |
131 mp2 = ctx.parents()[1].manifest() | |
132 files |= (set(mp1) | set(mp2)) - set(mc) | |
133 for f in mc: | |
134 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None): | |
135 files.add(f) | |
136 | |
137 def getfilectx(repo, memctx, f): | |
138 if lfutil.standin(f) in files: | |
139 # if the file isn't in the manifest then it was removed | |
140 # or renamed, raise IOError to indicate this | |
141 try: | |
142 fctx = ctx.filectx(lfutil.standin(f)) | |
143 except error.LookupError: | |
144 raise IOError() | |
145 renamed = fctx.renamed() | |
146 if renamed: | |
147 renamed = lfutil.splitstandin(renamed[0]) | |
148 | |
149 hash = fctx.data().strip() | |
150 path = lfutil.findfile(rsrc, hash) | |
151 ### TODO: What if the file is not cached? | |
152 data = '' | |
153 fd = None | |
154 try: | |
155 fd = open(path, 'rb') | |
156 data = fd.read() | |
157 finally: | |
15172
fb1dcd2aae2a
largefiles: fix multistatement line
Matt Mackall <mpm@selenic.com>
parents:
15171
diff
changeset
|
158 if fd: |
fb1dcd2aae2a
largefiles: fix multistatement line
Matt Mackall <mpm@selenic.com>
parents:
15171
diff
changeset
|
159 fd.close() |
15168 | 160 return context.memfilectx(f, data, 'l' in fctx.flags(), |
161 'x' in fctx.flags(), renamed) | |
162 else: | |
163 try: | |
164 fctx = ctx.filectx(f) | |
165 except error.LookupError: | |
166 raise IOError() | |
167 renamed = fctx.renamed() | |
168 if renamed: | |
169 renamed = renamed[0] | |
170 data = fctx.data() | |
171 if f == '.hgtags': | |
172 newdata = [] | |
173 for line in data.splitlines(): | |
174 id, name = line.split(' ', 1) | |
175 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]), | |
176 name)) | |
177 data = ''.join(newdata) | |
178 return context.memfilectx(f, data, 'l' in fctx.flags(), | |
179 'x' in fctx.flags(), renamed) | |
180 | |
181 dstfiles = [] | |
182 for file in files: | |
183 if lfutil.isstandin(file): | |
184 dstfiles.append(lfutil.splitstandin(file)) | |
185 else: | |
186 dstfiles.append(file) | |
187 # Commit | |
188 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles, | |
189 getfilectx, ctx.user(), ctx.date(), ctx.extra()) | |
190 ret = rdst.commitctx(mctx) | |
191 rdst.dirstate.setparents(ret) | |
192 revmap[ctx.node()] = rdst.changelog.tip() | |
193 | |
194 def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles, | |
195 matcher, size, lfiletohash): | |
196 # Convert src parents to dst parents | |
197 parents = [] | |
198 for p in ctx.parents(): | |
199 parents.append(revmap[p.node()]) | |
200 while len(parents) < 2: | |
201 parents.append(node.nullid) | |
202 | |
203 # Generate list of changed files | |
204 files = set(ctx.files()) | |
205 if node.nullid not in parents: | |
206 mc = ctx.manifest() | |
207 mp1 = ctx.parents()[0].manifest() | |
208 mp2 = ctx.parents()[1].manifest() | |
209 files |= (set(mp1) | set(mp2)) - set(mc) | |
210 for f in mc: | |
211 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None): | |
212 files.add(f) | |
213 | |
214 dstfiles = [] | |
215 for f in files: | |
216 if f not in lfiles and f not in normalfiles: | |
217 islfile = _islfile(f, ctx, matcher, size) | |
218 # If this file was renamed or copied then copy | |
219 # the lfileness of its predecessor | |
220 if f in ctx.manifest(): | |
221 fctx = ctx.filectx(f) | |
222 renamed = fctx.renamed() | |
223 renamedlfile = renamed and renamed[0] in lfiles | |
224 islfile |= renamedlfile | |
225 if 'l' in fctx.flags(): | |
226 if renamedlfile: | |
227 raise util.Abort( | |
15380
a53888685a6c
largefiles: fix uppercase in abort message
Martin Geisler <mg@aragost.com>
parents:
15371
diff
changeset
|
228 _('renamed/copied largefile %s becomes symlink') |
15170
c1a4a3220711
largefiles: fix over-long lines
Matt Mackall <mpm@selenic.com>
parents:
15168
diff
changeset
|
229 % f) |
15168 | 230 islfile = False |
231 if islfile: | |
232 lfiles.add(f) | |
233 else: | |
234 normalfiles.add(f) | |
235 | |
236 if f in lfiles: | |
237 dstfiles.append(lfutil.standin(f)) | |
15254
dd03d3a9f888
largefiles: more work on cleaning up comments
Greg Ward <greg@gerg.ca>
parents:
15253
diff
changeset
|
238 # largefile in manifest if it has not been removed/renamed |
15168 | 239 if f in ctx.manifest(): |
240 if 'l' in ctx.filectx(f).flags(): | |
241 if renamed and renamed[0] in lfiles: | |
242 raise util.Abort(_('largefile %s becomes symlink') % f) | |
243 | |
15254
dd03d3a9f888
largefiles: more work on cleaning up comments
Greg Ward <greg@gerg.ca>
parents:
15253
diff
changeset
|
244 # largefile was modified, update standins |
15168 | 245 fullpath = rdst.wjoin(f) |
15371
f26ed4ea46d8
largefiles: remove lfutil.createdir, replace calls with util.makedirs
Hao Lian <hao@fogcreek.com>
parents:
15340
diff
changeset
|
246 util.makedirs(os.path.dirname(fullpath)) |
15168 | 247 m = util.sha1('') |
248 m.update(ctx[f].data()) | |
249 hash = m.hexdigest() | |
250 if f not in lfiletohash or lfiletohash[f] != hash: | |
251 try: | |
252 fd = open(fullpath, 'wb') | |
253 fd.write(ctx[f].data()) | |
254 finally: | |
255 if fd: | |
256 fd.close() | |
257 executable = 'x' in ctx[f].flags() | |
258 os.chmod(fullpath, lfutil.getmode(executable)) | |
259 lfutil.writestandin(rdst, lfutil.standin(f), hash, | |
260 executable) | |
261 lfiletohash[f] = hash | |
262 else: | |
263 # normal file | |
264 dstfiles.append(f) | |
265 | |
266 def getfilectx(repo, memctx, f): | |
267 if lfutil.isstandin(f): | |
268 # if the file isn't in the manifest then it was removed | |
269 # or renamed, raise IOError to indicate this | |
270 srcfname = lfutil.splitstandin(f) | |
271 try: | |
272 fctx = ctx.filectx(srcfname) | |
273 except error.LookupError: | |
274 raise IOError() | |
275 renamed = fctx.renamed() | |
276 if renamed: | |
15254
dd03d3a9f888
largefiles: more work on cleaning up comments
Greg Ward <greg@gerg.ca>
parents:
15253
diff
changeset
|
277 # standin is always a largefile because largefile-ness |
15168 | 278 # doesn't change after rename or copy |
279 renamed = lfutil.standin(renamed[0]) | |
280 | |
15313
3eb1a90ea409
largefiles: fix newline for lfconverted repos
Eli Carter <eli.carter@tektronix.com>
parents:
15303
diff
changeset
|
281 return context.memfilectx(f, lfiletohash[srcfname] + '\n', 'l' in |
15168 | 282 fctx.flags(), 'x' in fctx.flags(), renamed) |
283 else: | |
284 try: | |
285 fctx = ctx.filectx(f) | |
286 except error.LookupError: | |
287 raise IOError() | |
288 renamed = fctx.renamed() | |
289 if renamed: | |
290 renamed = renamed[0] | |
291 | |
292 data = fctx.data() | |
293 if f == '.hgtags': | |
294 newdata = [] | |
295 for line in data.splitlines(): | |
296 id, name = line.split(' ', 1) | |
297 newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]), | |
298 name)) | |
299 data = ''.join(newdata) | |
300 return context.memfilectx(f, data, 'l' in fctx.flags(), | |
301 'x' in fctx.flags(), renamed) | |
302 | |
303 # Commit | |
304 mctx = context.memctx(rdst, parents, ctx.description(), dstfiles, | |
305 getfilectx, ctx.user(), ctx.date(), ctx.extra()) | |
306 ret = rdst.commitctx(mctx) | |
307 rdst.dirstate.setparents(ret) | |
308 revmap[ctx.node()] = rdst.changelog.tip() | |
309 | |
310 def _islfile(file, ctx, matcher, size): | |
15252
6e809bb4f969
largefiles: improve comments, internal docstrings
Greg Ward <greg@gerg.ca>
parents:
15230
diff
changeset
|
311 '''Return true if file should be considered a largefile, i.e. |
6e809bb4f969
largefiles: improve comments, internal docstrings
Greg Ward <greg@gerg.ca>
parents:
15230
diff
changeset
|
312 matcher matches it or it is larger than size.''' |
6e809bb4f969
largefiles: improve comments, internal docstrings
Greg Ward <greg@gerg.ca>
parents:
15230
diff
changeset
|
313 # never store special .hg* files as largefiles |
15168 | 314 if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs': |
315 return False | |
316 if matcher and matcher(file): | |
317 return True | |
318 try: | |
319 return ctx.filectx(file).size() >= size * 1024 * 1024 | |
320 except error.LookupError: | |
321 return False | |
322 | |
323 def uploadlfiles(ui, rsrc, rdst, files): | |
324 '''upload largefiles to the central store''' | |
325 | |
15317
41f371150ccb
largefiles: make the store primary, and the user cache secondary
Benjamin Pollack <benjamin@bitquabit.com>
parents:
15313
diff
changeset
|
326 if not files: |
15168 | 327 return |
328 | |
329 store = basestore._openstore(rsrc, rdst, put=True) | |
330 | |
331 at = 0 | |
332 files = filter(lambda h: not store.exists(h), files) | |
333 for hash in files: | |
15170
c1a4a3220711
largefiles: fix over-long lines
Matt Mackall <mpm@selenic.com>
parents:
15168
diff
changeset
|
334 ui.progress(_('uploading largefiles'), at, unit='largefile', |
c1a4a3220711
largefiles: fix over-long lines
Matt Mackall <mpm@selenic.com>
parents:
15168
diff
changeset
|
335 total=len(files)) |
15168 | 336 source = lfutil.findfile(rsrc, hash) |
337 if not source: | |
15253
67d010779907
largefiles: improve error reporting
Greg Ward <greg@gerg.ca>
parents:
15252
diff
changeset
|
338 raise util.Abort(_('largefile %s missing from store' |
67d010779907
largefiles: improve error reporting
Greg Ward <greg@gerg.ca>
parents:
15252
diff
changeset
|
339 ' (needs to be uploaded)') % hash) |
15168 | 340 # XXX check for errors here |
341 store.put(source, hash) | |
342 at += 1 | |
15173
3d27a8ff895f
largefiles: mark a string for translation
Matt Mackall <mpm@selenic.com>
parents:
15172
diff
changeset
|
343 ui.progress(_('uploading largefiles'), None) |
15168 | 344 |
345 def verifylfiles(ui, repo, all=False, contents=False): | |
346 '''Verify that every big file revision in the current changeset | |
347 exists in the central store. With --contents, also verify that | |
348 the contents of each big file revision are correct (SHA-1 hash | |
349 matches the revision ID). With --all, check every changeset in | |
350 this repository.''' | |
351 if all: | |
352 # Pass a list to the function rather than an iterator because we know a | |
353 # list will work. | |
354 revs = range(len(repo)) | |
355 else: | |
356 revs = ['.'] | |
357 | |
358 store = basestore._openstore(repo) | |
359 return store.verify(revs, contents=contents) | |
360 | |
361 def cachelfiles(ui, repo, node): | |
362 '''cachelfiles ensures that all largefiles needed by the specified revision | |
363 are present in the repository's largefile cache. | |
364 | |
365 returns a tuple (cached, missing). cached is the list of files downloaded | |
366 by this operation; missing is the list of files that were needed but could | |
367 not be found.''' | |
368 lfiles = lfutil.listlfiles(repo, node) | |
369 toget = [] | |
370 | |
371 for lfile in lfiles: | |
372 expectedhash = repo[node][lfutil.standin(lfile)].data().strip() | |
373 # if it exists and its hash matches, it might have been locally | |
374 # modified before updating and the user chose 'local'. in this case, | |
375 # it will not be in any store, so don't look for it. | |
15255
7ab05d752405
largefiles: cosmetics, whitespace, code style
Greg Ward <greg@gerg.ca>
parents:
15254
diff
changeset
|
376 if ((not os.path.exists(repo.wjoin(lfile)) or |
7ab05d752405
largefiles: cosmetics, whitespace, code style
Greg Ward <greg@gerg.ca>
parents:
15254
diff
changeset
|
377 expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and |
7ab05d752405
largefiles: cosmetics, whitespace, code style
Greg Ward <greg@gerg.ca>
parents:
15254
diff
changeset
|
378 not lfutil.findfile(repo, expectedhash)): |
15168 | 379 toget.append((lfile, expectedhash)) |
380 | |
381 if toget: | |
382 store = basestore._openstore(repo) | |
383 ret = store.get(toget) | |
384 return ret | |
385 | |
386 return ([], []) | |
387 | |
388 def updatelfiles(ui, repo, filelist=None, printmessage=True): | |
389 wlock = repo.wlock() | |
390 try: | |
391 lfdirstate = lfutil.openlfdirstate(ui, repo) | |
392 lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate) | |
393 | |
394 if filelist is not None: | |
395 lfiles = [f for f in lfiles if f in filelist] | |
396 | |
397 printed = False | |
398 if printmessage and lfiles: | |
399 ui.status(_('getting changed largefiles\n')) | |
400 printed = True | |
401 cachelfiles(ui, repo, '.') | |
402 | |
403 updated, removed = 0, 0 | |
404 for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles): | |
405 # increment the appropriate counter according to _updatelfile's | |
406 # return value | |
407 updated += i > 0 and i or 0 | |
408 removed -= i < 0 and i or 0 | |
409 if printmessage and (removed or updated) and not printed: | |
410 ui.status(_('getting changed largefiles\n')) | |
411 printed = True | |
412 | |
413 lfdirstate.write() | |
414 if printed and printmessage: | |
415 ui.status(_('%d largefiles updated, %d removed\n') % (updated, | |
416 removed)) | |
417 finally: | |
418 wlock.release() | |
419 | |
420 def _updatelfile(repo, lfdirstate, lfile): | |
421 '''updates a single largefile and copies the state of its standin from | |
422 the repository's dirstate to its state in the lfdirstate. | |
423 | |
424 returns 1 if the file was modified, -1 if the file was removed, 0 if the | |
425 file was unchanged, and None if the needed largefile was missing from the | |
426 cache.''' | |
427 ret = 0 | |
428 abslfile = repo.wjoin(lfile) | |
429 absstandin = repo.wjoin(lfutil.standin(lfile)) | |
430 if os.path.exists(absstandin): | |
431 if os.path.exists(absstandin+'.orig'): | |
432 shutil.copyfile(abslfile, abslfile+'.orig') | |
433 expecthash = lfutil.readstandin(repo, lfile) | |
15255
7ab05d752405
largefiles: cosmetics, whitespace, code style
Greg Ward <greg@gerg.ca>
parents:
15254
diff
changeset
|
434 if (expecthash != '' and |
7ab05d752405
largefiles: cosmetics, whitespace, code style
Greg Ward <greg@gerg.ca>
parents:
15254
diff
changeset
|
435 (not os.path.exists(abslfile) or |
7ab05d752405
largefiles: cosmetics, whitespace, code style
Greg Ward <greg@gerg.ca>
parents:
15254
diff
changeset
|
436 expecthash != lfutil.hashfile(abslfile))): |
15168 | 437 if not lfutil.copyfromcache(repo, expecthash, lfile): |
15472
6a7e874390b0
largefiles: treat status of cache missed largefiles as "missing" correctly
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
15380
diff
changeset
|
438 # use normallookup() to allocate entry in largefiles dirstate, |
6a7e874390b0
largefiles: treat status of cache missed largefiles as "missing" correctly
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
15380
diff
changeset
|
439 # because lack of it misleads lfiles_repo.status() into |
6a7e874390b0
largefiles: treat status of cache missed largefiles as "missing" correctly
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
15380
diff
changeset
|
440 # recognition that such cache missing files are REMOVED. |
6a7e874390b0
largefiles: treat status of cache missed largefiles as "missing" correctly
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
15380
diff
changeset
|
441 lfdirstate.normallookup(lfile) |
6a7e874390b0
largefiles: treat status of cache missed largefiles as "missing" correctly
FUJIWARA Katsunori <foozy@lares.dti.ne.jp>
parents:
15380
diff
changeset
|
442 return None # don't try to set the mode |
15168 | 443 ret = 1 |
444 mode = os.stat(absstandin).st_mode | |
445 if mode != os.stat(abslfile).st_mode: | |
446 os.chmod(abslfile, mode) | |
447 ret = 1 | |
448 else: | |
15663
9036c7d106bf
largefiles: handle merges between normal files and largefiles (issue3084)
Martin Geisler <mg@aragost.com>
parents:
15579
diff
changeset
|
449 # Remove lfiles for which the standin is deleted, unless the |
9036c7d106bf
largefiles: handle merges between normal files and largefiles (issue3084)
Martin Geisler <mg@aragost.com>
parents:
15579
diff
changeset
|
450 # lfile is added to the repository again. This happens when a |
9036c7d106bf
largefiles: handle merges between normal files and largefiles (issue3084)
Martin Geisler <mg@aragost.com>
parents:
15579
diff
changeset
|
451 # largefile is converted back to a normal file: the standin |
9036c7d106bf
largefiles: handle merges between normal files and largefiles (issue3084)
Martin Geisler <mg@aragost.com>
parents:
15579
diff
changeset
|
452 # disappears, but a new (normal) file appears as the lfile. |
9036c7d106bf
largefiles: handle merges between normal files and largefiles (issue3084)
Martin Geisler <mg@aragost.com>
parents:
15579
diff
changeset
|
453 if os.path.exists(abslfile) and lfile not in repo[None]: |
15168 | 454 os.unlink(abslfile) |
455 ret = -1 | |
456 state = repo.dirstate[lfutil.standin(lfile)] | |
457 if state == 'n': | |
15793
3ef07ecdb0d5
largefiles: correctly handle dirstate status when rebasing
Na'Tosha Bard <natosha@unity3d.com>
parents:
15663
diff
changeset
|
458 # When rebasing, we need to synchronize the standin and the largefile, |
3ef07ecdb0d5
largefiles: correctly handle dirstate status when rebasing
Na'Tosha Bard <natosha@unity3d.com>
parents:
15663
diff
changeset
|
459 # because otherwise the largefile will get reverted. But for commit's |
3ef07ecdb0d5
largefiles: correctly handle dirstate status when rebasing
Na'Tosha Bard <natosha@unity3d.com>
parents:
15663
diff
changeset
|
460 # sake, we have to mark the file as unclean. |
3ef07ecdb0d5
largefiles: correctly handle dirstate status when rebasing
Na'Tosha Bard <natosha@unity3d.com>
parents:
15663
diff
changeset
|
461 if getattr(repo, "_isrebasing", False): |
3ef07ecdb0d5
largefiles: correctly handle dirstate status when rebasing
Na'Tosha Bard <natosha@unity3d.com>
parents:
15663
diff
changeset
|
462 lfdirstate.normallookup(lfile) |
3ef07ecdb0d5
largefiles: correctly handle dirstate status when rebasing
Na'Tosha Bard <natosha@unity3d.com>
parents:
15663
diff
changeset
|
463 else: |
3ef07ecdb0d5
largefiles: correctly handle dirstate status when rebasing
Na'Tosha Bard <natosha@unity3d.com>
parents:
15663
diff
changeset
|
464 lfdirstate.normal(lfile) |
15168 | 465 elif state == 'r': |
466 lfdirstate.remove(lfile) | |
467 elif state == 'a': | |
468 lfdirstate.add(lfile) | |
469 elif state == '?': | |
15224
7c604d8c7e83
largefiles: remove pre-1.9 code from extension first bundled with 1.9
Na'Tosha Bard <natosha@unity3d.com>
parents:
15173
diff
changeset
|
470 lfdirstate.drop(lfile) |
15168 | 471 return ret |
472 | |
473 # -- hg commands declarations ------------------------------------------------ | |
474 | |
475 cmdtable = { | |
476 'lfconvert': (lfconvert, | |
15230 | 477 [('s', 'size', '', |
478 _('minimum size (MB) for files to be converted ' | |
479 'as largefiles'), | |
480 'SIZE'), | |
15332
0db47b8d025f
largefiles: rename lfconvert --tonormal option to --to-normal
Greg Ward <greg@gerg.ca>
parents:
15317
diff
changeset
|
481 ('', 'to-normal', False, |
15230 | 482 _('convert from a largefiles repo to a normal repo')), |
483 ], | |
15168 | 484 _('hg lfconvert SOURCE DEST [FILE ...]')), |
485 } |