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