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