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