Mercurial > public > mercurial-scm > hg-stable
diff hgext/largefiles/overrides.py @ 15168:cfccd3bee7b3
hgext: add largefiles extension
This code has a number of contributors and a complicated history prior to its
introduction that can be seen by visiting:
https://developers.kilnhg.com/Repo/Kiln/largefiles/largefiles
http://hg.gerg.ca/hg-bfiles
and looking at the included copyright notices and contributors list.
author | various |
---|---|
date | Sat, 24 Sep 2011 17:35:45 +0200 |
parents | |
children | aa262fff87ac |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/overrides.py Sat Sep 24 17:35:45 2011 +0200 @@ -0,0 +1,902 @@ +# Copyright 2009-2010 Gregory P. Ward +# Copyright 2009-2010 Intelerad Medical Systems Incorporated +# Copyright 2010-2011 Fog Creek Software +# Copyright 2010-2011 Unity Technologies +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''Overridden Mercurial commands and functions for the largefiles extension''' + +import os +import copy + +from mercurial import hg, commands, util, cmdutil, match as match_, node, \ + archival, error, merge +from mercurial.i18n import _ +from mercurial.node import hex +from hgext import rebase + +try: + from mercurial import scmutil +except ImportError: + pass + +import lfutil +import lfcommands + +def installnormalfilesmatchfn(manifest): + '''overrides scmutil.match so that the matcher it returns will ignore all + largefiles''' + oldmatch = None # for the closure + def override_match(repo, pats=[], opts={}, globbed=False, + default='relpath'): + match = oldmatch(repo, pats, opts, globbed, default) + m = copy.copy(match) + notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in + manifest) + m._files = filter(notlfile, m._files) + m._fmap = set(m._files) + orig_matchfn = m.matchfn + m.matchfn = lambda f: notlfile(f) and orig_matchfn(f) or None + return m + oldmatch = installmatchfn(override_match) + +def installmatchfn(f): + try: + # Mercurial >= 1.9 + oldmatch = scmutil.match + except ImportError: + # Mercurial <= 1.8 + oldmatch = cmdutil.match + setattr(f, 'oldmatch', oldmatch) + try: + # Mercurial >= 1.9 + scmutil.match = f + except ImportError: + # Mercurial <= 1.8 + cmdutil.match = f + return oldmatch + +def restorematchfn(): + '''restores scmutil.match to what it was before installnormalfilesmatchfn + was called. no-op if scmutil.match is its original function. + + Note that n calls to installnormalfilesmatchfn will require n calls to + restore matchfn to reverse''' + try: + # Mercurial >= 1.9 + scmutil.match = getattr(scmutil.match, 'oldmatch', scmutil.match) + except ImportError: + # Mercurial <= 1.8 + cmdutil.match = getattr(cmdutil.match, 'oldmatch', cmdutil.match) + +# -- Wrappers: modify existing commands -------------------------------- + +# Add works by going through the files that the user wanted to add +# and checking if they should be added as lfiles. Then making a new +# matcher which matches only the normal files and running the original +# version of add. +def override_add(orig, ui, repo, *pats, **opts): + large = opts.pop('large', None) + + lfsize = opts.pop('lfsize', None) + if not lfsize and lfutil.islfilesrepo(repo): + lfsize = ui.config(lfutil.longname, 'size', default='10') + if lfsize: + try: + lfsize = int(lfsize) + except ValueError: + raise util.Abort(_('largefiles: size must be an integer, was %s\n') % lfsize) + + lfmatcher = None + if os.path.exists(repo.wjoin(lfutil.shortname)): + lfpats = ui.config(lfutil.longname, 'patterns', default=()) + if lfpats: + lfpats = lfpats.split(' ') + lfmatcher = match_.match(repo.root, '', list(lfpats)) + + lfnames = [] + try: + # Mercurial >= 1.9 + m = scmutil.match(repo[None], pats, opts) + except ImportError: + # Mercurial <= 1.8 + m = cmdutil.match(repo, pats, opts) + m.bad = lambda x, y: None + wctx = repo[None] + for f in repo.walk(m): + exact = m.exact(f) + lfile = lfutil.standin(f) in wctx + nfile = f in wctx + exists = lfile or nfile + + # Don't warn the user when they attempt to add a normal tracked file. + # The normal add code will do that for us. + if exact and exists: + if lfile: + ui.warn(_('%s already a largefile\n') % f) + continue + + if exact or not exists: + if large or (lfsize and os.path.getsize(repo.wjoin(f)) >= \ + lfsize * 1024 * 1024) or (lfmatcher and lfmatcher(f)): + lfnames.append(f) + if ui.verbose or not exact: + ui.status(_('adding %s as a largefile\n') % m.rel(f)) + + bad = [] + standins = [] + + # Need to lock otherwise there could be a race condition inbetween when + # standins are created and added to the repo + wlock = repo.wlock() + try: + if not opts.get('dry_run'): + lfdirstate = lfutil.openlfdirstate(ui, repo) + for f in lfnames: + standinname = lfutil.standin(f) + lfutil.writestandin(repo, standinname, hash='', + executable=lfutil.getexecutable(repo.wjoin(f))) + standins.append(standinname) + if lfdirstate[f] == 'r': + lfdirstate.normallookup(f) + else: + lfdirstate.add(f) + lfdirstate.write() + bad += [lfutil.splitstandin(f) for f in lfutil.repo_add(repo, + standins) if f in m.files()] + finally: + wlock.release() + + installnormalfilesmatchfn(repo[None].manifest()) + result = orig(ui, repo, *pats, **opts) + restorematchfn() + + return (result == 1 or bad) and 1 or 0 + +def override_remove(orig, ui, repo, *pats, **opts): + manifest = repo[None].manifest() + installnormalfilesmatchfn(manifest) + orig(ui, repo, *pats, **opts) + restorematchfn() + + after, force = opts.get('after'), opts.get('force') + if not pats and not after: + raise util.Abort(_('no files specified')) + try: + # Mercurial >= 1.9 + m = scmutil.match(repo[None], pats, opts) + except ImportError: + # Mercurial <= 1.8 + m = cmdutil.match(repo, pats, opts) + try: + repo.lfstatus = True + s = repo.status(match=m, clean=True) + finally: + repo.lfstatus = False + modified, added, deleted, clean = [[f for f in list if lfutil.standin(f) \ + in manifest] for list in [s[0], s[1], s[3], s[6]]] + + def warn(files, reason): + for f in files: + ui.warn(_('not removing %s: file %s (use -f to force removal)\n') + % (m.rel(f), reason)) + + if force: + remove, forget = modified + deleted + clean, added + elif after: + remove, forget = deleted, [] + warn(modified + added + clean, _('still exists')) + else: + remove, forget = deleted + clean, [] + warn(modified, _('is modified')) + warn(added, _('has been marked for add')) + + for f in sorted(remove + forget): + if ui.verbose or not m.exact(f): + ui.status(_('removing %s\n') % m.rel(f)) + + # Need to lock because standin files are deleted then removed from the + # repository and we could race inbetween. + wlock = repo.wlock() + try: + lfdirstate = lfutil.openlfdirstate(ui, repo) + for f in remove: + if not after: + os.unlink(repo.wjoin(f)) + currentdir = os.path.split(f)[0] + while currentdir and not os.listdir(repo.wjoin(currentdir)): + os.rmdir(repo.wjoin(currentdir)) + currentdir = os.path.split(currentdir)[0] + lfdirstate.remove(f) + lfdirstate.write() + + forget = [lfutil.standin(f) for f in forget] + remove = [lfutil.standin(f) for f in remove] + lfutil.repo_forget(repo, forget) + lfutil.repo_remove(repo, remove, unlink=True) + finally: + wlock.release() + +def override_status(orig, ui, repo, *pats, **opts): + try: + repo.lfstatus = True + return orig(ui, repo, *pats, **opts) + finally: + repo.lfstatus = False + +def override_log(orig, ui, repo, *pats, **opts): + try: + repo.lfstatus = True + orig(ui, repo, *pats, **opts) + finally: + repo.lfstatus = False + +def override_verify(orig, ui, repo, *pats, **opts): + large = opts.pop('large', False) + all = opts.pop('lfa', False) + contents = opts.pop('lfc', False) + + result = orig(ui, repo, *pats, **opts) + if large: + result = result or lfcommands.verifylfiles(ui, repo, all, contents) + return result + +# Override needs to refresh standins so that update's normal merge +# will go through properly. Then the other update hook (overriding repo.update) +# will get the new files. Filemerge is also overriden so that the merge +# will merge standins correctly. +def override_update(orig, ui, repo, *pats, **opts): + lfdirstate = lfutil.openlfdirstate(ui, repo) + s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False, + False, False) + (unsure, modified, added, removed, missing, unknown, ignored, clean) = s + + # Need to lock between the standins getting updated and their lfiles + # getting updated + wlock = repo.wlock() + try: + if opts['check']: + mod = len(modified) > 0 + for lfile in unsure: + standin = lfutil.standin(lfile) + if repo['.'][standin].data().strip() != \ + lfutil.hashfile(repo.wjoin(lfile)): + mod = True + else: + lfdirstate.normal(lfile) + lfdirstate.write() + if mod: + raise util.Abort(_('uncommitted local changes')) + # XXX handle removed differently + if not opts['clean']: + for lfile in unsure + modified + added: + lfutil.updatestandin(repo, lfutil.standin(lfile)) + finally: + wlock.release() + return orig(ui, repo, *pats, **opts) + +# Override filemerge to prompt the user about how they wish to merge lfiles. +# This will handle identical edits, and copy/rename + edit without prompting +# the user. +def override_filemerge(origfn, repo, mynode, orig, fcd, fco, fca): + # Use better variable names here. Because this is a wrapper we cannot + # change the variable names in the function declaration. + fcdest, fcother, fcancestor = fcd, fco, fca + if not lfutil.isstandin(orig): + return origfn(repo, mynode, orig, fcdest, fcother, fcancestor) + else: + if not fcother.cmp(fcdest): # files identical? + return None + + # backwards, use working dir parent as ancestor + if fcancestor == fcother: + fcancestor = fcdest.parents()[0] + + if orig != fcother.path(): + repo.ui.status(_('merging %s and %s to %s\n') + % (lfutil.splitstandin(orig), + lfutil.splitstandin(fcother.path()), + lfutil.splitstandin(fcdest.path()))) + else: + repo.ui.status(_('merging %s\n') + % lfutil.splitstandin(fcdest.path())) + + if fcancestor.path() != fcother.path() and fcother.data() == \ + fcancestor.data(): + return 0 + if fcancestor.path() != fcdest.path() and fcdest.data() == \ + fcancestor.data(): + repo.wwrite(fcdest.path(), fcother.data(), fcother.flags()) + return 0 + + if repo.ui.promptchoice(_('largefile %s has a merge conflict\n' + 'keep (l)ocal or take (o)ther?') % + lfutil.splitstandin(orig), + (_('&Local'), _('&Other')), 0) == 0: + return 0 + else: + repo.wwrite(fcdest.path(), fcother.data(), fcother.flags()) + return 0 + +# Copy first changes the matchers to match standins instead of lfiles. +# Then it overrides util.copyfile in that function it checks if the destination +# lfile already exists. It also keeps a list of copied files so that the lfiles +# can be copied and the dirstate updated. +def override_copy(orig, ui, repo, pats, opts, rename=False): + # doesn't remove lfile on rename + if len(pats) < 2: + # this isn't legal, let the original function deal with it + return orig(ui, repo, pats, opts, rename) + + def makestandin(relpath): + try: + # Mercurial >= 1.9 + path = scmutil.canonpath(repo.root, repo.getcwd(), relpath) + except ImportError: + # Mercurial <= 1.8 + path = util.canonpath(repo.root, repo.getcwd(), relpath) + return os.path.join(os.path.relpath('.', repo.getcwd()), + lfutil.standin(path)) + + try: + # Mercurial >= 1.9 + fullpats = scmutil.expandpats(pats) + except ImportError: + # Mercurial <= 1.8 + fullpats = cmdutil.expandpats(pats) + dest = fullpats[-1] + + if os.path.isdir(dest): + if not os.path.isdir(makestandin(dest)): + os.makedirs(makestandin(dest)) + # This could copy both lfiles and normal files in one command, but we don't + # want to do that first replace their matcher to only match normal files + # and run it then replace it to just match lfiles and run it again + nonormalfiles = False + nolfiles = False + try: + installnormalfilesmatchfn(repo[None].manifest()) + result = orig(ui, repo, pats, opts, rename) + except util.Abort, e: + if str(e) != 'no files to copy': + raise e + else: + nonormalfiles = True + result = 0 + finally: + restorematchfn() + + # The first rename can cause our current working directory to be removed. + # In that case there is nothing left to copy/rename so just quit. + try: + repo.getcwd() + except OSError: + return result + + try: + # When we call orig below it creates the standins but we don't add them + # to the dir state until later so lock during that time. + wlock = repo.wlock() + + manifest = repo[None].manifest() + oldmatch = None # for the closure + def override_match(repo, pats=[], opts={}, globbed=False, + default='relpath'): + newpats = [] + # The patterns were previously mangled to add the standin + # directory; we need to remove that now + for pat in pats: + if match_.patkind(pat) is None and lfutil.shortname in pat: + newpats.append(pat.replace(lfutil.shortname, '')) + else: + newpats.append(pat) + match = oldmatch(repo, newpats, opts, globbed, default) + m = copy.copy(match) + lfile = lambda f: lfutil.standin(f) in manifest + m._files = [lfutil.standin(f) for f in m._files if lfile(f)] + m._fmap = set(m._files) + orig_matchfn = m.matchfn + m.matchfn = lambda f: lfutil.isstandin(f) and \ + lfile(lfutil.splitstandin(f)) and \ + orig_matchfn(lfutil.splitstandin(f)) or None + return m + oldmatch = installmatchfn(override_match) + listpats = [] + for pat in pats: + if match_.patkind(pat) is not None: + listpats.append(pat) + else: + listpats.append(makestandin(pat)) + + try: + origcopyfile = util.copyfile + copiedfiles = [] + def override_copyfile(src, dest): + if lfutil.shortname in src and lfutil.shortname in dest: + destlfile = dest.replace(lfutil.shortname, '') + if not opts['force'] and os.path.exists(destlfile): + raise IOError('', + _('destination largefile already exists')) + copiedfiles.append((src, dest)) + origcopyfile(src, dest) + + util.copyfile = override_copyfile + result += orig(ui, repo, listpats, opts, rename) + finally: + util.copyfile = origcopyfile + + lfdirstate = lfutil.openlfdirstate(ui, repo) + for (src, dest) in copiedfiles: + if lfutil.shortname in src and lfutil.shortname in dest: + srclfile = src.replace(lfutil.shortname, '') + destlfile = dest.replace(lfutil.shortname, '') + destlfiledir = os.path.dirname(destlfile) or '.' + if not os.path.isdir(destlfiledir): + os.makedirs(destlfiledir) + if rename: + os.rename(srclfile, destlfile) + lfdirstate.remove(os.path.relpath(srclfile, + repo.root)) + else: + util.copyfile(srclfile, destlfile) + lfdirstate.add(os.path.relpath(destlfile, + repo.root)) + lfdirstate.write() + except util.Abort, e: + if str(e) != 'no files to copy': + raise e + else: + nolfiles = True + finally: + restorematchfn() + wlock.release() + + if nolfiles and nonormalfiles: + raise util.Abort(_('no files to copy')) + + return result + +# When the user calls revert, we have to be careful to not revert any changes +# to other lfiles accidentally. This means we have to keep track of the lfiles +# that are being reverted so we only pull down the necessary lfiles. +# +# Standins are only updated (to match the hash of lfiles) before commits. +# Update the standins then run the original revert (changing the matcher to hit +# standins instead of lfiles). Based on the resulting standins update the +# lfiles. Then return the standins to their proper state +def override_revert(orig, ui, repo, *pats, **opts): + # Because we put the standins in a bad state (by updating them) and then + # return them to a correct state we need to lock to prevent others from + # changing them in their incorrect state. + wlock = repo.wlock() + try: + lfdirstate = lfutil.openlfdirstate(ui, repo) + (modified, added, removed, missing, unknown, ignored, clean) = \ + lfutil.lfdirstate_status(lfdirstate, repo, repo['.'].rev()) + for lfile in modified: + lfutil.updatestandin(repo, lfutil.standin(lfile)) + + try: + ctx = repo[opts.get('rev')] + oldmatch = None # for the closure + def override_match(ctxorrepo, pats=[], opts={}, globbed=False, + default='relpath'): + if hasattr(ctxorrepo, 'match'): + ctx0 = ctxorrepo + else: + ctx0 = ctxorrepo[None] + match = oldmatch(ctxorrepo, pats, opts, globbed, default) + m = copy.copy(match) + def tostandin(f): + if lfutil.standin(f) in ctx0 or lfutil.standin(f) in ctx: + return lfutil.standin(f) + elif lfutil.standin(f) in repo[None]: + return None + return f + m._files = [tostandin(f) for f in m._files] + m._files = [f for f in m._files if f is not None] + m._fmap = set(m._files) + orig_matchfn = m.matchfn + def matchfn(f): + if lfutil.isstandin(f): + # We need to keep track of what lfiles are being + # matched so we know which ones to update later + # (otherwise we revert changes to other lfiles + # accidentally). This is repo specific, so duckpunch + # the repo object to keep the list of lfiles for us + # later. + if orig_matchfn(lfutil.splitstandin(f)) and \ + (f in repo[None] or f in ctx): + lfileslist = getattr(repo, '_lfilestoupdate', []) + lfileslist.append(lfutil.splitstandin(f)) + repo._lfilestoupdate = lfileslist + return True + else: + return False + return orig_matchfn(f) + m.matchfn = matchfn + return m + oldmatch = installmatchfn(override_match) + try: + # Mercurial >= 1.9 + scmutil.match + matches = override_match(repo[None], pats, opts) + except ImportError: + # Mercurial <= 1.8 + matches = override_match(repo, pats, opts) + orig(ui, repo, *pats, **opts) + finally: + restorematchfn() + lfileslist = getattr(repo, '_lfilestoupdate', []) + lfcommands.updatelfiles(ui, repo, filelist=lfileslist, printmessage=False) + # Empty out the lfiles list so we start fresh next time + repo._lfilestoupdate = [] + for lfile in modified: + if lfile in lfileslist: + if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\ + in repo['.']: + lfutil.writestandin(repo, lfutil.standin(lfile), + repo['.'][lfile].data().strip(), + 'x' in repo['.'][lfile].flags()) + lfdirstate = lfutil.openlfdirstate(ui, repo) + for lfile in added: + standin = lfutil.standin(lfile) + if standin not in ctx and (standin in matches or opts.get('all')): + if lfile in lfdirstate: + try: + # Mercurial >= 1.9 + lfdirstate.drop(lfile) + except AttributeError: + # Mercurial <= 1.8 + lfdirstate.forget(lfile) + util.unlinkpath(repo.wjoin(standin)) + lfdirstate.write() + finally: + wlock.release() + +def hg_update(orig, repo, node): + result = orig(repo, node) + # XXX check if it worked first + lfcommands.updatelfiles(repo.ui, repo) + return result + +def hg_clean(orig, repo, node, show_stats=True): + result = orig(repo, node, show_stats) + lfcommands.updatelfiles(repo.ui, repo) + return result + +def hg_merge(orig, repo, node, force=None, remind=True): + result = orig(repo, node, force, remind) + lfcommands.updatelfiles(repo.ui, repo) + return result + +# When we rebase a repository with remotely changed lfiles, we need +# to take some extra care so that the lfiles are correctly updated +# in the working copy +def override_pull(orig, ui, repo, source=None, **opts): + if opts.get('rebase', False): + repo._isrebasing = True + try: + if opts.get('update'): + del opts['update'] + ui.debug('--update and --rebase are not compatible, ignoring ' + 'the update flag\n') + del opts['rebase'] + try: + # Mercurial >= 1.9 + cmdutil.bailifchanged(repo) + except AttributeError: + # Mercurial <= 1.8 + cmdutil.bail_if_changed(repo) + revsprepull = len(repo) + origpostincoming = commands.postincoming + def _dummy(*args, **kwargs): + pass + commands.postincoming = _dummy + repo.lfpullsource = source + if not source: + source = 'default' + try: + result = commands.pull(ui, repo, source, **opts) + finally: + commands.postincoming = origpostincoming + revspostpull = len(repo) + if revspostpull > revsprepull: + result = result or rebase.rebase(ui, repo) + finally: + repo._isrebasing = False + else: + repo.lfpullsource = source + if not source: + source = 'default' + result = orig(ui, repo, source, **opts) + return result + +def override_rebase(orig, ui, repo, **opts): + repo._isrebasing = True + try: + orig(ui, repo, **opts) + finally: + repo._isrebasing = False + +def override_archive(orig, repo, dest, node, kind, decode=True, matchfn=None, + prefix=None, mtime=None, subrepos=None): + # No need to lock because we are only reading history and lfile caches + # neither of which are modified + + lfcommands.cachelfiles(repo.ui, repo, node) + + if kind not in archival.archivers: + raise util.Abort(_("unknown archive type '%s'") % kind) + + ctx = repo[node] + + # In Mercurial <= 1.5 the prefix is passed to the archiver so try that + # if that doesn't work we are probably in Mercurial >= 1.6 where the + # prefix is not handled by the archiver + try: + archiver = archival.archivers[kind](dest, prefix, mtime or \ + ctx.date()[0]) + + def write(name, mode, islink, getdata): + if matchfn and not matchfn(name): + return + data = getdata() + if decode: + data = repo.wwritedata(name, data) + archiver.addfile(name, mode, islink, data) + except TypeError: + if kind == 'files': + if prefix: + raise util.Abort( + _('cannot give prefix when archiving to files')) + else: + prefix = archival.tidyprefix(dest, kind, prefix) + + def write(name, mode, islink, getdata): + if matchfn and not matchfn(name): + return + data = getdata() + if decode: + data = repo.wwritedata(name, data) + archiver.addfile(prefix + name, mode, islink, data) + + archiver = archival.archivers[kind](dest, mtime or ctx.date()[0]) + + if repo.ui.configbool("ui", "archivemeta", True): + def metadata(): + base = 'repo: %s\nnode: %s\nbranch: %s\n' % ( + hex(repo.changelog.node(0)), hex(node), ctx.branch()) + + tags = ''.join('tag: %s\n' % t for t in ctx.tags() + if repo.tagtype(t) == 'global') + if not tags: + repo.ui.pushbuffer() + opts = {'template': '{latesttag}\n{latesttagdistance}', + 'style': '', 'patch': None, 'git': None} + cmdutil.show_changeset(repo.ui, repo, opts).show(ctx) + ltags, dist = repo.ui.popbuffer().split('\n') + tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':')) + tags += 'latesttagdistance: %s\n' % dist + + return base + tags + + write('.hg_archival.txt', 0644, False, metadata) + + for f in ctx: + ff = ctx.flags(f) + getdata = ctx[f].data + if lfutil.isstandin(f): + path = lfutil.findfile(repo, getdata().strip()) + f = lfutil.splitstandin(f) + + def getdatafn(): + try: + fd = open(path, 'rb') + return fd.read() + finally: + fd.close() + + getdata = getdatafn + write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata) + + if subrepos: + for subpath in ctx.substate: + sub = ctx.sub(subpath) + try: + sub.archive(repo.ui, archiver, prefix) + except TypeError: + sub.archive(archiver, prefix) + + archiver.done() + +# If a lfile is modified the change is not reflected in its standin until a +# commit. cmdutil.bailifchanged raises an exception if the repo has +# uncommitted changes. Wrap it to also check if lfiles were changed. This is +# used by bisect and backout. +def override_bailifchanged(orig, repo): + orig(repo) + repo.lfstatus = True + modified, added, removed, deleted = repo.status()[:4] + repo.lfstatus = False + if modified or added or removed or deleted: + raise util.Abort(_('outstanding uncommitted changes')) + +# Fetch doesn't use cmdutil.bail_if_changed so override it to add the check +def override_fetch(orig, ui, repo, *pats, **opts): + repo.lfstatus = True + modified, added, removed, deleted = repo.status()[:4] + repo.lfstatus = False + if modified or added or removed or deleted: + raise util.Abort(_('outstanding uncommitted changes')) + return orig(ui, repo, *pats, **opts) + +def override_forget(orig, ui, repo, *pats, **opts): + installnormalfilesmatchfn(repo[None].manifest()) + orig(ui, repo, *pats, **opts) + restorematchfn() + try: + # Mercurial >= 1.9 + m = scmutil.match(repo[None], pats, opts) + except ImportError: + # Mercurial <= 1.8 + m = cmdutil.match(repo, pats, opts) + + try: + repo.lfstatus = True + s = repo.status(match=m, clean=True) + finally: + repo.lfstatus = False + forget = sorted(s[0] + s[1] + s[3] + s[6]) + forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()] + + for f in forget: + if lfutil.standin(f) not in repo.dirstate and not \ + os.path.isdir(m.rel(lfutil.standin(f))): + ui.warn(_('not removing %s: file is already untracked\n') + % m.rel(f)) + + for f in forget: + if ui.verbose or not m.exact(f): + ui.status(_('removing %s\n') % m.rel(f)) + + # Need to lock because standin files are deleted then removed from the + # repository and we could race inbetween. + wlock = repo.wlock() + try: + lfdirstate = lfutil.openlfdirstate(ui, repo) + for f in forget: + if lfdirstate[f] == 'a': + lfdirstate.drop(f) + else: + lfdirstate.remove(f) + lfdirstate.write() + lfutil.repo_remove(repo, [lfutil.standin(f) for f in forget], + unlink=True) + finally: + wlock.release() + +def getoutgoinglfiles(ui, repo, dest=None, **opts): + dest = ui.expandpath(dest or 'default-push', dest or 'default') + dest, branches = hg.parseurl(dest, opts.get('branch')) + revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev')) + if revs: + revs = [repo.lookup(rev) for rev in revs] + + # Mercurial <= 1.5 had remoteui in cmdutil, then it moved to hg + try: + remoteui = cmdutil.remoteui + except AttributeError: + remoteui = hg.remoteui + + try: + remote = hg.repository(remoteui(repo, opts), dest) + except error.RepoError: + return None + o = lfutil.findoutgoing(repo, remote, False) + if not o: + return None + o = repo.changelog.nodesbetween(o, revs)[0] + if opts.get('newest_first'): + o.reverse() + + toupload = set() + for n in o: + parents = [p for p in repo.changelog.parents(n) if p != node.nullid] + ctx = repo[n] + files = set(ctx.files()) + if len(parents) == 2: + mc = ctx.manifest() + mp1 = ctx.parents()[0].manifest() + mp2 = ctx.parents()[1].manifest() + for f in mp1: + if f not in mc: + files.add(f) + for f in mp2: + if f not in mc: + files.add(f) + for f in mc: + if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None): + files.add(f) + toupload = toupload.union(set([f for f in files if lfutil.isstandin(f)\ + and f in ctx])) + return toupload + +def override_outgoing(orig, ui, repo, dest=None, **opts): + orig(ui, repo, dest, **opts) + + if opts.pop('large', None): + toupload = getoutgoinglfiles(ui, repo, dest, **opts) + if toupload is None: + ui.status(_('largefiles: No remote repo\n')) + else: + ui.status(_('largefiles to upload:\n')) + for file in toupload: + ui.status(lfutil.splitstandin(file) + '\n') + ui.status('\n') + +def override_summary(orig, ui, repo, *pats, **opts): + orig(ui, repo, *pats, **opts) + + if opts.pop('large', None): + toupload = getoutgoinglfiles(ui, repo, None, **opts) + if toupload is None: + ui.status(_('largefiles: No remote repo\n')) + else: + ui.status(_('largefiles: %d to upload\n') % len(toupload)) + +def override_addremove(orig, ui, repo, *pats, **opts): + # Check if the parent or child has lfiles if they do don't allow it. If + # there is a symlink in the manifest then getting the manifest throws an + # exception catch it and let addremove deal with it. This happens in + # Mercurial's test test-addremove-symlink + try: + manifesttip = set(repo['tip'].manifest()) + except util.Abort: + manifesttip = set() + try: + manifestworking = set(repo[None].manifest()) + except util.Abort: + manifestworking = set() + + # Manifests are only iterable so turn them into sets then union + for file in manifesttip.union(manifestworking): + if file.startswith(lfutil.shortname): + raise util.Abort( + _('addremove cannot be run on a repo with largefiles')) + + return orig(ui, repo, *pats, **opts) + +# Calling purge with --all will cause the lfiles to be deleted. +# Override repo.status to prevent this from happening. +def override_purge(orig, ui, repo, *dirs, **opts): + oldstatus = repo.status + def override_status(node1='.', node2=None, match=None, ignored=False, + clean=False, unknown=False, listsubrepos=False): + r = oldstatus(node1, node2, match, ignored, clean, unknown, + listsubrepos) + lfdirstate = lfutil.openlfdirstate(ui, repo) + modified, added, removed, deleted, unknown, ignored, clean = r + unknown = [f for f in unknown if lfdirstate[f] == '?'] + ignored = [f for f in ignored if lfdirstate[f] == '?'] + return modified, added, removed, deleted, unknown, ignored, clean + repo.status = override_status + orig(ui, repo, *dirs, **opts) + repo.status = oldstatus + +def override_rollback(orig, ui, repo, **opts): + result = orig(ui, repo, **opts) + merge.update(repo, node=None, branchmerge=False, force=True, + partial=lfutil.isstandin) + lfdirstate = lfutil.openlfdirstate(ui, repo) + lfiles = lfutil.listlfiles(repo) + oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev()) + for file in lfiles: + if file in oldlfiles: + lfdirstate.normallookup(file) + else: + lfdirstate.add(file) + lfdirstate.write() + return result