Mercurial > hg
changeset 51183:028498b04a84
branching: merge with stable
This recreates `37b52b938579` right as a `hg branch --rev 5b186ba40001` screwed
up the content.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Fri, 15 Dec 2023 11:08:41 +0100 |
parents | 933551630b0d (diff) 1486d8c63f64 (current diff) |
children | a9f96e809c2a |
files | |
diffstat | 23 files changed, 539 insertions(+), 362 deletions(-) [+] |
line wrap: on
line diff
--- a/mercurial/cmdutil.py Thu Dec 07 03:49:48 2023 +0100 +++ b/mercurial/cmdutil.py Fri Dec 15 11:08:41 2023 +0100 @@ -2381,8 +2381,19 @@ full=False, ) ): + entry = dirstate.get_entry(f) + # We don't want to even attmpt to add back files that have been removed + # It would lead to a misleading message saying we're adding the path, + # and can also lead to file/dir conflicts when attempting to add it. + removed = entry and entry.removed exact = match.exact(f) - if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f): + if ( + exact + or not explicitonly + and f not in wctx + and repo.wvfs.lexists(f) + and not removed + ): if cca: cca(f) names.append(f)
--- a/mercurial/commands.py Thu Dec 07 03:49:48 2023 +0100 +++ b/mercurial/commands.py Fri Dec 15 11:08:41 2023 +0100 @@ -29,6 +29,7 @@ copies, debugcommands as debugcommandsmod, destutil, + diffutil, discovery, encoding, error, @@ -2655,7 +2656,7 @@ if change: repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn') ctx2 = logcmdutil.revsingle(repo, change, None) - ctx1 = logcmdutil.diff_parent(ctx2) + ctx1 = diffutil.diff_parent(ctx2) elif from_rev or to_rev: repo = scmutil.unhidehashlikerevs( repo, [from_rev] + [to_rev], b'nowarn'
--- a/mercurial/diffutil.py Thu Dec 07 03:49:48 2023 +0100 +++ b/mercurial/diffutil.py Fri Dec 15 11:08:41 2023 +0100 @@ -16,6 +16,7 @@ ) from .i18n import _ +from .node import nullrev from . import ( mdiff, @@ -155,3 +156,35 @@ ) return mdiff.diffopts(**pycompat.strkwargs(buildopts)) + + +def diff_parent(ctx): + """get the context object to use as parent when diffing + + + If diff.merge is enabled, an overlayworkingctx of the auto-merged parents will be returned. + """ + repo = ctx.repo() + if repo.ui.configbool(b"diff", b"merge") and ctx.p2().rev() != nullrev: + # avoid circular import + from . import ( + context, + merge, + ) + + wctx = context.overlayworkingctx(repo) + wctx.setbase(ctx.p1()) + with repo.ui.configoverride( + { + ( + b"ui", + b"forcemerge", + ): b"internal:merge3-lie-about-conflicts", + }, + b"merge-diff", + ): + with repo.ui.silent(): + merge.merge(ctx.p2(), wc=wctx) + return wctx + else: + return ctx.p1()
--- a/mercurial/dirstate.py Thu Dec 07 03:49:48 2023 +0100 +++ b/mercurial/dirstate.py Fri Dec 15 11:08:41 2023 +0100 @@ -408,16 +408,6 @@ """ return self._changing_level > 0 - def pendingparentchange(self): - return self.is_changing_parent() - - def is_changing_parent(self): - """Returns true if the dirstate is in the middle of a set of changes - that modify the dirstate parent. - """ - self._ui.deprecwarn(b"dirstate.is_changing_parents", b"6.5") - return self.is_changing_parents - @property def is_changing_parents(self): """Returns true if the dirstate is in the middle of a set of changes
--- a/mercurial/extensions.py Thu Dec 07 03:49:48 2023 +0100 +++ b/mercurial/extensions.py Fri Dec 15 11:08:41 2023 +0100 @@ -625,9 +625,8 @@ def __init__(self, container, funcname, wrapper): assert callable(wrapper) if not isinstance(funcname, str): - msg = b"pass wrappedfunction target name as `str`, not `bytes`" - util.nouideprecwarn(msg, b"6.6", stacklevel=2) - funcname = pycompat.sysstr(funcname) + msg = b"wrappedfunction target name should be `str`, not `bytes`" + raise TypeError(msg) self._container = container self._funcname = funcname self._wrapper = wrapper @@ -675,9 +674,8 @@ assert callable(wrapper) if not isinstance(funcname, str): - msg = b"pass wrapfunction target name as `str`, not `bytes`" - util.nouideprecwarn(msg, b"6.6", stacklevel=2) - funcname = pycompat.sysstr(funcname) + msg = b"wrapfunction target name should be `str`, not `bytes`" + raise TypeError(msg) origfn = getattr(container, funcname) assert callable(origfn)
--- a/mercurial/localrepo.py Thu Dec 07 03:49:48 2023 +0100 +++ b/mercurial/localrepo.py Fri Dec 15 11:08:41 2023 +0100 @@ -2921,17 +2921,7 @@ unfi = self.unfiltered() - if full: - msg = ( - "`full` argument for `repo.updatecaches` is deprecated\n" - "(use `caches=repository.CACHE_ALL` instead)" - ) - self.ui.deprecwarn(msg, b"5.9") - caches = repository.CACHES_ALL - if full == b"post-clone": - caches = repository.CACHES_POST_CLONE - caches = repository.CACHES_ALL - elif caches is None: + if caches is None: caches = repository.CACHES_DEFAULT if repository.CACHE_BRANCHMAP_SERVED in caches:
--- a/mercurial/logcmdutil.py Thu Dec 07 03:49:48 2023 +0100 +++ b/mercurial/logcmdutil.py Fri Dec 15 11:08:41 2023 +0100 @@ -11,18 +11,18 @@ import posixpath from .i18n import _ -from .node import nullrev, wdirrev +from .node import wdirrev from .thirdparty import attr from . import ( dagop, + diffutil, error, formatter, graphmod, match as matchmod, mdiff, - merge, patch, pathutil, pycompat, @@ -69,36 +69,7 @@ return limit -def diff_parent(ctx): - """get the context object to use as parent when diffing - - - If diff.merge is enabled, an overlayworkingctx of the auto-merged parents will be returned. - """ - repo = ctx.repo() - if repo.ui.configbool(b"diff", b"merge") and ctx.p2().rev() != nullrev: - # avoid cycle context -> subrepo -> cmdutil -> logcmdutil - from . import context - - wctx = context.overlayworkingctx(repo) - wctx.setbase(ctx.p1()) - with repo.ui.configoverride( - { - ( - b"ui", - b"forcemerge", - ): b"internal:merge3-lie-about-conflicts", - }, - b"merge-diff", - ): - with repo.ui.silent(): - merge.merge(ctx.p2(), wc=wctx) - return wctx - else: - return ctx.p1() - - -def diffordiffstat( +def get_diff_chunks( ui, repo, diffopts, @@ -107,14 +78,10 @@ match, changes=None, stat=False, - fp=None, - graphwidth=0, prefix=b'', root=b'', - listsubrepos=False, hunksfilterfn=None, ): - '''show diff or diffstat.''' if root: relroot = pathutil.canonpath(repo.root, repo.getcwd(), root) else: @@ -159,14 +126,11 @@ if stat: diffopts = diffopts.copy(context=0, noprefix=False) - width = 80 - if not ui.plain(): - width = ui.termwidth() - graphwidth # If an explicit --root was given, don't respect ui.relative-paths if not relroot: pathfn = compose(scmutil.getuipathfn(repo), pathfn) - chunks = ctx2.diff( + return ctx2.diff( ctx1, match, changes, @@ -176,6 +140,45 @@ hunksfilterfn=hunksfilterfn, ) + +def diffordiffstat( + ui, + repo, + diffopts, + ctx1, + ctx2, + match, + changes=None, + stat=False, + fp=None, + graphwidth=0, + prefix=b'', + root=b'', + listsubrepos=False, + hunksfilterfn=None, +): + '''show diff or diffstat.''' + + chunks = get_diff_chunks( + ui, + repo, + diffopts, + ctx1, + ctx2, + match, + changes=changes, + stat=stat, + prefix=prefix, + root=root, + hunksfilterfn=hunksfilterfn, + ) + + if stat: + diffopts = diffopts.copy(context=0, noprefix=False) + width = 80 + if not ui.plain(): + width = ui.termwidth() - graphwidth + if fp is not None or ui.canwritewithoutlabels(): out = fp or ui if stat: @@ -241,7 +244,7 @@ ui, ctx.repo(), diffopts, - diff_parent(ctx), + diffutil.diff_parent(ctx), ctx, match=self._makefilematcher(ctx), stat=stat, @@ -249,6 +252,33 @@ hunksfilterfn=self._makehunksfilter(ctx), ) + def getdiffstats(self, ui, ctx, diffopts, stat=False): + chunks = get_diff_chunks( + ui, + ctx.repo(), + diffopts, + diffutil.diff_parent(ctx), + ctx, + match=self._makefilematcher(ctx), + stat=stat, + hunksfilterfn=self._makehunksfilter(ctx), + ) + + diffdata = [] + for filename, additions, removals, binary in patch.diffstatdata( + util.iterlines(chunks) + ): + diffdata.append( + { + b"name": filename, + b"additions": additions, + b"removals": removals, + b"binary": binary, + } + ) + + return diffdata + def changesetlabels(ctx): labels = [b'log.changeset', b'changeset.%s' % ctx.phasestr()] @@ -525,9 +555,10 @@ ) if self._includestat or b'diffstat' in datahint: - self.ui.pushbuffer() - self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True) - fm.data(diffstat=self.ui.popbuffer()) + data = self._differ.getdiffstats( + self.ui, ctx, self._diffopts, stat=True + ) + fm.data(diffstat=fm.formatlist(data, name=b'diffstat')) if self._includediff or b'diff' in datahint: self.ui.pushbuffer() self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
--- a/mercurial/pycompat.py Thu Dec 07 03:49:48 2023 +0100 +++ b/mercurial/pycompat.py Fri Dec 15 11:08:41 2023 +0100 @@ -12,7 +12,6 @@ import builtins import codecs import concurrent.futures as futures -import functools import getopt import http.client as httplib import http.cookiejar as cookielib @@ -352,26 +351,11 @@ return sysbytes(doc) -def _wrapattrfunc(f): - @functools.wraps(f) - def w(object, name, *args): - if isinstance(name, bytes): - from . import util - - msg = b'function "%s" take `str` as argument, not `bytes`' - fname = f.__name__.encode('ascii') - msg %= fname - util.nouideprecwarn(msg, b"6.6", stacklevel=2) - return f(object, sysstr(name), *args) - - return w - - # these wrappers are automagically imported by hgloader -delattr = _wrapattrfunc(builtins.delattr) -getattr = _wrapattrfunc(builtins.getattr) -hasattr = _wrapattrfunc(builtins.hasattr) -setattr = _wrapattrfunc(builtins.setattr) +delattr = builtins.delattr +getattr = builtins.getattr +hasattr = builtins.hasattr +setattr = builtins.setattr xrange = builtins.range unicode = str @@ -386,7 +370,7 @@ return builtins.open(name, sysstr(mode), buffering, encoding) -safehasattr = _wrapattrfunc(builtins.hasattr) +safehasattr = builtins.hasattr def _getoptbwrapper(
--- a/mercurial/revlog.py Thu Dec 07 03:49:48 2023 +0100 +++ b/mercurial/revlog.py Fri Dec 15 11:08:41 2023 +0100 @@ -1391,194 +1391,6 @@ self._load_inner(chunk_cache) self._concurrencychecker = concurrencychecker - @property - def _generaldelta(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.delta_config.general_delta", b"6.6", stacklevel=2 - ) - return self.delta_config.general_delta - - @property - def _checkambig(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.data_config.checkambig", b"6.6", stacklevel=2 - ) - return self.data_config.check_ambig - - @property - def _mmaplargeindex(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.data_config.mmap_large_index", b"6.6", stacklevel=2 - ) - return self.data_config.mmap_large_index - - @property - def _censorable(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.feature_config.censorable", b"6.6", stacklevel=2 - ) - return self.feature_config.censorable - - @property - def _chunkcachesize(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.data_config.chunk_cache_size", b"6.6", stacklevel=2 - ) - return self.data_config.chunk_cache_size - - @property - def _maxchainlen(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.delta_config.max_chain_len", b"6.6", stacklevel=2 - ) - return self.delta_config.max_chain_len - - @property - def _deltabothparents(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.delta_config.delta_both_parents", b"6.6", stacklevel=2 - ) - return self.delta_config.delta_both_parents - - @property - def _candidate_group_chunk_size(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.delta_config.candidate_group_chunk_size", - b"6.6", - stacklevel=2, - ) - return self.delta_config.candidate_group_chunk_size - - @property - def _debug_delta(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.delta_config.debug_delta", b"6.6", stacklevel=2 - ) - return self.delta_config.debug_delta - - @property - def _compengine(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.feature_config.compression_engine", - b"6.6", - stacklevel=2, - ) - return self.feature_config.compression_engine - - @property - def upperboundcomp(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.delta_config.upper_bound_comp", - b"6.6", - stacklevel=2, - ) - return self.delta_config.upper_bound_comp - - @property - def _compengineopts(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.feature_config.compression_engine_options", - b"6.6", - stacklevel=2, - ) - return self.feature_config.compression_engine_options - - @property - def _maxdeltachainspan(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.delta_config.max_deltachain_span", b"6.6", stacklevel=2 - ) - return self.delta_config.max_deltachain_span - - @property - def _withsparseread(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.data_config.with_sparse_read", b"6.6", stacklevel=2 - ) - return self.data_config.with_sparse_read - - @property - def _sparserevlog(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.delta_config.sparse_revlog", b"6.6", stacklevel=2 - ) - return self.delta_config.sparse_revlog - - @property - def hassidedata(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.feature_config.has_side_data", b"6.6", stacklevel=2 - ) - return self.feature_config.has_side_data - - @property - def _srdensitythreshold(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.data_config.sr_density_threshold", - b"6.6", - stacklevel=2, - ) - return self.data_config.sr_density_threshold - - @property - def _srmingapsize(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.data_config.sr_min_gap_size", b"6.6", stacklevel=2 - ) - return self.data_config.sr_min_gap_size - - @property - def _compute_rank(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.feature_config.compute_rank", b"6.6", stacklevel=2 - ) - return self.feature_config.compute_rank - - @property - def canonical_parent_order(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.feature_config.canonical_parent_order", - b"6.6", - stacklevel=2, - ) - return self.feature_config.canonical_parent_order - - @property - def _lazydelta(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.delta_config.lazy_delta", b"6.6", stacklevel=2 - ) - return self.delta_config.lazy_delta - - @property - def _lazydeltabase(self): - """temporary compatibility proxy""" - util.nouideprecwarn( - b"use revlog.delta_config.lazy_delta_base", b"6.6", stacklevel=2 - ) - return self.delta_config.lazy_delta_base - def _init_opts(self): """process options (from above/config) to setup associated default revlog mode
--- a/mercurial/statprof.py Thu Dec 07 03:49:48 2023 +0100 +++ b/mercurial/statprof.py Fri Dec 15 11:08:41 2023 +0100 @@ -384,7 +384,7 @@ time = sample.time stack = sample.stack sites = [ - b'\1'.join([s.path, b'%d' % s.lineno, s.function]) + b'\1'.join([s.path, b'%d' % s.lineno or -1, s.function]) for s in stack ] file.write(b"%d\0%s\n" % (time, b'\0'.join(sites))) @@ -663,7 +663,7 @@ count / relevant_samples * 100, pycompat.fsencode(parent.filename()), pycompat.sysbytes(parent.function), - parent.lineno, + parent.lineno or -1, pycompat.sysbytes(parent.getsource(50)), ) ) @@ -705,7 +705,7 @@ b' %6.2f%% line %s: %s\n' % ( count / relevant_samples * 100, - child.lineno, + child.lineno or -1, pycompat.sysbytes(child.getsource(50)), ) ) @@ -865,7 +865,7 @@ stack.append( ( pycompat.sysstr(frame.path), - frame.lineno, + frame.lineno or -1, pycompat.sysstr(frame.function), ) ) @@ -954,7 +954,10 @@ ( ( '%s:%d' - % (simplifypath(pycompat.sysstr(frame.path)), frame.lineno), + % ( + simplifypath(pycompat.sysstr(frame.path)), + frame.lineno or -1, + ), pycompat.sysstr(frame.function), ) for frame in sample.stack
--- a/mercurial/templatekw.py Thu Dec 07 03:49:48 2023 +0100 +++ b/mercurial/templatekw.py Fri Dec 15 11:08:41 2023 +0100 @@ -270,7 +270,7 @@ ui = context.resource(mapping, b'ui') ctx = context.resource(mapping, b'ctx') diffopts = diffutil.diffallopts(ui, {b'noprefix': False}) - diff = ctx.diff(opts=diffopts) + diff = ctx.diff(diffutil.diff_parent(ctx), opts=diffopts) stats = patch.diffstatdata(util.iterlines(diff)) maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats) return b'%d: +%d/-%d' % (len(stats), adds, removes)
--- a/mercurial/upgrade_utils/actions.py Thu Dec 07 03:49:48 2023 +0100 +++ b/mercurial/upgrade_utils/actions.py Fri Dec 15 11:08:41 2023 +0100 @@ -5,6 +5,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. +import random from ..i18n import _ from .. import ( @@ -409,9 +410,17 @@ def fromrepo(repo): # Mercurial 4.0 changed changelogs to not use delta chains. Search for # changelogs with deltas. - cl = repo.changelog + cl = repo.unfiltered().changelog + if len(cl) <= 1000: + some_rev = list(cl) + else: + # do a random sampling to speeds things up Scanning the whole + # repository can get really slow on bigger repo. + some_rev = sorted( + {random.randint(0, len(cl) - 1) for x in range(1000)} + ) chainbase = cl.chainbase - return all(rev == chainbase(rev) for rev in cl) + return all(rev == chainbase(rev) for rev in some_rev) @staticmethod def fromconfig(repo):
--- a/mercurial/utils/urlutil.py Thu Dec 07 03:49:48 2023 +0100 +++ b/mercurial/utils/urlutil.py Fri Dec 15 11:08:41 2023 +0100 @@ -14,7 +14,6 @@ error, pycompat, urllibcompat, - util, ) from . import ( @@ -680,8 +679,7 @@ """ if isinstance(attr, bytes): msg = b'pathsuboption take `str` as "attr" argument, not `bytes`' - util.nouideprecwarn(msg, b"6.6", stacklevel=2) - attr = attr.decode('ascii') + raise TypeError(msg) def register(func): _pathsuboptions[option] = (attr, func) @@ -923,14 +921,6 @@ new._setup_url(self._pushloc) return new - def pushloc(self): - """compatibility layer for the deprecated attributes""" - from .. import util # avoid a cycle - - msg = "don't use path.pushloc, use path.get_push_variant()" - util.nouideprecwarn(msg, b"6.5") - return self._pushloc - def _validate_path(self): # When given a raw location but not a symbolic name, validate the # location is valid.
--- a/rust/hg-core/src/operations/mod.rs Thu Dec 07 03:49:48 2023 +0100 +++ b/rust/hg-core/src/operations/mod.rs Fri Dec 15 11:08:41 2023 +0100 @@ -5,6 +5,8 @@ mod cat; mod debugdata; mod list_tracked_files; +mod status_rev_rev; pub use cat::{cat, CatOutput}; pub use debugdata::{debug_data, DebugDataKind}; pub use list_tracked_files::{list_rev_tracked_files, FilesForRev}; +pub use status_rev_rev::{status_rev_rev_no_copies, DiffStatus, StatusRevRev};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hg-core/src/operations/status_rev_rev.rs Fri Dec 15 11:08:41 2023 +0100 @@ -0,0 +1,89 @@ +use crate::errors::HgError; +use crate::matchers::Matcher; +use crate::repo::Repo; +use crate::revlog::manifest::Manifest; +use crate::utils::filter_map_results; +use crate::utils::hg_path::HgPath; +use crate::utils::merge_join_results_by; + +use crate::Revision; + +use itertools::EitherOrBoth; + +#[derive(Debug, Copy, Clone)] +pub enum DiffStatus { + Removed, + Added, + Matching, + Modified, +} + +pub struct StatusRevRev { + manifest1: Manifest, + manifest2: Manifest, + narrow_matcher: Box<dyn Matcher>, +} + +fn manifest_for_rev(repo: &Repo, rev: Revision) -> Result<Manifest, HgError> { + repo.manifest_for_rev(rev.into()).map_err(|e| { + HgError::corrupted(format!( + "manifest lookup failed for revision {}: {}", + rev, e + )) + }) +} + +pub fn status_rev_rev_no_copies( + repo: &Repo, + rev1: Revision, + rev2: Revision, + narrow_matcher: Box<dyn Matcher>, +) -> Result<StatusRevRev, HgError> { + let manifest1 = manifest_for_rev(repo, rev1)?; + let manifest2 = manifest_for_rev(repo, rev2)?; + Ok(StatusRevRev { + manifest1, + manifest2, + narrow_matcher, + }) +} + +impl StatusRevRev { + pub fn iter( + &self, + ) -> impl Iterator<Item = Result<(&HgPath, DiffStatus), HgError>> { + let iter1 = self.manifest1.iter(); + let iter2 = self.manifest2.iter(); + + let merged = + merge_join_results_by(iter1, iter2, |i1, i2| i1.path.cmp(i2.path)); + + filter_map_results(merged, |entry| { + let (path, status) = match entry { + EitherOrBoth::Left(entry) => { + let path = entry.path; + (path, DiffStatus::Removed) + } + EitherOrBoth::Right(entry) => { + let path = entry.path; + (path, DiffStatus::Added) + } + EitherOrBoth::Both(entry1, entry2) => { + let path = entry1.path; + if entry1.node_id().unwrap() == entry2.node_id().unwrap() + && entry1.flags == entry2.flags + { + (path, DiffStatus::Matching) + } else { + (path, DiffStatus::Modified) + } + } + }; + Ok(if self.narrow_matcher.matches(path) { + Some((path, status)) + } else { + None + }) + }) + } +}
--- a/rust/hg-core/src/utils.rs Thu Dec 07 03:49:48 2023 +0100 +++ b/rust/hg-core/src/utils.rs Fri Dec 15 11:08:41 2023 +0100 @@ -11,7 +11,10 @@ use crate::utils::hg_path::HgPath; use im_rc::ordmap::DiffItem; use im_rc::ordmap::OrdMap; +use itertools::EitherOrBoth; +use itertools::Itertools; use std::cell::Cell; +use std::cmp::Ordering; use std::fmt; use std::{io::Write, ops::Deref}; @@ -499,6 +502,43 @@ }) } +/// Like `itertools::merge_join_by`, but merges fallible iterators. +/// +/// The callback is only used for Ok values. Errors are passed through as-is. +/// Errors compare less than Ok values, which makes the error handling +/// conservative. +pub fn merge_join_results_by<'a, I1, I2, F, A, B, E>( + iter1: I1, + iter2: I2, + f: F, +) -> impl Iterator<Item = Result<EitherOrBoth<A, B>, E>> + 'a +where + I1: Iterator<Item = Result<A, E>> + 'a, + I2: Iterator<Item = Result<B, E>> + 'a, + F: FnMut(&A, &B) -> Ordering + 'a, +{ + let mut g = f; + iter1 + .merge_join_by(iter2, move |i1, i2| match i1 { + Err(_) => Ordering::Less, + Ok(i1) => match i2 { + Err(_) => Ordering::Greater, + Ok(i2) => g(i1, i2), + }, + }) + .map(|result| match result { + EitherOrBoth::Left(Err(e)) => Err(e), + EitherOrBoth::Right(Err(e)) => Err(e), + EitherOrBoth::Both(Err(e), _) => Err(e), + EitherOrBoth::Both(_, Err(e)) => Err(e), + EitherOrBoth::Left(Ok(v)) => Ok(EitherOrBoth::Left(v)), + EitherOrBoth::Right(Ok(v)) => Ok(EitherOrBoth::Right(v)), + EitherOrBoth::Both(Ok(v1), Ok(v2)) => { + Ok(EitherOrBoth::Both(v1, v2)) + } + }) +} + /// Force the global rayon threadpool to not exceed 16 concurrent threads /// unless the user has specified a value. /// This is a stop-gap measure until we figure out why using more than 16
--- a/rust/rhg/src/commands/status.rs Thu Dec 07 03:49:48 2023 +0100 +++ b/rust/rhg/src/commands/status.rs Fri Dec 15 11:08:41 2023 +0100 @@ -30,12 +30,15 @@ use hg::utils::hg_path::{hg_path_to_path_buf, HgPath}; use hg::DirstateStatus; use hg::PatternFileWarning; +use hg::Revision; use hg::StatusError; use hg::StatusOptions; use hg::{self, narrow, sparse}; use log::info; use rayon::prelude::*; +use std::borrow::Cow; use std::io; +use std::mem::take; use std::path::PathBuf; pub const HELP_TEXT: &str = " @@ -140,6 +143,38 @@ .action(clap::ArgAction::SetTrue) .long("verbose"), ) + .arg( + Arg::new("rev") + .help("show difference from/to revision") + .long("rev") + .num_args(1) + .action(clap::ArgAction::Append) + .value_name("REV"), + ) +} + +fn parse_revpair( + repo: &Repo, + revs: Option<Vec<String>>, +) -> Result<Option<(Revision, Revision)>, CommandError> { + let revs = match revs { + None => return Ok(None), + Some(revs) => revs, + }; + if revs.is_empty() { + return Ok(None); + } + if revs.len() != 2 { + return Err(CommandError::unsupported("expected 0 or 2 --rev flags")); + } + + let rev1 = &revs[0]; + let rev2 = &revs[1]; + let rev1 = hg::revset::resolve_single(rev1, repo) + .map_err(|e| (e, rev1.as_str()))?; + let rev2 = hg::revset::resolve_single(rev2, repo) + .map_err(|e| (e, rev2.as_str()))?; + Ok(Some((rev1, rev2))) } /// Pure data type allowing the caller to specify file states to display @@ -229,6 +264,7 @@ let config = invocation.config; let args = invocation.subcommand_args; + let revs = args.get_many::<String>("rev"); let print0 = args.get_flag("print0"); let verbose = args.get_flag("verbose") || config.get_bool(b"ui", b"verbose")? @@ -262,6 +298,7 @@ || config.get_bool(b"ui", b"statuscopies")?; let repo = invocation.repo?; + let revpair = parse_revpair(repo, revs.map(|i| i.cloned().collect()))?; if verbose && has_unfinished_state(repo)? { return Err(CommandError::unsupported( @@ -285,13 +322,37 @@ type StatusResult<'a> = Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>; + let relative_status = config + .get_option(b"commands", b"status.relative")? + .expect("commands.status.relative should have a default value"); + + let relativize_paths = relative_status || { + // See in Python code with `getuipathfn` usage in `commands.py`. + let legacy_relative_behavior = args.contains_id("file"); + match relative_paths(invocation.config)? { + RelativePaths::Legacy => legacy_relative_behavior, + RelativePaths::Bool(v) => v, + } + }; + + let mut output = DisplayStatusPaths { + ui, + no_status, + relativize: if relativize_paths { + Some(RelativizePaths::new(repo)?) + } else { + None + }, + print0, + }; + let after_status = |res: StatusResult| -> Result<_, CommandError> { let (mut ds_status, pattern_warnings) = res?; for warning in pattern_warnings { ui.write_stderr(&format_pattern_file_warning(&warning, repo))?; } - for (path, error) in ds_status.bad { + for (path, error) in take(&mut ds_status.bad) { let error = match error { hg::BadMatch::OsError(code) => { std::io::Error::from_raw_os_error(code).to_string() @@ -322,8 +383,7 @@ })?; let working_directory_vfs = repo.working_directory_vfs(); let store_vfs = repo.store_vfs(); - let res: Vec<_> = ds_status - .unsure + let res: Vec<_> = take(&mut ds_status.unsure) .into_par_iter() .map(|to_check| { // The compiler seems to get a bit confused with complex @@ -370,55 +430,12 @@ } } - let relative_status = config - .get_option(b"commands", b"status.relative")? - .expect("commands.status.relative should have a default value"); - - let relativize_paths = relative_status || { - // See in Python code with `getuipathfn` usage in `commands.py`. - let legacy_relative_behavior = args.contains_id("file"); - match relative_paths(invocation.config)? { - RelativePaths::Legacy => legacy_relative_behavior, - RelativePaths::Bool(v) => v, - } - }; - - let output = DisplayStatusPaths { - ui, - no_status, - relativize: if relativize_paths { - Some(RelativizePaths::new(repo)?) - } else { - None - }, - print0, - }; - if display_states.modified { - output.display(b"M ", "status.modified", ds_status.modified)?; - } - if display_states.added { - output.display(b"A ", "status.added", ds_status.added)?; - } - if display_states.removed { - output.display(b"R ", "status.removed", ds_status.removed)?; - } - if display_states.deleted { - output.display(b"! ", "status.deleted", ds_status.deleted)?; - } - if display_states.unknown { - output.display(b"? ", "status.unknown", ds_status.unknown)?; - } - if display_states.ignored { - output.display(b"I ", "status.ignored", ds_status.ignored)?; - } - if display_states.clean { - output.display(b"C ", "status.clean", ds_status.clean)?; - } - let dirstate_write_needed = ds_status.dirty; let filesystem_time_at_status_start = ds_status.filesystem_time_at_status_start; + output.output(display_states, ds_status)?; + Ok(( fixup, dirstate_write_needed, @@ -426,6 +443,57 @@ )) }; let (narrow_matcher, narrow_warnings) = narrow::matcher(repo)?; + + match revpair { + Some((rev1, rev2)) => { + let mut ds_status = DirstateStatus::default(); + if list_copies { + return Err(CommandError::unsupported( + "status --rev --rev with copy information is not implemented yet", + )); + } + + let stat = hg::operations::status_rev_rev_no_copies( + repo, + rev1, + rev2, + narrow_matcher, + )?; + for entry in stat.iter() { + let (path, status) = entry?; + let path = StatusPath { + path: Cow::Borrowed(path), + copy_source: None, + }; + match status { + hg::operations::DiffStatus::Removed => { + if display_states.removed { + ds_status.removed.push(path) + } + } + hg::operations::DiffStatus::Added => { + if display_states.added { + ds_status.added.push(path) + } + } + hg::operations::DiffStatus::Modified => { + if display_states.modified { + ds_status.modified.push(path) + } + } + hg::operations::DiffStatus::Matching => { + if display_states.clean { + ds_status.clean.push(path) + } + } + } + } + output.output(display_states, ds_status)?; + return Ok(()); + } + None => (), + } + let (sparse_matcher, sparse_warnings) = sparse::matcher(repo)?; let matcher = match (repo.has_narrow(), repo.has_sparse()) { (true, true) => { @@ -628,6 +696,35 @@ } Ok(()) } + + fn output( + &mut self, + display_states: DisplayStates, + ds_status: DirstateStatus, + ) -> Result<(), CommandError> { + if display_states.modified { + self.display(b"M ", "status.modified", ds_status.modified)?; + } + if display_states.added { + self.display(b"A ", "status.added", ds_status.added)?; + } + if display_states.removed { + self.display(b"R ", "status.removed", ds_status.removed)?; + } + if display_states.deleted { + self.display(b"! ", "status.deleted", ds_status.deleted)?; + } + if display_states.unknown { + self.display(b"? ", "status.unknown", ds_status.unknown)?; + } + if display_states.ignored { + self.display(b"I ", "status.ignored", ds_status.ignored)?; + } + if display_states.clean { + self.display(b"C ", "status.clean", ds_status.clean)?; + } + Ok(()) + } } /// Outcome of the additional check for an ambiguous tracked file
--- a/rust/rhg/src/main.rs Thu Dec 07 03:49:48 2023 +0100 +++ b/rust/rhg/src/main.rs Fri Dec 15 11:08:41 2023 +0100 @@ -524,13 +524,20 @@ std::process::exit(exit_code(&result, use_detailed_exit_code)) } +mod commands { + pub mod cat; + pub mod config; + pub mod debugdata; + pub mod debugignorerhg; + pub mod debugrequirements; + pub mod debugrhgsparse; + pub mod files; + pub mod root; + pub mod status; +} + macro_rules! subcommands { ($( $command: ident )+) => { - mod commands { - $( - pub mod $command; - )+ - } fn add_subcommand_args(app: clap::Command) -> clap::Command { app
--- a/setup.py Thu Dec 07 03:49:48 2023 +0100 +++ b/setup.py Fri Dec 15 11:08:41 2023 +0100 @@ -221,6 +221,9 @@ self.cmd = cmd self.env = env + def __repr__(self): + return f"<hgcommand cmd={self.cmd} env={self.env}>" + def run(self, args): cmd = self.cmd + args returncode, out, err = runcmd(cmd, self.env) @@ -295,9 +298,15 @@ if attempt(hgcmd + check_cmd, hgenv): return hgcommand(hgcmd, hgenv) - # Fall back to trying the local hg installation. + # Fall back to trying the local hg installation (pure python) + repo_hg = os.path.join(os.path.dirname(__file__), 'hg') hgenv = localhgenv() - hgcmd = [sys.executable, 'hg'] + hgcmd = [sys.executable, repo_hg] + if attempt(hgcmd + check_cmd, hgenv): + return hgcommand(hgcmd, hgenv) + # Fall back to trying the local hg installation (whatever we can) + hgenv = localhgenv(pure_python=False) + hgcmd = [sys.executable, repo_hg] if attempt(hgcmd + check_cmd, hgenv): return hgcommand(hgcmd, hgenv) @@ -319,17 +328,18 @@ return None -def localhgenv(): +def localhgenv(pure_python=True): """Get an environment dictionary to use for invoking or importing mercurial from the local repository.""" # Execute hg out of this directory with a custom environment which takes # care to not use any hgrc files and do no localization. env = { - 'HGMODULEPOLICY': 'py', 'HGRCPATH': '', 'LANGUAGE': 'C', 'PATH': '', } # make pypi modules that use os.environ['PATH'] happy + if pure_python: + env['HGMODULEPOLICY'] = 'py' if 'LD_LIBRARY_PATH' in os.environ: env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH'] if 'SystemRoot' in os.environ: @@ -1821,5 +1831,5 @@ 'welcome': 'contrib/packaging/macosx/Welcome.html', }, }, - **extra + **extra, )
--- a/tests/test-log.t Thu Dec 07 03:49:48 2023 +0100 +++ b/tests/test-log.t Fri Dec 15 11:08:41 2023 +0100 @@ -2001,6 +2001,8 @@ @@ -0,0 +1,1 @@ +b + $ hg log -r 3 -T'{diffstat}\n' + 2: +2/-1 Test that diff.merge is respected (file b was added on one side and and therefore merged cleanly) @@ -2021,6 +2023,9 @@ -b +c + $ hg log -r 3 -T'{diffstat}\n' --config diff.merge=yes + 1: +1/-1 + $ cd .. 'hg log -r rev fn' when last(filelog(fn)) != rev
--- a/tests/test-status-rev.t Thu Dec 07 03:49:48 2023 +0100 +++ b/tests/test-status-rev.t Fri Dec 15 11:08:41 2023 +0100 @@ -88,6 +88,33 @@ Status between first and second commit. Should ignore dirstate status. + $ hg status -marc --rev 0 --rev 1 --config rhg.on-unsupported=abort + M content1_content2_content1-tracked + M content1_content2_content1-untracked + M content1_content2_content2-tracked + M content1_content2_content2-untracked + M content1_content2_content3-tracked + M content1_content2_content3-untracked + M content1_content2_missing-tracked + M content1_content2_missing-untracked + A missing_content2_content2-tracked + A missing_content2_content2-untracked + A missing_content2_content3-tracked + A missing_content2_content3-untracked + A missing_content2_missing-tracked + A missing_content2_missing-untracked + R content1_missing_content1-tracked + R content1_missing_content1-untracked + R content1_missing_content3-tracked + R content1_missing_content3-untracked + R content1_missing_missing-tracked + R content1_missing_missing-untracked + C content1_content1_content1-tracked + C content1_content1_content1-untracked + C content1_content1_content3-tracked + C content1_content1_content3-untracked + C content1_content1_missing-tracked + C content1_content1_missing-untracked $ hg status -A --rev 0:1 'glob:content1_content2_*' M content1_content2_content1-tracked M content1_content2_content1-untracked
--- a/tests/test-symlinks.t Thu Dec 07 03:49:48 2023 +0100 +++ b/tests/test-symlinks.t Fri Dec 15 11:08:41 2023 +0100 @@ -188,6 +188,35 @@ $ cd .. +== symlinks and add with --include == + +directory moved and symlinked + + $ hg init add-include + $ cd add-include + $ mkdir foo + $ touch foo/a + $ hg ci -Ama + adding foo/a + $ hg mv foo bar + moving foo/a to bar/a + $ ln -s bar foo + $ hg status + A bar/a + R foo/a + ? foo + +can add with --include + + $ hg add -I foo + adding foo + $ hg status + A bar/a + A foo + R foo/a + + $ cd .. + == root of repository is symlinked == $ hg init root
--- a/tests/test-template-map.t Thu Dec 07 03:49:48 2023 +0100 +++ b/tests/test-template-map.t Fri Dec 15 11:08:41 2023 +0100 @@ -766,7 +766,26 @@ ], 'desc': 'third', 'diff': 'diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n', - 'diffstat': ' fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n', + 'diffstat': [ + { + 'additions': 1, + 'binary': False, + 'name': 'fourth', + 'removals': 0 + }, + { + 'additions': 0, + 'binary': False, + 'name': 'second', + 'removals': 1 + }, + { + 'additions': 1, + 'binary': False, + 'name': 'third', + 'removals': 0 + } + ], 'files': [ 'fourth', 'second', @@ -820,7 +839,7 @@ "date": [1577872860, 0], "desc": "third", "diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n", - "diffstat": " fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n", + "diffstat": [{"additions": 1, "binary": false, "name": "fourth", "removals": 0}, {"additions": 0, "binary": false, "name": "second", "removals": 1}, {"additions": 1, "binary": false, "name": "third", "removals": 0}], "files": ["fourth", "second", "third"], "node": "95c24699272ef57d062b8bccc32c878bf841784a", "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"], @@ -1180,7 +1199,7 @@ $ hg log -r. -T'json(diffstat)' [ - {"diffstat": " fourth | 1 +\n second | 1 -\n third | 1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n"} + {"diffstat": [{"additions": 1, "binary": false, "name": "fourth", "removals": 0}, {"additions": 0, "binary": false, "name": "second", "removals": 1}, {"additions": 1, "binary": false, "name": "third", "removals": 0}]} ] $ hg log -r. -T'json(manifest)'