##// END OF EJS Templates
largefiles: don't create chain of __contains__ calls...
Martin von Zweigbergk -
r24287:f7825242 default
parent child Browse files
Show More
@@ -1,374 +1,375 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 os
12 12
13 13 from mercurial import error, match as match_, util
14 14 from mercurial.i18n import _
15 15 from mercurial import scmutil, localrepo
16 16
17 17 import lfcommands
18 18 import lfutil
19 19
20 20 def reposetup(ui, repo):
21 21 # wire repositories should be given new wireproto functions
22 22 # by "proto.wirereposetup()" via "hg.wirepeersetupfuncs"
23 23 if not repo.local():
24 24 return
25 25
26 26 class lfilesrepo(repo.__class__):
27 27 # the mark to examine whether "repo" object enables largefiles or not
28 28 _largefilesenabled = True
29 29
30 30 lfstatus = False
31 31 def status_nolfiles(self, *args, **kwargs):
32 32 return super(lfilesrepo, self).status(*args, **kwargs)
33 33
34 34 # When lfstatus is set, return a context that gives the names
35 35 # of largefiles instead of their corresponding standins and
36 36 # identifies the largefiles as always binary, regardless of
37 37 # their actual contents.
38 38 def __getitem__(self, changeid):
39 39 ctx = super(lfilesrepo, self).__getitem__(changeid)
40 40 if self.lfstatus:
41 41 class lfilesctx(ctx.__class__):
42 42 def files(self):
43 43 filenames = super(lfilesctx, self).files()
44 44 return [lfutil.splitstandin(f) or f for f in filenames]
45 45 def manifest(self):
46 46 man1 = super(lfilesctx, self).manifest()
47 orig = man1.__contains__
47 class lfilesmanifest(man1.__class):
48 48 def __contains__(self, filename):
49 orig = super(lfilesmanifest, self).__contains__
49 50 return (orig(filename) or
50 51 orig(lfutil.standin(filename)))
51 man1.__contains__ = __contains__.__get__(man1)
52 man1.__class__ = lfilesmanifest
52 53 return man1
53 54 def filectx(self, path, fileid=None, filelog=None):
54 55 orig = super(lfilesctx, self).filectx
55 56 try:
56 57 if filelog is not None:
57 58 result = orig(path, fileid, filelog)
58 59 else:
59 60 result = orig(path, fileid)
60 61 except error.LookupError:
61 62 # Adding a null character will cause Mercurial to
62 63 # identify this as a binary file.
63 64 if filelog is not None:
64 65 result = orig(lfutil.standin(path), fileid,
65 66 filelog)
66 67 else:
67 68 result = orig(lfutil.standin(path), fileid)
68 69 olddata = result.data
69 70 result.data = lambda: olddata() + '\0'
70 71 return result
71 72 ctx.__class__ = lfilesctx
72 73 return ctx
73 74
74 75 # Figure out the status of big files and insert them into the
75 76 # appropriate list in the result. Also removes standin files
76 77 # from the listing. Revert to the original status if
77 78 # self.lfstatus is False.
78 79 # XXX large file status is buggy when used on repo proxy.
79 80 # XXX this needs to be investigated.
80 81 @localrepo.unfilteredmethod
81 82 def status(self, node1='.', node2=None, match=None, ignored=False,
82 83 clean=False, unknown=False, listsubrepos=False):
83 84 listignored, listclean, listunknown = ignored, clean, unknown
84 85 orig = super(lfilesrepo, self).status
85 86 if not self.lfstatus:
86 87 return orig(node1, node2, match, listignored, listclean,
87 88 listunknown, listsubrepos)
88 89
89 90 # some calls in this function rely on the old version of status
90 91 self.lfstatus = False
91 92 ctx1 = self[node1]
92 93 ctx2 = self[node2]
93 94 working = ctx2.rev() is None
94 95 parentworking = working and ctx1 == self['.']
95 96
96 97 if match is None:
97 98 match = match_.always(self.root, self.getcwd())
98 99
99 100 wlock = None
100 101 try:
101 102 try:
102 103 # updating the dirstate is optional
103 104 # so we don't wait on the lock
104 105 wlock = self.wlock(False)
105 106 except error.LockError:
106 107 pass
107 108
108 109 # First check if paths or patterns were specified on the
109 110 # command line. If there were, and they don't match any
110 111 # largefiles, we should just bail here and let super
111 112 # handle it -- thus gaining a big performance boost.
112 113 lfdirstate = lfutil.openlfdirstate(ui, self)
113 114 if not match.always():
114 115 for f in lfdirstate:
115 116 if match(f):
116 117 break
117 118 else:
118 119 return orig(node1, node2, match, listignored, listclean,
119 120 listunknown, listsubrepos)
120 121
121 122 # Create a copy of match that matches standins instead
122 123 # of largefiles.
123 124 def tostandins(files):
124 125 if not working:
125 126 return files
126 127 newfiles = []
127 128 dirstate = self.dirstate
128 129 for f in files:
129 130 sf = lfutil.standin(f)
130 131 if sf in dirstate:
131 132 newfiles.append(sf)
132 133 elif sf in dirstate.dirs():
133 134 # Directory entries could be regular or
134 135 # standin, check both
135 136 newfiles.extend((f, sf))
136 137 else:
137 138 newfiles.append(f)
138 139 return newfiles
139 140
140 141 m = copy.copy(match)
141 142 m._files = tostandins(m._files)
142 143
143 144 result = orig(node1, node2, m, ignored, clean, unknown,
144 145 listsubrepos)
145 146 if working:
146 147
147 148 def sfindirstate(f):
148 149 sf = lfutil.standin(f)
149 150 dirstate = self.dirstate
150 151 return sf in dirstate or sf in dirstate.dirs()
151 152
152 153 match._files = [f for f in match._files
153 154 if sfindirstate(f)]
154 155 # Don't waste time getting the ignored and unknown
155 156 # files from lfdirstate
156 157 unsure, s = lfdirstate.status(match, [], False, listclean,
157 158 False)
158 159 (modified, added, removed, clean) = (s.modified, s.added,
159 160 s.removed, s.clean)
160 161 if parentworking:
161 162 for lfile in unsure:
162 163 standin = lfutil.standin(lfile)
163 164 if standin not in ctx1:
164 165 # from second parent
165 166 modified.append(lfile)
166 167 elif ctx1[standin].data().strip() \
167 168 != lfutil.hashfile(self.wjoin(lfile)):
168 169 modified.append(lfile)
169 170 else:
170 171 if listclean:
171 172 clean.append(lfile)
172 173 lfdirstate.normal(lfile)
173 174 else:
174 175 tocheck = unsure + modified + added + clean
175 176 modified, added, clean = [], [], []
176 177 checkexec = self.dirstate._checkexec
177 178
178 179 for lfile in tocheck:
179 180 standin = lfutil.standin(lfile)
180 181 if standin in ctx1:
181 182 abslfile = self.wjoin(lfile)
182 183 if ((ctx1[standin].data().strip() !=
183 184 lfutil.hashfile(abslfile)) or
184 185 (checkexec and
185 186 ('x' in ctx1.flags(standin)) !=
186 187 bool(lfutil.getexecutable(abslfile)))):
187 188 modified.append(lfile)
188 189 elif listclean:
189 190 clean.append(lfile)
190 191 else:
191 192 added.append(lfile)
192 193
193 194 # at this point, 'removed' contains largefiles
194 195 # marked as 'R' in the working context.
195 196 # then, largefiles not managed also in the target
196 197 # context should be excluded from 'removed'.
197 198 removed = [lfile for lfile in removed
198 199 if lfutil.standin(lfile) in ctx1]
199 200
200 201 # Standins no longer found in lfdirstate has been
201 202 # removed
202 203 for standin in ctx1.walk(lfutil.getstandinmatcher(self)):
203 204 lfile = lfutil.splitstandin(standin)
204 205 if not match(lfile):
205 206 continue
206 207 if lfile not in lfdirstate:
207 208 removed.append(lfile)
208 209
209 210 # Filter result lists
210 211 result = list(result)
211 212
212 213 # Largefiles are not really removed when they're
213 214 # still in the normal dirstate. Likewise, normal
214 215 # files are not really removed if they are still in
215 216 # lfdirstate. This happens in merges where files
216 217 # change type.
217 218 removed = [f for f in removed
218 219 if f not in self.dirstate]
219 220 result[2] = [f for f in result[2]
220 221 if f not in lfdirstate]
221 222
222 223 lfiles = set(lfdirstate._map)
223 224 # Unknown files
224 225 result[4] = set(result[4]).difference(lfiles)
225 226 # Ignored files
226 227 result[5] = set(result[5]).difference(lfiles)
227 228 # combine normal files and largefiles
228 229 normals = [[fn for fn in filelist
229 230 if not lfutil.isstandin(fn)]
230 231 for filelist in result]
231 232 lfstatus = (modified, added, removed, s.deleted, [], [],
232 233 clean)
233 234 result = [sorted(list1 + list2)
234 235 for (list1, list2) in zip(normals, lfstatus)]
235 236 else: # not against working directory
236 237 result = [[lfutil.splitstandin(f) or f for f in items]
237 238 for items in result]
238 239
239 240 if wlock:
240 241 lfdirstate.write()
241 242
242 243 finally:
243 244 if wlock:
244 245 wlock.release()
245 246
246 247 self.lfstatus = True
247 248 return scmutil.status(*result)
248 249
249 250 def commitctx(self, ctx, *args, **kwargs):
250 251 node = super(lfilesrepo, self).commitctx(ctx, *args, **kwargs)
251 252 class lfilesctx(ctx.__class__):
252 253 def markcommitted(self, node):
253 254 orig = super(lfilesctx, self).markcommitted
254 255 return lfutil.markcommitted(orig, self, node)
255 256 ctx.__class__ = lfilesctx
256 257 return node
257 258
258 259 # Before commit, largefile standins have not had their
259 260 # contents updated to reflect the hash of their largefile.
260 261 # Do that here.
261 262 def commit(self, text="", user=None, date=None, match=None,
262 263 force=False, editor=False, extra={}):
263 264 orig = super(lfilesrepo, self).commit
264 265
265 266 wlock = self.wlock()
266 267 try:
267 268 lfcommithook = self._lfcommithooks[-1]
268 269 match = lfcommithook(self, match)
269 270 result = orig(text=text, user=user, date=date, match=match,
270 271 force=force, editor=editor, extra=extra)
271 272 return result
272 273 finally:
273 274 wlock.release()
274 275
275 276 def push(self, remote, force=False, revs=None, newbranch=False):
276 277 if remote.local():
277 278 missing = set(self.requirements) - remote.local().supported
278 279 if missing:
279 280 msg = _("required features are not"
280 281 " supported in the destination:"
281 282 " %s") % (', '.join(sorted(missing)))
282 283 raise util.Abort(msg)
283 284 return super(lfilesrepo, self).push(remote, force=force, revs=revs,
284 285 newbranch=newbranch)
285 286
286 287 # TODO: _subdirlfs should be moved into "lfutil.py", because
287 288 # it is referred only from "lfutil.updatestandinsbymatch"
288 289 def _subdirlfs(self, files, lfiles):
289 290 '''
290 291 Adjust matched file list
291 292 If we pass a directory to commit whose only committable files
292 293 are largefiles, the core commit code aborts before finding
293 294 the largefiles.
294 295 So we do the following:
295 296 For directories that only have largefiles as matches,
296 297 we explicitly add the largefiles to the match list and remove
297 298 the directory.
298 299 In other cases, we leave the match list unmodified.
299 300 '''
300 301 actualfiles = []
301 302 dirs = []
302 303 regulars = []
303 304
304 305 for f in files:
305 306 if lfutil.isstandin(f + '/'):
306 307 raise util.Abort(
307 308 _('file "%s" is a largefile standin') % f,
308 309 hint=('commit the largefile itself instead'))
309 310 # Scan directories
310 311 if os.path.isdir(self.wjoin(f)):
311 312 dirs.append(f)
312 313 else:
313 314 regulars.append(f)
314 315
315 316 for f in dirs:
316 317 matcheddir = False
317 318 d = self.dirstate.normalize(f) + '/'
318 319 # Check for matched normal files
319 320 for mf in regulars:
320 321 if self.dirstate.normalize(mf).startswith(d):
321 322 actualfiles.append(f)
322 323 matcheddir = True
323 324 break
324 325 if not matcheddir:
325 326 # If no normal match, manually append
326 327 # any matching largefiles
327 328 for lf in lfiles:
328 329 if self.dirstate.normalize(lf).startswith(d):
329 330 actualfiles.append(lf)
330 331 if not matcheddir:
331 332 # There may still be normal files in the dir, so
332 333 # add a directory to the list, which
333 334 # forces status/dirstate to walk all files and
334 335 # call the match function on the matcher, even
335 336 # on case sensitive filesystems.
336 337 actualfiles.append('.')
337 338 matcheddir = True
338 339 # Nothing in dir, so readd it
339 340 # and let commit reject it
340 341 if not matcheddir:
341 342 actualfiles.append(f)
342 343
343 344 # Always add normal files
344 345 actualfiles += regulars
345 346 return actualfiles
346 347
347 348 repo.__class__ = lfilesrepo
348 349
349 350 # stack of hooks being executed before committing.
350 351 # only last element ("_lfcommithooks[-1]") is used for each committing.
351 352 repo._lfcommithooks = [lfutil.updatestandinsbymatch]
352 353
353 354 # Stack of status writer functions taking "*msg, **opts" arguments
354 355 # like "ui.status()". Only last element ("_lfstatuswriters[-1]")
355 356 # is used to write status out.
356 357 repo._lfstatuswriters = [ui.status]
357 358
358 359 def prepushoutgoinghook(local, remote, outgoing):
359 360 if outgoing.missing:
360 361 toupload = set()
361 362 addfunc = lambda fn, lfhash: toupload.add(lfhash)
362 363 lfutil.getlfilestoupload(local, outgoing.missing, addfunc)
363 364 lfcommands.uploadlfiles(ui, local, remote, toupload)
364 365 repo.prepushoutgoinghooks.add("largefiles", prepushoutgoinghook)
365 366
366 367 def checkrequireslfiles(ui, repo, **kwargs):
367 368 if 'largefiles' not in repo.requirements and util.any(
368 369 lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()):
369 370 repo.requirements.add('largefiles')
370 371 repo._writerequirements()
371 372
372 373 ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles,
373 374 'largefiles')
374 375 ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles, 'largefiles')
General Comments 0
You need to be logged in to leave comments. Login now