--- /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