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