narrowcommands.py
693 lines
| 23.8 KiB
| text/x-python
|
PythonLexer
|
r36096 | # narrowcommands.py - command modifications for narrowhg extension | ||
# | ||||
# Copyright 2017 Google, Inc. | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
import itertools | ||||
|
r39283 | import os | ||
|
r36096 | |||
from mercurial.i18n import _ | ||||
|
r46729 | from mercurial.node import ( | ||
hex, | ||||
short, | ||||
) | ||||
|
r36096 | from mercurial import ( | ||
|
r40106 | bundle2, | ||
|
r36096 | cmdutil, | ||
commands, | ||||
discovery, | ||||
|
r39471 | encoding, | ||
|
r36096 | error, | ||
exchange, | ||||
extensions, | ||||
hg, | ||||
|
r36178 | narrowspec, | ||
r43923 | pathutil, | |||
|
r36172 | pycompat, | ||
|
r36096 | registrar, | ||
repair, | ||||
repoview, | ||||
|
r45932 | requirements, | ||
|
r39283 | sparse, | ||
|
r36096 | util, | ||
|
r40110 | wireprototypes, | ||
|
r36096 | ) | ||
r47669 | from mercurial.utils import ( | |||
urlutil, | ||||
) | ||||
|
r36096 | |||
table = {} | ||||
command = registrar.command(table) | ||||
|
r43346 | |||
|
r36096 | def setup(): | ||
"""Wraps user-facing mercurial commands with narrow-aware versions.""" | ||||
|
r43347 | entry = extensions.wrapcommand(commands.table, b'clone', clonenarrowcmd) | ||
|
r43346 | entry[1].append( | ||
|
r43347 | (b'', b'narrow', None, _(b"create a narrow clone of select files")) | ||
|
r43346 | ) | ||
entry[1].append( | ||||
|
r43347 | ( | ||
b'', | ||||
b'depth', | ||||
b'', | ||||
_(b"limit the history fetched by distance from heads"), | ||||
) | ||||
|
r43346 | ) | ||
|
r43347 | entry[1].append((b'', b'narrowspec', b'', _(b"read narrowspecs from file"))) | ||
|
r36096 | # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit | ||
|
r43347 | if b'sparse' not in extensions.enabled(): | ||
|
r43346 | entry[1].append( | ||
|
r43347 | (b'', b'include', [], _(b"specifically fetch this file/directory")) | ||
|
r43346 | ) | ||
|
r36096 | entry[1].append( | ||
|
r43346 | ( | ||
|
r43347 | b'', | ||
b'exclude', | ||||
|
r43346 | [], | ||
|
r43347 | _(b"do not fetch this file/directory, even if included"), | ||
|
r43346 | ) | ||
) | ||||
|
r36096 | |||
|
r43347 | entry = extensions.wrapcommand(commands.table, b'pull', pullnarrowcmd) | ||
|
r43346 | entry[1].append( | ||
|
r43347 | ( | ||
b'', | ||||
b'depth', | ||||
b'', | ||||
_(b"limit the history fetched by distance from heads"), | ||||
) | ||||
|
r43346 | ) | ||
|
r36096 | |||
|
r43347 | extensions.wrapcommand(commands.table, b'archive', archivenarrowcmd) | ||
|
r36096 | |||
|
r43346 | |||
|
r36096 | def clonenarrowcmd(orig, ui, repo, *args, **opts): | ||
"""Wraps clone command, so 'hg clone' first wraps localrepo.clone().""" | ||||
|
r36172 | opts = pycompat.byteskwargs(opts) | ||
|
r36096 | wrappedextraprepare = util.nullcontextmanager() | ||
|
r43347 | narrowspecfile = opts[b'narrowspec'] | ||
|
r39283 | |||
if narrowspecfile: | ||||
|
r39843 | filepath = os.path.join(encoding.getcwd(), narrowspecfile) | ||
|
r43347 | ui.status(_(b"reading narrowspec from '%s'\n") % filepath) | ||
|
r39283 | try: | ||
|
r39501 | fdata = util.readfile(filepath) | ||
except IOError as inst: | ||||
|
r43346 | raise error.Abort( | ||
|
r43347 | _(b"cannot read narrowspecs from '%s': %s") | ||
|
r43346 | % (filepath, encoding.strtolocal(inst.strerror)) | ||
) | ||||
|
r39283 | |||
|
r43347 | includes, excludes, profiles = sparse.parseconfig(ui, fdata, b'narrow') | ||
|
r39283 | if profiles: | ||
|
r46733 | raise error.ConfigError( | ||
|
r43346 | _( | ||
|
r43347 | b"cannot specify other files using '%include' in" | ||
b" narrowspec" | ||||
|
r43346 | ) | ||
) | ||||
|
r39283 | |||
|
r39577 | narrowspec.validatepatterns(includes) | ||
narrowspec.validatepatterns(excludes) | ||||
|
r39283 | # narrowspec is passed so we should assume that user wants narrow clone | ||
|
r43347 | opts[b'narrow'] = True | ||
opts[b'include'].extend(includes) | ||||
opts[b'exclude'].extend(excludes) | ||||
|
r39283 | |||
|
r43347 | if opts[b'narrow']: | ||
|
r43346 | |||
|
r36096 | def pullbundle2extraprepare_widen(orig, pullop, kwargs): | ||
orig(pullop, kwargs) | ||||
|
r43347 | if opts.get(b'depth'): | ||
kwargs[b'depth'] = opts[b'depth'] | ||||
|
r43346 | |||
wrappedextraprepare = extensions.wrappedfunction( | ||||
|
r43347 | exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen | ||
|
r43346 | ) | ||
|
r36096 | |||
|
r39590 | with wrappedextraprepare: | ||
|
r36172 | return orig(ui, repo, *args, **pycompat.strkwargs(opts)) | ||
|
r36096 | |||
|
r43346 | |||
|
r36096 | def pullnarrowcmd(orig, ui, repo, *args, **opts): | ||
"""Wraps pull command to allow modifying narrow spec.""" | ||||
wrappedextraprepare = util.nullcontextmanager() | ||||
|
r45932 | if requirements.NARROW_REQUIREMENT in repo.requirements: | ||
|
r36096 | |||
def pullbundle2extraprepare_widen(orig, pullop, kwargs): | ||||
orig(pullop, kwargs) | ||||
|
r43906 | if opts.get('depth'): | ||
kwargs[b'depth'] = opts['depth'] | ||||
|
r43346 | |||
wrappedextraprepare = extensions.wrappedfunction( | ||||
|
r43347 | exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen | ||
|
r43346 | ) | ||
|
r36096 | |||
with wrappedextraprepare: | ||||
return orig(ui, repo, *args, **opts) | ||||
|
r43346 | |||
|
r36096 | def archivenarrowcmd(orig, ui, repo, *args, **opts): | ||
"""Wraps archive command to narrow the default includes.""" | ||||
|
r45932 | if requirements.NARROW_REQUIREMENT in repo.requirements: | ||
|
r36096 | repo_includes, repo_excludes = repo.narrowpats | ||
|
r43906 | includes = set(opts.get('include', [])) | ||
excludes = set(opts.get('exclude', [])) | ||||
|
r36117 | includes, excludes, unused_invalid = narrowspec.restrictpatterns( | ||
|
r43346 | includes, excludes, repo_includes, repo_excludes | ||
) | ||||
|
r36096 | if includes: | ||
|
r43906 | opts['include'] = includes | ||
|
r36096 | if excludes: | ||
|
r43906 | opts['exclude'] = excludes | ||
|
r36096 | return orig(ui, repo, *args, **opts) | ||
|
r43346 | |||
|
r36096 | def pullbundle2extraprepare(orig, pullop, kwargs): | ||
repo = pullop.repo | ||||
|
r45932 | if requirements.NARROW_REQUIREMENT not in repo.requirements: | ||
|
r36096 | return orig(pullop, kwargs) | ||
|
r40110 | if wireprototypes.NARROWCAP not in pullop.remote.capabilities(): | ||
|
r43347 | raise error.Abort(_(b"server does not support narrow clones")) | ||
|
r36096 | orig(pullop, kwargs) | ||
|
r43347 | kwargs[b'narrow'] = True | ||
|
r36096 | include, exclude = repo.narrowpats | ||
|
r43347 | kwargs[b'oldincludepats'] = include | ||
kwargs[b'oldexcludepats'] = exclude | ||||
|
r40528 | if include: | ||
|
r43347 | kwargs[b'includepats'] = include | ||
|
r40528 | if exclude: | ||
|
r43347 | kwargs[b'excludepats'] = exclude | ||
|
r39560 | # calculate known nodes only in ellipses cases because in non-ellipses cases | ||
# we have all the nodes | ||||
|
r42605 | if wireprototypes.ELLIPSESCAP1 in pullop.remote.capabilities(): | ||
|
r43347 | kwargs[b'known'] = [ | ||
|
r46729 | hex(ctx.node()) | ||
|
r43347 | for ctx in repo.set(b'::%ln', pullop.common) | ||
|
r47771 | if ctx.node() != repo.nullid | ||
|
r43346 | ] | ||
|
r43347 | if not kwargs[b'known']: | ||
|
r39560 | # Mercurial serializes an empty list as '' and deserializes it as | ||
# [''], so delete it instead to avoid handling the empty string on | ||||
# the server. | ||||
|
r43347 | del kwargs[b'known'] | ||
|
r36096 | |||
|
r43346 | |||
extensions.wrapfunction( | ||||
|
r43347 | exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare | ||
|
r43346 | ) | ||
|
r36096 | |||
|
r43346 | def _narrow( | ||
ui, | ||||
repo, | ||||
remote, | ||||
commoninc, | ||||
oldincludes, | ||||
oldexcludes, | ||||
newincludes, | ||||
newexcludes, | ||||
force, | ||||
|
r47206 | backup, | ||
|
r43346 | ): | ||
|
r36096 | oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes) | ||
newmatch = narrowspec.match(repo.root, newincludes, newexcludes) | ||||
# This is essentially doing "hg outgoing" to find all local-only | ||||
# commits. We will then check that the local-only commits don't | ||||
# have any changes to files that will be untracked. | ||||
unfi = repo.unfiltered() | ||||
|
r43346 | outgoing = discovery.findcommonoutgoing(unfi, remote, commoninc=commoninc) | ||
|
r43347 | ui.status(_(b'looking for local changes to affected paths\n')) | ||
|
r47799 | progress = ui.makeprogress( | ||
topic=_(b'changesets'), | ||||
unit=_(b'changesets'), | ||||
total=len(outgoing.missing) + len(outgoing.excluded), | ||||
) | ||||
|
r36096 | localnodes = [] | ||
|
r47799 | with progress: | ||
for n in itertools.chain(outgoing.missing, outgoing.excluded): | ||||
progress.increment() | ||||
if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()): | ||||
localnodes.append(n) | ||||
|
r43347 | revstostrip = unfi.revs(b'descendants(%ln)', localnodes) | ||
hiddenrevs = repoview.filterrevs(repo, b'visible') | ||||
|
r43346 | visibletostrip = list( | ||
repo.changelog.node(r) for r in (revstostrip - hiddenrevs) | ||||
) | ||||
|
r36096 | if visibletostrip: | ||
|
r43346 | ui.status( | ||
_( | ||||
|
r43347 | b'The following changeset(s) or their ancestors have ' | ||
b'local changes not on the remote:\n' | ||||
|
r43346 | ) | ||
) | ||||
|
r36096 | maxnodes = 10 | ||
if ui.verbose or len(visibletostrip) <= maxnodes: | ||||
for n in visibletostrip: | ||||
|
r46729 | ui.status(b'%s\n' % short(n)) | ||
|
r36096 | else: | ||
for n in visibletostrip[:maxnodes]: | ||||
|
r46729 | ui.status(b'%s\n' % short(n)) | ||
|
r43346 | ui.status( | ||
|
r43347 | _(b'...and %d more, use --verbose to list all\n') | ||
|
r43346 | % (len(visibletostrip) - maxnodes) | ||
) | ||||
|
r36096 | if not force: | ||
|
r46733 | raise error.StateError( | ||
|
r43347 | _(b'local changes found'), | ||
|
r43387 | hint=_(b'use --force-delete-local-changes to ignore'), | ||
|
r43346 | ) | ||
|
r36096 | |||
|
r41106 | with ui.uninterruptible(): | ||
|
r38547 | if revstostrip: | ||
tostrip = [unfi.changelog.node(r) for r in revstostrip] | ||||
|
r43347 | if repo[b'.'].node() in tostrip: | ||
|
r38547 | # stripping working copy, so move to a different commit first | ||
|
r43346 | urev = max( | ||
repo.revs( | ||||
|
r43347 | b'(::%n) - %ln + null', | ||
repo[b'.'].node(), | ||||
visibletostrip, | ||||
|
r43346 | ) | ||
) | ||||
|
r38547 | hg.clean(repo, urev) | ||
|
r43347 | overrides = {(b'devel', b'strip-obsmarkers'): False} | ||
|
r47800 | if backup: | ||
ui.status(_(b'moving unwanted changesets to backup\n')) | ||||
else: | ||||
ui.status(_(b'deleting unwanted changesets\n')) | ||||
|
r43347 | with ui.configoverride(overrides, b'narrow'): | ||
|
r47206 | repair.strip(ui, unfi, tostrip, topic=b'narrow', backup=backup) | ||
|
r36096 | |||
|
r38547 | todelete = [] | ||
|
r48691 | for t, f, size in repo.store.datafiles(): | ||
|
r43347 | if f.startswith(b'data/'): | ||
|
r38547 | file = f[5:-2] | ||
if not newmatch(file): | ||||
todelete.append(f) | ||||
|
r43347 | elif f.startswith(b'meta/'): | ||
|
r38547 | dir = f[5:-13] | ||
r43923 | dirs = sorted(pathutil.dirs({dir})) + [dir] | |||
|
r38547 | include = True | ||
for d in dirs: | ||||
visit = newmatch.visitdir(d) | ||||
if not visit: | ||||
include = False | ||||
break | ||||
|
r43347 | if visit == b'all': | ||
|
r38547 | break | ||
if not include: | ||||
todelete.append(f) | ||||
|
r36096 | |||
|
r38547 | repo.destroying() | ||
|
r36096 | |||
|
r43347 | with repo.transaction(b'narrowing'): | ||
|
r41057 | # Update narrowspec before removing revlogs, so repo won't be | ||
# corrupt in case of crash | ||||
repo.setnarrowpats(newincludes, newexcludes) | ||||
|
r38547 | for f in todelete: | ||
|
r43347 | ui.status(_(b'deleting %s\n') % f) | ||
|
r38547 | util.unlinkpath(repo.svfs.join(f)) | ||
repo.store.markremoved(f) | ||||
|
r36096 | |||
|
r47800 | ui.status(_(b'deleting unwanted files from working copy\n')) | ||
r48404 | with repo.dirstate.parentchange(): | |||
narrowspec.updateworkingcopy(repo, assumeclean=True) | ||||
narrowspec.copytoworkingcopy(repo) | ||||
|
r36096 | |||
|
r38547 | repo.destroyed() | ||
|
r36096 | |||
|
r43346 | |||
def _widen( | ||||
ui, | ||||
repo, | ||||
remote, | ||||
commoninc, | ||||
oldincludes, | ||||
oldexcludes, | ||||
newincludes, | ||||
newexcludes, | ||||
): | ||||
|
r39999 | # for now we assume that if a server has ellipses enabled, we will be | ||
# exchanging ellipses nodes. In future we should add ellipses as a client | ||||
# side requirement (maybe) to distinguish a client is shallow or not and | ||||
# then send that information to server whether we want ellipses or not. | ||||
# Theoretically a non-ellipses repo should be able to use narrow | ||||
# functionality from an ellipses enabled server | ||||
|
r42605 | remotecap = remote.capabilities() | ||
|
r43346 | ellipsesremote = any( | ||
cap in remotecap for cap in wireprototypes.SUPPORTED_ELLIPSESCAP | ||||
) | ||||
|
r42605 | |||
# check whether we are talking to a server which supports old version of | ||||
# ellipses capabilities | ||||
|
r43346 | isoldellipses = ( | ||
ellipsesremote | ||||
and wireprototypes.ELLIPSESCAP1 in remotecap | ||||
and wireprototypes.ELLIPSESCAP not in remotecap | ||||
) | ||||
|
r39999 | |||
|
r36096 | def pullbundle2extraprepare_widen(orig, pullop, kwargs): | ||
orig(pullop, kwargs) | ||||
# The old{in,ex}cludepats have already been set by orig() | ||||
|
r43347 | kwargs[b'includepats'] = newincludes | ||
kwargs[b'excludepats'] = newexcludes | ||||
|
r43346 | |||
wrappedextraprepare = extensions.wrappedfunction( | ||||
|
r43347 | exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen | ||
|
r43346 | ) | ||
|
r36096 | |||
# define a function that narrowbundle2 can call after creating the | ||||
# backup bundle, but before applying the bundle from the server | ||||
def setnewnarrowpats(): | ||||
repo.setnarrowpats(newincludes, newexcludes) | ||||
|
r43346 | |||
|
r36096 | repo.setnewnarrowpats = setnewnarrowpats | ||
|
r39709 | # silence the devel-warning of applying an empty changegroup | ||
|
r43347 | overrides = {(b'devel', b'all-warnings'): False} | ||
|
r36096 | |||
|
r42606 | common = commoninc[0] | ||
|
r41106 | with ui.uninterruptible(): | ||
|
r39999 | if ellipsesremote: | ||
ds = repo.dirstate | ||||
p1, p2 = ds.p1(), ds.p2() | ||||
with ds.parentchange(): | ||||
|
r47771 | ds.setparents(repo.nullid, repo.nullid) | ||
|
r42606 | if isoldellipses: | ||
|
r41926 | with wrappedextraprepare: | ||
|
r42606 | exchange.pull(repo, remote, heads=common) | ||
|
r39999 | else: | ||
|
r42606 | known = [] | ||
if ellipsesremote: | ||||
|
r43346 | known = [ | ||
ctx.node() | ||||
|
r43347 | for ctx in repo.set(b'::%ln', common) | ||
|
r47771 | if ctx.node() != repo.nullid | ||
|
r43346 | ] | ||
|
r40106 | with remote.commandexecutor() as e: | ||
|
r43346 | bundle = e.callcommand( | ||
|
r43347 | b'narrow_widen', | ||
|
r43346 | { | ||
|
r43347 | b'oldincludes': oldincludes, | ||
b'oldexcludes': oldexcludes, | ||||
b'newincludes': newincludes, | ||||
b'newexcludes': newexcludes, | ||||
b'cgversion': b'03', | ||||
b'commonheads': common, | ||||
b'known': known, | ||||
b'ellipses': ellipsesremote, | ||||
|
r43346 | }, | ||
).result() | ||||
|
r40106 | |||
|
r43347 | trmanager = exchange.transactionmanager( | ||
repo, b'widen', remote.url() | ||||
) | ||||
with trmanager, repo.ui.configoverride(overrides, b'widen'): | ||||
|
r43346 | op = bundle2.bundleoperation( | ||
|
r43347 | repo, trmanager.transaction, source=b'widen' | ||
|
r43346 | ) | ||
|
r42606 | # TODO: we should catch error.Abort here | ||
bundle2.processbundle(repo, bundle, op=op) | ||||
if ellipsesremote: | ||||
with ds.parentchange(): | ||||
ds.setparents(p1, p2) | ||||
|
r36096 | |||
r48404 | with repo.transaction(b'widening'), repo.dirstate.parentchange(): | |||
|
r41273 | repo.setnewnarrowpats() | ||
narrowspec.updateworkingcopy(repo) | ||||
narrowspec.copytoworkingcopy(repo) | ||||
|
r36096 | |||
|
r43346 | |||
|
r36096 | # TODO(rdamazio): Make new matcher format and update description | ||
|
r43346 | @command( | ||
|
r43347 | b'tracked', | ||
|
r43346 | [ | ||
|
r43347 | (b'', b'addinclude', [], _(b'new paths to include')), | ||
(b'', b'removeinclude', [], _(b'old paths to no longer include')), | ||||
|
r43346 | ( | ||
|
r43347 | b'', | ||
b'auto-remove-includes', | ||||
|
r43346 | False, | ||
|
r43347 | _(b'automatically choose unused includes to remove'), | ||
|
r43346 | ), | ||
|
r43347 | (b'', b'addexclude', [], _(b'new paths to exclude')), | ||
(b'', b'import-rules', b'', _(b'import narrowspecs from a file')), | ||||
(b'', b'removeexclude', [], _(b'old paths to no longer exclude')), | ||||
|
r43346 | ( | ||
|
r43347 | b'', | ||
b'clear', | ||||
|
r43346 | False, | ||
|
r43347 | _(b'whether to replace the existing narrowspec'), | ||
|
r43346 | ), | ||
( | ||||
|
r43347 | b'', | ||
b'force-delete-local-changes', | ||||
|
r43346 | False, | ||
|
r43347 | _(b'forces deletion of local changes when narrowing'), | ||
), | ||||
( | ||||
b'', | ||||
|
r47206 | b'backup', | ||
True, | ||||
_(b'back up local changes when narrowing'), | ||||
), | ||||
( | ||||
b'', | ||||
|
r43347 | b'update-working-copy', | ||
False, | ||||
_(b'update working copy when the store has changed'), | ||||
|
r43346 | ), | ||
] | ||||
+ commands.remoteopts, | ||||
|
r43347 | _(b'[OPTIONS]... [REMOTE]'), | ||
|
r43346 | inferrepo=True, | ||
|
r43451 | helpcategory=command.CATEGORY_MAINTENANCE, | ||
|
r43346 | ) | ||
|
r36096 | def trackedcmd(ui, repo, remotepath=None, *pats, **opts): | ||
"""show or change the current narrowspec | ||||
With no argument, shows the current narrowspec entries, one per line. Each | ||||
line will be prefixed with 'I' or 'X' for included or excluded patterns, | ||||
respectively. | ||||
The narrowspec is comprised of expressions to match remote files and/or | ||||
directories that should be pulled into your client. | ||||
The narrowspec has *include* and *exclude* expressions, with excludes always | ||||
trumping includes: that is, if a file matches an exclude expression, it will | ||||
be excluded even if it also matches an include expression. | ||||
Excluding files that were never included has no effect. | ||||
Each included or excluded entry is in the format described by | ||||
'hg help patterns'. | ||||
The options allow you to add or remove included and excluded expressions. | ||||
If --clear is specified, then all previous includes and excludes are DROPPED | ||||
and replaced by the new ones specified to --addinclude and --addexclude. | ||||
If --clear is specified without any further options, the narrowspec will be | ||||
empty and will not match any files. | ||||
|
r42146 | |||
|
r43215 | If --auto-remove-includes is specified, then those includes that don't match | ||
any files modified by currently visible local commits (those not shared by | ||||
the remote) will be added to the set of explicitly specified includes to | ||||
remove. | ||||
|
r42146 | --import-rules accepts a path to a file containing rules, allowing you to | ||
add --addinclude, --addexclude rules in bulk. Like the other include and | ||||
exclude switches, the changes are applied immediately. | ||||
|
r36096 | """ | ||
|
r36184 | opts = pycompat.byteskwargs(opts) | ||
|
r45932 | if requirements.NARROW_REQUIREMENT not in repo.requirements: | ||
|
r46733 | raise error.InputError( | ||
|
r43346 | _( | ||
|
r43347 | b'the tracked command is only supported on ' | ||
b'repositories cloned with --narrow' | ||||
|
r43346 | ) | ||
) | ||||
|
r36096 | |||
# Before supporting, decide whether it "hg tracked --clear" should mean | ||||
# tracking no paths or all paths. | ||||
|
r43347 | if opts[b'clear']: | ||
|
r46733 | raise error.InputError(_(b'the --clear option is not yet supported')) | ||
|
r36096 | |||
|
r39471 | # import rules from a file | ||
|
r43347 | newrules = opts.get(b'import_rules') | ||
|
r39471 | if newrules: | ||
try: | ||||
|
r39843 | filepath = os.path.join(encoding.getcwd(), newrules) | ||
|
r39471 | fdata = util.readfile(filepath) | ||
except IOError as inst: | ||||
|
r46733 | raise error.StorageError( | ||
|
r43347 | _(b"cannot read narrowspecs from '%s': %s") | ||
|
r43346 | % (filepath, encoding.strtolocal(inst.strerror)) | ||
) | ||||
includepats, excludepats, profiles = sparse.parseconfig( | ||||
|
r43347 | ui, fdata, b'narrow' | ||
|
r43346 | ) | ||
|
r39471 | if profiles: | ||
|
r46733 | raise error.InputError( | ||
|
r43346 | _( | ||
|
r43347 | b"including other spec files using '%include' " | ||
b"is not supported in narrowspec" | ||||
|
r43346 | ) | ||
) | ||||
|
r43347 | opts[b'addinclude'].extend(includepats) | ||
opts[b'addexclude'].extend(excludepats) | ||||
|
r39471 | |||
|
r43347 | addedincludes = narrowspec.parsepatterns(opts[b'addinclude']) | ||
removedincludes = narrowspec.parsepatterns(opts[b'removeinclude']) | ||||
addedexcludes = narrowspec.parsepatterns(opts[b'addexclude']) | ||||
removedexcludes = narrowspec.parsepatterns(opts[b'removeexclude']) | ||||
autoremoveincludes = opts[b'auto_remove_includes'] | ||||
|
r40462 | |||
|
r43347 | update_working_copy = opts[b'update_working_copy'] | ||
|
r43346 | only_show = not ( | ||
addedincludes | ||||
or removedincludes | ||||
or addedexcludes | ||||
or removedexcludes | ||||
or newrules | ||||
or autoremoveincludes | ||||
or update_working_copy | ||||
) | ||||
|
r40462 | |||
oldincludes, oldexcludes = repo.narrowpats | ||||
# filter the user passed additions and deletions into actual additions and | ||||
# deletions of excludes and includes | ||||
|
r40465 | addedincludes -= oldincludes | ||
removedincludes &= oldincludes | ||||
addedexcludes -= oldexcludes | ||||
removedexcludes &= oldexcludes | ||||
|
r40462 | |||
|
r36096 | widening = addedincludes or removedexcludes | ||
narrowing = removedincludes or addedexcludes | ||||
# Only print the current narrowspec. | ||||
if only_show: | ||||
|
r43347 | ui.pager(b'tracked') | ||
fm = ui.formatter(b'narrow', opts) | ||||
|
r40802 | for i in sorted(oldincludes): | ||
|
r36096 | fm.startitem() | ||
|
r43347 | fm.write(b'status', b'%s ', b'I', label=b'narrow.included') | ||
fm.write(b'pat', b'%s\n', i, label=b'narrow.included') | ||||
|
r40802 | for i in sorted(oldexcludes): | ||
|
r36096 | fm.startitem() | ||
|
r43347 | fm.write(b'status', b'%s ', b'X', label=b'narrow.excluded') | ||
fm.write(b'pat', b'%s\n', i, label=b'narrow.excluded') | ||||
|
r36096 | fm.end() | ||
return 0 | ||||
|
r41072 | if update_working_copy: | ||
r48404 | with repo.wlock(), repo.lock(), repo.transaction( | |||
b'narrow-wc' | ||||
), repo.dirstate.parentchange(): | ||||
|
r41212 | narrowspec.updateworkingcopy(repo) | ||
|
r41265 | narrowspec.copytoworkingcopy(repo) | ||
|
r41072 | return 0 | ||
|
r43215 | if not (widening or narrowing or autoremoveincludes): | ||
|
r43347 | ui.status(_(b"nothing to widen or narrow\n")) | ||
|
r40462 | return 0 | ||
|
r36096 | with repo.wlock(), repo.lock(): | ||
cmdutil.bailifchanged(repo) | ||||
# Find the revisions we have in common with the remote. These will | ||||
# be used for finding local-only changes for narrowing. They will | ||||
# also define the set of revisions to update for widening. | ||||
r47699 | r = urlutil.get_unique_pull_path(b'tracked', repo, ui, remotepath) | |||
url, branches = r | ||||
r47669 | ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(url)) | |||
|
r36096 | remote = hg.peer(repo, opts, url) | ||
|
r40000 | |||
|
r47419 | try: | ||
# check narrow support before doing anything if widening needs to be | ||||
# performed. In future we should also abort if client is ellipses and | ||||
# server does not support ellipses | ||||
if ( | ||||
widening | ||||
and wireprototypes.NARROWCAP not in remote.capabilities() | ||||
): | ||||
raise error.Abort(_(b"server does not support narrow clones")) | ||||
|
r40000 | |||
|
r47419 | commoninc = discovery.findcommonincoming(repo, remote) | ||
|
r36096 | |||
|
r47419 | if autoremoveincludes: | ||
outgoing = discovery.findcommonoutgoing( | ||||
repo, remote, commoninc=commoninc | ||||
) | ||||
ui.status(_(b'looking for unused includes to remove\n')) | ||||
localfiles = set() | ||||
for n in itertools.chain(outgoing.missing, outgoing.excluded): | ||||
localfiles.update(repo[n].files()) | ||||
suggestedremovals = [] | ||||
for include in sorted(oldincludes): | ||||
match = narrowspec.match(repo.root, [include], oldexcludes) | ||||
if not any(match(f) for f in localfiles): | ||||
suggestedremovals.append(include) | ||||
if suggestedremovals: | ||||
for s in suggestedremovals: | ||||
ui.status(b'%s\n' % s) | ||||
if ( | ||||
ui.promptchoice( | ||||
_( | ||||
b'remove these unused includes (yn)?' | ||||
b'$$ &Yes $$ &No' | ||||
) | ||||
|
r43347 | ) | ||
|
r47419 | == 0 | ||
): | ||||
removedincludes.update(suggestedremovals) | ||||
narrowing = True | ||||
else: | ||||
ui.status(_(b'found no unused includes\n')) | ||||
|
r43215 | |||
|
r47419 | if narrowing: | ||
newincludes = oldincludes - removedincludes | ||||
newexcludes = oldexcludes | addedexcludes | ||||
_narrow( | ||||
ui, | ||||
repo, | ||||
remote, | ||||
commoninc, | ||||
oldincludes, | ||||
oldexcludes, | ||||
newincludes, | ||||
newexcludes, | ||||
opts[b'force_delete_local_changes'], | ||||
opts[b'backup'], | ||||
) | ||||
# _narrow() updated the narrowspec and _widen() below needs to | ||||
# use the updated values as its base (otherwise removed includes | ||||
# and addedexcludes will be lost in the resulting narrowspec) | ||||
oldincludes = newincludes | ||||
oldexcludes = newexcludes | ||||
|
r36096 | |||
|
r47419 | if widening: | ||
newincludes = oldincludes | addedincludes | ||||
newexcludes = oldexcludes - removedexcludes | ||||
_widen( | ||||
ui, | ||||
repo, | ||||
remote, | ||||
commoninc, | ||||
oldincludes, | ||||
oldexcludes, | ||||
newincludes, | ||||
newexcludes, | ||||
) | ||||
finally: | ||||
remote.close() | ||||
|
r36096 | |||
return 0 | ||||