##// END OF EJS Templates
largefiles: optimize status when files are specified (issue3144)...
Na'Tosha Bard -
r15653:93c77d5b default
parent child Browse files
Show More
@@ -1,426 +1,437 b''
1 1 # Copyright 2009-2010 Gregory P. Ward
2 2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 3 # Copyright 2010-2011 Fog Creek Software
4 4 # Copyright 2010-2011 Unity Technologies
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''setup for largefiles repositories: reposetup'''
10 10 import copy
11 11 import types
12 12 import os
13 13
14 14 from mercurial import context, error, manifest, match as match_, node, util
15 15 from mercurial.i18n import _
16 16
17 17 import lfcommands
18 18 import proto
19 19 import lfutil
20 20
21 21 def reposetup(ui, repo):
22 22 # wire repositories should be given new wireproto functions but not the
23 23 # other largefiles modifications
24 24 if not repo.local():
25 25 return proto.wirereposetup(ui, repo)
26 26
27 27 for name in ('status', 'commitctx', 'commit', 'push'):
28 28 method = getattr(repo, name)
29 29 #if not (isinstance(method, types.MethodType) and
30 30 # method.im_func is repo.__class__.commitctx.im_func):
31 31 if (isinstance(method, types.FunctionType) and
32 32 method.func_name == 'wrap'):
33 33 ui.warn(_('largefiles: repo method %r appears to have already been'
34 34 ' wrapped by another extension: '
35 35 'largefiles may behave incorrectly\n')
36 36 % name)
37 37
38 38 class lfiles_repo(repo.__class__):
39 39 lfstatus = False
40 40 def status_nolfiles(self, *args, **kwargs):
41 41 return super(lfiles_repo, self).status(*args, **kwargs)
42 42
43 43 # When lfstatus is set, return a context that gives the names
44 44 # of largefiles instead of their corresponding standins and
45 45 # identifies the largefiles as always binary, regardless of
46 46 # their actual contents.
47 47 def __getitem__(self, changeid):
48 48 ctx = super(lfiles_repo, self).__getitem__(changeid)
49 49 if self.lfstatus:
50 50 class lfiles_manifestdict(manifest.manifestdict):
51 51 def __contains__(self, filename):
52 52 if super(lfiles_manifestdict,
53 53 self).__contains__(filename):
54 54 return True
55 55 return super(lfiles_manifestdict,
56 56 self).__contains__(lfutil.standin(filename))
57 57 class lfiles_ctx(ctx.__class__):
58 58 def files(self):
59 59 filenames = super(lfiles_ctx, self).files()
60 60 return [lfutil.splitstandin(f) or f for f in filenames]
61 61 def manifest(self):
62 62 man1 = super(lfiles_ctx, self).manifest()
63 63 man1.__class__ = lfiles_manifestdict
64 64 return man1
65 65 def filectx(self, path, fileid=None, filelog=None):
66 66 try:
67 67 result = super(lfiles_ctx, self).filectx(path,
68 68 fileid, filelog)
69 69 except error.LookupError:
70 70 # Adding a null character will cause Mercurial to
71 71 # identify this as a binary file.
72 72 result = super(lfiles_ctx, self).filectx(
73 73 lfutil.standin(path), fileid, filelog)
74 74 olddata = result.data
75 75 result.data = lambda: olddata() + '\0'
76 76 return result
77 77 ctx.__class__ = lfiles_ctx
78 78 return ctx
79 79
80 80 # Figure out the status of big files and insert them into the
81 81 # appropriate list in the result. Also removes standin files
82 82 # from the listing. Revert to the original status if
83 83 # self.lfstatus is False.
84 84 def status(self, node1='.', node2=None, match=None, ignored=False,
85 85 clean=False, unknown=False, listsubrepos=False):
86 86 listignored, listclean, listunknown = ignored, clean, unknown
87 87 if not self.lfstatus:
88 88 return super(lfiles_repo, self).status(node1, node2, match,
89 89 listignored, listclean, listunknown, listsubrepos)
90 90 else:
91 91 # some calls in this function rely on the old version of status
92 92 self.lfstatus = False
93 93 if isinstance(node1, context.changectx):
94 94 ctx1 = node1
95 95 else:
96 96 ctx1 = repo[node1]
97 97 if isinstance(node2, context.changectx):
98 98 ctx2 = node2
99 99 else:
100 100 ctx2 = repo[node2]
101 101 working = ctx2.rev() is None
102 102 parentworking = working and ctx1 == self['.']
103 103
104 104 def inctx(file, ctx):
105 105 try:
106 106 if ctx.rev() is None:
107 107 return file in ctx.manifest()
108 108 ctx[file]
109 109 return True
110 110 except KeyError:
111 111 return False
112 112
113 113 if match is None:
114 114 match = match_.always(self.root, self.getcwd())
115 115
116 # First check if there were files specified on the
117 # command line. If there were, and none of them were
118 # largefiles, we should just bail here and let super
119 # handle it -- thus gaining a big performance boost.
120 lfdirstate = lfutil.openlfdirstate(ui, self)
121 if match.files() and not match.anypats():
122 matchedfiles = [f for f in match.files() if f in lfdirstate]
123 if not matchedfiles:
124 return super(lfiles_repo, self).status(node1, node2,
125 match, listignored, listclean,
126 listunknown, listsubrepos)
127
116 128 # Create a copy of match that matches standins instead
117 129 # of largefiles.
118 130 def tostandin(file):
119 131 if inctx(lfutil.standin(file), ctx2):
120 132 return lfutil.standin(file)
121 133 return file
122 134
123 135 # Create a function that we can use to override what is
124 136 # normally the ignore matcher. We've already checked
125 137 # for ignored files on the first dirstate walk, and
126 138 # unecessarily re-checking here causes a huge performance
127 139 # hit because lfdirstate only knows about largefiles
128 140 def _ignoreoverride(self):
129 141 return False
130 142
131 143 m = copy.copy(match)
132 144 m._files = [tostandin(f) for f in m._files]
133 145
134 146 # Get ignored files here even if we weren't asked for them; we
135 147 # must use the result here for filtering later
136 148 result = super(lfiles_repo, self).status(node1, node2, m,
137 149 True, clean, unknown, listsubrepos)
138 150 if working:
139 151 # hold the wlock while we read largefiles and
140 152 # update the lfdirstate
141 153 wlock = repo.wlock()
142 154 try:
143 155 # Any non-largefiles that were explicitly listed must be
144 156 # taken out or lfdirstate.status will report an error.
145 157 # The status of these files was already computed using
146 158 # super's status.
147 lfdirstate = lfutil.openlfdirstate(ui, self)
148 159 # Override lfdirstate's ignore matcher to not do
149 160 # anything
150 161 orig_ignore = lfdirstate._ignore
151 162 lfdirstate._ignore = _ignoreoverride
152 163
153 164 match._files = [f for f in match._files if f in
154 165 lfdirstate]
155 166 # Don't waste time getting the ignored and unknown
156 167 # files again; we already have them
157 168 s = lfdirstate.status(match, [], False,
158 169 listclean, False)
159 170 (unsure, modified, added, removed, missing, unknown,
160 171 ignored, clean) = s
161 172 # Replace the list of ignored and unknown files with
162 173 # the previously caclulated lists, and strip out the
163 174 # largefiles
164 175 lfiles = set(lfdirstate._map)
165 176 ignored = set(result[5]).difference(lfiles)
166 177 unknown = set(result[4]).difference(lfiles)
167 178 if parentworking:
168 179 for lfile in unsure:
169 180 standin = lfutil.standin(lfile)
170 181 if standin not in ctx1:
171 182 # from second parent
172 183 modified.append(lfile)
173 184 elif ctx1[standin].data().strip() \
174 185 != lfutil.hashfile(self.wjoin(lfile)):
175 186 modified.append(lfile)
176 187 else:
177 188 clean.append(lfile)
178 189 lfdirstate.normal(lfile)
179 190 lfdirstate.write()
180 191 else:
181 192 tocheck = unsure + modified + added + clean
182 193 modified, added, clean = [], [], []
183 194
184 195 for lfile in tocheck:
185 196 standin = lfutil.standin(lfile)
186 197 if inctx(standin, ctx1):
187 198 if ctx1[standin].data().strip() != \
188 199 lfutil.hashfile(self.wjoin(lfile)):
189 200 modified.append(lfile)
190 201 else:
191 202 clean.append(lfile)
192 203 else:
193 204 added.append(lfile)
194 205 # Replace the original ignore function
195 206 lfdirstate._ignore = orig_ignore
196 207 finally:
197 208 wlock.release()
198 209
199 210 for standin in ctx1.manifest():
200 211 if not lfutil.isstandin(standin):
201 212 continue
202 213 lfile = lfutil.splitstandin(standin)
203 214 if not match(lfile):
204 215 continue
205 216 if lfile not in lfdirstate:
206 217 removed.append(lfile)
207 218 # Handle unknown and ignored differently
208 219 lfiles = (modified, added, removed, missing, [], [], clean)
209 220 result = list(result)
210 221 # Unknown files
211 222 unknown = set(unknown).difference(ignored)
212 223 result[4] = [f for f in unknown
213 224 if (repo.dirstate[f] == '?' and
214 225 not lfutil.isstandin(f))]
215 226 # Ignored files were calculated earlier by the dirstate,
216 227 # and we already stripped out the largefiles from the list
217 228 result[5] = ignored
218 229 # combine normal files and largefiles
219 230 normals = [[fn for fn in filelist
220 231 if not lfutil.isstandin(fn)]
221 232 for filelist in result]
222 233 result = [sorted(list1 + list2)
223 234 for (list1, list2) in zip(normals, lfiles)]
224 235 else:
225 236 def toname(f):
226 237 if lfutil.isstandin(f):
227 238 return lfutil.splitstandin(f)
228 239 return f
229 240 result = [[toname(f) for f in items] for items in result]
230 241
231 242 if not listunknown:
232 243 result[4] = []
233 244 if not listignored:
234 245 result[5] = []
235 246 if not listclean:
236 247 result[6] = []
237 248 self.lfstatus = True
238 249 return result
239 250
240 251 # As part of committing, copy all of the largefiles into the
241 252 # cache.
242 253 def commitctx(self, *args, **kwargs):
243 254 node = super(lfiles_repo, self).commitctx(*args, **kwargs)
244 255 ctx = self[node]
245 256 for filename in ctx.files():
246 257 if lfutil.isstandin(filename) and filename in ctx.manifest():
247 258 realfile = lfutil.splitstandin(filename)
248 259 lfutil.copytostore(self, ctx.node(), realfile)
249 260
250 261 return node
251 262
252 263 # Before commit, largefile standins have not had their
253 264 # contents updated to reflect the hash of their largefile.
254 265 # Do that here.
255 266 def commit(self, text="", user=None, date=None, match=None,
256 267 force=False, editor=False, extra={}):
257 268 orig = super(lfiles_repo, self).commit
258 269
259 270 wlock = repo.wlock()
260 271 try:
261 272 if getattr(repo, "_isrebasing", False):
262 273 # We have to take the time to pull down the new
263 274 # largefiles now. Otherwise if we are rebasing,
264 275 # any largefiles that were modified in the
265 276 # destination changesets get overwritten, either
266 277 # by the rebase or in the first commit after the
267 278 # rebase.
268 279 lfcommands.updatelfiles(repo.ui, repo)
269 280 # Case 1: user calls commit with no specific files or
270 281 # include/exclude patterns: refresh and commit all files that
271 282 # are "dirty".
272 283 if ((match is None) or
273 284 (not match.anypats() and not match.files())):
274 285 # Spend a bit of time here to get a list of files we know
275 286 # are modified so we can compare only against those.
276 287 # It can cost a lot of time (several seconds)
277 288 # otherwise to update all standins if the largefiles are
278 289 # large.
279 290 lfdirstate = lfutil.openlfdirstate(ui, self)
280 291 dirtymatch = match_.always(repo.root, repo.getcwd())
281 292 s = lfdirstate.status(dirtymatch, [], False, False, False)
282 293 modifiedfiles = []
283 294 for i in s:
284 295 modifiedfiles.extend(i)
285 296 lfiles = lfutil.listlfiles(self)
286 297 # this only loops through largefiles that exist (not
287 298 # removed/renamed)
288 299 for lfile in lfiles:
289 300 if lfile in modifiedfiles:
290 301 if os.path.exists(self.wjoin(lfutil.standin(lfile))):
291 302 # this handles the case where a rebase is being
292 303 # performed and the working copy is not updated
293 304 # yet.
294 305 if os.path.exists(self.wjoin(lfile)):
295 306 lfutil.updatestandin(self,
296 307 lfutil.standin(lfile))
297 308 lfdirstate.normal(lfile)
298 309 for lfile in lfdirstate:
299 310 if lfile in modifiedfiles:
300 311 if not os.path.exists(
301 312 repo.wjoin(lfutil.standin(lfile))):
302 313 lfdirstate.drop(lfile)
303 314 lfdirstate.write()
304 315
305 316 return orig(text=text, user=user, date=date, match=match,
306 317 force=force, editor=editor, extra=extra)
307 318
308 319 for f in match.files():
309 320 if lfutil.isstandin(f):
310 321 raise util.Abort(
311 322 _('file "%s" is a largefile standin') % f,
312 323 hint=('commit the largefile itself instead'))
313 324
314 325 # Case 2: user calls commit with specified patterns: refresh
315 326 # any matching big files.
316 327 smatcher = lfutil.composestandinmatcher(self, match)
317 328 standins = lfutil.dirstate_walk(self.dirstate, smatcher)
318 329
319 330 # No matching big files: get out of the way and pass control to
320 331 # the usual commit() method.
321 332 if not standins:
322 333 return orig(text=text, user=user, date=date, match=match,
323 334 force=force, editor=editor, extra=extra)
324 335
325 336 # Refresh all matching big files. It's possible that the
326 337 # commit will end up failing, in which case the big files will
327 338 # stay refreshed. No harm done: the user modified them and
328 339 # asked to commit them, so sooner or later we're going to
329 340 # refresh the standins. Might as well leave them refreshed.
330 341 lfdirstate = lfutil.openlfdirstate(ui, self)
331 342 for standin in standins:
332 343 lfile = lfutil.splitstandin(standin)
333 344 if lfdirstate[lfile] <> 'r':
334 345 lfutil.updatestandin(self, standin)
335 346 lfdirstate.normal(lfile)
336 347 else:
337 348 lfdirstate.drop(lfile)
338 349 lfdirstate.write()
339 350
340 351 # Cook up a new matcher that only matches regular files or
341 352 # standins corresponding to the big files requested by the
342 353 # user. Have to modify _files to prevent commit() from
343 354 # complaining "not tracked" for big files.
344 355 lfiles = lfutil.listlfiles(repo)
345 356 match = copy.copy(match)
346 357 orig_matchfn = match.matchfn
347 358
348 359 # Check both the list of largefiles and the list of
349 360 # standins because if a largefile was removed, it
350 361 # won't be in the list of largefiles at this point
351 362 match._files += sorted(standins)
352 363
353 364 actualfiles = []
354 365 for f in match._files:
355 366 fstandin = lfutil.standin(f)
356 367
357 368 # ignore known largefiles and standins
358 369 if f in lfiles or fstandin in standins:
359 370 continue
360 371
361 372 # append directory separator to avoid collisions
362 373 if not fstandin.endswith(os.sep):
363 374 fstandin += os.sep
364 375
365 376 # prevalidate matching standin directories
366 377 if util.any(st for st in match._files
367 378 if st.startswith(fstandin)):
368 379 continue
369 380 actualfiles.append(f)
370 381 match._files = actualfiles
371 382
372 383 def matchfn(f):
373 384 if orig_matchfn(f):
374 385 return f not in lfiles
375 386 else:
376 387 return f in standins
377 388
378 389 match.matchfn = matchfn
379 390 return orig(text=text, user=user, date=date, match=match,
380 391 force=force, editor=editor, extra=extra)
381 392 finally:
382 393 wlock.release()
383 394
384 395 def push(self, remote, force=False, revs=None, newbranch=False):
385 396 o = lfutil.findoutgoing(repo, remote, force)
386 397 if o:
387 398 toupload = set()
388 399 o = repo.changelog.nodesbetween(o, revs)[0]
389 400 for n in o:
390 401 parents = [p for p in repo.changelog.parents(n)
391 402 if p != node.nullid]
392 403 ctx = repo[n]
393 404 files = set(ctx.files())
394 405 if len(parents) == 2:
395 406 mc = ctx.manifest()
396 407 mp1 = ctx.parents()[0].manifest()
397 408 mp2 = ctx.parents()[1].manifest()
398 409 for f in mp1:
399 410 if f not in mc:
400 411 files.add(f)
401 412 for f in mp2:
402 413 if f not in mc:
403 414 files.add(f)
404 415 for f in mc:
405 416 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f,
406 417 None):
407 418 files.add(f)
408 419
409 420 toupload = toupload.union(
410 421 set([ctx[f].data().strip()
411 422 for f in files
412 423 if lfutil.isstandin(f) and f in ctx]))
413 424 lfcommands.uploadlfiles(ui, self, remote, toupload)
414 425 return super(lfiles_repo, self).push(remote, force, revs,
415 426 newbranch)
416 427
417 428 repo.__class__ = lfiles_repo
418 429
419 430 def checkrequireslfiles(ui, repo, **kwargs):
420 431 if 'largefiles' not in repo.requirements and util.any(
421 432 lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()):
422 433 repo.requirements.add('largefiles')
423 434 repo._writerequirements()
424 435
425 436 ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles)
426 437 ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles)
General Comments 0
You need to be logged in to leave comments. Login now