PageRenderTime 55ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/hgext/largefiles/reposetup.py

https://bitbucket.org/mirror/mercurial/
Python | 490 lines | 412 code | 26 blank | 52 comment | 73 complexity | 94fae8dde32d395d808d060d2d9e6541 MD5 | raw file
Possible License(s): GPL-2.0
  1. # Copyright 2009-2010 Gregory P. Ward
  2. # Copyright 2009-2010 Intelerad Medical Systems Incorporated
  3. # Copyright 2010-2011 Fog Creek Software
  4. # Copyright 2010-2011 Unity Technologies
  5. #
  6. # This software may be used and distributed according to the terms of the
  7. # GNU General Public License version 2 or any later version.
  8. '''setup for largefiles repositories: reposetup'''
  9. import copy
  10. import os
  11. from mercurial import error, manifest, match as match_, util
  12. from mercurial.i18n import _
  13. from mercurial import localrepo
  14. import lfcommands
  15. import lfutil
  16. def reposetup(ui, repo):
  17. # wire repositories should be given new wireproto functions
  18. # by "proto.wirereposetup()" via "hg.wirepeersetupfuncs"
  19. if not repo.local():
  20. return
  21. class lfilesrepo(repo.__class__):
  22. lfstatus = False
  23. def status_nolfiles(self, *args, **kwargs):
  24. return super(lfilesrepo, self).status(*args, **kwargs)
  25. # When lfstatus is set, return a context that gives the names
  26. # of largefiles instead of their corresponding standins and
  27. # identifies the largefiles as always binary, regardless of
  28. # their actual contents.
  29. def __getitem__(self, changeid):
  30. ctx = super(lfilesrepo, self).__getitem__(changeid)
  31. if self.lfstatus:
  32. class lfilesmanifestdict(manifest.manifestdict):
  33. def __contains__(self, filename):
  34. if super(lfilesmanifestdict,
  35. self).__contains__(filename):
  36. return True
  37. return super(lfilesmanifestdict,
  38. self).__contains__(lfutil.standin(filename))
  39. class lfilesctx(ctx.__class__):
  40. def files(self):
  41. filenames = super(lfilesctx, self).files()
  42. return [lfutil.splitstandin(f) or f for f in filenames]
  43. def manifest(self):
  44. man1 = super(lfilesctx, self).manifest()
  45. man1.__class__ = lfilesmanifestdict
  46. return man1
  47. def filectx(self, path, fileid=None, filelog=None):
  48. try:
  49. if filelog is not None:
  50. result = super(lfilesctx, self).filectx(
  51. path, fileid, filelog)
  52. else:
  53. result = super(lfilesctx, self).filectx(
  54. path, fileid)
  55. except error.LookupError:
  56. # Adding a null character will cause Mercurial to
  57. # identify this as a binary file.
  58. if filelog is not None:
  59. result = super(lfilesctx, self).filectx(
  60. lfutil.standin(path), fileid, filelog)
  61. else:
  62. result = super(lfilesctx, self).filectx(
  63. lfutil.standin(path), fileid)
  64. olddata = result.data
  65. result.data = lambda: olddata() + '\0'
  66. return result
  67. ctx.__class__ = lfilesctx
  68. return ctx
  69. # Figure out the status of big files and insert them into the
  70. # appropriate list in the result. Also removes standin files
  71. # from the listing. Revert to the original status if
  72. # self.lfstatus is False.
  73. # XXX large file status is buggy when used on repo proxy.
  74. # XXX this needs to be investigated.
  75. @localrepo.unfilteredmethod
  76. def status(self, node1='.', node2=None, match=None, ignored=False,
  77. clean=False, unknown=False, listsubrepos=False):
  78. listignored, listclean, listunknown = ignored, clean, unknown
  79. if not self.lfstatus:
  80. return super(lfilesrepo, self).status(node1, node2, match,
  81. listignored, listclean, listunknown, listsubrepos)
  82. else:
  83. # some calls in this function rely on the old version of status
  84. self.lfstatus = False
  85. ctx1 = self[node1]
  86. ctx2 = self[node2]
  87. working = ctx2.rev() is None
  88. parentworking = working and ctx1 == self['.']
  89. def inctx(file, ctx):
  90. try:
  91. if ctx.rev() is None:
  92. return file in ctx.manifest()
  93. ctx[file]
  94. return True
  95. except KeyError:
  96. return False
  97. if match is None:
  98. match = match_.always(self.root, self.getcwd())
  99. wlock = None
  100. try:
  101. try:
  102. # updating the dirstate is optional
  103. # so we don't wait on the lock
  104. wlock = self.wlock(False)
  105. except error.LockError:
  106. pass
  107. # First check if there were files specified on the
  108. # command line. If there were, and none of them were
  109. # largefiles, we should just bail here and let super
  110. # handle it -- thus gaining a big performance boost.
  111. lfdirstate = lfutil.openlfdirstate(ui, self)
  112. if match.files() and not match.anypats():
  113. for f in lfdirstate:
  114. if match(f):
  115. break
  116. else:
  117. return super(lfilesrepo, self).status(node1, node2,
  118. match, listignored, listclean,
  119. listunknown, listsubrepos)
  120. # Create a copy of match that matches standins instead
  121. # of largefiles.
  122. def tostandins(files):
  123. if not working:
  124. return files
  125. newfiles = []
  126. dirstate = self.dirstate
  127. for f in files:
  128. sf = lfutil.standin(f)
  129. if sf in dirstate:
  130. newfiles.append(sf)
  131. elif sf in dirstate.dirs():
  132. # Directory entries could be regular or
  133. # standin, check both
  134. newfiles.extend((f, sf))
  135. else:
  136. newfiles.append(f)
  137. return newfiles
  138. m = copy.copy(match)
  139. m._files = tostandins(m._files)
  140. result = super(lfilesrepo, self).status(node1, node2, m,
  141. ignored, clean, unknown, listsubrepos)
  142. if working:
  143. def sfindirstate(f):
  144. sf = lfutil.standin(f)
  145. dirstate = self.dirstate
  146. return sf in dirstate or sf in dirstate.dirs()
  147. match._files = [f for f in match._files
  148. if sfindirstate(f)]
  149. # Don't waste time getting the ignored and unknown
  150. # files from lfdirstate
  151. s = lfdirstate.status(match, [], False,
  152. listclean, False)
  153. (unsure, modified, added, removed, missing, _unknown,
  154. _ignored, clean) = s
  155. if parentworking:
  156. for lfile in unsure:
  157. standin = lfutil.standin(lfile)
  158. if standin not in ctx1:
  159. # from second parent
  160. modified.append(lfile)
  161. elif ctx1[standin].data().strip() \
  162. != lfutil.hashfile(self.wjoin(lfile)):
  163. modified.append(lfile)
  164. else:
  165. clean.append(lfile)
  166. lfdirstate.normal(lfile)
  167. else:
  168. tocheck = unsure + modified + added + clean
  169. modified, added, clean = [], [], []
  170. for lfile in tocheck:
  171. standin = lfutil.standin(lfile)
  172. if inctx(standin, ctx1):
  173. if ctx1[standin].data().strip() != \
  174. lfutil.hashfile(self.wjoin(lfile)):
  175. modified.append(lfile)
  176. else:
  177. clean.append(lfile)
  178. else:
  179. added.append(lfile)
  180. # Standins no longer found in lfdirstate has been
  181. # removed
  182. for standin in ctx1.manifest():
  183. if not lfutil.isstandin(standin):
  184. continue
  185. lfile = lfutil.splitstandin(standin)
  186. if not match(lfile):
  187. continue
  188. if lfile not in lfdirstate:
  189. removed.append(lfile)
  190. # Filter result lists
  191. result = list(result)
  192. # Largefiles are not really removed when they're
  193. # still in the normal dirstate. Likewise, normal
  194. # files are not really removed if they are still in
  195. # lfdirstate. This happens in merges where files
  196. # change type.
  197. removed = [f for f in removed
  198. if f not in self.dirstate]
  199. result[2] = [f for f in result[2]
  200. if f not in lfdirstate]
  201. lfiles = set(lfdirstate._map)
  202. # Unknown files
  203. result[4] = set(result[4]).difference(lfiles)
  204. # Ignored files
  205. result[5] = set(result[5]).difference(lfiles)
  206. # combine normal files and largefiles
  207. normals = [[fn for fn in filelist
  208. if not lfutil.isstandin(fn)]
  209. for filelist in result]
  210. lfiles = (modified, added, removed, missing, [], [],
  211. clean)
  212. result = [sorted(list1 + list2)
  213. for (list1, list2) in zip(normals, lfiles)]
  214. else:
  215. def toname(f):
  216. if lfutil.isstandin(f):
  217. return lfutil.splitstandin(f)
  218. return f
  219. result = [[toname(f) for f in items]
  220. for items in result]
  221. if wlock:
  222. lfdirstate.write()
  223. finally:
  224. if wlock:
  225. wlock.release()
  226. if not listunknown:
  227. result[4] = []
  228. if not listignored:
  229. result[5] = []
  230. if not listclean:
  231. result[6] = []
  232. self.lfstatus = True
  233. return result
  234. # As part of committing, copy all of the largefiles into the
  235. # cache.
  236. def commitctx(self, *args, **kwargs):
  237. node = super(lfilesrepo, self).commitctx(*args, **kwargs)
  238. lfutil.copyalltostore(self, node)
  239. return node
  240. # Before commit, largefile standins have not had their
  241. # contents updated to reflect the hash of their largefile.
  242. # Do that here.
  243. def commit(self, text="", user=None, date=None, match=None,
  244. force=False, editor=False, extra={}):
  245. orig = super(lfilesrepo, self).commit
  246. wlock = self.wlock()
  247. try:
  248. # Case 0: Rebase or Transplant
  249. # We have to take the time to pull down the new largefiles now.
  250. # Otherwise, any largefiles that were modified in the
  251. # destination changesets get overwritten, either by the rebase
  252. # or in the first commit after the rebase or transplant.
  253. # updatelfiles will update the dirstate to mark any pulled
  254. # largefiles as modified
  255. if getattr(self, "_isrebasing", False) or \
  256. getattr(self, "_istransplanting", False):
  257. lfcommands.updatelfiles(self.ui, self, filelist=None,
  258. printmessage=False)
  259. result = orig(text=text, user=user, date=date, match=match,
  260. force=force, editor=editor, extra=extra)
  261. return result
  262. # Case 1: user calls commit with no specific files or
  263. # include/exclude patterns: refresh and commit all files that
  264. # are "dirty".
  265. if ((match is None) or
  266. (not match.anypats() and not match.files())):
  267. # Spend a bit of time here to get a list of files we know
  268. # are modified so we can compare only against those.
  269. # It can cost a lot of time (several seconds)
  270. # otherwise to update all standins if the largefiles are
  271. # large.
  272. lfdirstate = lfutil.openlfdirstate(ui, self)
  273. dirtymatch = match_.always(self.root, self.getcwd())
  274. s = lfdirstate.status(dirtymatch, [], False, False, False)
  275. (unsure, modified, added, removed, _missing, _unknown,
  276. _ignored, _clean) = s
  277. modifiedfiles = unsure + modified + added + removed
  278. lfiles = lfutil.listlfiles(self)
  279. # this only loops through largefiles that exist (not
  280. # removed/renamed)
  281. for lfile in lfiles:
  282. if lfile in modifiedfiles:
  283. if os.path.exists(
  284. self.wjoin(lfutil.standin(lfile))):
  285. # this handles the case where a rebase is being
  286. # performed and the working copy is not updated
  287. # yet.
  288. if os.path.exists(self.wjoin(lfile)):
  289. lfutil.updatestandin(self,
  290. lfutil.standin(lfile))
  291. lfdirstate.normal(lfile)
  292. result = orig(text=text, user=user, date=date, match=match,
  293. force=force, editor=editor, extra=extra)
  294. if result is not None:
  295. for lfile in lfdirstate:
  296. if lfile in modifiedfiles:
  297. if (not os.path.exists(self.wjoin(
  298. lfutil.standin(lfile)))) or \
  299. (not os.path.exists(self.wjoin(lfile))):
  300. lfdirstate.drop(lfile)
  301. # This needs to be after commit; otherwise precommit hooks
  302. # get the wrong status
  303. lfdirstate.write()
  304. return result
  305. lfiles = lfutil.listlfiles(self)
  306. match._files = self._subdirlfs(match.files(), lfiles)
  307. # Case 2: user calls commit with specified patterns: refresh
  308. # any matching big files.
  309. smatcher = lfutil.composestandinmatcher(self, match)
  310. standins = self.dirstate.walk(smatcher, [], False, False)
  311. # No matching big files: get out of the way and pass control to
  312. # the usual commit() method.
  313. if not standins:
  314. return orig(text=text, user=user, date=date, match=match,
  315. force=force, editor=editor, extra=extra)
  316. # Refresh all matching big files. It's possible that the
  317. # commit will end up failing, in which case the big files will
  318. # stay refreshed. No harm done: the user modified them and
  319. # asked to commit them, so sooner or later we're going to
  320. # refresh the standins. Might as well leave them refreshed.
  321. lfdirstate = lfutil.openlfdirstate(ui, self)
  322. for standin in standins:
  323. lfile = lfutil.splitstandin(standin)
  324. if lfdirstate[lfile] != 'r':
  325. lfutil.updatestandin(self, standin)
  326. lfdirstate.normal(lfile)
  327. else:
  328. lfdirstate.drop(lfile)
  329. # Cook up a new matcher that only matches regular files or
  330. # standins corresponding to the big files requested by the
  331. # user. Have to modify _files to prevent commit() from
  332. # complaining "not tracked" for big files.
  333. match = copy.copy(match)
  334. origmatchfn = match.matchfn
  335. # Check both the list of largefiles and the list of
  336. # standins because if a largefile was removed, it
  337. # won't be in the list of largefiles at this point
  338. match._files += sorted(standins)
  339. actualfiles = []
  340. for f in match._files:
  341. fstandin = lfutil.standin(f)
  342. # ignore known largefiles and standins
  343. if f in lfiles or fstandin in standins:
  344. continue
  345. # append directory separator to avoid collisions
  346. if not fstandin.endswith(os.sep):
  347. fstandin += os.sep
  348. actualfiles.append(f)
  349. match._files = actualfiles
  350. def matchfn(f):
  351. if origmatchfn(f):
  352. return f not in lfiles
  353. else:
  354. return f in standins
  355. match.matchfn = matchfn
  356. result = orig(text=text, user=user, date=date, match=match,
  357. force=force, editor=editor, extra=extra)
  358. # This needs to be after commit; otherwise precommit hooks
  359. # get the wrong status
  360. lfdirstate.write()
  361. return result
  362. finally:
  363. wlock.release()
  364. def push(self, remote, force=False, revs=None, newbranch=False):
  365. if remote.local():
  366. missing = set(self.requirements) - remote.local().supported
  367. if missing:
  368. msg = _("required features are not"
  369. " supported in the destination:"
  370. " %s") % (', '.join(sorted(missing)))
  371. raise util.Abort(msg)
  372. return super(lfilesrepo, self).push(remote, force=force, revs=revs,
  373. newbranch=newbranch)
  374. def _subdirlfs(self, files, lfiles):
  375. '''
  376. Adjust matched file list
  377. If we pass a directory to commit whose only commitable files
  378. are largefiles, the core commit code aborts before finding
  379. the largefiles.
  380. So we do the following:
  381. For directories that only have largefiles as matches,
  382. we explicitly add the largefiles to the match list and remove
  383. the directory.
  384. In other cases, we leave the match list unmodified.
  385. '''
  386. actualfiles = []
  387. dirs = []
  388. regulars = []
  389. for f in files:
  390. if lfutil.isstandin(f + '/'):
  391. raise util.Abort(
  392. _('file "%s" is a largefile standin') % f,
  393. hint=('commit the largefile itself instead'))
  394. # Scan directories
  395. if os.path.isdir(self.wjoin(f)):
  396. dirs.append(f)
  397. else:
  398. regulars.append(f)
  399. for f in dirs:
  400. matcheddir = False
  401. d = self.dirstate.normalize(f) + '/'
  402. # Check for matched normal files
  403. for mf in regulars:
  404. if self.dirstate.normalize(mf).startswith(d):
  405. actualfiles.append(f)
  406. matcheddir = True
  407. break
  408. if not matcheddir:
  409. # If no normal match, manually append
  410. # any matching largefiles
  411. for lf in lfiles:
  412. if self.dirstate.normalize(lf).startswith(d):
  413. actualfiles.append(lf)
  414. if not matcheddir:
  415. actualfiles.append(lfutil.standin(f))
  416. matcheddir = True
  417. # Nothing in dir, so readd it
  418. # and let commit reject it
  419. if not matcheddir:
  420. actualfiles.append(f)
  421. # Always add normal files
  422. actualfiles += regulars
  423. return actualfiles
  424. repo.__class__ = lfilesrepo
  425. def prepushoutgoinghook(local, remote, outgoing):
  426. if outgoing.missing:
  427. toupload = set()
  428. addfunc = lambda fn, lfhash: toupload.add(lfhash)
  429. lfutil.getlfilestoupload(local, outgoing.missing, addfunc)
  430. lfcommands.uploadlfiles(ui, local, remote, toupload)
  431. repo.prepushoutgoinghooks.add("largefiles", prepushoutgoinghook)
  432. def checkrequireslfiles(ui, repo, **kwargs):
  433. if 'largefiles' not in repo.requirements and util.any(
  434. lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()):
  435. repo.requirements.add('largefiles')
  436. repo._writerequirements()
  437. ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles,
  438. 'largefiles')
  439. ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles, 'largefiles')