# HG changeset patch # User Kevin Bullock # Date 1519516150 21600 # Node ID eb73f8a6177edf48a4f09f29490ffaa1fcd730ac # Parent 7a3590e67868351e7f8c5f155b36adc5705003b0# Parent fb39f6a8a864d673868b483686ae10024d3dea32 merge with stable diff -r fb39f6a8a864 -r eb73f8a6177e .clang-format --- a/.clang-format Fri Feb 23 17:57:04 2018 -0800 +++ b/.clang-format Sat Feb 24 17:49:10 2018 -0600 @@ -6,3 +6,8 @@ IndentCaseLabels: false AllowShortBlocksOnASingleLine: false AllowShortFunctionsOnASingleLine: false +IncludeCategories: + - Regex: '^<' + Priority: 1 + - Regex: '^"' + Priority: 2 diff -r fb39f6a8a864 -r eb73f8a6177e Makefile --- a/Makefile Fri Feb 23 17:57:04 2018 -0800 +++ b/Makefile Sat Feb 24 17:49:10 2018 -0600 @@ -234,18 +234,6 @@ docker-ubuntu-xenial-ppa: contrib/docker/ubuntu-xenial contrib/dockerdeb ubuntu xenial --source-only -docker-ubuntu-yakkety: contrib/docker/ubuntu-yakkety - contrib/dockerdeb ubuntu yakkety - -docker-ubuntu-yakkety-ppa: contrib/docker/ubuntu-yakkety - contrib/dockerdeb ubuntu yakkety --source-only - -docker-ubuntu-zesty: contrib/docker/ubuntu-zesty - contrib/dockerdeb ubuntu zesty - -docker-ubuntu-zesty-ppa: contrib/docker/ubuntu-zesty - contrib/dockerdeb ubuntu zesty --source-only - docker-ubuntu-artful: contrib/docker/ubuntu-artful contrib/dockerdeb ubuntu artful @@ -318,8 +306,6 @@ osx deb ppa docker-debian-jessie docker-debian-stretch \ docker-ubuntu-trusty docker-ubuntu-trusty-ppa \ docker-ubuntu-xenial docker-ubuntu-xenial-ppa \ - docker-ubuntu-yakkety docker-ubuntu-yakkety-ppa \ - docker-ubuntu-zesty docker-ubuntu-zesty-ppa \ docker-ubuntu-artful docker-ubuntu-artful-ppa \ fedora20 docker-fedora20 fedora21 docker-fedora21 \ centos5 docker-centos5 centos6 docker-centos6 centos7 docker-centos7 \ diff -r fb39f6a8a864 -r eb73f8a6177e contrib/Makefile.python --- a/contrib/Makefile.python Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/Makefile.python Sat Feb 24 17:49:10 2018 -0600 @@ -1,4 +1,4 @@ -PYTHONVER=2.7.10 +PYTHONVER=2.7.14 PYTHONNAME=python- PREFIX=$(HOME)/bin/prefix-$(PYTHONNAME)$(PYTHONVER) SYMLINKDIR=$(HOME)/bin diff -r fb39f6a8a864 -r eb73f8a6177e contrib/buildrpm --- a/contrib/buildrpm Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/buildrpm Sat Feb 24 17:49:10 2018 -0600 @@ -20,8 +20,8 @@ ;; --withpython | --with-python) shift - PYTHONVER=2.7.10 - PYTHONMD5=d7547558fd673bd9d38e2108c6b42521 + PYTHONVER=2.7.14 + PYTHONMD5=cee2e4b33ad3750da77b2e85f2f8b724 ;; --rpmbuilddir ) shift diff -r fb39f6a8a864 -r eb73f8a6177e contrib/check-code.py --- a/contrib/check-code.py Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/check-code.py Sat Feb 24 17:49:10 2018 -0600 @@ -150,6 +150,7 @@ (r'grep.* -[ABC]', "don't use grep's context flags"), (r'find.*-printf', "don't use 'find -printf', it doesn't exist on BSD find(1)"), + (r'\$RANDOM ', "don't use bash-only $RANDOM to generate random values"), ], # warnings [ diff -r fb39f6a8a864 -r eb73f8a6177e contrib/check-config.py --- a/contrib/check-config.py Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/check-config.py Sat Feb 24 17:49:10 2018 -0600 @@ -15,7 +15,7 @@ documented = {} allowinconsistent = set() -configre = re.compile(r''' +configre = re.compile(br''' # Function call ui\.config(?P|int|bool|list)\( # First argument. @@ -25,7 +25,7 @@ (?:default=)?(?P\S+?))? \)''', re.VERBOSE | re.MULTILINE) -configwithre = re.compile(''' +configwithre = re.compile(b''' ui\.config(?Pwith)\( # First argument is callback function. This doesn't parse robustly # if it is e.g. a function call. @@ -35,57 +35,57 @@ (?:default=)?(?P\S+?))? \)''', re.VERBOSE | re.MULTILINE) -configpartialre = (r"""ui\.config""") +configpartialre = (br"""ui\.config""") -ignorere = re.compile(r''' +ignorere = re.compile(br''' \#\s(?Pinternal|experimental|deprecated|developer|inconsistent)\s config:\s(?P\S+\.\S+)$ ''', re.VERBOSE | re.MULTILINE) def main(args): for f in args: - sect = '' - prevname = '' - confsect = '' - carryover = '' + sect = b'' + prevname = b'' + confsect = b'' + carryover = b'' linenum = 0 - for l in open(f): + for l in open(f, 'rb'): linenum += 1 # check topic-like bits - m = re.match('\s*``(\S+)``', l) + m = re.match(b'\s*``(\S+)``', l) if m: prevname = m.group(1) - if re.match('^\s*-+$', l): + if re.match(b'^\s*-+$', l): sect = prevname - prevname = '' + prevname = b'' if sect and prevname: - name = sect + '.' + prevname + name = sect + b'.' + prevname documented[name] = 1 # check docstring bits - m = re.match(r'^\s+\[(\S+)\]', l) + m = re.match(br'^\s+\[(\S+)\]', l) if m: confsect = m.group(1) continue - m = re.match(r'^\s+(?:#\s*)?(\S+) = ', l) + m = re.match(br'^\s+(?:#\s*)?(\S+) = ', l) if m: - name = confsect + '.' + m.group(1) + name = confsect + b'.' + m.group(1) documented[name] = 1 # like the bugzilla extension - m = re.match(r'^\s*(\S+\.\S+)$', l) + m = re.match(br'^\s*(\S+\.\S+)$', l) if m: documented[m.group(1)] = 1 # like convert - m = re.match(r'^\s*:(\S+\.\S+):\s+', l) + m = re.match(br'^\s*:(\S+\.\S+):\s+', l) if m: documented[m.group(1)] = 1 # quoted in help or docstrings - m = re.match(r'.*?``(\S+\.\S+)``', l) + m = re.match(br'.*?``(\S+\.\S+)``', l) if m: documented[m.group(1)] = 1 @@ -108,7 +108,7 @@ default = m.group('default') if default in (None, 'False', 'None', '0', '[]', '""', "''"): default = '' - if re.match('[a-z.]+$', default): + if re.match(b'[a-z.]+$', default): default = '' if (name in foundopts and (ctype, default) != foundopts[name] and name not in allowinconsistent): diff -r fb39f6a8a864 -r eb73f8a6177e contrib/chg/chg.c --- a/contrib/chg/chg.c Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/chg/chg.c Sat Feb 24 17:49:10 2018 -0600 @@ -38,11 +38,13 @@ const char **args; }; -static void initcmdserveropts(struct cmdserveropts *opts) { +static void initcmdserveropts(struct cmdserveropts *opts) +{ memset(opts, 0, sizeof(struct cmdserveropts)); } -static void freecmdserveropts(struct cmdserveropts *opts) { +static void freecmdserveropts(struct cmdserveropts *opts) +{ free(opts->args); opts->args = NULL; opts->argsize = 0; @@ -59,12 +61,8 @@ const char *name; size_t narg; } flags[] = { - {"--config", 1}, - {"--cwd", 1}, - {"--repo", 1}, - {"--repository", 1}, - {"--traceback", 0}, - {"-R", 1}, + {"--config", 1}, {"--cwd", 1}, {"--repo", 1}, + {"--repository", 1}, {"--traceback", 0}, {"-R", 1}, }; size_t i; for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) { @@ -89,21 +87,21 @@ /* * Parse argv[] and put sensitive flags to opts->args */ -static void setcmdserverargs(struct cmdserveropts *opts, - int argc, const char *argv[]) +static void setcmdserverargs(struct cmdserveropts *opts, int argc, + const char *argv[]) { size_t i, step; opts->argsize = 0; for (i = 0, step = 1; i < (size_t)argc; i += step, step = 1) { if (!argv[i]) - continue; /* pass clang-analyse */ + continue; /* pass clang-analyse */ if (strcmp(argv[i], "--") == 0) break; size_t n = testsensitiveflag(argv[i]); if (n == 0 || i + n > (size_t)argc) continue; - opts->args = reallocx(opts->args, - (n + opts->argsize) * sizeof(char *)); + opts->args = + reallocx(opts->args, (n + opts->argsize) * sizeof(char *)); memcpy(opts->args + opts->argsize, argv + i, sizeof(char *) * n); opts->argsize += n; @@ -180,8 +178,8 @@ r = snprintf(opts->sockname, sizeof(opts->sockname), sockfmt, basename); if (r < 0 || (size_t)r >= sizeof(opts->sockname)) abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r); - r = snprintf(opts->initsockname, sizeof(opts->initsockname), - "%s.%u", opts->sockname, (unsigned)getpid()); + r = snprintf(opts->initsockname, sizeof(opts->initsockname), "%s.%u", + opts->sockname, (unsigned)getpid()); if (r < 0 || (size_t)r >= sizeof(opts->initsockname)) abortmsg("too long TMPDIR or CHGSOCKNAME (r = %d)", r); } @@ -208,11 +206,14 @@ const char *hgcmd = gethgcmd(); const char *baseargv[] = { - hgcmd, - "serve", - "--cmdserver", "chgunix", - "--address", opts->initsockname, - "--daemon-postexec", "chdir:/", + hgcmd, + "serve", + "--cmdserver", + "chgunix", + "--address", + opts->initsockname, + "--daemon-postexec", + "chdir:/", }; size_t baseargvsize = sizeof(baseargv) / sizeof(baseargv[0]); size_t argsize = baseargvsize + opts->argsize + 1; @@ -237,7 +238,7 @@ debugmsg("try connect to %s repeatedly", opts->initsockname); - unsigned int timeoutsec = 60; /* default: 60 seconds */ + unsigned int timeoutsec = 60; /* default: 60 seconds */ const char *timeoutenv = getenv("CHGTIMEOUT"); if (timeoutenv) sscanf(timeoutenv, "%u", &timeoutsec); @@ -246,7 +247,7 @@ hgclient_t *hgc = hgc_open(opts->initsockname); if (hgc) { debugmsg("rename %s to %s", opts->initsockname, - opts->sockname); + opts->sockname); int r = rename(opts->initsockname, opts->sockname); if (r != 0) abortmsgerrno("cannot rename"); @@ -270,7 +271,7 @@ if (WIFEXITED(pst)) { if (WEXITSTATUS(pst) == 0) abortmsg("could not connect to cmdserver " - "(exited with status 0)"); + "(exited with status 0)"); debugmsg("cmdserver exited with status %d", WEXITSTATUS(pst)); exit(WEXITSTATUS(pst)); } else if (WIFSIGNALED(pst)) { @@ -284,8 +285,8 @@ /* Connect to a cmdserver. Will start a new server on demand. */ static hgclient_t *connectcmdserver(struct cmdserveropts *opts) { - const char *sockname = opts->redirectsockname[0] ? - opts->redirectsockname : opts->sockname; + const char *sockname = + opts->redirectsockname[0] ? opts->redirectsockname : opts->sockname; debugmsg("try connect to %s", sockname); hgclient_t *hgc = hgc_open(sockname); if (hgc) @@ -339,8 +340,8 @@ unlink(*pinst + 7); } else if (strncmp(*pinst, "redirect ", 9) == 0) { int r = snprintf(opts->redirectsockname, - sizeof(opts->redirectsockname), - "%s", *pinst + 9); + sizeof(opts->redirectsockname), "%s", + *pinst + 9); if (r < 0 || r >= (int)sizeof(opts->redirectsockname)) abortmsg("redirect path is too long (%d)", r); needreconnect = 1; @@ -365,10 +366,9 @@ */ static int isunsupported(int argc, const char *argv[]) { - enum { - SERVE = 1, - DAEMON = 2, - SERVEDAEMON = SERVE | DAEMON, + enum { SERVE = 1, + DAEMON = 2, + SERVEDAEMON = SERVE | DAEMON, }; unsigned int state = 0; int i; @@ -378,7 +378,7 @@ if (i == 0 && strcmp("serve", argv[i]) == 0) state |= SERVE; else if (strcmp("-d", argv[i]) == 0 || - strcmp("--daemon", argv[i]) == 0) + strcmp("--daemon", argv[i]) == 0) state |= DAEMON; } return (state & SERVEDAEMON) == SERVEDAEMON; @@ -401,9 +401,9 @@ if (getenv("CHGINTERNALMARK")) abortmsg("chg started by chg detected.\n" - "Please make sure ${HG:-hg} is not a symlink or " - "wrapper to chg. Alternatively, set $CHGHG to the " - "path of real hg."); + "Please make sure ${HG:-hg} is not a symlink or " + "wrapper to chg. Alternatively, set $CHGHG to the " + "path of real hg."); if (isunsupported(argc - 1, argv + 1)) execoriginalhg(argv); @@ -435,11 +435,11 @@ hgc_close(hgc); if (++retry > 10) abortmsg("too many redirections.\n" - "Please make sure %s is not a wrapper which " - "changes sensitive environment variables " - "before executing hg. If you have to use a " - "wrapper, wrap chg instead of hg.", - gethgcmd()); + "Please make sure %s is not a wrapper which " + "changes sensitive environment variables " + "before executing hg. If you have to use a " + "wrapper, wrap chg instead of hg.", + gethgcmd()); } setupsignalhandler(hgc_peerpid(hgc), hgc_peerpgid(hgc)); diff -r fb39f6a8a864 -r eb73f8a6177e contrib/chg/hgclient.c --- a/contrib/chg/hgclient.c Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/chg/hgclient.c Sat Feb 24 17:49:10 2018 -0600 @@ -7,7 +7,7 @@ * GNU General Public License version 2 or any later version. */ -#include /* for ntohl(), htonl() */ +#include /* for ntohl(), htonl() */ #include #include #include @@ -26,16 +26,15 @@ #include "procutil.h" #include "util.h" -enum { - CAP_GETENCODING = 0x0001, - CAP_RUNCOMMAND = 0x0002, - /* cHg extension: */ - CAP_ATTACHIO = 0x0100, - CAP_CHDIR = 0x0200, - CAP_SETENV = 0x0800, - CAP_SETUMASK = 0x1000, - CAP_VALIDATE = 0x2000, - CAP_SETPROCNAME = 0x4000, +enum { CAP_GETENCODING = 0x0001, + CAP_RUNCOMMAND = 0x0002, + /* cHg extension: */ + CAP_ATTACHIO = 0x0100, + CAP_CHDIR = 0x0200, + CAP_SETENV = 0x0800, + CAP_SETUMASK = 0x1000, + CAP_VALIDATE = 0x2000, + CAP_SETPROCNAME = 0x4000, }; typedef struct { @@ -44,15 +43,15 @@ } cappair_t; static const cappair_t captable[] = { - {"getencoding", CAP_GETENCODING}, - {"runcommand", CAP_RUNCOMMAND}, - {"attachio", CAP_ATTACHIO}, - {"chdir", CAP_CHDIR}, - {"setenv", CAP_SETENV}, - {"setumask", CAP_SETUMASK}, - {"validate", CAP_VALIDATE}, - {"setprocname", CAP_SETPROCNAME}, - {NULL, 0}, /* terminator */ + {"getencoding", CAP_GETENCODING}, + {"runcommand", CAP_RUNCOMMAND}, + {"attachio", CAP_ATTACHIO}, + {"chdir", CAP_CHDIR}, + {"setenv", CAP_SETENV}, + {"setumask", CAP_SETUMASK}, + {"validate", CAP_VALIDATE}, + {"setprocname", CAP_SETPROCNAME}, + {NULL, 0}, /* terminator */ }; typedef struct { @@ -88,8 +87,8 @@ if (newsize <= ctx->maxdatasize) return; - newsize = defaultdatasize - * ((newsize + defaultdatasize - 1) / defaultdatasize); + newsize = defaultdatasize * + ((newsize + defaultdatasize - 1) / defaultdatasize); ctx->data = reallocx(ctx->data, newsize); ctx->maxdatasize = newsize; debugmsg("enlarge context buffer to %zu", ctx->maxdatasize); @@ -126,12 +125,12 @@ enlargecontext(&hgc->ctx, hgc->ctx.datasize); if (isupper(hgc->ctx.ch) && hgc->ctx.ch != 'S') - return; /* assumes input request */ + return; /* assumes input request */ size_t cursize = 0; while (cursize < hgc->ctx.datasize) { rsize = recv(hgc->sockfd, hgc->ctx.data + cursize, - hgc->ctx.datasize - cursize, 0); + hgc->ctx.datasize - cursize, 0); if (rsize < 1) abortmsg("failed to read data block"); cursize += rsize; @@ -176,19 +175,19 @@ /* Build '\0'-separated list of args. argsize < 0 denotes that args are * terminated by NULL. */ static void packcmdargs(context_t *ctx, const char *const args[], - ssize_t argsize) + ssize_t argsize) { ctx->datasize = 0; const char *const *const end = (argsize >= 0) ? args + argsize : NULL; for (const char *const *it = args; it != end && *it; ++it) { - const size_t n = strlen(*it) + 1; /* include '\0' */ + const size_t n = strlen(*it) + 1; /* include '\0' */ enlargecontext(ctx, ctx->datasize + n); memcpy(ctx->data + ctx->datasize, *it, n); ctx->datasize += n; } if (ctx->datasize > 0) - --ctx->datasize; /* strip last '\0' */ + --ctx->datasize; /* strip last '\0' */ } /* Extract '\0'-separated list of args to new buffer, terminated by NULL */ @@ -199,7 +198,7 @@ const char *s = ctx->data; const char *e = ctx->data + ctx->datasize; for (;;) { - if (nargs + 1 >= maxnargs) { /* including last NULL */ + if (nargs + 1 >= maxnargs) { /* including last NULL */ maxnargs += 256; args = reallocx(args, maxnargs * sizeof(args[0])); } @@ -237,7 +236,7 @@ { context_t *ctx = &hgc->ctx; enlargecontext(ctx, ctx->datasize + 1); - ctx->data[ctx->datasize] = '\0'; /* terminate last string */ + ctx->data[ctx->datasize] = '\0'; /* terminate last string */ const char **args = unpackcmdargsnul(ctx); if (!args[0] || !args[1] || !args[2]) @@ -269,8 +268,8 @@ for (;;) { readchannel(hgc); context_t *ctx = &hgc->ctx; - debugmsg("response read from channel %c, size %zu", - ctx->ch, ctx->datasize); + debugmsg("response read from channel %c, size %zu", ctx->ch, + ctx->datasize); switch (ctx->ch) { case 'o': fwrite(ctx->data, sizeof(ctx->data[0]), ctx->datasize, @@ -299,7 +298,7 @@ default: if (isupper(ctx->ch)) abortmsg("cannot handle response (ch = %c)", - ctx->ch); + ctx->ch); } } } @@ -366,8 +365,8 @@ static void updateprocname(hgclient_t *hgc) { - int r = snprintf(hgc->ctx.data, hgc->ctx.maxdatasize, - "chg[worker/%d]", (int)getpid()); + int r = snprintf(hgc->ctx.data, hgc->ctx.maxdatasize, "chg[worker/%d]", + (int)getpid()); if (r < 0 || (size_t)r >= hgc->ctx.maxdatasize) abortmsg("insufficient buffer to write procname (r = %d)", r); hgc->ctx.datasize = (size_t)r; @@ -387,7 +386,7 @@ static const int fds[3] = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; struct msghdr msgh; memset(&msgh, 0, sizeof(msgh)); - struct iovec iov = {ctx->data, ctx->datasize}; /* dummy payload */ + struct iovec iov = {ctx->data, ctx->datasize}; /* dummy payload */ msgh.msg_iov = &iov; msgh.msg_iovlen = 1; char fdbuf[CMSG_SPACE(sizeof(fds))]; @@ -552,7 +551,7 @@ * the last string is guaranteed to be NULL. */ const char **hgc_validate(hgclient_t *hgc, const char *const args[], - size_t argsize) + size_t argsize) { assert(hgc); if (!(hgc->capflags & CAP_VALIDATE)) diff -r fb39f6a8a864 -r eb73f8a6177e contrib/chg/hgclient.h --- a/contrib/chg/hgclient.h Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/chg/hgclient.h Sat Feb 24 17:49:10 2018 -0600 @@ -22,9 +22,9 @@ pid_t hgc_peerpid(const hgclient_t *hgc); const char **hgc_validate(hgclient_t *hgc, const char *const args[], - size_t argsize); + size_t argsize); int hgc_runcommand(hgclient_t *hgc, const char *const args[], size_t argsize); void hgc_attachio(hgclient_t *hgc); void hgc_setenv(hgclient_t *hgc, const char *const envp[]); -#endif /* HGCLIENT_H_ */ +#endif /* HGCLIENT_H_ */ diff -r fb39f6a8a864 -r eb73f8a6177e contrib/chg/procutil.c --- a/contrib/chg/procutil.c Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/chg/procutil.c Sat Feb 24 17:49:10 2018 -0600 @@ -54,7 +54,7 @@ goto error; forwardsignal(sig); - if (raise(sig) < 0) /* resend to self */ + if (raise(sig) < 0) /* resend to self */ goto error; if (sigaction(sig, &sa, &oldsa) < 0) goto error; @@ -205,8 +205,8 @@ close(pipefds[0]); close(pipefds[1]); - int r = execle("/bin/sh", "/bin/sh", "-c", pagercmd, NULL, - envp); + int r = + execle("/bin/sh", "/bin/sh", "-c", pagercmd, NULL, envp); if (r < 0) { abortmsgerrno("cannot start pager '%s'", pagercmd); } diff -r fb39f6a8a864 -r eb73f8a6177e contrib/chg/util.c --- a/contrib/chg/util.c Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/chg/util.c Sat Feb 24 17:49:10 2018 -0600 @@ -62,7 +62,8 @@ static int debugmsgenabled = 0; static double debugstart = 0; -static double now() { +static double now() +{ struct timeval t; gettimeofday(&t, NULL); return t.tv_usec / 1e6 + t.tv_sec; diff -r fb39f6a8a864 -r eb73f8a6177e contrib/chg/util.h --- a/contrib/chg/util.h Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/chg/util.h Sat Feb 24 17:49:10 2018 -0600 @@ -32,4 +32,4 @@ int runshellcmd(const char *cmd, const char *envp[], const char *cwd); -#endif /* UTIL_H_ */ +#endif /* UTIL_H_ */ diff -r fb39f6a8a864 -r eb73f8a6177e contrib/clang-format-blacklist --- a/contrib/clang-format-blacklist Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/clang-format-blacklist Sat Feb 24 17:49:10 2018 -0600 @@ -1,23 +1,8 @@ # Files that just need to be migrated to the formatter. # Do not add new files here! -contrib/chg/chg.c -contrib/chg/hgclient.c -contrib/chg/hgclient.h -contrib/chg/procutil.c -contrib/chg/procutil.h -contrib/chg/util.c -contrib/chg/util.h -contrib/hgsh/hgsh.c -mercurial/cext/base85.c -mercurial/cext/bdiff.c -mercurial/cext/charencode.c -mercurial/cext/charencode.h -mercurial/cext/diffhelpers.c mercurial/cext/dirs.c mercurial/cext/manifest.c -mercurial/cext/mpatch.c mercurial/cext/osutil.c -mercurial/cext/pathencode.c mercurial/cext/revlog.c # Vendored code that we should never format: contrib/python-zstandard/c-ext/bufferutil.c diff -r fb39f6a8a864 -r eb73f8a6177e contrib/dirstatenonnormalcheck.py --- a/contrib/dirstatenonnormalcheck.py Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/dirstatenonnormalcheck.py Sat Feb 24 17:49:10 2018 -0600 @@ -17,7 +17,7 @@ """Compute nonnormal entries from dirstate's dmap""" res = set() for f, e in dmap.iteritems(): - if e[0] != 'n' or e[3] == -1: + if e[0] != b'n' or e[3] == -1: res.add(f) return res @@ -25,24 +25,25 @@ """Compute nonnormalset from dmap, check that it matches _nonnormalset""" nonnormalcomputedmap = nonnormalentries(dmap) if _nonnormalset != nonnormalcomputedmap: - ui.develwarn("%s call to %s\n" % (label, orig), config='dirstate') - ui.develwarn("inconsistency in nonnormalset\n", config='dirstate') - ui.develwarn("[nonnormalset] %s\n" % _nonnormalset, config='dirstate') - ui.develwarn("[map] %s\n" % nonnormalcomputedmap, config='dirstate') + ui.develwarn(b"%s call to %s\n" % (label, orig), config=b'dirstate') + ui.develwarn(b"inconsistency in nonnormalset\n", config=b'dirstate') + ui.develwarn(b"[nonnormalset] %s\n" % _nonnormalset, config=b'dirstate') + ui.develwarn(b"[map] %s\n" % nonnormalcomputedmap, config=b'dirstate') def _checkdirstate(orig, self, arg): """Check nonnormal set consistency before and after the call to orig""" checkconsistency(self._ui, orig, self._map, self._map.nonnormalset, - "before") + b"before") r = orig(self, arg) - checkconsistency(self._ui, orig, self._map, self._map.nonnormalset, "after") + checkconsistency(self._ui, orig, self._map, self._map.nonnormalset, + b"after") return r def extsetup(ui): """Wrap functions modifying dirstate to check nonnormalset consistency""" dirstatecl = dirstate.dirstate - devel = ui.configbool('devel', 'all-warnings') - paranoid = ui.configbool('experimental', 'nonnormalparanoidcheck') + devel = ui.configbool(b'devel', b'all-warnings') + paranoid = ui.configbool(b'experimental', b'nonnormalparanoidcheck') if devel: extensions.wrapfunction(dirstatecl, '_writedirstate', _checkdirstate) if paranoid: diff -r fb39f6a8a864 -r eb73f8a6177e contrib/dumprevlog --- a/contrib/dumprevlog Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/dumprevlog Sat Feb 24 17:49:10 2018 -0600 @@ -14,8 +14,12 @@ for fp in (sys.stdin, sys.stdout, sys.stderr): util.setbinary(fp) +def binopen(path, mode='rb'): + if 'b' not in mode: + mode = mode + 'b' + return open(path, mode) + for f in sys.argv[1:]: - binopen = lambda fn: open(fn, 'rb') r = revlog.revlog(binopen, f) print("file:", f) for i in r: diff -r fb39f6a8a864 -r eb73f8a6177e contrib/hgsh/hgsh.c --- a/contrib/hgsh/hgsh.c Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/hgsh/hgsh.c Sat Feb 24 17:49:10 2018 -0600 @@ -48,7 +48,7 @@ * have such machine, set to NULL. */ #ifndef HG_GATEWAY -#define HG_GATEWAY "gateway" +#define HG_GATEWAY "gateway" #endif /* @@ -56,7 +56,7 @@ * NULL. */ #ifndef HG_HOST -#define HG_HOST "mercurial" +#define HG_HOST "mercurial" #endif /* @@ -64,7 +64,7 @@ * host username are same, set to NULL. */ #ifndef HG_USER -#define HG_USER "hg" +#define HG_USER "hg" #endif /* @@ -72,14 +72,14 @@ * validate location of repo when someone is try to access, set to NULL. */ #ifndef HG_ROOT -#define HG_ROOT "/home/hg/repos" +#define HG_ROOT "/home/hg/repos" #endif /* * HG: path to the mercurial executable to run. */ #ifndef HG -#define HG "/home/hg/bin/hg" +#define HG "/home/hg/bin/hg" #endif /* @@ -88,7 +88,7 @@ * impossible, set to NULL. */ #ifndef HG_SHELL -#define HG_SHELL NULL +#define HG_SHELL NULL /* #define HG_SHELL "/bin/bash" */ #endif @@ -97,7 +97,7 @@ * should not get helpful message, set to NULL. */ #ifndef HG_HELP -#define HG_HELP "please contact support@example.com for help." +#define HG_HELP "please contact support@example.com for help." #endif /* @@ -106,7 +106,7 @@ * arguments it is called with. see forward_through_gateway. */ #ifndef SSH -#define SSH "/usr/bin/ssh" +#define SSH "/usr/bin/ssh" #endif /* @@ -249,7 +249,6 @@ hg_serve, }; - /* * attempt to verify that a directory is really a hg repo, by testing * for the existence of a subdirectory. @@ -310,8 +309,7 @@ if (sscanf(argv[2], "hg init %as", &repo) == 1) { cmd = hg_init; - } - else if (sscanf(argv[2], "hg -R %as serve --stdio", &repo) == 1) { + } else if (sscanf(argv[2], "hg -R %as serve --stdio", &repo) == 1) { cmd = hg_serve; } else { goto badargs; diff -r fb39f6a8a864 -r eb73f8a6177e contrib/mercurial.spec --- a/contrib/mercurial.spec Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/mercurial.spec Sat Feb 24 17:49:10 2018 -0600 @@ -6,8 +6,8 @@ %global pythonver %{withpython} %global pythonname Python-%{withpython} -%global docutilsname docutils-0.12 -%global docutilsmd5 4622263b62c5c771c03502afa3157768 +%global docutilsname docutils-0.14 +%global docutilsmd5 c53768d63db3873b7d452833553469de %global pythonhg python-hg %global hgpyprefix /opt/%{pythonhg} # byte compilation will fail on some some Python /test/ files diff -r fb39f6a8a864 -r eb73f8a6177e contrib/perf.py --- a/contrib/perf.py Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/perf.py Sat Feb 24 17:49:10 2018 -0600 @@ -64,6 +64,12 @@ from mercurial import scmutil # since 1.9 (or 8b252e826c68) except ImportError: pass +try: + from mercurial import pycompat + getargspec = pycompat.getargspec # added to module after 4.5 +except (ImportError, AttributeError): + import inspect + getargspec = inspect.getargspec # for "historical portability": # define util.safehasattr forcibly, because util.safehasattr has been @@ -114,9 +120,8 @@ if safehasattr(registrar, 'command'): command = registrar.command(cmdtable) elif safehasattr(cmdutil, 'command'): - import inspect command = cmdutil.command(cmdtable) - if 'norepo' not in inspect.getargspec(command)[0]: + if 'norepo' not in getargspec(command).args: # for "historical portability": # wrap original cmdutil.command, because "norepo" option has # been available since 3.1 (or 75a96326cecb) @@ -1031,6 +1036,71 @@ with ready: ready.notify_all() +@command('perfunidiff', revlogopts + formatteropts + [ + ('', 'count', 1, 'number of revisions to test (when using --startrev)'), + ('', 'alldata', False, 'test unidiffs for all associated revisions'), + ], '-c|-m|FILE REV') +def perfunidiff(ui, repo, file_, rev=None, count=None, **opts): + """benchmark a unified diff between revisions + + This doesn't include any copy tracing - it's just a unified diff + of the texts. + + By default, benchmark a diff between its delta parent and itself. + + With ``--count``, benchmark diffs between delta parents and self for N + revisions starting at the specified revision. + + With ``--alldata``, assume the requested revision is a changeset and + measure diffs for all changes related to that changeset (manifest + and filelogs). + """ + if opts['alldata']: + opts['changelog'] = True + + if opts.get('changelog') or opts.get('manifest'): + file_, rev = None, file_ + elif rev is None: + raise error.CommandError('perfunidiff', 'invalid arguments') + + textpairs = [] + + r = cmdutil.openrevlog(repo, 'perfunidiff', file_, opts) + + startrev = r.rev(r.lookup(rev)) + for rev in range(startrev, min(startrev + count, len(r) - 1)): + if opts['alldata']: + # Load revisions associated with changeset. + ctx = repo[rev] + mtext = repo.manifestlog._revlog.revision(ctx.manifestnode()) + for pctx in ctx.parents(): + pman = repo.manifestlog._revlog.revision(pctx.manifestnode()) + textpairs.append((pman, mtext)) + + # Load filelog revisions by iterating manifest delta. + man = ctx.manifest() + pman = ctx.p1().manifest() + for filename, change in pman.diff(man).items(): + fctx = repo.file(filename) + f1 = fctx.revision(change[0][0] or -1) + f2 = fctx.revision(change[1][0] or -1) + textpairs.append((f1, f2)) + else: + dp = r.deltaparent(rev) + textpairs.append((r.revision(dp), r.revision(rev))) + + def d(): + for left, right in textpairs: + # The date strings don't matter, so we pass empty strings. + headerlines, hunks = mdiff.unidiff( + left, '', right, '', 'left', 'right', binary=False) + # consume iterators in roughly the way patch.py does + b'\n'.join(headerlines) + b''.join(sum((list(hlines) for hrange, hlines in hunks), [])) + timer, fm = gettimer(ui, opts) + timer(d) + fm.end() + @command('perfdiffwd', formatteropts) def perfdiffwd(ui, repo, **opts): """Profile diff of working directory changes""" @@ -1498,11 +1568,13 @@ ('', 'clear-revbranch', False, 'purge the revbranch cache between computation'), ] + formatteropts) -def perfbranchmap(ui, repo, full=False, clear_revbranch=False, **opts): +def perfbranchmap(ui, repo, *filternames, **opts): """benchmark the update of a branchmap This benchmarks the full repo.branchmap() call with read and write disabled """ + full = opts.get("full", False) + clear_revbranch = opts.get("clear_revbranch", False) timer, fm = gettimer(ui, opts) def getbranchmap(filtername): """generate a benchmark function for the filtername""" @@ -1521,6 +1593,8 @@ return d # add filter in smaller subset to bigger subset possiblefilters = set(repoview.filtertable) + if filternames: + possiblefilters &= set(filternames) subsettable = getbranchmapsubsettable() allfilters = [] while possiblefilters: @@ -1537,8 +1611,9 @@ if not full: for name in allfilters: repo.filtered(name).branchmap() - # add unfiltered - allfilters.append(None) + if not filternames or 'unfiltered' in filternames: + # add unfiltered + allfilters.append(None) branchcacheread = safeattrsetter(branchmap, 'read') branchcachewrite = safeattrsetter(branchmap.branchcache, 'write') @@ -1546,7 +1621,10 @@ branchcachewrite.set(lambda bc, repo: None) try: for name in allfilters: - timer(getbranchmap(name), title=str(name)) + printname = name + if name is None: + printname = 'unfiltered' + timer(getbranchmap(name), title=str(printname)) finally: branchcacheread.restore() branchcachewrite.restore() diff -r fb39f6a8a864 -r eb73f8a6177e contrib/python3-whitelist --- a/contrib/python3-whitelist Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/python3-whitelist Sat Feb 24 17:49:10 2018 -0600 @@ -1,16 +1,26 @@ +test-abort-checkin.t test-add.t test-addremove-similar.t test-addremove.t +test-amend-subrepo.t test-ancestor.py +test-annotate.py test-automv.t test-backwards-remove.t test-bheads.t test-bisect2.t +test-bookmarks-current.t test-bookmarks-merge.t +test-bookmarks-rebase.t test-bookmarks-strip.t +test-branch-option.t test-branch-tag-confict.t +test-bundle-phases.t +test-bundle-vs-outgoing.t +test-cappedreader.py test-casecollision.t test-cat.t +test-censor.t test-changelog-exec.t test-check-commit.t test-check-execute.t @@ -19,11 +29,25 @@ test-check-pylint.t test-check-shbang.t test-children.t +test-clone-pull-corruption.t +test-clone-r.t +test-clone-update-order.t test-commit-unresolved.t test-completion.t +test-confused-revert.t test-contrib-check-code.t test-contrib-check-commit.t +test-convert-authormap.t +test-convert-clonebranches.t +test-convert-datesort.t +test-convert-hg-startrev.t +test-copy-move-merge.t +test-copytrace-heuristics.t +test-debugbuilddag.t +test-debugindexdot.t test-debugrename.t +test-diff-binary-file.t +test-diff-change.t test-diff-copy-depth.t test-diff-hashes.t test-diff-issue2761.t @@ -32,42 +56,109 @@ test-diff-subdir.t test-diffdir.t test-directaccess.t +test-dirstate-backup.t test-dirstate-nonnormalset.t test-doctest.py test-double-merge.t +test-drawdag.t test-duplicateoptions.py test-empty-dir.t test-empty-file.t +test-empty-group.t test-empty.t test-encoding-func.py +test-eol-add.t +test-eol-clone.t +test-eol-tag.t +test-eol-update.t test-excessive-merge.t +test-exchange-obsmarkers-case-A1.t +test-exchange-obsmarkers-case-A2.t +test-exchange-obsmarkers-case-A3.t +test-exchange-obsmarkers-case-A4.t +test-exchange-obsmarkers-case-A5.t +test-exchange-obsmarkers-case-A6.t +test-exchange-obsmarkers-case-A7.t +test-exchange-obsmarkers-case-B1.t +test-exchange-obsmarkers-case-B2.t +test-exchange-obsmarkers-case-B3.t +test-exchange-obsmarkers-case-B4.t +test-exchange-obsmarkers-case-B5.t +test-exchange-obsmarkers-case-B6.t +test-exchange-obsmarkers-case-B7.t +test-exchange-obsmarkers-case-C1.t +test-exchange-obsmarkers-case-C2.t +test-exchange-obsmarkers-case-C3.t +test-exchange-obsmarkers-case-C4.t +test-exchange-obsmarkers-case-D1.t +test-exchange-obsmarkers-case-D2.t +test-exchange-obsmarkers-case-D3.t +test-exchange-obsmarkers-case-D4.t test-execute-bit.t +test-extdiff.t +test-extra-filelog-entry.t +test-filebranch.t +test-fileset-generated.t +test-flags.t +test-generaldelta.t +test-git-export.t +test-glog-topological.t test-gpg.t test-hghave.t +test-histedit-arguments.t +test-histedit-base.t +test-histedit-bookmark-motion.t +test-histedit-commute.t +test-histedit-drop.t +test-histedit-edit.t +test-histedit-fold-non-commute.t +test-histedit-no-change.t +test-histedit-non-commute.t +test-histedit-obsolete.t +test-histedit-outgoing.t +test-http-branchmap.t +test-http-clone-r.t +test-identify.t test-imports-checker.t +test-inherit-mode.t test-issue1089.t test-issue1175.t +test-issue1306.t +test-issue1438.t test-issue1502.t test-issue1802.t test-issue1877.t test-issue1993.t +test-issue3084.t +test-issue4074.t test-issue522.t +test-issue586.t test-issue612.t test-issue619.t test-issue672.t test-issue842.t test-journal-exists.t +test-largefiles-small-disk.t test-locate.t +test-logexchange.t test-lrucachedict.py +test-mactext.t +test-manifest-merging.t test-manifest.py -test-manifest-merging.t +test-manifest.t test-match.py +test-mdiff.py +test-merge-closedheads.t +test-merge-commit.t +test-merge-criss-cross.t test-merge-default.t test-merge-internal-tools-pattern.t +test-merge-local.t test-merge-remove.t test-merge-revert.t test-merge-revert2.t test-merge-subrepos.t +test-merge1.t test-merge10.t test-merge2.t test-merge4.t @@ -75,9 +166,41 @@ test-merge6.t test-merge7.t test-merge8.t +test-mq-git.t +test-mq-pull-from-bundle.t +test-mq-qdiff.t test-mq-qimport-fail-cleanup.t +test-mq-qqueue.t +test-mq-qrefresh.t +test-mq-qsave.t +test-narrow-clone-no-ellipsis.t +test-narrow-clone-nonlinear.t +test-narrow-clone.t +test-narrow-copies.t +test-narrow-debugrebuilddirstate.t +test-narrow-exchange-merges.t +test-narrow-merge.t +test-narrow-patch.t +test-narrow-patterns.t +test-narrow-pull.t +test-narrow-rebase.t +test-narrow-shallow-merges.t +test-narrow-update.t +test-newbranch.t test-obshistory.t +test-obsmarkers-effectflag.t +test-obsolete-bundle-strip.t +test-obsolete-changeset-exchange.t +test-obsolete-checkheads.t +test-obsolete-distributed.t +test-parents.t test-permissions.t +test-pull-branch.t +test-pull-http.t +test-pull-permission.t +test-pull-pull-corruption.t +test-pull-r.t +test-pull-update.t test-push-checkheads-partial-C1.t test-push-checkheads-partial-C2.t test-push-checkheads-partial-C3.t @@ -105,27 +228,63 @@ test-push-checkheads-unpushed-D5.t test-push-checkheads-unpushed-D6.t test-push-checkheads-unpushed-D7.t +test-push-warn.t +test-rebase-bookmarks.t +test-rebase-check-restore.t +test-rebase-dest.t +test-rebase-emptycommit.t +test-rebase-inmemory.t +test-rebase-issue-noparam-single-rev.t +test-rebase-legacy.t +test-rebase-named-branches.t +test-rebase-newancestor.t +test-rebase-partial.t +test-rebase-pull.t +test-rebase-rename.t +test-rebase-transaction.t test-record.t +test-remove.t +test-rename-after-merge.t test-rename-dir-merge.t test-rename-merge1.t test-rename.t +test-repair-strip.t +test-repo-compengines.t test-revert-flags.t test-revert-unknown.t test-revlog-group-emptyiter.t test-revlog-mmapindex.t test-revlog-packentry.t +test-revset-dirstate-parents.t +test-revset-outgoing.t test-run-tests.py +test-serve.t test-show-stack.t +test-show.t test-simple-update.t +test-single-head.t test-sparse-clear.t test-sparse-merges.t test-sparse-requirement.t test-sparse-verbose-json.t +test-ssh-clone-r.t +test-ssh-proto.t +test-sshserver.py +test-status-rev.t test-status-terse.t +test-strip-cross.t +test-strip.t +test-unamend.t test-uncommit.t test-unified-test.t test-unrelated-pull.t +test-up-local-change.t +test-update-branches.t +test-update-dest.t test-update-issue1456.t test-update-names.t test-update-reverse.t +test-url-rev.t +test-username-newline.t +test-win32text.t test-xdg.t diff -r fb39f6a8a864 -r eb73f8a6177e contrib/synthrepo.py --- a/contrib/synthrepo.py Fri Feb 23 17:57:04 2018 -0800 +++ b/contrib/synthrepo.py Sat Feb 24 17:49:10 2018 -0600 @@ -381,7 +381,7 @@ ui.progress(_synthesizing, None) message = 'synthesized wide repo with %d files' % (len(files),) mc = context.memctx(repo, [pctx.node(), nullid], message, - files.iterkeys(), filectxfn, ui.username(), + files, filectxfn, ui.username(), '%d %d' % util.makedate()) initnode = mc.commit() if ui.debugflag: diff -r fb39f6a8a864 -r eb73f8a6177e hgext/acl.py --- a/hgext/acl.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/acl.py Sat Feb 24 17:49:10 2018 -0600 @@ -200,6 +200,7 @@ error, extensions, match, + pycompat, registrar, util, ) @@ -334,13 +335,13 @@ return user = None - if source == 'serve' and 'url' in kwargs: - url = kwargs['url'].split(':') + if source == 'serve' and r'url' in kwargs: + url = kwargs[r'url'].split(':') if url[0] == 'remote' and url[1].startswith('http'): user = urlreq.unquote(url[3]) if user is None: - user = getpass.getuser() + user = pycompat.bytestr(getpass.getuser()) ui.debug('acl: checking access for user "%s"\n' % user) @@ -355,7 +356,7 @@ allow = buildmatch(ui, repo, user, 'acl.allow') deny = buildmatch(ui, repo, user, 'acl.deny') - for rev in xrange(repo[node], len(repo)): + for rev in xrange(repo[node].rev(), len(repo)): ctx = repo[rev] branch = ctx.branch() if denybranches and denybranches(branch): diff -r fb39f6a8a864 -r eb73f8a6177e hgext/bugzilla.py --- a/hgext/bugzilla.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/bugzilla.py Sat Feb 24 17:49:10 2018 -0600 @@ -300,8 +300,8 @@ from mercurial.i18n import _ from mercurial.node import short from mercurial import ( - cmdutil, error, + logcmdutil, mail, registrar, url, @@ -1090,9 +1090,8 @@ if not mapfile and not tmpl: tmpl = _('changeset {node|short} in repo {root} refers ' 'to bug {bug}.\ndetails:\n\t{desc|tabindent}') - spec = cmdutil.logtemplatespec(tmpl, mapfile) - t = cmdutil.changeset_templater(self.ui, self.repo, spec, - False, None, False) + spec = logcmdutil.templatespec(tmpl, mapfile) + t = logcmdutil.changesettemplater(self.ui, self.repo, spec) self.ui.pushbuffer() t.show(ctx, changes=ctx.changeset(), bug=str(bugid), diff -r fb39f6a8a864 -r eb73f8a6177e hgext/children.py --- a/hgext/children.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/children.py Sat Feb 24 17:49:10 2018 -0600 @@ -19,6 +19,7 @@ from mercurial.i18n import _ from mercurial import ( cmdutil, + logcmdutil, pycompat, registrar, ) @@ -65,7 +66,7 @@ ctx = repo[rev] childctxs = ctx.children() - displayer = cmdutil.show_changeset(ui, repo, opts) + displayer = logcmdutil.changesetdisplayer(ui, repo, opts) for cctx in childctxs: displayer.show(cctx) displayer.close() diff -r fb39f6a8a864 -r eb73f8a6177e hgext/churn.py --- a/hgext/churn.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/churn.py Sat Feb 24 17:49:10 2018 -0600 @@ -18,6 +18,7 @@ from mercurial import ( cmdutil, encoding, + logcmdutil, patch, pycompat, registrar, @@ -54,7 +55,7 @@ return date.strftime(opts['dateformat']) else: tmpl = opts.get('oldtemplate') or opts.get('template') - tmpl = cmdutil.makelogtemplater(ui, repo, tmpl) + tmpl = logcmdutil.maketemplater(ui, repo, tmpl) def getkey(ctx): ui.pushbuffer() tmpl.show(ctx) @@ -170,7 +171,7 @@ ui.warn(_("skipping malformed alias: %s\n") % l) continue - rate = countrate(ui, repo, amap, *pats, **opts).items() + rate = list(countrate(ui, repo, amap, *pats, **opts).items()) if not rate: return diff -r fb39f6a8a864 -r eb73f8a6177e hgext/convert/common.py --- a/hgext/convert/common.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/convert/common.py Sat Feb 24 17:49:10 2018 -0600 @@ -18,6 +18,7 @@ encoding, error, phases, + pycompat, util, ) @@ -322,6 +323,7 @@ pass def _cmdline(self, cmd, *args, **kwargs): + kwargs = pycompat.byteskwargs(kwargs) cmdline = [self.command, cmd] + list(args) for k, v in kwargs.iteritems(): if len(k) == 1: @@ -416,17 +418,17 @@ def _limit_arglist(self, arglist, cmd, *args, **kwargs): cmdlen = len(self._cmdline(cmd, *args, **kwargs)) limit = self.argmax - cmdlen - bytes = 0 + numbytes = 0 fl = [] for fn in arglist: b = len(fn) + 3 - if bytes + b < limit or len(fl) == 0: + if numbytes + b < limit or len(fl) == 0: fl.append(fn) - bytes += b + numbytes += b else: yield fl fl = [fn] - bytes = b + numbytes = b if fl: yield fl @@ -447,7 +449,7 @@ if not self.path: return try: - fp = open(self.path, 'r') + fp = open(self.path, 'rb') except IOError as err: if err.errno != errno.ENOENT: raise @@ -471,12 +473,12 @@ def __setitem__(self, key, value): if self.fp is None: try: - self.fp = open(self.path, 'a') + self.fp = open(self.path, 'ab') except IOError as err: raise error.Abort( _('could not open map file %r: %s') % (self.path, encoding.strtolocal(err.strerror))) - self.fp.write('%s %s\n' % (key, value)) + self.fp.write(util.tonativeeol('%s %s\n' % (key, value))) self.fp.flush() super(mapfile, self).__setitem__(key, value) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/convert/convcmd.py --- a/hgext/convert/convcmd.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/convert/convcmd.py Sat Feb 24 17:49:10 2018 -0600 @@ -16,6 +16,7 @@ encoding, error, hg, + pycompat, scmutil, util, ) @@ -55,9 +56,10 @@ def recode(s): if isinstance(s, unicode): - return s.encode(orig_encoding, 'replace') + return s.encode(pycompat.sysstr(orig_encoding), 'replace') else: - return s.decode('utf-8').encode(orig_encoding, 'replace') + return s.decode('utf-8').encode( + pycompat.sysstr(orig_encoding), 'replace') def mapbranch(branch, branchmap): ''' @@ -202,7 +204,7 @@ return {} m = {} try: - fp = open(path, 'r') + fp = open(path, 'rb') for i, line in enumerate(util.iterfile(fp)): line = line.splitlines()[0].rstrip() if not line: @@ -407,13 +409,14 @@ authorfile = self.authorfile if authorfile: self.ui.status(_('writing author map file %s\n') % authorfile) - ofile = open(authorfile, 'w+') + ofile = open(authorfile, 'wb+') for author in self.authors: - ofile.write("%s=%s\n" % (author, self.authors[author])) + ofile.write(util.tonativeeol("%s=%s\n" + % (author, self.authors[author]))) ofile.close() def readauthormap(self, authorfile): - afile = open(authorfile, 'r') + afile = open(authorfile, 'rb') for line in afile: line = line.strip() @@ -564,6 +567,7 @@ self.map.close() def convert(ui, src, dest=None, revmapfile=None, **opts): + opts = pycompat.byteskwargs(opts) global orig_encoding orig_encoding = encoding.encoding encoding.encoding = 'UTF-8' diff -r fb39f6a8a864 -r eb73f8a6177e hgext/convert/cvs.py --- a/hgext/convert/cvs.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/convert/cvs.py Sat Feb 24 17:49:10 2018 -0600 @@ -46,8 +46,8 @@ self.tags = {} self.lastbranch = {} self.socket = None - self.cvsroot = open(os.path.join(cvs, "Root")).read()[:-1] - self.cvsrepo = open(os.path.join(cvs, "Repository")).read()[:-1] + self.cvsroot = open(os.path.join(cvs, "Root"), 'rb').read()[:-1] + self.cvsrepo = open(os.path.join(cvs, "Repository"), 'rb').read()[:-1] self.encoding = encoding.encoding self._connect() @@ -141,7 +141,7 @@ passw = "A" cvspass = os.path.expanduser("~/.cvspass") try: - pf = open(cvspass) + pf = open(cvspass, 'rb') for line in pf.read().splitlines(): part1, part2 = line.split(' ', 1) # /1 :pserver:user@example.com:2401/cvsroot/foo @@ -179,7 +179,7 @@ # :ext:user@host/home/user/path/to/cvsroot if root.startswith(":ext:"): root = root[5:] - m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root) + m = re.match(br'(?:([^@:/]+)@)?([^:/]+):?(.*)', root) # Do not take Windows path "c:\foo\bar" for a connection strings if os.path.isdir(root) or not m: conntype = "local" diff -r fb39f6a8a864 -r eb73f8a6177e hgext/convert/cvsps.py --- a/hgext/convert/cvsps.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/convert/cvsps.py Sat Feb 24 17:49:10 2018 -0600 @@ -132,7 +132,7 @@ # Get the real directory in the repository try: - prefix = open(os.path.join('CVS','Repository')).read().strip() + prefix = open(os.path.join('CVS','Repository'), 'rb').read().strip() directory = prefix if prefix == ".": prefix = "" @@ -144,7 +144,7 @@ # Use the Root file in the sandbox, if it exists try: - root = open(os.path.join('CVS','Root')).read().strip() + root = open(os.path.join('CVS','Root'), 'rb').read().strip() except IOError: pass @@ -177,7 +177,7 @@ if cache == 'update': try: ui.note(_('reading cvs log cache %s\n') % cachefile) - oldlog = pickle.load(open(cachefile)) + oldlog = pickle.load(open(cachefile, 'rb')) for e in oldlog: if not (util.safehasattr(e, 'branchpoints') and util.safehasattr(e, 'commitid') and @@ -486,7 +486,7 @@ # write the new cachefile ui.note(_('writing cvs log cache %s\n') % cachefile) - pickle.dump(log, open(cachefile, 'w')) + pickle.dump(log, open(cachefile, 'wb')) else: log = oldlog @@ -855,6 +855,7 @@ repository, and convert the log to changesets based on matching commit log entries and dates. ''' + opts = pycompat.byteskwargs(opts) if opts["new_cache"]: cache = "write" elif opts["update_cache"]: diff -r fb39f6a8a864 -r eb73f8a6177e hgext/convert/filemap.py --- a/hgext/convert/filemap.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/convert/filemap.py Sat Feb 24 17:49:10 2018 -0600 @@ -71,7 +71,7 @@ (lex.infile, lex.lineno, listname, name)) return 1 return 0 - lex = shlex.shlex(open(path), path, True) + lex = shlex.shlex(open(path, 'rb'), path, True) lex.wordchars += '!@#$%^&*()-=+[]{}|;:,./<>?' cmd = lex.get_token() while cmd: diff -r fb39f6a8a864 -r eb73f8a6177e hgext/convert/git.py --- a/hgext/convert/git.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/convert/git.py Sat Feb 24 17:49:10 2018 -0600 @@ -168,19 +168,19 @@ raise error.Abort(_('cannot retrieve git head "%s"') % rev) return heads - def catfile(self, rev, type): + def catfile(self, rev, ftype): if rev == nodemod.nullhex: raise IOError self.catfilepipe[0].write(rev+'\n') self.catfilepipe[0].flush() info = self.catfilepipe[1].readline().split() - if info[1] != type: - raise error.Abort(_('cannot read %r object at %s') % (type, rev)) + if info[1] != ftype: + raise error.Abort(_('cannot read %r object at %s') % (ftype, rev)) size = int(info[2]) data = self.catfilepipe[1].read(size) if len(data) < size: raise error.Abort(_('cannot read %r object at %s: unexpected size') - % (type, rev)) + % (ftype, rev)) # read the trailing newline self.catfilepipe[1].read(1) return data diff -r fb39f6a8a864 -r eb73f8a6177e hgext/convert/hg.py --- a/hgext/convert/hg.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/convert/hg.py Sat Feb 24 17:49:10 2018 -0600 @@ -42,7 +42,7 @@ mapfile = common.mapfile NoRepo = common.NoRepo -sha1re = re.compile(r'\b[0-9a-f]{12,40}\b') +sha1re = re.compile(br'\b[0-9a-f]{12,40}\b') class mercurial_sink(common.converter_sink): def __init__(self, ui, repotype, path): @@ -563,12 +563,7 @@ if copysource in self.ignored: continue # Ignore copy sources not in parent revisions - found = False - for p in parents: - if copysource in p: - found = True - break - if not found: + if not any(copysource in p for p in parents): continue copies[name] = copysource except TypeError: @@ -625,8 +620,8 @@ def converted(self, rev, destrev): if self.convertfp is None: - self.convertfp = open(self.repo.vfs.join('shamap'), 'a') - self.convertfp.write('%s %s\n' % (destrev, rev)) + self.convertfp = open(self.repo.vfs.join('shamap'), 'ab') + self.convertfp.write(util.tonativeeol('%s %s\n' % (destrev, rev))) self.convertfp.flush() def before(self): diff -r fb39f6a8a864 -r eb73f8a6177e hgext/convert/monotone.py --- a/hgext/convert/monotone.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/convert/monotone.py Sat Feb 24 17:49:10 2018 -0600 @@ -13,6 +13,7 @@ from mercurial.i18n import _ from mercurial import ( error, + pycompat, util, ) @@ -36,7 +37,7 @@ if not os.path.exists(os.path.join(path, '_MTN')): # Could be a monotone repository (SQLite db file) try: - f = file(path, 'rb') + f = open(path, 'rb') header = f.read(16) f.close() except IOError: @@ -45,11 +46,11 @@ raise norepo # regular expressions for parsing monotone output - space = r'\s*' - name = r'\s+"((?:\\"|[^"])*)"\s*' + space = br'\s*' + name = br'\s+"((?:\\"|[^"])*)"\s*' value = name - revision = r'\s+\[(\w+)\]\s*' - lines = r'(?:.|\n)+' + revision = br'\s+\[(\w+)\]\s*' + lines = br'(?:.|\n)+' self.dir_re = re.compile(space + "dir" + name) self.file_re = re.compile(space + "file" + name + @@ -84,11 +85,12 @@ return self.mtnrunsingle(*args, **kwargs) def mtnrunsingle(self, *args, **kwargs): - kwargs['d'] = self.path + kwargs[r'd'] = self.path return self.run0('automate', *args, **kwargs) def mtnrunstdio(self, *args, **kwargs): # Prepare the command in automate stdio format + kwargs = pycompat.byteskwargs(kwargs) command = [] for k, v in kwargs.iteritems(): command.append("%s:%s" % (len(k), k)) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/convert/subversion.py --- a/hgext/convert/subversion.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/convert/subversion.py Sat Feb 24 17:49:10 2018 -0600 @@ -231,7 +231,7 @@ def httpcheck(ui, path, proto): try: opener = urlreq.buildopener() - rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path)) + rsp = opener.open('%s://%s/!svn/ver/0/.svn' % (proto, path), 'rb') data = rsp.read() except urlerr.httperror as inst: if inst.code != 404: @@ -384,7 +384,7 @@ def setrevmap(self, revmap): lastrevs = {} - for revid in revmap.iterkeys(): + for revid in revmap: uuid, module, revnum = revsplit(revid) lastrevnum = lastrevs.setdefault(module, revnum) if revnum > lastrevnum: @@ -639,8 +639,9 @@ return if self.convertfp is None: self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'), - 'a') - self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev))) + 'ab') + self.convertfp.write(util.tonativeeol('%s %d\n' + % (destrev, self.revnum(rev)))) self.convertfp.flush() def revid(self, revnum, module=None): @@ -1158,7 +1159,7 @@ if created: hook = os.path.join(created, 'hooks', 'pre-revprop-change') - fp = open(hook, 'w') + fp = open(hook, 'wb') fp.write(pre_revprop_change) fp.close() util.setflags(hook, False, True) @@ -1308,8 +1309,8 @@ self.setexec = [] fd, messagefile = tempfile.mkstemp(prefix='hg-convert-') - fp = os.fdopen(fd, pycompat.sysstr('w')) - fp.write(commit.desc) + fp = os.fdopen(fd, pycompat.sysstr('wb')) + fp.write(util.tonativeeol(commit.desc)) fp.close() try: output = self.run0('commit', diff -r fb39f6a8a864 -r eb73f8a6177e hgext/extdiff.py --- a/hgext/extdiff.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/extdiff.py Sat Feb 24 17:49:10 2018 -0600 @@ -88,12 +88,12 @@ configtable = {} configitem = registrar.configitem(configtable) -configitem('extdiff', r'opts\..*', +configitem('extdiff', br'opts\..*', default='', generic=True, ) -configitem('diff-tools', r'.*\.diffargs$', +configitem('diff-tools', br'.*\.diffargs$', default=None, generic=True, ) @@ -256,8 +256,8 @@ cmdutil.export(repo, [repo[node1a].rev(), repo[node2].rev()], fntemplate=repo.vfs.reljoin(tmproot, template), match=matcher) - label1a = cmdutil.makefilename(repo, template, node1a) - label2 = cmdutil.makefilename(repo, template, node2) + label1a = cmdutil.makefilename(repo[node1a], template) + label2 = cmdutil.makefilename(repo[node2], template) dir1a = repo.vfs.reljoin(tmproot, label1a) dir2 = repo.vfs.reljoin(tmproot, label2) dir1b = None @@ -279,13 +279,13 @@ return pre + util.shellquote(replace[key]) # Match parent2 first, so 'parent1?' will match both parent1 and parent - regex = (r'''(['"]?)([^\s'"$]*)''' - r'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1') + regex = (br'''(['"]?)([^\s'"$]*)''' + br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1') if not do3way and not re.search(regex, cmdline): cmdline += ' $parent1 $child' cmdline = re.sub(regex, quote, cmdline) - ui.debug('running %r in %s\n' % (cmdline, tmproot)) + ui.debug('running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot)) ui.system(cmdline, cwd=tmproot, blockedtag='extdiff') for copy_fn, working_fn, st in fnsandstat: @@ -366,7 +366,7 @@ # We can't pass non-ASCII through docstrings (and path is # in an unknown encoding anyway) docpath = util.escapestr(path) - self.__doc__ = self.__doc__ % {'path': util.uirepr(docpath)} + self.__doc__ %= {r'path': pycompat.sysstr(util.uirepr(docpath))} self._cmdline = cmdline def __call__(self, ui, repo, *pats, **opts): diff -r fb39f6a8a864 -r eb73f8a6177e hgext/githelp.py --- a/hgext/githelp.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/githelp.py Sat Feb 24 17:49:10 2018 -0600 @@ -22,6 +22,7 @@ from mercurial.i18n import _ from mercurial import ( + encoding, error, fancyopts, registrar, @@ -109,7 +110,7 @@ self.args = [] self.opts = {} - def __str__(self): + def __bytes__(self): cmd = "hg " + self.name if self.opts: for k, values in sorted(self.opts.iteritems()): @@ -123,6 +124,8 @@ cmd += " ".join(self.args) return cmd + __str__ = encoding.strmethod(__bytes__) + def append(self, value): self.args.append(value) @@ -167,14 +170,14 @@ ui.status(_("note: use hg addremove to remove files that have " "been deleted.\n\n")) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def am(ui, repo, *args, **kwargs): cmdoptions=[ ] args, opts = parseoptions(ui, cmdoptions, args) cmd = Command('import') - ui.status(str(cmd), "\n") + ui.status(bytes(cmd), "\n") def apply(ui, repo, *args, **kwargs): cmdoptions = [ @@ -187,7 +190,7 @@ cmd['-p'] = opts.get('p') cmd.extend(args) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def bisect(ui, repo, *args, **kwargs): ui.status(_("See 'hg help bisect' for how to use bisect.\n\n")) @@ -198,7 +201,7 @@ args, opts = parseoptions(ui, cmdoptions, args) cmd = Command('annotate -udl') cmd.extend([convert(v) for v in args]) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def branch(ui, repo, *args, **kwargs): cmdoptions = [ @@ -239,7 +242,7 @@ cmd.append(args[0]) elif len(args) == 1: cmd.append(args[0]) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def ispath(repo, string): """ @@ -330,7 +333,7 @@ else: raise error.Abort("a commit must be specified") - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def cherrypick(ui, repo, *args, **kwargs): cmdoptions = [ @@ -352,7 +355,7 @@ else: cmd.extend(args) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def clean(ui, repo, *args, **kwargs): cmdoptions = [ @@ -367,7 +370,7 @@ cmd['--all'] = None cmd.extend(args) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def clone(ui, repo, *args, **kwargs): cmdoptions = [ @@ -397,7 +400,7 @@ cocmd.append(opts.get('branch')) cmd = cmd & cocmd - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def commit(ui, repo, *args, **kwargs): cmdoptions = [ @@ -445,7 +448,7 @@ cmd.extend(args) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def deprecated(ui, repo, *args, **kwargs): ui.warn(_('This command has been deprecated in the git project, ' + @@ -476,7 +479,7 @@ except Exception: cmd.append(a) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def difftool(ui, repo, *args, **kwargs): ui.status(_('Mercurial does not enable external difftool by default. You ' @@ -509,7 +512,7 @@ else: cmd['-r'] = v - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def grep(ui, repo, *args, **kwargs): cmdoptions = [ @@ -522,7 +525,7 @@ # pattern first, followed by paths. cmd.extend(args) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def init(ui, repo, *args, **kwargs): cmdoptions = [ @@ -534,7 +537,7 @@ if len(args) > 0: cmd.append(args[0]) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def log(ui, repo, *args, **kwargs): cmdoptions = [ @@ -588,7 +591,7 @@ del args[0] cmd.extend(args) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def lsfiles(ui, repo, *args, **kwargs): cmdoptions = [ @@ -624,7 +627,7 @@ for include in args: cmd['-I'] = util.shellquote(include) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def merge(ui, repo, *args, **kwargs): cmdoptions = [ @@ -636,7 +639,7 @@ if len(args) > 0: cmd.append(args[len(args) - 1]) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def mergebase(ui, repo, *args, **kwargs): cmdoptions = [] @@ -650,7 +653,7 @@ ui.status(_('NOTE: ancestors() is part of the revset language.\n'), _("Learn more about revsets with 'hg help revsets'\n\n")) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def mergetool(ui, repo, *args, **kwargs): cmdoptions = [] @@ -661,7 +664,7 @@ if len(args) == 0: cmd['--all'] = None cmd.extend(args) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def mv(ui, repo, *args, **kwargs): cmdoptions = [ @@ -675,7 +678,7 @@ if opts.get('force'): cmd['-f'] = None - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def pull(ui, repo, *args, **kwargs): cmdoptions = [ @@ -701,7 +704,7 @@ else: cmd['-r'] = v - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def push(ui, repo, *args, **kwargs): cmdoptions = [ @@ -728,7 +731,7 @@ if opts.get('force'): cmd['-f'] = None - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def rebase(ui, repo, *args, **kwargs): cmdoptions = [ @@ -748,12 +751,12 @@ if len(args) > 0: ui.status(_("also note: 'hg histedit' will automatically detect" " your stack, so no second argument is necessary.\n\n")) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") return if opts.get('skip'): cmd = Command('revert --all -r .') - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") cmd = Command('rebase') @@ -777,7 +780,7 @@ cmd['-d'] = convert(args[0]) cmd['-b'] = convert(args[1]) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def reflog(ui, repo, *args, **kwargs): cmdoptions = [ @@ -791,7 +794,7 @@ if len(args) > 0: cmd.append(args[0]) - ui.status(str(cmd), "\n\n") + ui.status(bytes(cmd), "\n\n") ui.status(_("note: in hg commits can be deleted from repo but we always" " have backups.\n")) @@ -819,7 +822,7 @@ cmd.append(commit) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def revert(ui, repo, *args, **kwargs): cmdoptions = [ @@ -834,7 +837,7 @@ if args: cmd.append(args[0]) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def revparse(ui, repo, *args, **kwargs): cmdoptions = [ @@ -847,7 +850,7 @@ cmd = Command('root') if opts.get('show_cdup'): ui.status(_("note: hg root prints the root of the repository\n\n")) - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") else: ui.status(_("note: see hg help revset for how to refer to commits\n")) @@ -866,7 +869,7 @@ if opts.get('dry_run'): cmd['-n'] = None - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def show(ui, repo, *args, **kwargs): cmdoptions = [ @@ -898,7 +901,7 @@ else: cmd = Command('export') - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def stash(ui, repo, *args, **kwargs): cmdoptions = [ @@ -934,7 +937,7 @@ elif len(args) > 1: cmd['--name'] = args[1] - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def status(ui, repo, *args, **kwargs): cmdoptions = [ @@ -948,7 +951,7 @@ if opts.get('ignored'): cmd['-i'] = None - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def svn(ui, repo, *args, **kwargs): svncmd = args[0] @@ -965,7 +968,7 @@ cmd = Command('push') - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def svnfetch(ui, repo, *args, **kwargs): cmdoptions = [ @@ -975,7 +978,7 @@ cmd = Command('pull') cmd.append('default-push') - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def svnfindrev(ui, repo, *args, **kwargs): cmdoptions = [ @@ -985,7 +988,7 @@ cmd = Command('log') cmd['-r'] = args[0] - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def svnrebase(ui, repo, *args, **kwargs): cmdoptions = [ @@ -1000,7 +1003,7 @@ cmd = pullcmd & rebasecmd - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") def tag(ui, repo, *args, **kwargs): cmdoptions = [ @@ -1024,7 +1027,7 @@ if opts.get('force'): cmd['-f'] = None - ui.status((str(cmd)), "\n") + ui.status((bytes(cmd)), "\n") gitcommands = { 'add': add, diff -r fb39f6a8a864 -r eb73f8a6177e hgext/gpg.py --- a/hgext/gpg.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/gpg.py Sat Feb 24 17:49:10 2018 -0600 @@ -153,8 +153,7 @@ # warn for expired key and/or sigs for key in keys: if key[0] == "ERRSIG": - ui.write(_("%s Unknown key ID \"%s\"\n") - % (prefix, shortkey(ui, key[1][:15]))) + ui.write(_("%s Unknown key ID \"%s\"\n") % (prefix, key[1])) continue if key[0] == "BADSIG": ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2])) @@ -320,13 +319,6 @@ except ValueError as inst: raise error.Abort(str(inst)) -def shortkey(ui, key): - if len(key) != 16: - ui.debug("key ID \"%s\" format error\n" % key) - return key - - return key[-8:] - def node2txt(repo, node, ver): """map a manifest into some text""" if ver == "0": diff -r fb39f6a8a864 -r eb73f8a6177e hgext/histedit.py --- a/hgext/histedit.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/histedit.py Sat Feb 24 17:49:10 2018 -0600 @@ -344,7 +344,7 @@ fp.write('v1\n') fp.write('%s\n' % node.hex(self.parentctxnode)) fp.write('%s\n' % node.hex(self.topmost)) - fp.write('%s\n' % self.keep) + fp.write('%s\n' % ('True' if self.keep else 'False')) fp.write('%d\n' % len(self.actions)) for action in self.actions: fp.write('%s\n' % action.tostate()) @@ -491,7 +491,7 @@ repo.dirstate.setbranch(rulectx.branch()) if stats and stats[3] > 0: buf = repo.ui.popbuffer() - repo.ui.write(*buf) + repo.ui.write(buf) raise error.InterventionRequired( _('Fix up the change (%s %s)') % (self.verb, node.short(self.node)), @@ -1415,9 +1415,8 @@ # Save edit rules in .hg/histedit-last-edit.txt in case # the user needs to ask for help after something # surprising happens. - f = open(repo.vfs.join('histedit-last-edit.txt'), 'w') - f.write(rules) - f.close() + with repo.vfs('histedit-last-edit.txt', 'wb') as f: + f.write(rules) return rules diff -r fb39f6a8a864 -r eb73f8a6177e hgext/journal.py --- a/hgext/journal.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/journal.py Sat Feb 24 17:49:10 2018 -0600 @@ -29,14 +29,13 @@ hg, localrepo, lock, + logcmdutil, node, pycompat, registrar, util, ) -from . import share - cmdtable = {} command = registrar.command(cmdtable) @@ -168,7 +167,7 @@ """Copy shared journal entries into this repo when unsharing""" if (repo.path == repopath and repo.shared() and util.safehasattr(repo, 'journal')): - sharedrepo = share._getsrcrepo(repo) + sharedrepo = hg.sharedreposource(repo) sharedfeatures = _readsharedfeatures(repo) if sharedrepo and sharedfeatures > {'journal'}: # there is a shared repository and there are shared journal entries @@ -257,7 +256,7 @@ self.sharedfeatures = self.sharedvfs = None if repo.shared(): features = _readsharedfeatures(repo) - sharedrepo = share._getsrcrepo(repo) + sharedrepo = hg.sharedreposource(repo) if sharedrepo is not None and 'journal' in features: self.sharedvfs = sharedrepo.vfs self.sharedfeatures = features @@ -478,7 +477,7 @@ displayname = "'%s'" % name ui.status(_("previous locations of %s:\n") % displayname) - limit = cmdutil.loglimit(opts) + limit = logcmdutil.getlimit(opts) entry = None ui.pager('journal') for count, entry in enumerate(repo.journal.filtered(name=name)): @@ -502,7 +501,7 @@ fm.write('command', ' %s\n', entry.command) if opts.get("commits"): - displayer = cmdutil.show_changeset(ui, repo, opts, buffered=False) + displayer = logcmdutil.changesetdisplayer(ui, repo, opts) for hash in entry.newhashes: try: ctx = repo[hash] diff -r fb39f6a8a864 -r eb73f8a6177e hgext/keyword.py --- a/hgext/keyword.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/keyword.py Sat Feb 24 17:49:10 2018 -0600 @@ -101,6 +101,7 @@ extensions, filelog, localrepo, + logcmdutil, match, patch, pathutil, @@ -254,7 +255,7 @@ '''Replaces keywords in data with expanded template.''' def kwsub(mobj): kw = mobj.group(1) - ct = cmdutil.makelogtemplater(self.ui, self.repo, + ct = logcmdutil.maketemplater(self.ui, self.repo, self.templates[kw]) self.ui.pushbuffer() ct.show(ctx, root=self.repo.root, file=path) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/largefiles/lfcommands.py --- a/hgext/largefiles/lfcommands.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/largefiles/lfcommands.py Sat Feb 24 17:49:10 2018 -0600 @@ -365,7 +365,7 @@ at = 0 ui.debug("sending statlfile command for %d largefiles\n" % len(files)) retval = store.exists(files) - files = filter(lambda h: not retval[h], files) + files = [h for h in files if not retval[h]] ui.debug("%d largefiles need to be uploaded\n" % len(files)) for hash in files: diff -r fb39f6a8a864 -r eb73f8a6177e hgext/largefiles/lfutil.py --- a/hgext/largefiles/lfutil.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/largefiles/lfutil.py Sat Feb 24 17:49:10 2018 -0600 @@ -15,6 +15,7 @@ import stat from mercurial.i18n import _ +from mercurial.node import hex from mercurial import ( dirstate, @@ -371,7 +372,7 @@ for data in instream: hasher.update(data) outfile.write(data) - return hasher.hexdigest() + return hex(hasher.digest()) def hashfile(file): if not os.path.exists(file): @@ -404,7 +405,7 @@ h = hashlib.sha1() for chunk in util.filechunkiter(fileobj): h.update(chunk) - return h.hexdigest() + return hex(h.digest()) def httpsendfile(ui, filename): return httpconnection.httpsendfile(ui, filename, 'rb') diff -r fb39f6a8a864 -r eb73f8a6177e hgext/largefiles/overrides.py --- a/hgext/largefiles/overrides.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/largefiles/overrides.py Sat Feb 24 17:49:10 2018 -0600 @@ -19,6 +19,7 @@ cmdutil, error, hg, + logcmdutil, match as matchmod, pathutil, pycompat, @@ -41,7 +42,7 @@ matcher''' m = copy.copy(match) lfile = lambda f: lfutil.standin(f) in manifest - m._files = filter(lfile, m._files) + m._files = [lf for lf in m._files if lfile(lf)] m._fileset = set(m._files) m.always = lambda: False origmatchfn = m.matchfn @@ -56,7 +57,7 @@ m = copy.copy(match) notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in manifest or f in excluded) - m._files = filter(notlfile, m._files) + m._files = [lf for lf in m._files if notlfile(lf)] m._fileset = set(m._files) m.always = lambda: False origmatchfn = m.matchfn @@ -388,20 +389,20 @@ # (2) to determine what files to print out diffs for. # The magic matchandpats override should be used for case (1) but not for # case (2). - def overridemakelogfilematcher(repo, pats, opts, badfn=None): + def overridemakefilematcher(repo, pats, opts, badfn=None): wctx = repo[None] match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn) - return lambda rev: match + return lambda ctx: match oldmatchandpats = installmatchandpatsfn(overridematchandpats) - oldmakelogfilematcher = cmdutil._makenofollowlogfilematcher - setattr(cmdutil, '_makenofollowlogfilematcher', overridemakelogfilematcher) + oldmakefilematcher = logcmdutil._makenofollowfilematcher + setattr(logcmdutil, '_makenofollowfilematcher', overridemakefilematcher) try: return orig(ui, repo, *pats, **opts) finally: restorematchandpatsfn() - setattr(cmdutil, '_makenofollowlogfilematcher', oldmakelogfilematcher) + setattr(logcmdutil, '_makenofollowfilematcher', oldmakefilematcher) def overrideverify(orig, ui, repo, *pats, **opts): large = opts.pop(r'large', False) @@ -1237,10 +1238,11 @@ matchfn = m.matchfn m.matchfn = lambda f: f in s.deleted and matchfn(f) - removelargefiles(repo.ui, repo, True, m, **opts) + removelargefiles(repo.ui, repo, True, m, **pycompat.strkwargs(opts)) # Call into the normal add code, and any files that *should* be added as # largefiles will be - added, bad = addlargefiles(repo.ui, repo, True, matcher, **opts) + added, bad = addlargefiles(repo.ui, repo, True, matcher, + **pycompat.strkwargs(opts)) # Now that we've handled largefiles, hand off to the original addremove # function to take care of the rest. Make sure it doesn't do anything with # largefiles by passing a matcher that will ignore them. @@ -1358,8 +1360,7 @@ m.visitdir = lfvisitdirfn for f in ctx.walk(m): - with cmdutil.makefileobj(repo, opts.get('output'), ctx.node(), - pathname=f) as fp: + with cmdutil.makefileobj(ctx, opts.get('output'), pathname=f) as fp: lf = lfutil.splitstandin(f) if lf is None or origmatchfn(f): # duplicating unreachable code from commands.cat diff -r fb39f6a8a864 -r eb73f8a6177e hgext/largefiles/proto.py --- a/hgext/largefiles/proto.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/largefiles/proto.py Sat Feb 24 17:49:10 2018 -0600 @@ -14,6 +14,7 @@ httppeer, util, wireproto, + wireprototypes, ) from . import ( @@ -34,27 +35,26 @@ def putlfile(repo, proto, sha): '''Server command for putting a largefile into a repository's local store and into the user cache.''' - proto.redirect() - - path = lfutil.storepath(repo, sha) - util.makedirs(os.path.dirname(path)) - tmpfp = util.atomictempfile(path, createmode=repo.store.createmode) + with proto.mayberedirectstdio() as output: + path = lfutil.storepath(repo, sha) + util.makedirs(os.path.dirname(path)) + tmpfp = util.atomictempfile(path, createmode=repo.store.createmode) - try: - proto.getfile(tmpfp) - tmpfp._fp.seek(0) - if sha != lfutil.hexsha1(tmpfp._fp): - raise IOError(0, _('largefile contents do not match hash')) - tmpfp.close() - lfutil.linktousercache(repo, sha) - except IOError as e: - repo.ui.warn(_('largefiles: failed to put %s into store: %s\n') % - (sha, e.strerror)) - return wireproto.pushres(1) - finally: - tmpfp.discard() + try: + proto.forwardpayload(tmpfp) + tmpfp._fp.seek(0) + if sha != lfutil.hexsha1(tmpfp._fp): + raise IOError(0, _('largefile contents do not match hash')) + tmpfp.close() + lfutil.linktousercache(repo, sha) + except IOError as e: + repo.ui.warn(_('largefiles: failed to put %s into store: %s\n') % + (sha, e.strerror)) + return wireproto.pushres(1, output.getvalue() if output else '') + finally: + tmpfp.discard() - return wireproto.pushres(0) + return wireproto.pushres(0, output.getvalue() if output else '') def getlfile(repo, proto, sha): '''Server command for retrieving a largefile from the repository-local @@ -86,8 +86,8 @@ server side.''' filename = lfutil.findfile(repo, sha) if not filename: - return '2\n' - return '0\n' + return wireprototypes.bytesresponse('2\n') + return wireprototypes.bytesresponse('0\n') def wirereposetup(ui, repo): class lfileswirerepository(repo.__class__): @@ -180,7 +180,7 @@ args[r'cmds'] = args[r'cmds'].replace('heads ', 'lheads ') return ssholdcallstream(self, cmd, **args) -headsre = re.compile(r'(^|;)heads\b') +headsre = re.compile(br'(^|;)heads\b') def httprepocallstream(self, cmd, **args): if cmd == 'heads' and self.capable('largefiles'): diff -r fb39f6a8a864 -r eb73f8a6177e hgext/largefiles/storefactory.py --- a/hgext/largefiles/storefactory.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/largefiles/storefactory.py Sat Feb 24 17:49:10 2018 -0600 @@ -80,7 +80,7 @@ 'ssh': [wirestore.wirestore], } -_scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://') +_scheme_re = re.compile(br'^([a-zA-Z0-9+-.]+)://') def getlfile(ui, hash): return util.chunkbuffer(openstore(ui=ui)._get(hash)) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/largefiles/uisetup.py --- a/hgext/largefiles/uisetup.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/largefiles/uisetup.py Sat Feb 24 17:49:10 2018 -0600 @@ -165,13 +165,13 @@ overrides.openlargefile) # create the new wireproto commands ... - wireproto.commands['putlfile'] = (proto.putlfile, 'sha') - wireproto.commands['getlfile'] = (proto.getlfile, 'sha') - wireproto.commands['statlfile'] = (proto.statlfile, 'sha') + wireproto.wireprotocommand('putlfile', 'sha')(proto.putlfile) + wireproto.wireprotocommand('getlfile', 'sha')(proto.getlfile) + wireproto.wireprotocommand('statlfile', 'sha')(proto.statlfile) + wireproto.wireprotocommand('lheads', '')(wireproto.heads) # ... and wrap some existing ones - wireproto.commands['heads'] = (proto.heads, '') - wireproto.commands['lheads'] = (wireproto.heads, '') + wireproto.commands['heads'].func = proto.heads # make putlfile behave the same as push and {get,stat}lfile behave # the same as pull w.r.t. permissions checks @@ -185,9 +185,9 @@ # can't do this in reposetup because it needs to have happened before # wirerepo.__init__ is called - proto.ssholdcallstream = sshpeer.sshpeer._callstream + proto.ssholdcallstream = sshpeer.sshv1peer._callstream proto.httpoldcallstream = httppeer.httppeer._callstream - sshpeer.sshpeer._callstream = proto.sshrepocallstream + sshpeer.sshv1peer._callstream = proto.sshrepocallstream httppeer.httppeer._callstream = proto.httprepocallstream # override some extensions' stuff as well diff -r fb39f6a8a864 -r eb73f8a6177e hgext/lfs/__init__.py --- a/hgext/lfs/__init__.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/lfs/__init__.py Sat Feb 24 17:49:10 2018 -0600 @@ -192,6 +192,7 @@ command = registrar.command(cmdtable) templatekeyword = registrar.templatekeyword() +filesetpredicate = registrar.filesetpredicate() def featuresetup(ui, supported): # don't die on seeing a repo with the lfs requirement @@ -211,7 +212,7 @@ class lfsrepo(repo.__class__): @localrepo.unfilteredmethod def commitctx(self, ctx, error=False): - repo.svfs.options['lfstrack'] = _trackedmatcher(self, ctx) + repo.svfs.options['lfstrack'] = _trackedmatcher(self) return super(lfsrepo, self).commitctx(ctx, error) repo.__class__ = lfsrepo @@ -238,7 +239,7 @@ else: repo.prepushoutgoinghooks.add('lfs', wrapper.prepush) -def _trackedmatcher(repo, ctx): +def _trackedmatcher(repo): """Return a function (path, size) -> bool indicating whether or not to track a given file with lfs.""" if not repo.wvfs.exists('.hglfs'): @@ -331,6 +332,8 @@ wrapfunction(hg, 'clone', wrapper.hgclone) wrapfunction(hg, 'postshare', wrapper.hgpostshare) + scmutil.fileprefetchhooks.add('lfs', wrapper._prefetchfiles) + # Make bundle choose changegroup3 instead of changegroup2. This affects # "hg bundle" command. Note: it does not cover all bundle formats like # "packed1". Using "packed1" with lfs will likely cause trouble. @@ -345,12 +348,21 @@ # when writing a bundle via "hg bundle" command, upload related LFS blobs wrapfunction(bundle2, 'writenewbundle', wrapper.writenewbundle) +@filesetpredicate('lfs()', callstatus=True) +def lfsfileset(mctx, x): + """File that uses LFS storage.""" + # i18n: "lfs" is a keyword + fileset.getargs(x, 0, 0, _("lfs takes no arguments")) + return [f for f in mctx.subset + if wrapper.pointerfromctx(mctx.ctx, f, removed=True) is not None] + @templatekeyword('lfs_files') def lfsfiles(repo, ctx, **args): - """List of strings. LFS files added or modified by the changeset.""" + """List of strings. All files modified, added, or removed by this + changeset.""" args = pycompat.byteskwargs(args) - pointers = wrapper.pointersfromctx(ctx) # {path: pointer} + pointers = wrapper.pointersfromctx(ctx, removed=True) # {path: pointer} files = sorted(pointers.keys()) def pointer(v): @@ -361,7 +373,7 @@ makemap = lambda v: { 'file': v, - 'lfsoid': pointers[v].oid(), + 'lfsoid': pointers[v].oid() if pointers[v] else None, 'lfspointer': templatekw.hybriddict(pointer(v)), } diff -r fb39f6a8a864 -r eb73f8a6177e hgext/lfs/blobstore.py --- a/hgext/lfs/blobstore.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/lfs/blobstore.py Sat Feb 24 17:49:10 2018 -0600 @@ -194,11 +194,11 @@ def writebatch(self, pointers, fromstore): """Batch upload from local to remote blobstore.""" - self._batch(pointers, fromstore, 'upload') + self._batch(_deduplicate(pointers), fromstore, 'upload') def readbatch(self, pointers, tostore): """Batch download from remote to local blostore.""" - self._batch(pointers, tostore, 'download') + self._batch(_deduplicate(pointers), tostore, 'download') def _batchrequest(self, pointers, action): """Get metadata about objects pointed by pointers for given action @@ -366,12 +366,23 @@ oids = transfer(sorted(objects, key=lambda o: o.get('oid'))) processed = 0 + blobs = 0 for _one, oid in oids: processed += sizes[oid] + blobs += 1 self.ui.progress(topic, processed, total=total) self.ui.note(_('lfs: processed: %s\n') % oid) self.ui.progress(topic, pos=None, total=total) + if blobs > 0: + if action == 'upload': + self.ui.status(_('lfs: uploaded %d files (%s)\n') + % (blobs, util.bytecount(processed))) + # TODO: coalesce the download requests, and comment this in + #elif action == 'download': + # self.ui.status(_('lfs: downloaded %d files (%s)\n') + # % (blobs, util.bytecount(processed))) + def __del__(self): # copied from mercurial/httppeer.py urlopener = getattr(self, 'urlopener', None) @@ -388,13 +399,13 @@ self.vfs = lfsvfs(fullpath) def writebatch(self, pointers, fromstore): - for p in pointers: + for p in _deduplicate(pointers): content = fromstore.read(p.oid(), verify=True) with self.vfs(p.oid(), 'wb', atomictemp=True) as fp: fp.write(content) def readbatch(self, pointers, tostore): - for p in pointers: + for p in _deduplicate(pointers): with self.vfs(p.oid(), 'rb') as fp: tostore.download(p.oid(), fp) @@ -433,6 +444,13 @@ None: _promptremote, } +def _deduplicate(pointers): + """Remove any duplicate oids that exist in the list""" + reduced = util.sortdict() + for p in pointers: + reduced[p.oid()] = p + return reduced.values() + def _verify(oid, content): realoid = hashlib.sha256(content).hexdigest() if realoid != oid: diff -r fb39f6a8a864 -r eb73f8a6177e hgext/lfs/wrapper.py --- a/hgext/lfs/wrapper.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/lfs/wrapper.py Sat Feb 24 17:49:10 2018 -0600 @@ -249,6 +249,21 @@ if 'lfs' in destrepo.requirements: destrepo.vfs.append('hgrc', util.tonativeeol('\n[extensions]\nlfs=\n')) +def _prefetchfiles(repo, ctx, files): + """Ensure that required LFS blobs are present, fetching them as a group if + needed.""" + pointers = [] + localstore = repo.svfs.lfslocalblobstore + + for f in files: + p = pointerfromctx(ctx, f) + if p and not localstore.has(p.oid()): + p.filename = f + pointers.append(p) + + if pointers: + repo.svfs.lfsremoteblobstore.readbatch(pointers, localstore) + def _canskipupload(repo): # if remotestore is a null store, upload is a no-op and can be skipped return isinstance(repo.svfs.lfsremoteblobstore, blobstore._nullremote) @@ -307,20 +322,47 @@ pointers[p.oid()] = p return sorted(pointers.values()) -def pointersfromctx(ctx): - """return a dict {path: pointer} for given single changectx""" +def pointerfromctx(ctx, f, removed=False): + """return a pointer for the named file from the given changectx, or None if + the file isn't LFS. + + Optionally, the pointer for a file deleted from the context can be returned. + Since no such pointer is actually stored, and to distinguish from a non LFS + file, this pointer is represented by an empty dict. + """ + _ctx = ctx + if f not in ctx: + if not removed: + return None + if f in ctx.p1(): + _ctx = ctx.p1() + elif f in ctx.p2(): + _ctx = ctx.p2() + else: + return None + fctx = _ctx[f] + if not _islfs(fctx.filelog(), fctx.filenode()): + return None + try: + p = pointer.deserialize(fctx.rawdata()) + if ctx == _ctx: + return p + return {} + except pointer.InvalidPointer as ex: + raise error.Abort(_('lfs: corrupted pointer (%s@%s): %s\n') + % (f, short(_ctx.node()), ex)) + +def pointersfromctx(ctx, removed=False): + """return a dict {path: pointer} for given single changectx. + + If ``removed`` == True and the LFS file was removed from ``ctx``, the value + stored for the path is an empty dict. + """ result = {} for f in ctx.files(): - if f not in ctx: - continue - fctx = ctx[f] - if not _islfs(fctx.filelog(), fctx.filenode()): - continue - try: - result[f] = pointer.deserialize(fctx.rawdata()) - except pointer.InvalidPointer as ex: - raise error.Abort(_('lfs: corrupted pointer (%s@%s): %s\n') - % (f, short(ctx.node()), ex)) + p = pointerfromctx(ctx, f, removed=removed) + if p is not None: + result[f] = p return result def uploadblobs(repo, pointers): diff -r fb39f6a8a864 -r eb73f8a6177e hgext/mq.py --- a/hgext/mq.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/mq.py Sat Feb 24 17:49:10 2018 -0600 @@ -86,6 +86,7 @@ hg, localrepo, lock as lockmod, + logcmdutil, patch as patchmod, phases, pycompat, @@ -93,7 +94,7 @@ revsetlang, scmutil, smartset, - subrepo, + subrepoutil, util, vfs as vfsmod, ) @@ -148,9 +149,13 @@ class statusentry(object): def __init__(self, node, name): self.node, self.name = node, name - def __repr__(self): + + def __bytes__(self): return hex(self.node) + ':' + self.name + __str__ = encoding.strmethod(__bytes__) + __repr__ = encoding.strmethod(__bytes__) + # The order of the headers in 'hg export' HG patches: HGHEADERS = [ # '# HG changeset patch', @@ -276,7 +281,7 @@ nodeid = None diffstart = 0 - for line in file(pf): + for line in open(pf, 'rb'): line = line.rstrip() if (line.startswith('diff --git') or (diffstart and line.startswith('+++ '))): @@ -391,12 +396,14 @@ self.comments.append('') self.comments.append(message) - def __str__(self): + def __bytes__(self): s = '\n'.join(self.comments).rstrip() if not s: return '' return s + '\n\n' + __str__ = encoding.strmethod(__bytes__) + def _delmsg(self): '''Remove existing message, keeping the rest of the comments fields. If comments contains 'subject: ', message will prepend @@ -438,9 +445,9 @@ def __init__(self, ui, baseui, path, patchdir=None): self.basepath = path try: - fh = open(os.path.join(path, 'patches.queue')) - cur = fh.read().rstrip() - fh.close() + with open(os.path.join(path, 'patches.queue'), r'rb') as fh: + cur = fh.read().rstrip() + if not cur: curpath = os.path.join(path, 'patches') else: @@ -546,10 +553,8 @@ for patchfn in patches: patchf = self.opener(patchfn, 'r') # if the patch was a git patch, refresh it as a git patch - for line in patchf: - if line.startswith('diff --git'): - diffopts.git = True - break + diffopts.git = any(line.startswith('diff --git') + for line in patchf) patchf.close() return diffopts @@ -643,7 +648,7 @@ self.seriesdirty = True def pushable(self, idx): - if isinstance(idx, str): + if isinstance(idx, bytes): idx = self.series.index(idx) patchguards = self.seriesguards[idx] if not patchguards: @@ -691,12 +696,12 @@ def savedirty(self): def writelist(items, path): - fp = self.opener(path, 'w') + fp = self.opener(path, 'wb') for i in items: fp.write("%s\n" % i) fp.close() if self.applieddirty: - writelist(map(str, self.applied), self.statuspath) + writelist(map(bytes, self.applied), self.statuspath) self.applieddirty = False if self.seriesdirty: writelist(self.fullseries, self.seriespath) @@ -739,8 +744,8 @@ opts = {} stat = opts.get('stat') m = scmutil.match(repo[node1], files, opts) - cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m, - changes, stat, fp) + logcmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m, + changes, stat, fp) def mergeone(self, repo, mergeq, head, patch, rev, diffopts): # first try just applying the patch @@ -850,7 +855,7 @@ files=files, eolmode=None) return (True, list(files), fuzz) except Exception as inst: - self.ui.note(str(inst) + '\n') + self.ui.note(util.forcebytestr(inst) + '\n') if not self.ui.verbose: self.ui.warn(_("patch failed, unable to continue (try -v)\n")) self.ui.traceback() @@ -963,8 +968,8 @@ wctx = repo[None] pctx = repo['.'] overwrite = False - mergedsubstate = subrepo.submerge(repo, pctx, wctx, wctx, - overwrite) + mergedsubstate = subrepoutil.submerge(repo, pctx, wctx, wctx, + overwrite) files += mergedsubstate.keys() match = scmutil.matchfiles(repo, files or []) @@ -1178,7 +1183,7 @@ except error.Abort: pass i += 1 - name = '%s__%s' % (namebase, i) + name = '%s__%d' % (namebase, i) return name def checkkeepchanges(self, keepchanges, force): @@ -1189,6 +1194,7 @@ """options: msg: a string or a no-argument function returning a string """ + opts = pycompat.byteskwargs(opts) msg = opts.get('msg') edit = opts.get('edit') editform = opts.get('editform', 'mq.qnew') @@ -1259,13 +1265,13 @@ if user: ph.setuser(user) if date: - ph.setdate('%s %s' % date) + ph.setdate('%d %d' % date) ph.setparent(hex(nctx.p1().node())) msg = nctx.description().strip() if msg == defaultmsg.strip(): msg = '' ph.setmessage(msg) - p.write(str(ph)) + p.write(bytes(ph)) if commitfiles: parent = self.qparents(repo, n) if inclsubs: @@ -1550,12 +1556,8 @@ update = True else: parents = [p.node() for p in repo[None].parents()] - needupdate = False - for entry in self.applied[start:]: - if entry.node in parents: - needupdate = True - break - update = needupdate + update = any(entry.node in parents + for entry in self.applied[start:]) tobackup = set() if update: @@ -1632,6 +1634,7 @@ self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts) def refresh(self, repo, pats=None, **opts): + opts = pycompat.byteskwargs(opts) if not self.applied: self.ui.write(_("no patches applied\n")) return 1 @@ -1846,7 +1849,7 @@ self.putsubstate2changes(substatestate, c) chunks = patchmod.diff(repo, patchparent, changes=c, opts=diffopts) - comments = str(ph) + comments = bytes(ph) if comments: patchf.write(comments) for chunk in chunks: @@ -2260,7 +2263,7 @@ To stop managing a patch and move it into permanent history, use the :hg:`qfinish` command.""" q = repo.mq - q.delete(repo, patches, opts) + q.delete(repo, patches, pycompat.byteskwargs(opts)) q.savedirty() return 0 @@ -3189,7 +3192,7 @@ guards[g] += 1 if ui.verbose: guards['NONE'] = noguards - guards = guards.items() + guards = list(guards.items()) guards.sort(key=lambda x: x[0][1:]) if guards: ui.note(_('guards in series file:\n')) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/narrow/TODO.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/narrow/TODO.rst Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,37 @@ +Integration with the share extension needs improvement. Right now +we've seen some odd bugs, and the way we modify the contents of the +.hg/shared file is unfortunate. See wrappostshare() and unsharenarrowspec(). + +Resolve commentary on narrowrepo.wraprepo.narrowrepository.status +about the filtering of status being done at an awkward layer. This +came up the import to hgext, but nobody's got concrete improvement +ideas as of then. + +Fold most (or preferably all) of narrowrevlog.py into core. + +Address commentary in narrowrevlog.excludedmanifestrevlog.add - +specifically we should improve the collaboration with core so that +add() never gets called on an excluded directory and we can improve +the stand-in to raise a ProgrammingError. + +Figure out how to correctly produce narrowmanifestrevlog and +narrowfilelog instances instead of monkeypatching regular revlogs at +runtime to our subclass. Even better, merge the narrowing logic +directly into core. + +Reason more completely about rename-filtering logic in +narrowfilelog. There could be some surprises lurking there. + +Formally document the narrowspec format. Unify with sparse, if at all +possible. For bonus points, unify with the server-specified narrowspec +format. + +narrowrepo.setnarrowpats() or narrowspec.save() need to make sure +they're holding the wlock. + +Implement a simple version of the expandnarrow wireproto command for +core. Having configurable shorthands for narrowspecs has been useful +at Google (and sparse has a similar feature from Facebook), so it +probably makes sense to implement the feature in core. (Google's +handler is entirely custom to Google, with a custom format related to +bazel's build language, so it's not in the narrowhg distribution.) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/narrow/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/narrow/__init__.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,95 @@ +# __init__.py - 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. +'''create clones which fetch history data for subset of files (EXPERIMENTAL)''' + +from __future__ import absolute_import + +# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. +testedwith = 'ships-with-hg-core' + +from mercurial import ( + extensions, + hg, + localrepo, + registrar, + verify as verifymod, +) + +from . import ( + narrowbundle2, + narrowchangegroup, + narrowcommands, + narrowcopies, + narrowdirstate, + narrowmerge, + narrowpatch, + narrowrepo, + narrowrevlog, + narrowtemplates, + narrowwirepeer, +) + +configtable = {} +configitem = registrar.configitem(configtable) +# Narrowhg *has* support for serving ellipsis nodes (which are used at +# least by Google's internal server), but that support is pretty +# fragile and has a lot of problems on real-world repositories that +# have complex graph topologies. This could probably be corrected, but +# absent someone needing the full support for ellipsis nodes in +# repositories with merges, it's unlikely this work will get done. As +# of this writining in late 2017, all repositories large enough for +# ellipsis nodes to be a hard requirement also enforce strictly linear +# history for other scaling reasons. +configitem('experimental', 'narrowservebrokenellipses', + default=False, + alias=[('narrow', 'serveellipses')], +) + +# Export the commands table for Mercurial to see. +cmdtable = narrowcommands.table + +localrepo.localrepository._basesupported.add(narrowrepo.REQUIREMENT) + +def uisetup(ui): + """Wraps user-facing mercurial commands with narrow-aware versions.""" + narrowrevlog.setup() + narrowbundle2.setup() + narrowmerge.setup() + narrowcommands.setup() + narrowchangegroup.setup() + narrowwirepeer.uisetup() + +def reposetup(ui, repo): + """Wraps local repositories with narrow repo support.""" + if not isinstance(repo, localrepo.localrepository): + return + + if narrowrepo.REQUIREMENT in repo.requirements: + narrowrepo.wraprepo(repo, True) + narrowcopies.setup(repo) + narrowdirstate.setup(repo) + narrowpatch.setup(repo) + narrowwirepeer.reposetup(repo) + +def _verifierinit(orig, self, repo, matcher=None): + # The verifier's matcher argument was desgined for narrowhg, so it should + # be None from core. If another extension passes a matcher (unlikely), + # we'll have to fail until matchers can be composed more easily. + assert matcher is None + matcher = getattr(repo, 'narrowmatch', lambda: None)() + orig(self, repo, matcher) + +def extsetup(ui): + extensions.wrapfunction(verifymod.verifier, '__init__', _verifierinit) + extensions.wrapfunction(hg, 'postshare', narrowrepo.wrappostshare) + extensions.wrapfunction(hg, 'copystore', narrowrepo.unsharenarrowspec) + +templatekeyword = narrowtemplates.templatekeyword +revsetpredicate = narrowtemplates.revsetpredicate diff -r fb39f6a8a864 -r eb73f8a6177e hgext/narrow/narrowbundle2.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/narrow/narrowbundle2.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,494 @@ +# narrowbundle2.py - bundle2 extensions for narrow repository support +# +# 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. + +from __future__ import absolute_import + +import collections +import errno +import struct + +from mercurial.i18n import _ +from mercurial.node import ( + bin, + nullid, + nullrev, +) +from mercurial import ( + bundle2, + changegroup, + dagutil, + error, + exchange, + extensions, + narrowspec, + repair, + util, + wireproto, +) + +from . import ( + narrowrepo, +) + +NARROWCAP = 'narrow' +_NARROWACL_SECTION = 'narrowhgacl' +_CHANGESPECPART = NARROWCAP + ':changespec' +_SPECPART = NARROWCAP + ':spec' +_SPECPART_INCLUDE = 'include' +_SPECPART_EXCLUDE = 'exclude' +_KILLNODESIGNAL = 'KILL' +_DONESIGNAL = 'DONE' +_ELIDEDCSHEADER = '>20s20s20sl' # cset id, p1, p2, len(text) +_ELIDEDMFHEADER = '>20s20s20s20sl' # manifest id, p1, p2, link id, len(text) +_CSHEADERSIZE = struct.calcsize(_ELIDEDCSHEADER) +_MFHEADERSIZE = struct.calcsize(_ELIDEDMFHEADER) + +# When advertising capabilities, always include narrow clone support. +def getrepocaps_narrow(orig, repo, **kwargs): + caps = orig(repo, **kwargs) + caps[NARROWCAP] = ['v0'] + return caps + +def _computeellipsis(repo, common, heads, known, match, depth=None): + """Compute the shape of a narrowed DAG. + + Args: + repo: The repository we're transferring. + common: The roots of the DAG range we're transferring. + May be just [nullid], which means all ancestors of heads. + heads: The heads of the DAG range we're transferring. + match: The narrowmatcher that allows us to identify relevant changes. + depth: If not None, only consider nodes to be full nodes if they are at + most depth changesets away from one of heads. + + Returns: + A tuple of (visitnodes, relevant_nodes, ellipsisroots) where: + + visitnodes: The list of nodes (either full or ellipsis) which + need to be sent to the client. + relevant_nodes: The set of changelog nodes which change a file inside + the narrowspec. The client needs these as non-ellipsis nodes. + ellipsisroots: A dict of {rev: parents} that is used in + narrowchangegroup to produce ellipsis nodes with the + correct parents. + """ + cl = repo.changelog + mfl = repo.manifestlog + + cldag = dagutil.revlogdag(cl) + # dagutil does not like nullid/nullrev + commonrevs = cldag.internalizeall(common - set([nullid])) | set([nullrev]) + headsrevs = cldag.internalizeall(heads) + if depth: + revdepth = {h: 0 for h in headsrevs} + + ellipsisheads = collections.defaultdict(set) + ellipsisroots = collections.defaultdict(set) + + def addroot(head, curchange): + """Add a root to an ellipsis head, splitting heads with 3 roots.""" + ellipsisroots[head].add(curchange) + # Recursively split ellipsis heads with 3 roots by finding the + # roots' youngest common descendant which is an elided merge commit. + # That descendant takes 2 of the 3 roots as its own, and becomes a + # root of the head. + while len(ellipsisroots[head]) > 2: + child, roots = splithead(head) + splitroots(head, child, roots) + head = child # Recurse in case we just added a 3rd root + + def splitroots(head, child, roots): + ellipsisroots[head].difference_update(roots) + ellipsisroots[head].add(child) + ellipsisroots[child].update(roots) + ellipsisroots[child].discard(child) + + def splithead(head): + r1, r2, r3 = sorted(ellipsisroots[head]) + for nr1, nr2 in ((r2, r3), (r1, r3), (r1, r2)): + mid = repo.revs('sort(merge() & %d::%d & %d::%d, -rev)', + nr1, head, nr2, head) + for j in mid: + if j == nr2: + return nr2, (nr1, nr2) + if j not in ellipsisroots or len(ellipsisroots[j]) < 2: + return j, (nr1, nr2) + raise error.Abort('Failed to split up ellipsis node! head: %d, ' + 'roots: %d %d %d' % (head, r1, r2, r3)) + + missing = list(cl.findmissingrevs(common=commonrevs, heads=headsrevs)) + visit = reversed(missing) + relevant_nodes = set() + visitnodes = [cl.node(m) for m in missing] + required = set(headsrevs) | known + for rev in visit: + clrev = cl.changelogrevision(rev) + ps = cldag.parents(rev) + if depth is not None: + curdepth = revdepth[rev] + for p in ps: + revdepth[p] = min(curdepth + 1, revdepth.get(p, depth + 1)) + needed = False + shallow_enough = depth is None or revdepth[rev] <= depth + if shallow_enough: + curmf = mfl[clrev.manifest].read() + if ps: + # We choose to not trust the changed files list in + # changesets because it's not always correct. TODO: could + # we trust it for the non-merge case? + p1mf = mfl[cl.changelogrevision(ps[0]).manifest].read() + needed = bool(curmf.diff(p1mf, match)) + if not needed and len(ps) > 1: + # For merge changes, the list of changed files is not + # helpful, since we need to emit the merge if a file + # in the narrow spec has changed on either side of the + # merge. As a result, we do a manifest diff to check. + p2mf = mfl[cl.changelogrevision(ps[1]).manifest].read() + needed = bool(curmf.diff(p2mf, match)) + else: + # For a root node, we need to include the node if any + # files in the node match the narrowspec. + needed = any(curmf.walk(match)) + + if needed: + for head in ellipsisheads[rev]: + addroot(head, rev) + for p in ps: + required.add(p) + relevant_nodes.add(cl.node(rev)) + else: + if not ps: + ps = [nullrev] + if rev in required: + for head in ellipsisheads[rev]: + addroot(head, rev) + for p in ps: + ellipsisheads[p].add(rev) + else: + for p in ps: + ellipsisheads[p] |= ellipsisheads[rev] + + # add common changesets as roots of their reachable ellipsis heads + for c in commonrevs: + for head in ellipsisheads[c]: + addroot(head, c) + return visitnodes, relevant_nodes, ellipsisroots + +def _packellipsischangegroup(repo, common, match, relevant_nodes, + ellipsisroots, visitnodes, depth, source, version): + if version in ('01', '02'): + raise error.Abort( + 'ellipsis nodes require at least cg3 on client and server, ' + 'but negotiated version %s' % version) + # We wrap cg1packer.revchunk, using a side channel to pass + # relevant_nodes into that area. Then if linknode isn't in the + # set, we know we have an ellipsis node and we should defer + # sending that node's data. We override close() to detect + # pending ellipsis nodes and flush them. + packer = changegroup.getbundler(version, repo) + # Let the packer have access to the narrow matcher so it can + # omit filelogs and dirlogs as needed + packer._narrow_matcher = lambda : match + # Give the packer the list of nodes which should not be + # ellipsis nodes. We store this rather than the set of nodes + # that should be an ellipsis because for very large histories + # we expect this to be significantly smaller. + packer.full_nodes = relevant_nodes + # Maps ellipsis revs to their roots at the changelog level. + packer.precomputed_ellipsis = ellipsisroots + # Maps CL revs to per-revlog revisions. Cleared in close() at + # the end of each group. + packer.clrev_to_localrev = {} + packer.next_clrev_to_localrev = {} + # Maps changelog nodes to changelog revs. Filled in once + # during changelog stage and then left unmodified. + packer.clnode_to_rev = {} + packer.changelog_done = False + # If true, informs the packer that it is serving shallow content and might + # need to pack file contents not introduced by the changes being packed. + packer.is_shallow = depth is not None + + return packer.generate(common, visitnodes, False, source) + +# Serve a changegroup for a client with a narrow clone. +def getbundlechangegrouppart_narrow(bundler, repo, source, + bundlecaps=None, b2caps=None, heads=None, + common=None, **kwargs): + cgversions = b2caps.get('changegroup') + if cgversions: # 3.1 and 3.2 ship with an empty value + cgversions = [v for v in cgversions + if v in changegroup.supportedoutgoingversions(repo)] + if not cgversions: + raise ValueError(_('no common changegroup version')) + version = max(cgversions) + else: + raise ValueError(_("server does not advertise changegroup version," + " can't negotiate support for ellipsis nodes")) + + include = sorted(filter(bool, kwargs.get(r'includepats', []))) + exclude = sorted(filter(bool, kwargs.get(r'excludepats', []))) + newmatch = narrowspec.match(repo.root, include=include, exclude=exclude) + if not repo.ui.configbool("experimental", "narrowservebrokenellipses"): + outgoing = exchange._computeoutgoing(repo, heads, common) + if not outgoing.missing: + return + def wrappedgetbundler(orig, *args, **kwargs): + bundler = orig(*args, **kwargs) + bundler._narrow_matcher = lambda : newmatch + return bundler + with extensions.wrappedfunction(changegroup, 'getbundler', + wrappedgetbundler): + cg = changegroup.makestream(repo, outgoing, version, source) + part = bundler.newpart('changegroup', data=cg) + part.addparam('version', version) + if 'treemanifest' in repo.requirements: + part.addparam('treemanifest', '1') + + if include or exclude: + narrowspecpart = bundler.newpart(_SPECPART) + if include: + narrowspecpart.addparam( + _SPECPART_INCLUDE, '\n'.join(include), mandatory=True) + if exclude: + narrowspecpart.addparam( + _SPECPART_EXCLUDE, '\n'.join(exclude), mandatory=True) + + return + + depth = kwargs.get(r'depth', None) + if depth is not None: + depth = int(depth) + if depth < 1: + raise error.Abort(_('depth must be positive, got %d') % depth) + + heads = set(heads or repo.heads()) + common = set(common or [nullid]) + oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', []))) + oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', []))) + known = {bin(n) for n in kwargs.get(r'known', [])} + if known and (oldinclude != include or oldexclude != exclude): + # Steps: + # 1. Send kill for "$known & ::common" + # + # 2. Send changegroup for ::common + # + # 3. Proceed. + # + # In the future, we can send kills for only the specific + # nodes we know should go away or change shape, and then + # send a data stream that tells the client something like this: + # + # a) apply this changegroup + # b) apply nodes XXX, YYY, ZZZ that you already have + # c) goto a + # + # until they've built up the full new state. + # Convert to revnums and intersect with "common". The client should + # have made it a subset of "common" already, but let's be safe. + known = set(repo.revs("%ln & ::%ln", known, common)) + # TODO: we could send only roots() of this set, and the + # list of nodes in common, and the client could work out + # what to strip, instead of us explicitly sending every + # single node. + deadrevs = known + def genkills(): + for r in deadrevs: + yield _KILLNODESIGNAL + yield repo.changelog.node(r) + yield _DONESIGNAL + bundler.newpart(_CHANGESPECPART, data=genkills()) + newvisit, newfull, newellipsis = _computeellipsis( + repo, set(), common, known, newmatch) + if newvisit: + cg = _packellipsischangegroup( + repo, common, newmatch, newfull, newellipsis, + newvisit, depth, source, version) + part = bundler.newpart('changegroup', data=cg) + part.addparam('version', version) + if 'treemanifest' in repo.requirements: + part.addparam('treemanifest', '1') + + visitnodes, relevant_nodes, ellipsisroots = _computeellipsis( + repo, common, heads, set(), newmatch, depth=depth) + + repo.ui.debug('Found %d relevant revs\n' % len(relevant_nodes)) + if visitnodes: + cg = _packellipsischangegroup( + repo, common, newmatch, relevant_nodes, ellipsisroots, + visitnodes, depth, source, version) + part = bundler.newpart('changegroup', data=cg) + part.addparam('version', version) + if 'treemanifest' in repo.requirements: + part.addparam('treemanifest', '1') + +def applyacl_narrow(repo, kwargs): + ui = repo.ui + username = ui.shortuser(ui.environ.get('REMOTE_USER') or ui.username()) + user_includes = ui.configlist( + _NARROWACL_SECTION, username + '.includes', + ui.configlist(_NARROWACL_SECTION, 'default.includes')) + user_excludes = ui.configlist( + _NARROWACL_SECTION, username + '.excludes', + ui.configlist(_NARROWACL_SECTION, 'default.excludes')) + if not user_includes: + raise error.Abort(_("{} configuration for user {} is empty") + .format(_NARROWACL_SECTION, username)) + + user_includes = [ + 'path:.' if p == '*' else 'path:' + p for p in user_includes] + user_excludes = [ + 'path:.' if p == '*' else 'path:' + p for p in user_excludes] + + req_includes = set(kwargs.get(r'includepats', [])) + req_excludes = set(kwargs.get(r'excludepats', [])) + + req_includes, req_excludes, invalid_includes = narrowspec.restrictpatterns( + req_includes, req_excludes, user_includes, user_excludes) + + if invalid_includes: + raise error.Abort( + _("The following includes are not accessible for {}: {}") + .format(username, invalid_includes)) + + new_args = {} + new_args.update(kwargs) + new_args['includepats'] = req_includes + if req_excludes: + new_args['excludepats'] = req_excludes + return new_args + +@bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE)) +def _handlechangespec_2(op, inpart): + includepats = set(inpart.params.get(_SPECPART_INCLUDE, '').splitlines()) + excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, '').splitlines()) + narrowspec.save(op.repo, includepats, excludepats) + if not narrowrepo.REQUIREMENT in op.repo.requirements: + op.repo.requirements.add(narrowrepo.REQUIREMENT) + op.repo._writerequirements() + op.repo.invalidate(clearfilecache=True) + +@bundle2.parthandler(_CHANGESPECPART) +def _handlechangespec(op, inpart): + repo = op.repo + cl = repo.changelog + + # changesets which need to be stripped entirely. either they're no longer + # needed in the new narrow spec, or the server is sending a replacement + # in the changegroup part. + clkills = set() + + # A changespec part contains all the updates to ellipsis nodes + # that will happen as a result of widening or narrowing a + # repo. All the changes that this block encounters are ellipsis + # nodes or flags to kill an existing ellipsis. + chunksignal = changegroup.readexactly(inpart, 4) + while chunksignal != _DONESIGNAL: + if chunksignal == _KILLNODESIGNAL: + # a node used to be an ellipsis but isn't anymore + ck = changegroup.readexactly(inpart, 20) + if cl.hasnode(ck): + clkills.add(ck) + else: + raise error.Abort( + _('unexpected changespec node chunk type: %s') % chunksignal) + chunksignal = changegroup.readexactly(inpart, 4) + + if clkills: + # preserve bookmarks that repair.strip() would otherwise strip + bmstore = repo._bookmarks + class dummybmstore(dict): + def applychanges(self, repo, tr, changes): + pass + def recordchange(self, tr): # legacy version + pass + repo._bookmarks = dummybmstore() + chgrpfile = repair.strip(op.ui, repo, list(clkills), backup=True, + topic='widen') + repo._bookmarks = bmstore + if chgrpfile: + # presence of _widen_bundle attribute activates widen handler later + op._widen_bundle = chgrpfile + # Set the new narrowspec if we're widening. The setnewnarrowpats() method + # will currently always be there when using the core+narrowhg server, but + # other servers may include a changespec part even when not widening (e.g. + # because we're deepening a shallow repo). + if util.safehasattr(repo, 'setnewnarrowpats'): + repo.setnewnarrowpats() + +def handlechangegroup_widen(op, inpart): + """Changegroup exchange handler which restores temporarily-stripped nodes""" + # We saved a bundle with stripped node data we must now restore. + # This approach is based on mercurial/repair.py@6ee26a53c111. + repo = op.repo + ui = op.ui + + chgrpfile = op._widen_bundle + del op._widen_bundle + vfs = repo.vfs + + ui.note(_("adding branch\n")) + f = vfs.open(chgrpfile, "rb") + try: + gen = exchange.readbundle(ui, f, chgrpfile, vfs) + if not ui.verbose: + # silence internal shuffling chatter + ui.pushbuffer() + if isinstance(gen, bundle2.unbundle20): + with repo.transaction('strip') as tr: + bundle2.processbundle(repo, gen, lambda: tr) + else: + gen.apply(repo, 'strip', 'bundle:' + vfs.join(chgrpfile), True) + if not ui.verbose: + ui.popbuffer() + finally: + f.close() + + # remove undo files + for undovfs, undofile in repo.undofiles(): + try: + undovfs.unlink(undofile) + except OSError as e: + if e.errno != errno.ENOENT: + ui.warn(_('error removing %s: %s\n') % + (undovfs.join(undofile), str(e))) + + # Remove partial backup only if there were no exceptions + vfs.unlink(chgrpfile) + +def setup(): + """Enable narrow repo support in bundle2-related extension points.""" + extensions.wrapfunction(bundle2, 'getrepocaps', getrepocaps_narrow) + + wireproto.gboptsmap['narrow'] = 'boolean' + wireproto.gboptsmap['depth'] = 'plain' + wireproto.gboptsmap['oldincludepats'] = 'csv' + wireproto.gboptsmap['oldexcludepats'] = 'csv' + wireproto.gboptsmap['includepats'] = 'csv' + wireproto.gboptsmap['excludepats'] = 'csv' + wireproto.gboptsmap['known'] = 'csv' + + # Extend changegroup serving to handle requests from narrow clients. + origcgfn = exchange.getbundle2partsmapping['changegroup'] + def wrappedcgfn(*args, **kwargs): + repo = args[1] + if repo.ui.has_section(_NARROWACL_SECTION): + getbundlechangegrouppart_narrow( + *args, **applyacl_narrow(repo, kwargs)) + elif kwargs.get(r'narrow', False): + getbundlechangegrouppart_narrow(*args, **kwargs) + else: + origcgfn(*args, **kwargs) + exchange.getbundle2partsmapping['changegroup'] = wrappedcgfn + + # Extend changegroup receiver so client can fixup after widen requests. + origcghandler = bundle2.parthandlermapping['changegroup'] + def wrappedcghandler(op, inpart): + origcghandler(op, inpart) + if util.safehasattr(op, '_widen_bundle'): + handlechangegroup_widen(op, inpart) + wrappedcghandler.params = origcghandler.params + bundle2.parthandlermapping['changegroup'] = wrappedcghandler diff -r fb39f6a8a864 -r eb73f8a6177e hgext/narrow/narrowchangegroup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/narrow/narrowchangegroup.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,380 @@ +# narrowchangegroup.py - narrow clone changegroup creation and consumption +# +# 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. + +from __future__ import absolute_import + +from mercurial.i18n import _ +from mercurial import ( + changegroup, + error, + extensions, + manifest, + mdiff, + node, + revlog, + util, +) + +from . import ( + narrowrepo, +) + +def setup(): + + def supportedoutgoingversions(orig, repo): + versions = orig(repo) + if narrowrepo.REQUIREMENT in repo.requirements: + versions.discard('01') + versions.discard('02') + return versions + + extensions.wrapfunction(changegroup, 'supportedoutgoingversions', + supportedoutgoingversions) + + def prune(orig, self, revlog, missing, commonrevs): + if isinstance(revlog, manifest.manifestrevlog): + matcher = getattr(self._repo, 'narrowmatch', + getattr(self, '_narrow_matcher', None)) + if (matcher is not None and + not matcher().visitdir(revlog._dir[:-1] or '.')): + return [] + return orig(self, revlog, missing, commonrevs) + + extensions.wrapfunction(changegroup.cg1packer, 'prune', prune) + + def generatefiles(orig, self, changedfiles, linknodes, commonrevs, + source): + matcher = getattr(self._repo, 'narrowmatch', + getattr(self, '_narrow_matcher', None)) + if matcher is not None: + narrowmatch = matcher() + changedfiles = [f for f in changedfiles if narrowmatch(f)] + if getattr(self, 'is_shallow', False): + # See comment in generate() for why this sadness is a thing. + mfdicts = self._mfdicts + del self._mfdicts + # In a shallow clone, the linknodes callback needs to also include + # those file nodes that are in the manifests we sent but weren't + # introduced by those manifests. + commonctxs = [self._repo[c] for c in commonrevs] + oldlinknodes = linknodes + clrev = self._repo.changelog.rev + def linknodes(flog, fname): + for c in commonctxs: + try: + fnode = c.filenode(fname) + self.clrev_to_localrev[c.rev()] = flog.rev(fnode) + except error.ManifestLookupError: + pass + links = oldlinknodes(flog, fname) + if len(links) != len(mfdicts): + for mf, lr in mfdicts: + fnode = mf.get(fname, None) + if fnode in links: + links[fnode] = min(links[fnode], lr, key=clrev) + elif fnode: + links[fnode] = lr + return links + return orig(self, changedfiles, linknodes, commonrevs, source) + extensions.wrapfunction( + changegroup.cg1packer, 'generatefiles', generatefiles) + + def ellipsisdata(packer, rev, revlog_, p1, p2, data, linknode): + n = revlog_.node(rev) + p1n, p2n = revlog_.node(p1), revlog_.node(p2) + flags = revlog_.flags(rev) + flags |= revlog.REVIDX_ELLIPSIS + meta = packer.builddeltaheader( + n, p1n, p2n, node.nullid, linknode, flags) + # TODO: try and actually send deltas for ellipsis data blocks + diffheader = mdiff.trivialdiffheader(len(data)) + l = len(meta) + len(diffheader) + len(data) + return ''.join((changegroup.chunkheader(l), + meta, + diffheader, + data)) + + def close(orig, self): + getattr(self, 'clrev_to_localrev', {}).clear() + if getattr(self, 'next_clrev_to_localrev', {}): + self.clrev_to_localrev = self.next_clrev_to_localrev + del self.next_clrev_to_localrev + self.changelog_done = True + return orig(self) + extensions.wrapfunction(changegroup.cg1packer, 'close', close) + + # In a perfect world, we'd generate better ellipsis-ified graphs + # for non-changelog revlogs. In practice, we haven't started doing + # that yet, so the resulting DAGs for the manifestlog and filelogs + # are actually full of bogus parentage on all the ellipsis + # nodes. This has the side effect that, while the contents are + # correct, the individual DAGs might be completely out of whack in + # a case like 882681bc3166 and its ancestors (back about 10 + # revisions or so) in the main hg repo. + # + # The one invariant we *know* holds is that the new (potentially + # bogus) DAG shape will be valid if we order the nodes in the + # order that they're introduced in dramatis personae by the + # changelog, so what we do is we sort the non-changelog histories + # by the order in which they are used by the changelog. + def _sortgroup(orig, self, revlog, nodelist, lookup): + if not util.safehasattr(self, 'full_nodes') or not self.clnode_to_rev: + return orig(self, revlog, nodelist, lookup) + key = lambda n: self.clnode_to_rev[lookup(n)] + return [revlog.rev(n) for n in sorted(nodelist, key=key)] + + extensions.wrapfunction(changegroup.cg1packer, '_sortgroup', _sortgroup) + + def generate(orig, self, commonrevs, clnodes, fastpathlinkrev, source): + '''yield a sequence of changegroup chunks (strings)''' + # Note: other than delegating to orig, the only deviation in + # logic from normal hg's generate is marked with BEGIN/END + # NARROW HACK. + if not util.safehasattr(self, 'full_nodes'): + # not sending a narrow bundle + for x in orig(self, commonrevs, clnodes, fastpathlinkrev, source): + yield x + return + + repo = self._repo + cl = repo.changelog + mfl = repo.manifestlog + mfrevlog = mfl._revlog + + clrevorder = {} + mfs = {} # needed manifests + fnodes = {} # needed file nodes + changedfiles = set() + + # Callback for the changelog, used to collect changed files and manifest + # nodes. + # Returns the linkrev node (identity in the changelog case). + def lookupcl(x): + c = cl.read(x) + clrevorder[x] = len(clrevorder) + # BEGIN NARROW HACK + # + # Only update mfs if x is going to be sent. Otherwise we + # end up with bogus linkrevs specified for manifests and + # we skip some manifest nodes that we should otherwise + # have sent. + if x in self.full_nodes or cl.rev(x) in self.precomputed_ellipsis: + n = c[0] + # record the first changeset introducing this manifest version + mfs.setdefault(n, x) + # Set this narrow-specific dict so we have the lowest manifest + # revnum to look up for this cl revnum. (Part of mapping + # changelog ellipsis parents to manifest ellipsis parents) + self.next_clrev_to_localrev.setdefault(cl.rev(x), + mfrevlog.rev(n)) + # We can't trust the changed files list in the changeset if the + # client requested a shallow clone. + if self.is_shallow: + changedfiles.update(mfl[c[0]].read().keys()) + else: + changedfiles.update(c[3]) + # END NARROW HACK + # Record a complete list of potentially-changed files in + # this manifest. + return x + + self._verbosenote(_('uncompressed size of bundle content:\n')) + size = 0 + for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')): + size += len(chunk) + yield chunk + self._verbosenote(_('%8.i (changelog)\n') % size) + + # We need to make sure that the linkrev in the changegroup refers to + # the first changeset that introduced the manifest or file revision. + # The fastpath is usually safer than the slowpath, because the filelogs + # are walked in revlog order. + # + # When taking the slowpath with reorder=None and the manifest revlog + # uses generaldelta, the manifest may be walked in the "wrong" order. + # Without 'clrevorder', we would get an incorrect linkrev (see fix in + # cc0ff93d0c0c). + # + # When taking the fastpath, we are only vulnerable to reordering + # of the changelog itself. The changelog never uses generaldelta, so + # it is only reordered when reorder=True. To handle this case, we + # simply take the slowpath, which already has the 'clrevorder' logic. + # This was also fixed in cc0ff93d0c0c. + fastpathlinkrev = fastpathlinkrev and not self._reorder + # Treemanifests don't work correctly with fastpathlinkrev + # either, because we don't discover which directory nodes to + # send along with files. This could probably be fixed. + fastpathlinkrev = fastpathlinkrev and ( + 'treemanifest' not in repo.requirements) + # Shallow clones also don't work correctly with fastpathlinkrev + # because file nodes may need to be sent for a manifest even if they + # weren't introduced by that manifest. + fastpathlinkrev = fastpathlinkrev and not self.is_shallow + + for chunk in self.generatemanifests(commonrevs, clrevorder, + fastpathlinkrev, mfs, fnodes, source): + yield chunk + # BEGIN NARROW HACK + mfdicts = None + if self.is_shallow: + mfdicts = [(self._repo.manifestlog[n].read(), lr) + for (n, lr) in mfs.iteritems()] + # END NARROW HACK + mfs.clear() + clrevs = set(cl.rev(x) for x in clnodes) + + if not fastpathlinkrev: + def linknodes(unused, fname): + return fnodes.get(fname, {}) + else: + cln = cl.node + def linknodes(filerevlog, fname): + llr = filerevlog.linkrev + fln = filerevlog.node + revs = ((r, llr(r)) for r in filerevlog) + return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs) + + # BEGIN NARROW HACK + # + # We need to pass the mfdicts variable down into + # generatefiles(), but more than one command might have + # wrapped generatefiles so we can't modify the function + # signature. Instead, we pass the data to ourselves using an + # instance attribute. I'm sorry. + self._mfdicts = mfdicts + # END NARROW HACK + for chunk in self.generatefiles(changedfiles, linknodes, commonrevs, + source): + yield chunk + + yield self.close() + + if clnodes: + repo.hook('outgoing', node=node.hex(clnodes[0]), source=source) + extensions.wrapfunction(changegroup.cg1packer, 'generate', generate) + + def revchunk(orig, self, revlog, rev, prev, linknode): + if not util.safehasattr(self, 'full_nodes'): + # not sending a narrow changegroup + for x in orig(self, revlog, rev, prev, linknode): + yield x + return + # build up some mapping information that's useful later. See + # the local() nested function below. + if not self.changelog_done: + self.clnode_to_rev[linknode] = rev + linkrev = rev + self.clrev_to_localrev[linkrev] = rev + else: + linkrev = self.clnode_to_rev[linknode] + self.clrev_to_localrev[linkrev] = rev + # This is a node to send in full, because the changeset it + # corresponds to was a full changeset. + if linknode in self.full_nodes: + for x in orig(self, revlog, rev, prev, linknode): + yield x + return + # At this point, a node can either be one we should skip or an + # ellipsis. If it's not an ellipsis, bail immediately. + if linkrev not in self.precomputed_ellipsis: + return + linkparents = self.precomputed_ellipsis[linkrev] + def local(clrev): + """Turn a changelog revnum into a local revnum. + + The ellipsis dag is stored as revnums on the changelog, + but when we're producing ellipsis entries for + non-changelog revlogs, we need to turn those numbers into + something local. This does that for us, and during the + changelog sending phase will also expand the stored + mappings as needed. + """ + if clrev == node.nullrev: + return node.nullrev + if not self.changelog_done: + # If we're doing the changelog, it's possible that we + # have a parent that is already on the client, and we + # need to store some extra mapping information so that + # our contained ellipsis nodes will be able to resolve + # their parents. + if clrev not in self.clrev_to_localrev: + clnode = revlog.node(clrev) + self.clnode_to_rev[clnode] = clrev + return clrev + # Walk the ellipsis-ized changelog breadth-first looking for a + # change that has been linked from the current revlog. + # + # For a flat manifest revlog only a single step should be necessary + # as all relevant changelog entries are relevant to the flat + # manifest. + # + # For a filelog or tree manifest dirlog however not every changelog + # entry will have been relevant, so we need to skip some changelog + # nodes even after ellipsis-izing. + walk = [clrev] + while walk: + p = walk[0] + walk = walk[1:] + if p in self.clrev_to_localrev: + return self.clrev_to_localrev[p] + elif p in self.full_nodes: + walk.extend([pp for pp in self._repo.changelog.parentrevs(p) + if pp != node.nullrev]) + elif p in self.precomputed_ellipsis: + walk.extend([pp for pp in self.precomputed_ellipsis[p] + if pp != node.nullrev]) + else: + # In this case, we've got an ellipsis with parents + # outside the current bundle (likely an + # incremental pull). We "know" that we can use the + # value of this same revlog at whatever revision + # is pointed to by linknode. "Know" is in scare + # quotes because I haven't done enough examination + # of edge cases to convince myself this is really + # a fact - it works for all the (admittedly + # thorough) cases in our testsuite, but I would be + # somewhat unsurprised to find a case in the wild + # where this breaks down a bit. That said, I don't + # know if it would hurt anything. + for i in xrange(rev, 0, -1): + if revlog.linkrev(i) == clrev: + return i + # We failed to resolve a parent for this node, so + # we crash the changegroup construction. + raise error.Abort( + 'unable to resolve parent while packing %r %r' + ' for changeset %r' % (revlog.indexfile, rev, clrev)) + return node.nullrev + + if not linkparents or ( + revlog.parentrevs(rev) == (node.nullrev, node.nullrev)): + p1, p2 = node.nullrev, node.nullrev + elif len(linkparents) == 1: + p1, = sorted(local(p) for p in linkparents) + p2 = node.nullrev + else: + p1, p2 = sorted(local(p) for p in linkparents) + yield ellipsisdata( + self, rev, revlog, p1, p2, revlog.revision(rev), linknode) + extensions.wrapfunction(changegroup.cg1packer, 'revchunk', revchunk) + + def deltaparent(orig, self, revlog, rev, p1, p2, prev): + if util.safehasattr(self, 'full_nodes'): + # TODO: send better deltas when in narrow mode. + # + # changegroup.group() loops over revisions to send, + # including revisions we'll skip. What this means is that + # `prev` will be a potentially useless delta base for all + # ellipsis nodes, as the client likely won't have it. In + # the future we should do bookkeeping about which nodes + # have been sent to the client, and try to be + # significantly smarter about delta bases. This is + # slightly tricky because this same code has to work for + # all revlogs, and we don't have the linkrev/linknode here. + return p1 + return orig(self, revlog, rev, p1, p2, prev) + extensions.wrapfunction(changegroup.cg2packer, 'deltaparent', deltaparent) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/narrow/narrowcommands.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/narrow/narrowcommands.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,405 @@ +# 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. +from __future__ import absolute_import + +import itertools + +from mercurial.i18n import _ +from mercurial import ( + cmdutil, + commands, + discovery, + error, + exchange, + extensions, + hg, + merge, + narrowspec, + node, + pycompat, + registrar, + repair, + repoview, + util, +) + +from . import ( + narrowbundle2, + narrowrepo, +) + +table = {} +command = registrar.command(table) + +def setup(): + """Wraps user-facing mercurial commands with narrow-aware versions.""" + + entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd) + entry[1].append(('', 'narrow', None, + _("create a narrow clone of select files"))) + entry[1].append(('', 'depth', '', + _("limit the history fetched by distance from heads"))) + # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit + if 'sparse' not in extensions.enabled(): + entry[1].append(('', 'include', [], + _("specifically fetch this file/directory"))) + entry[1].append( + ('', 'exclude', [], + _("do not fetch this file/directory, even if included"))) + + entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd) + entry[1].append(('', 'depth', '', + _("limit the history fetched by distance from heads"))) + + extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd) + +def expandpull(pullop, includepats, excludepats): + if not narrowspec.needsexpansion(includepats): + return includepats, excludepats + + heads = pullop.heads or pullop.rheads + includepats, excludepats = pullop.remote.expandnarrow( + includepats, excludepats, heads) + pullop.repo.ui.debug('Expanded narrowspec to inc=%s, exc=%s\n' % ( + includepats, excludepats)) + return set(includepats), set(excludepats) + +def clonenarrowcmd(orig, ui, repo, *args, **opts): + """Wraps clone command, so 'hg clone' first wraps localrepo.clone().""" + opts = pycompat.byteskwargs(opts) + wrappedextraprepare = util.nullcontextmanager() + opts_narrow = opts['narrow'] + if opts_narrow: + def pullbundle2extraprepare_widen(orig, pullop, kwargs): + # Create narrow spec patterns from clone flags + includepats = narrowspec.parsepatterns(opts['include']) + excludepats = narrowspec.parsepatterns(opts['exclude']) + + # If necessary, ask the server to expand the narrowspec. + includepats, excludepats = expandpull( + pullop, includepats, excludepats) + + if not includepats and excludepats: + # If nothing was included, we assume the user meant to include + # everything, except what they asked to exclude. + includepats = {'path:.'} + + narrowspec.save(pullop.repo, includepats, excludepats) + + # This will populate 'includepats' etc with the values from the + # narrowspec we just saved. + orig(pullop, kwargs) + + if opts.get('depth'): + kwargs['depth'] = opts['depth'] + wrappedextraprepare = extensions.wrappedfunction(exchange, + '_pullbundle2extraprepare', pullbundle2extraprepare_widen) + + def pullnarrow(orig, repo, *args, **kwargs): + narrowrepo.wraprepo(repo.unfiltered(), opts_narrow) + if isinstance(repo, repoview.repoview): + repo.__class__.__bases__ = (repo.__class__.__bases__[0], + repo.unfiltered().__class__) + if opts_narrow: + repo.requirements.add(narrowrepo.REQUIREMENT) + repo._writerequirements() + + return orig(repo, *args, **kwargs) + + wrappedpull = extensions.wrappedfunction(exchange, 'pull', pullnarrow) + + with wrappedextraprepare, wrappedpull: + return orig(ui, repo, *args, **pycompat.strkwargs(opts)) + +def pullnarrowcmd(orig, ui, repo, *args, **opts): + """Wraps pull command to allow modifying narrow spec.""" + wrappedextraprepare = util.nullcontextmanager() + if narrowrepo.REQUIREMENT in repo.requirements: + + def pullbundle2extraprepare_widen(orig, pullop, kwargs): + orig(pullop, kwargs) + if opts.get('depth'): + kwargs['depth'] = opts['depth'] + wrappedextraprepare = extensions.wrappedfunction(exchange, + '_pullbundle2extraprepare', pullbundle2extraprepare_widen) + + with wrappedextraprepare: + return orig(ui, repo, *args, **opts) + +def archivenarrowcmd(orig, ui, repo, *args, **opts): + """Wraps archive command to narrow the default includes.""" + if narrowrepo.REQUIREMENT in repo.requirements: + repo_includes, repo_excludes = repo.narrowpats + includes = set(opts.get(r'include', [])) + excludes = set(opts.get(r'exclude', [])) + includes, excludes, unused_invalid = narrowspec.restrictpatterns( + includes, excludes, repo_includes, repo_excludes) + if includes: + opts[r'include'] = includes + if excludes: + opts[r'exclude'] = excludes + return orig(ui, repo, *args, **opts) + +def pullbundle2extraprepare(orig, pullop, kwargs): + repo = pullop.repo + if narrowrepo.REQUIREMENT not in repo.requirements: + return orig(pullop, kwargs) + + if narrowbundle2.NARROWCAP not in pullop.remotebundle2caps: + raise error.Abort(_("server doesn't support narrow clones")) + orig(pullop, kwargs) + kwargs['narrow'] = True + include, exclude = repo.narrowpats + kwargs['oldincludepats'] = include + kwargs['oldexcludepats'] = exclude + kwargs['includepats'] = include + kwargs['excludepats'] = exclude + kwargs['known'] = [node.hex(ctx.node()) for ctx in + repo.set('::%ln', pullop.common) + if ctx.node() != node.nullid] + if not kwargs['known']: + # Mercurial serialized an empty list as '' and deserializes it as + # [''], so delete it instead to avoid handling the empty string on the + # server. + del kwargs['known'] + +extensions.wrapfunction(exchange,'_pullbundle2extraprepare', + pullbundle2extraprepare) + +def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes, + newincludes, newexcludes, force): + 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() + outgoing = discovery.findcommonoutgoing(unfi, remote, + commoninc=commoninc) + ui.status(_('looking for local changes to affected paths\n')) + localnodes = [] + for n in itertools.chain(outgoing.missing, outgoing.excluded): + if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()): + localnodes.append(n) + revstostrip = unfi.revs('descendants(%ln)', localnodes) + hiddenrevs = repoview.filterrevs(repo, 'visible') + visibletostrip = list(repo.changelog.node(r) + for r in (revstostrip - hiddenrevs)) + if visibletostrip: + ui.status(_('The following changeset(s) or their ancestors have ' + 'local changes not on the remote:\n')) + maxnodes = 10 + if ui.verbose or len(visibletostrip) <= maxnodes: + for n in visibletostrip: + ui.status('%s\n' % node.short(n)) + else: + for n in visibletostrip[:maxnodes]: + ui.status('%s\n' % node.short(n)) + ui.status(_('...and %d more, use --verbose to list all\n') % + (len(visibletostrip) - maxnodes)) + if not force: + raise error.Abort(_('local changes found'), + hint=_('use --force-delete-local-changes to ' + 'ignore')) + + if revstostrip: + tostrip = [unfi.changelog.node(r) for r in revstostrip] + if repo['.'].node() in tostrip: + # stripping working copy, so move to a different commit first + urev = max(repo.revs('(::%n) - %ln + null', + repo['.'].node(), visibletostrip)) + hg.clean(repo, urev) + repair.strip(ui, unfi, tostrip, topic='narrow') + + todelete = [] + for f, f2, size in repo.store.datafiles(): + if f.startswith('data/'): + file = f[5:-2] + if not newmatch(file): + todelete.append(f) + elif f.startswith('meta/'): + dir = f[5:-13] + dirs = ['.'] + sorted(util.dirs({dir})) + [dir] + include = True + for d in dirs: + visit = newmatch.visitdir(d) + if not visit: + include = False + break + if visit == 'all': + break + if not include: + todelete.append(f) + + repo.destroying() + + with repo.transaction("narrowing"): + for f in todelete: + ui.status(_('deleting %s\n') % f) + util.unlinkpath(repo.svfs.join(f)) + repo.store.markremoved(f) + + for f in repo.dirstate: + if not newmatch(f): + repo.dirstate.drop(f) + repo.wvfs.unlinkpath(f) + repo.setnarrowpats(newincludes, newexcludes) + + repo.destroyed() + +def _widen(ui, repo, remote, commoninc, newincludes, newexcludes): + newmatch = narrowspec.match(repo.root, newincludes, newexcludes) + + # TODO(martinvonz): Get expansion working with widening/narrowing. + if narrowspec.needsexpansion(newincludes): + raise error.Abort('Expansion not yet supported on pull') + + def pullbundle2extraprepare_widen(orig, pullop, kwargs): + orig(pullop, kwargs) + # The old{in,ex}cludepats have already been set by orig() + kwargs['includepats'] = newincludes + kwargs['excludepats'] = newexcludes + wrappedextraprepare = extensions.wrappedfunction(exchange, + '_pullbundle2extraprepare', pullbundle2extraprepare_widen) + + # 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) + repo.setnewnarrowpats = setnewnarrowpats + + ds = repo.dirstate + p1, p2 = ds.p1(), ds.p2() + with ds.parentchange(): + ds.setparents(node.nullid, node.nullid) + common = commoninc[0] + with wrappedextraprepare: + exchange.pull(repo, remote, heads=common) + with ds.parentchange(): + ds.setparents(p1, p2) + + actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()} + addgaction = actions['g'].append + + mf = repo['.'].manifest().matches(newmatch) + for f, fn in mf.iteritems(): + if f not in repo.dirstate: + addgaction((f, (mf.flags(f), False), + "add from widened narrow clone")) + + merge.applyupdates(repo, actions, wctx=repo[None], + mctx=repo['.'], overwrite=False) + merge.recordupdates(repo, actions, branchmerge=False) + +# TODO(rdamazio): Make new matcher format and update description +@command('tracked', + [('', 'addinclude', [], _('new paths to include')), + ('', 'removeinclude', [], _('old paths to no longer include')), + ('', 'addexclude', [], _('new paths to exclude')), + ('', 'removeexclude', [], _('old paths to no longer exclude')), + ('', 'clear', False, _('whether to replace the existing narrowspec')), + ('', 'force-delete-local-changes', False, + _('forces deletion of local changes when narrowing')), + ] + commands.remoteopts, + _('[OPTIONS]... [REMOTE]'), + inferrepo=True) +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. + """ + opts = pycompat.byteskwargs(opts) + if narrowrepo.REQUIREMENT not in repo.requirements: + ui.warn(_('The narrow command is only supported on respositories cloned' + ' with --narrow.\n')) + return 1 + + # Before supporting, decide whether it "hg tracked --clear" should mean + # tracking no paths or all paths. + if opts['clear']: + ui.warn(_('The --clear option is not yet supported.\n')) + return 1 + + if narrowspec.needsexpansion(opts['addinclude'] + opts['addexclude']): + raise error.Abort('Expansion not yet supported on widen/narrow') + + addedincludes = narrowspec.parsepatterns(opts['addinclude']) + removedincludes = narrowspec.parsepatterns(opts['removeinclude']) + addedexcludes = narrowspec.parsepatterns(opts['addexclude']) + removedexcludes = narrowspec.parsepatterns(opts['removeexclude']) + widening = addedincludes or removedexcludes + narrowing = removedincludes or addedexcludes + only_show = not widening and not narrowing + + # Only print the current narrowspec. + if only_show: + include, exclude = repo.narrowpats + + ui.pager('tracked') + fm = ui.formatter('narrow', opts) + for i in sorted(include): + fm.startitem() + fm.write('status', '%s ', 'I', label='narrow.included') + fm.write('pat', '%s\n', i, label='narrow.included') + for i in sorted(exclude): + fm.startitem() + fm.write('status', '%s ', 'X', label='narrow.excluded') + fm.write('pat', '%s\n', i, label='narrow.excluded') + fm.end() + return 0 + + 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. + remotepath = ui.expandpath(remotepath or 'default') + url, branches = hg.parseurl(remotepath) + ui.status(_('comparing with %s\n') % util.hidepassword(url)) + remote = hg.peer(repo, opts, url) + commoninc = discovery.findcommonincoming(repo, remote) + + oldincludes, oldexcludes = repo.narrowpats + if narrowing: + newincludes = oldincludes - removedincludes + newexcludes = oldexcludes | addedexcludes + _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes, + newincludes, newexcludes, + opts['force_delete_local_changes']) + # _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 + + if widening: + newincludes = oldincludes | addedincludes + newexcludes = oldexcludes - removedexcludes + _widen(ui, repo, remote, commoninc, newincludes, newexcludes) + + return 0 diff -r fb39f6a8a864 -r eb73f8a6177e hgext/narrow/narrowcopies.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/narrow/narrowcopies.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,35 @@ +# narrowcopies.py - extensions to mercurial copies module to support narrow +# clones +# +# 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. + +from __future__ import absolute_import + +from mercurial import ( + copies, + extensions, + util, +) + +def setup(repo): + def _computeforwardmissing(orig, a, b, match=None): + missing = orig(a, b, match) + if util.safehasattr(repo, 'narrowmatch'): + narrowmatch = repo.narrowmatch() + missing = [f for f in missing if narrowmatch(f)] + return missing + + def _checkcopies(orig, srcctx, dstctx, f, base, tca, remotebase, limit, + data): + if util.safehasattr(repo, 'narrowmatch'): + narrowmatch = repo.narrowmatch() + if not narrowmatch(f): + return + orig(srcctx, dstctx, f, base, tca, remotebase, limit, data) + + extensions.wrapfunction(copies, '_computeforwardmissing', + _computeforwardmissing) + extensions.wrapfunction(copies, '_checkcopies', _checkcopies) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/narrow/narrowdirstate.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/narrow/narrowdirstate.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,82 @@ +# narrowdirstate.py - extensions to mercurial dirstate to support narrow clones +# +# 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. + +from __future__ import absolute_import + +from mercurial.i18n import _ +from mercurial import ( + dirstate, + error, + extensions, + match as matchmod, + narrowspec, + util as hgutil, +) + +def setup(repo): + """Add narrow spec dirstate ignore, block changes outside narrow spec.""" + + def walk(orig, self, match, subrepos, unknown, ignored, full=True, + narrowonly=True): + if narrowonly: + # hack to not exclude explicitly-specified paths so that they can + # be warned later on e.g. dirstate.add() + em = matchmod.exact(match._root, match._cwd, match.files()) + nm = matchmod.unionmatcher([repo.narrowmatch(), em]) + match = matchmod.intersectmatchers(match, nm) + return orig(self, match, subrepos, unknown, ignored, full) + + extensions.wrapfunction(dirstate.dirstate, 'walk', walk) + + # Prevent adding files that are outside the sparse checkout + editfuncs = ['normal', 'add', 'normallookup', 'copy', 'remove', 'merge'] + for func in editfuncs: + def _wrapper(orig, self, *args): + dirstate = repo.dirstate + narrowmatch = repo.narrowmatch() + for f in args: + if f is not None and not narrowmatch(f) and f not in dirstate: + raise error.Abort(_("cannot track '%s' - it is outside " + + "the narrow clone") % f) + return orig(self, *args) + extensions.wrapfunction(dirstate.dirstate, func, _wrapper) + + def filterrebuild(orig, self, parent, allfiles, changedfiles=None): + if changedfiles is None: + # Rebuilding entire dirstate, let's filter allfiles to match the + # narrowspec. + allfiles = [f for f in allfiles if repo.narrowmatch()(f)] + orig(self, parent, allfiles, changedfiles) + + extensions.wrapfunction(dirstate.dirstate, 'rebuild', filterrebuild) + + def _narrowbackupname(backupname): + assert 'dirstate' in backupname + return backupname.replace('dirstate', narrowspec.FILENAME) + + def restorebackup(orig, self, tr, backupname): + self._opener.rename(_narrowbackupname(backupname), narrowspec.FILENAME, + checkambig=True) + orig(self, tr, backupname) + + extensions.wrapfunction(dirstate.dirstate, 'restorebackup', restorebackup) + + def savebackup(orig, self, tr, backupname): + orig(self, tr, backupname) + + narrowbackupname = _narrowbackupname(backupname) + self._opener.tryunlink(narrowbackupname) + hgutil.copyfile(self._opener.join(narrowspec.FILENAME), + self._opener.join(narrowbackupname), hardlink=True) + + extensions.wrapfunction(dirstate.dirstate, 'savebackup', savebackup) + + def clearbackup(orig, self, tr, backupname): + orig(self, tr, backupname) + self._opener.unlink(_narrowbackupname(backupname)) + + extensions.wrapfunction(dirstate.dirstate, 'clearbackup', clearbackup) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/narrow/narrowmerge.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/narrow/narrowmerge.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,78 @@ +# narrowmerge.py - extensions to mercurial merge module to support narrow clones +# +# 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. + +from __future__ import absolute_import + +from mercurial.i18n import _ +from mercurial import ( + copies, + error, + extensions, + merge, + util, +) + +def setup(): + def _manifestmerge(orig, repo, wctx, p2, pa, branchmerge, *args, **kwargs): + """Filter updates to only lay out files that match the narrow spec.""" + actions, diverge, renamedelete = orig( + repo, wctx, p2, pa, branchmerge, *args, **kwargs) + + if not util.safehasattr(repo, 'narrowmatch'): + return actions, diverge, renamedelete + + nooptypes = set(['k']) # TODO: handle with nonconflicttypes + nonconflicttypes = set('a am c cm f g r e'.split()) + narrowmatch = repo.narrowmatch() + # We mutate the items in the dict during iteration, so iterate + # over a copy. + for f, action in list(actions.items()): + if narrowmatch(f): + pass + elif not branchmerge: + del actions[f] # just updating, ignore changes outside clone + elif action[0] in nooptypes: + del actions[f] # merge does not affect file + elif action[0] in nonconflicttypes: + raise error.Abort(_('merge affects file \'%s\' outside narrow, ' + 'which is not yet supported') % f, + hint=_('merging in the other direction ' + 'may work')) + else: + raise error.Abort(_('conflict in file \'%s\' is outside ' + 'narrow clone') % f) + + return actions, diverge, renamedelete + + extensions.wrapfunction(merge, 'manifestmerge', _manifestmerge) + + def _checkcollision(orig, repo, wmf, actions): + if util.safehasattr(repo, 'narrowmatch'): + narrowmatch = repo.narrowmatch() + wmf = wmf.matches(narrowmatch) + if actions: + narrowactions = {} + for m, actionsfortype in actions.iteritems(): + narrowactions[m] = [] + for (f, args, msg) in actionsfortype: + if narrowmatch(f): + narrowactions[m].append((f, args, msg)) + actions = narrowactions + return orig(repo, wmf, actions) + + extensions.wrapfunction(merge, '_checkcollision', _checkcollision) + + def _computenonoverlap(orig, repo, *args, **kwargs): + u1, u2 = orig(repo, *args, **kwargs) + if not util.safehasattr(repo, 'narrowmatch'): + return u1, u2 + + narrowmatch = repo.narrowmatch() + u1 = [f for f in u1 if narrowmatch(f)] + u2 = [f for f in u2 if narrowmatch(f)] + return u1, u2 + extensions.wrapfunction(copies, '_computenonoverlap', _computenonoverlap) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/narrow/narrowpatch.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/narrow/narrowpatch.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,42 @@ +# narrowpatch.py - extensions to mercurial patch module to support narrow clones +# +# 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. + +from __future__ import absolute_import + +from mercurial import ( + extensions, + patch, + util, +) + +def setup(repo): + def _filepairs(orig, *args): + """Only includes files within the narrow spec in the diff.""" + if util.safehasattr(repo, 'narrowmatch'): + narrowmatch = repo.narrowmatch() + for x in orig(*args): + f1, f2, copyop = x + if ((not f1 or narrowmatch(f1)) and + (not f2 or narrowmatch(f2))): + yield x + else: + for x in orig(*args): + yield x + + def trydiff(orig, repo, revs, ctx1, ctx2, modified, added, removed, + copy, getfilectx, *args, **kwargs): + if util.safehasattr(repo, 'narrowmatch'): + narrowmatch = repo.narrowmatch() + modified = [f for f in modified if narrowmatch(f)] + added = [f for f in added if narrowmatch(f)] + removed = [f for f in removed if narrowmatch(f)] + copy = {k: v for k, v in copy.iteritems() if narrowmatch(k)} + return orig(repo, revs, ctx1, ctx2, modified, added, removed, copy, + getfilectx, *args, **kwargs) + + extensions.wrapfunction(patch, '_filepairs', _filepairs) + extensions.wrapfunction(patch, 'trydiff', trydiff) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/narrow/narrowrepo.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/narrow/narrowrepo.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,113 @@ +# narrowrepo.py - repository which supports narrow revlogs, lazy loading +# +# 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. + +from __future__ import absolute_import + +from mercurial import ( + bundlerepo, + hg, + localrepo, + match as matchmod, + narrowspec, + scmutil, +) + +from . import ( + narrowrevlog, +) + +# When narrowing is finalized and no longer subject to format changes, +# we should move this to just "narrow" or similar. +REQUIREMENT = 'narrowhg-experimental' + +def wrappostshare(orig, sourcerepo, destrepo, **kwargs): + orig(sourcerepo, destrepo, **kwargs) + if REQUIREMENT in sourcerepo.requirements: + with destrepo.wlock(): + with destrepo.vfs('shared', 'a') as fp: + fp.write(narrowspec.FILENAME + '\n') + +def unsharenarrowspec(orig, ui, repo, repopath): + if (REQUIREMENT in repo.requirements + and repo.path == repopath and repo.shared()): + srcrepo = hg.sharedreposource(repo) + with srcrepo.vfs(narrowspec.FILENAME) as f: + spec = f.read() + with repo.vfs(narrowspec.FILENAME, 'w') as f: + f.write(spec) + return orig(ui, repo, repopath) + +def wraprepo(repo, opts_narrow): + """Enables narrow clone functionality on a single local repository.""" + + cacheprop = localrepo.storecache + if isinstance(repo, bundlerepo.bundlerepository): + # We have to use a different caching property decorator for + # bundlerepo because storecache blows up in strange ways on a + # bundlerepo. Fortunately, there's no risk of data changing in + # a bundlerepo. + cacheprop = lambda name: localrepo.unfilteredpropertycache + + class narrowrepository(repo.__class__): + + def _constructmanifest(self): + manifest = super(narrowrepository, self)._constructmanifest() + narrowrevlog.makenarrowmanifestrevlog(manifest, repo) + return manifest + + @cacheprop('00manifest.i') + def manifestlog(self): + mfl = super(narrowrepository, self).manifestlog + narrowrevlog.makenarrowmanifestlog(mfl, self) + return mfl + + def file(self, f): + fl = super(narrowrepository, self).file(f) + narrowrevlog.makenarrowfilelog(fl, self.narrowmatch()) + return fl + + @localrepo.repofilecache(narrowspec.FILENAME) + def narrowpats(self): + """matcher patterns for this repository's narrowspec + + A tuple of (includes, excludes). + """ + return narrowspec.load(self) + + @localrepo.repofilecache(narrowspec.FILENAME) + def _narrowmatch(self): + include, exclude = self.narrowpats + if not opts_narrow and not include and not exclude: + return matchmod.always(self.root, '') + return narrowspec.match(self.root, include=include, exclude=exclude) + + # TODO(martinvonz): make this property-like instead? + def narrowmatch(self): + return self._narrowmatch + + def setnarrowpats(self, newincludes, newexcludes): + narrowspec.save(self, newincludes, newexcludes) + self.invalidate(clearfilecache=True) + + # I'm not sure this is the right place to do this filter. + # context._manifestmatches() would probably be better, or perhaps + # move it to a later place, in case some of the callers do want to know + # which directories changed. This seems to work for now, though. + def status(self, *args, **kwargs): + s = super(narrowrepository, self).status(*args, **kwargs) + narrowmatch = self.narrowmatch() + modified = list(filter(narrowmatch, s.modified)) + added = list(filter(narrowmatch, s.added)) + removed = list(filter(narrowmatch, s.removed)) + deleted = list(filter(narrowmatch, s.deleted)) + unknown = list(filter(narrowmatch, s.unknown)) + ignored = list(filter(narrowmatch, s.ignored)) + clean = list(filter(narrowmatch, s.clean)) + return scmutil.status(modified, added, removed, deleted, unknown, + ignored, clean) + + repo.__class__ = narrowrepository diff -r fb39f6a8a864 -r eb73f8a6177e hgext/narrow/narrowrevlog.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/narrow/narrowrevlog.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,187 @@ +# narrowrevlog.py - revlog storing irrelevant nodes as "ellipsis" nodes +# +# 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. + +from __future__ import absolute_import + +from mercurial import ( + error, + manifest, + revlog, + util, +) + +def readtransform(self, text): + return text, False + +def writetransform(self, text): + return text, False + +def rawtransform(self, text): + return False + +revlog.addflagprocessor(revlog.REVIDX_ELLIPSIS, + (readtransform, writetransform, rawtransform)) + +def setup(): + # We just wanted to add the flag processor, which is done at module + # load time. + pass + +class excludeddir(manifest.treemanifest): + """Stand-in for a directory that is excluded from the repository. + + With narrowing active on a repository that uses treemanifests, + some of the directory revlogs will be excluded from the resulting + clone. This is a huge storage win for clients, but means we need + some sort of pseudo-manifest to surface to internals so we can + detect a merge conflict outside the narrowspec. That's what this + class is: it stands in for a directory whose node is known, but + whose contents are unknown. + """ + def __init__(self, dir, node): + super(excludeddir, self).__init__(dir) + self._node = node + # Add an empty file, which will be included by iterators and such, + # appearing as the directory itself (i.e. something like "dir/") + self._files[''] = node + self._flags[''] = 't' + + # Manifests outside the narrowspec should never be modified, so avoid + # copying. This makes a noticeable difference when there are very many + # directories outside the narrowspec. Also, it makes sense for the copy to + # be of the same type as the original, which would not happen with the + # super type's copy(). + def copy(self): + return self + +class excludeddirmanifestctx(manifest.treemanifestctx): + """context wrapper for excludeddir - see that docstring for rationale""" + def __init__(self, dir, node): + self._dir = dir + self._node = node + + def read(self): + return excludeddir(self._dir, self._node) + + def write(self, *args): + raise error.ProgrammingError( + 'attempt to write manifest from excluded dir %s' % self._dir) + +class excludedmanifestrevlog(manifest.manifestrevlog): + """Stand-in for excluded treemanifest revlogs. + + When narrowing is active on a treemanifest repository, we'll have + references to directories we can't see due to the revlog being + skipped. This class exists to conform to the manifestrevlog + interface for those directories and proactively prevent writes to + outside the narrowspec. + """ + + def __init__(self, dir): + self._dir = dir + + def __len__(self): + raise error.ProgrammingError( + 'attempt to get length of excluded dir %s' % self._dir) + + def rev(self, node): + raise error.ProgrammingError( + 'attempt to get rev from excluded dir %s' % self._dir) + + def linkrev(self, node): + raise error.ProgrammingError( + 'attempt to get linkrev from excluded dir %s' % self._dir) + + def node(self, rev): + raise error.ProgrammingError( + 'attempt to get node from excluded dir %s' % self._dir) + + def add(self, *args, **kwargs): + # We should never write entries in dirlogs outside the narrow clone. + # However, the method still gets called from writesubtree() in + # _addtree(), so we need to handle it. We should possibly make that + # avoid calling add() with a clean manifest (_dirty is always False + # in excludeddir instances). + pass + +def makenarrowmanifestrevlog(mfrevlog, repo): + if util.safehasattr(mfrevlog, '_narrowed'): + return + + class narrowmanifestrevlog(mfrevlog.__class__): + # This function is called via debug{revlog,index,data}, but also during + # at least some push operations. This will be used to wrap/exclude the + # child directories when using treemanifests. + def dirlog(self, d): + if d and not d.endswith('/'): + d = d + '/' + if not repo.narrowmatch().visitdir(d[:-1] or '.'): + return excludedmanifestrevlog(d) + result = super(narrowmanifestrevlog, self).dirlog(d) + makenarrowmanifestrevlog(result, repo) + return result + + mfrevlog.__class__ = narrowmanifestrevlog + mfrevlog._narrowed = True + +def makenarrowmanifestlog(mfl, repo): + class narrowmanifestlog(mfl.__class__): + def get(self, dir, node, verify=True): + if not repo.narrowmatch().visitdir(dir[:-1] or '.'): + return excludeddirmanifestctx(dir, node) + return super(narrowmanifestlog, self).get(dir, node, verify=verify) + mfl.__class__ = narrowmanifestlog + +def makenarrowfilelog(fl, narrowmatch): + class narrowfilelog(fl.__class__): + def renamed(self, node): + # Renames that come from outside the narrowspec are + # problematic at least for git-diffs, because we lack the + # base text for the rename. This logic was introduced in + # 3cd72b1 of narrowhg (authored by martinvonz, reviewed by + # adgar), but that revision doesn't have any additional + # commentary on what problems we can encounter. + m = super(narrowfilelog, self).renamed(node) + if m and not narrowmatch(m[0]): + return None + return m + + def size(self, rev): + # We take advantage of the fact that remotefilelog + # lacks a node() method to just skip the + # rename-checking logic when on remotefilelog. This + # might be incorrect on other non-revlog-based storage + # engines, but for now this seems to be fine. + # + # TODO: when remotefilelog is in core, improve this to + # explicitly look for remotefilelog instead of cheating + # with a hasattr check. + if util.safehasattr(self, 'node'): + node = self.node(rev) + # Because renamed() is overridden above to + # sometimes return None even if there is metadata + # in the revlog, size can be incorrect for + # copies/renames, so we need to make sure we call + # the super class's implementation of renamed() + # for the purpose of size calculation. + if super(narrowfilelog, self).renamed(node): + return len(self.read(node)) + return super(narrowfilelog, self).size(rev) + + def cmp(self, node, text): + different = super(narrowfilelog, self).cmp(node, text) + if different: + # Similar to size() above, if the file was copied from + # a file outside the narrowspec, the super class's + # would have returned True because we tricked it into + # thinking that the file was not renamed. + if super(narrowfilelog, self).renamed(node): + t2 = self.read(node) + return t2 != text + return different + + fl.__class__ = narrowfilelog diff -r fb39f6a8a864 -r eb73f8a6177e hgext/narrow/narrowtemplates.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/narrow/narrowtemplates.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,48 @@ +# narrowtemplates.py - added template keywords for narrow clones +# +# 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. + +from __future__ import absolute_import + +from mercurial import ( + registrar, + revlog, + util, +) + +keywords = {} +templatekeyword = registrar.templatekeyword(keywords) +revsetpredicate = registrar.revsetpredicate() + +def _isellipsis(repo, rev): + if repo.changelog.flags(rev) & revlog.REVIDX_ELLIPSIS: + return True + return False + +@templatekeyword('ellipsis') +def ellipsis(repo, ctx, templ, **args): + """:ellipsis: String. 'ellipsis' if the change is an ellipsis node, + else ''.""" + if _isellipsis(repo, ctx.rev()): + return 'ellipsis' + return '' + +@templatekeyword('outsidenarrow') +def outsidenarrow(repo, ctx, templ, **args): + """:outsidenarrow: String. 'outsidenarrow' if the change affects no + tracked files, else ''.""" + if util.safehasattr(repo, 'narrowmatch'): + m = repo.narrowmatch() + if not any(m(f) for f in ctx.files()): + return 'outsidenarrow' + return '' + +@revsetpredicate('ellipsis') +def ellipsisrevset(repo, subset, x): + """``ellipsis()`` + Changesets that are ellipsis nodes. + """ + return subset.filter(lambda r: _isellipsis(repo, r)) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/narrow/narrowwirepeer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/narrow/narrowwirepeer.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,52 @@ +# narrowwirepeer.py - passes narrow spec with unbundle command +# +# 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. + +from __future__ import absolute_import + +from mercurial.i18n import _ +from mercurial import ( + error, + extensions, + hg, + narrowspec, + node, +) + +def uisetup(): + def peersetup(ui, peer): + # We must set up the expansion before reposetup below, since it's used + # at clone time before we have a repo. + class expandingpeer(peer.__class__): + def expandnarrow(self, narrow_include, narrow_exclude, nodes): + ui.status(_("expanding narrowspec\n")) + if not self.capable('exp-expandnarrow'): + raise error.Abort( + 'peer does not support expanding narrowspecs') + + hex_nodes = (node.hex(n) for n in nodes) + new_narrowspec = self._call( + 'expandnarrow', + includepats=','.join(narrow_include), + excludepats=','.join(narrow_exclude), + nodes=','.join(hex_nodes)) + + return narrowspec.parseserverpatterns(new_narrowspec) + peer.__class__ = expandingpeer + hg.wirepeersetupfuncs.append(peersetup) + +def reposetup(repo): + def wirereposetup(ui, peer): + def wrapped(orig, cmd, *args, **kwargs): + if cmd == 'unbundle': + # TODO: don't blindly add include/exclude wireproto + # arguments to unbundle. + include, exclude = repo.narrowpats + kwargs[r"includepats"] = ','.join(include) + kwargs[r"excludepats"] = ','.join(exclude) + return orig(cmd, *args, **kwargs) + extensions.wrapfunction(peer, '_calltwowaystream', wrapped) + hg.wirepeersetupfuncs.append(wirereposetup) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/notify.py --- a/hgext/notify.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/notify.py Sat Feb 24 17:49:10 2018 -0600 @@ -142,8 +142,8 @@ from mercurial.i18n import _ from mercurial import ( - cmdutil, error, + logcmdutil, mail, patch, registrar, @@ -257,9 +257,8 @@ mapfile = self.ui.config('notify', 'style') if not mapfile and not template: template = deftemplates.get(hooktype) or single_template - spec = cmdutil.logtemplatespec(template, mapfile) - self.t = cmdutil.changeset_templater(self.ui, self.repo, spec, - False, None, False) + spec = logcmdutil.templatespec(template, mapfile) + self.t = logcmdutil.changesettemplater(self.ui, self.repo, spec) def strip(self, path): '''strip leading slashes from local path, turn into web-safe path.''' diff -r fb39f6a8a864 -r eb73f8a6177e hgext/patchbomb.py --- a/hgext/patchbomb.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/patchbomb.py Sat Feb 24 17:49:10 2018 -0600 @@ -265,11 +265,10 @@ if patchtags: patchname = patchtags[0] elif total > 1: - patchname = cmdutil.makefilename(repo, '%b-%n.patch', - binnode, seqno=idx, - total=total) + patchname = cmdutil.makefilename(repo[node], '%b-%n.patch', + seqno=idx, total=total) else: - patchname = cmdutil.makefilename(repo, '%b.patch', binnode) + patchname = cmdutil.makefilename(repo[node], '%b.patch') disposition = 'inline' if opts.get('attach'): disposition = 'attachment' diff -r fb39f6a8a864 -r eb73f8a6177e hgext/rebase.py --- a/hgext/rebase.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/rebase.py Sat Feb 24 17:49:10 2018 -0600 @@ -214,7 +214,7 @@ if v >= 0: newrev = repo[v].hex() else: - newrev = v + newrev = "%d" % v destnode = repo[destmap[d]].hex() f.write("%s:%s:%s\n" % (oldrev, newrev, destnode)) repo.ui.debug('rebase status stored\n') @@ -289,7 +289,7 @@ skipped.add(old) seen.add(new) repo.ui.debug('computed skipped revs: %s\n' % - (' '.join(str(r) for r in sorted(skipped)) or None)) + (' '.join('%d' % r for r in sorted(skipped)) or '')) repo.ui.debug('rebase status resumed\n') self.originalwd = originalwd @@ -312,10 +312,13 @@ if not self.ui.configbool('experimental', 'rebaseskipobsolete'): return obsoleteset = set(obsoleterevs) - self.obsoletenotrebased, self.obsoletewithoutsuccessorindestination = \ - _computeobsoletenotrebased(self.repo, obsoleteset, destmap) + (self.obsoletenotrebased, + self.obsoletewithoutsuccessorindestination, + obsoleteextinctsuccessors) = _computeobsoletenotrebased( + self.repo, obsoleteset, destmap) skippedset = set(self.obsoletenotrebased) skippedset.update(self.obsoletewithoutsuccessorindestination) + skippedset.update(obsoleteextinctsuccessors) _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset) def _prepareabortorcontinue(self, isabort): @@ -499,7 +502,8 @@ if not self.collapsef: merging = p2 != nullrev editform = cmdutil.mergeeditform(merging, 'rebase') - editor = cmdutil.getcommiteditor(editform=editform, **opts) + editor = cmdutil.getcommiteditor(editform=editform, + **pycompat.strkwargs(opts)) if self.wctx.isinmemory(): newnode = concludememorynode(repo, rev, p1, p2, wctx=self.wctx, @@ -537,7 +541,7 @@ 'to commit\n') % (rev, ctx)) self.skipped.add(rev) self.state[rev] = p1 - ui.debug('next revision set to %s\n' % p1) + ui.debug('next revision set to %d\n' % p1) else: ui.status(_('already rebased %s as %s\n') % (desc, repo[self.state[rev]])) @@ -585,7 +589,7 @@ date=self.date) if newnode is not None: newrev = repo[newnode].rev() - for oldrev in self.state.iterkeys(): + for oldrev in self.state: self.state[oldrev] = newrev if 'qtip' in repo.tags(): @@ -1220,7 +1224,7 @@ `rebaseobsrevs`: set of obsolete revision in source `rebaseobsskipped`: set of revisions from source skipped because they have - successors in destination + successors in destination or no non-obsolete successor. """ # Obsolete node with successors not in dest leads to divergence divergenceok = ui.configbool('experimental', @@ -1436,7 +1440,7 @@ def isagitpatch(repo, patchname): 'Return true if the given patch is in git format' mqpatch = os.path.join(repo.mq.path, patchname) - for line in patch.linereader(file(mqpatch, 'rb')): + for line in patch.linereader(open(mqpatch, 'rb')): if line.startswith('diff --git'): return True return False @@ -1646,7 +1650,9 @@ roots = list(repo.set('roots(%ld)', sortedsrc[0])) if not roots: raise error.Abort(_('no matching revisions')) - roots.sort() + def revof(r): + return r.rev() + roots = sorted(roots, key=revof) state = dict.fromkeys(rebaseset, revtodo) emptyrebase = (len(sortedsrc) == 1) for root in roots: @@ -1785,25 +1791,34 @@ `obsoletewithoutsuccessorindestination` is a set with obsolete revisions without a successor in destination. + + `obsoleteextinctsuccessors` is a set of obsolete revisions with only + obsolete successors. """ obsoletenotrebased = {} obsoletewithoutsuccessorindestination = set([]) + obsoleteextinctsuccessors = set([]) assert repo.filtername is None cl = repo.changelog nodemap = cl.nodemap + extinctnodes = set(cl.node(r) for r in repo.revs('extinct()')) for srcrev in rebaseobsrevs: srcnode = cl.node(srcrev) destnode = cl.node(destmap[srcrev]) # XXX: more advanced APIs are required to handle split correctly - successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode])) - if len(successors) == 1: - # obsutil.allsuccessors includes node itself. When the list only - # contains one element, it means there are no successors. + successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode])) + # obsutil.allsuccessors includes node itself + successors.remove(srcnode) + if successors.issubset(extinctnodes): + # all successors are extinct + obsoleteextinctsuccessors.add(srcrev) + if not successors: + # no successor obsoletenotrebased[srcrev] = None else: for succnode in successors: - if succnode == srcnode or succnode not in nodemap: + if succnode not in nodemap: continue if cl.isancestor(succnode, destnode): obsoletenotrebased[srcrev] = nodemap[succnode] @@ -1812,11 +1827,14 @@ # If 'srcrev' has a successor in rebase set but none in # destination (which would be catched above), we shall skip it # and its descendants to avoid divergence. - if any(nodemap[s] in destmap - for s in successors if s != srcnode): + if any(nodemap[s] in destmap for s in successors): obsoletewithoutsuccessorindestination.add(srcrev) - return obsoletenotrebased, obsoletewithoutsuccessorindestination + return ( + obsoletenotrebased, + obsoletewithoutsuccessorindestination, + obsoleteextinctsuccessors, + ) def summaryhook(ui, repo): if not repo.vfs.exists('rebasestate'): diff -r fb39f6a8a864 -r eb73f8a6177e hgext/relink.py --- a/hgext/relink.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/relink.py Sat Feb 24 17:49:10 2018 -0600 @@ -168,8 +168,8 @@ source = os.path.join(src, f) tgt = os.path.join(dst, f) # Binary mode, so that read() works correctly, especially on Windows - sfp = file(source, 'rb') - dfp = file(tgt, 'rb') + sfp = open(source, 'rb') + dfp = open(tgt, 'rb') sin = sfp.read(CHUNKLEN) while sin: din = dfp.read(CHUNKLEN) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/remotenames.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/remotenames.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,307 @@ +# remotenames.py - extension to display remotenames +# +# Copyright 2017 Augie Fackler +# Copyright 2017 Sean Farley +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +""" showing remotebookmarks and remotebranches in UI + +By default both remotebookmarks and remotebranches are turned on. Config knob to +control the individually are as follows. + +Config options to tweak the default behaviour: + +remotenames.bookmarks + Boolean value to enable or disable showing of remotebookmarks + +remotenames.branches + Boolean value to enable or disable showing of remotebranches +""" + +from __future__ import absolute_import + +import collections + +from mercurial.i18n import _ + +from mercurial.node import ( + bin, +) +from mercurial import ( + logexchange, + namespaces, + pycompat, + registrar, + revsetlang, + smartset, + templatekw, +) + +# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for +# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should +# be specifying the version(s) of Mercurial they are tested with, or +# leave the attribute unspecified. +testedwith = 'ships-with-hg-core' + +configtable = {} +configitem = registrar.configitem(configtable) +templatekeyword = registrar.templatekeyword() +revsetpredicate = registrar.revsetpredicate() + +configitem('remotenames', 'bookmarks', + default=True, +) +configitem('remotenames', 'branches', + default=True, +) + +class lazyremotenamedict(collections.MutableMapping): + """ + Read-only dict-like Class to lazily resolve remotename entries + + We are doing that because remotenames startup was slow. + We lazily read the remotenames file once to figure out the potential entries + and store them in self.potentialentries. Then when asked to resolve an + entry, if it is not in self.potentialentries, then it isn't there, if it + is in self.potentialentries we resolve it and store the result in + self.cache. We cannot be lazy is when asked all the entries (keys). + """ + def __init__(self, kind, repo): + self.cache = {} + self.potentialentries = {} + self._kind = kind # bookmarks or branches + self._repo = repo + self.loaded = False + + def _load(self): + """ Read the remotenames file, store entries matching selected kind """ + self.loaded = True + repo = self._repo + for node, rpath, rname in logexchange.readremotenamefile(repo, + self._kind): + name = rpath + '/' + rname + self.potentialentries[name] = (node, rpath, name) + + def _resolvedata(self, potentialentry): + """ Check that the node for potentialentry exists and return it """ + if not potentialentry in self.potentialentries: + return None + node, remote, name = self.potentialentries[potentialentry] + repo = self._repo + binnode = bin(node) + # if the node doesn't exist, skip it + try: + repo.changelog.rev(binnode) + except LookupError: + return None + # Skip closed branches + if (self._kind == 'branches' and repo[binnode].closesbranch()): + return None + return [binnode] + + def __getitem__(self, key): + if not self.loaded: + self._load() + val = self._fetchandcache(key) + if val is not None: + return val + else: + raise KeyError() + + def __iter__(self): + return iter(self.potentialentries) + + def __len__(self): + return len(self.potentialentries) + + def __setitem__(self): + raise NotImplementedError + + def __delitem__(self): + raise NotImplementedError + + def _fetchandcache(self, key): + if key in self.cache: + return self.cache[key] + val = self._resolvedata(key) + if val is not None: + self.cache[key] = val + return val + else: + return None + + def keys(self): + """ Get a list of bookmark or branch names """ + if not self.loaded: + self._load() + return self.potentialentries.keys() + + def iteritems(self): + """ Iterate over (name, node) tuples """ + + if not self.loaded: + self._load() + + for k, vtup in self.potentialentries.iteritems(): + yield (k, [bin(vtup[0])]) + +class remotenames(dict): + """ + This class encapsulates all the remotenames state. It also contains + methods to access that state in convenient ways. Remotenames are lazy + loaded. Whenever client code needs to ensure the freshest copy of + remotenames, use the `clearnames` method to force an eventual load. + """ + + def __init__(self, repo, *args): + dict.__init__(self, *args) + self._repo = repo + self.clearnames() + + def clearnames(self): + """ Clear all remote names state """ + self['bookmarks'] = lazyremotenamedict("bookmarks", self._repo) + self['branches'] = lazyremotenamedict("branches", self._repo) + self._invalidatecache() + + def _invalidatecache(self): + self._nodetobmarks = None + self._nodetobranch = None + + def bmarktonodes(self): + return self['bookmarks'] + + def nodetobmarks(self): + if not self._nodetobmarks: + bmarktonodes = self.bmarktonodes() + self._nodetobmarks = {} + for name, node in bmarktonodes.iteritems(): + self._nodetobmarks.setdefault(node[0], []).append(name) + return self._nodetobmarks + + def branchtonodes(self): + return self['branches'] + + def nodetobranch(self): + if not self._nodetobranch: + branchtonodes = self.branchtonodes() + self._nodetobranch = {} + for name, nodes in branchtonodes.iteritems(): + for node in nodes: + self._nodetobranch.setdefault(node, []).append(name) + return self._nodetobranch + +def reposetup(ui, repo): + if not repo.local(): + return + + repo._remotenames = remotenames(repo) + ns = namespaces.namespace + + if ui.configbool('remotenames', 'bookmarks'): + remotebookmarkns = ns( + 'remotebookmarks', + templatename='remotebookmarks', + colorname='remotebookmark', + logfmt='remote bookmark: %s\n', + listnames=lambda repo: repo._remotenames.bmarktonodes().keys(), + namemap=lambda repo, name: + repo._remotenames.bmarktonodes().get(name, []), + nodemap=lambda repo, node: + repo._remotenames.nodetobmarks().get(node, [])) + repo.names.addnamespace(remotebookmarkns) + + if ui.configbool('remotenames', 'branches'): + remotebranchns = ns( + 'remotebranches', + templatename='remotebranches', + colorname='remotebranch', + logfmt='remote branch: %s\n', + listnames = lambda repo: repo._remotenames.branchtonodes().keys(), + namemap = lambda repo, name: + repo._remotenames.branchtonodes().get(name, []), + nodemap = lambda repo, node: + repo._remotenames.nodetobranch().get(node, [])) + repo.names.addnamespace(remotebranchns) + +@templatekeyword('remotenames') +def remotenameskw(**args): + """:remotenames: List of strings. List of remote names associated with the + changeset. + """ + args = pycompat.byteskwargs(args) + repo, ctx = args['repo'], args['ctx'] + + remotenames = [] + if 'remotebookmarks' in repo.names: + remotenames = repo.names['remotebookmarks'].names(repo, ctx.node()) + + if 'remotebranches' in repo.names: + remotenames += repo.names['remotebranches'].names(repo, ctx.node()) + + return templatekw.showlist('remotename', remotenames, args, + plural='remotenames') + +@templatekeyword('remotebookmarks') +def remotebookmarkskw(**args): + """:remotebookmarks: List of strings. List of remote bookmarks associated + with the changeset. + """ + args = pycompat.byteskwargs(args) + repo, ctx = args['repo'], args['ctx'] + + remotebmarks = [] + if 'remotebookmarks' in repo.names: + remotebmarks = repo.names['remotebookmarks'].names(repo, ctx.node()) + + return templatekw.showlist('remotebookmark', remotebmarks, args, + plural='remotebookmarks') + +@templatekeyword('remotebranches') +def remotebrancheskw(**args): + """:remotebranches: List of strings. List of remote branches associated + with the changeset. + """ + args = pycompat.byteskwargs(args) + repo, ctx = args['repo'], args['ctx'] + + remotebranches = [] + if 'remotebranches' in repo.names: + remotebranches = repo.names['remotebranches'].names(repo, ctx.node()) + + return templatekw.showlist('remotebranch', remotebranches, args, + plural='remotebranches') + +def _revsetutil(repo, subset, x, rtypes): + """utility function to return a set of revs based on the rtypes""" + + revs = set() + cl = repo.changelog + for rtype in rtypes: + if rtype in repo.names: + ns = repo.names[rtype] + for name in ns.listnames(repo): + revs.update(ns.nodes(repo, name)) + + results = (cl.rev(n) for n in revs if cl.hasnode(n)) + return subset & smartset.baseset(sorted(results)) + +@revsetpredicate('remotenames()') +def remotenamesrevset(repo, subset, x): + """All changesets which have a remotename on them.""" + revsetlang.getargs(x, 0, 0, _("remotenames takes no arguments")) + return _revsetutil(repo, subset, x, ('remotebookmarks', 'remotebranches')) + +@revsetpredicate('remotebranches()') +def remotebranchesrevset(repo, subset, x): + """All changesets which are branch heads on remotes.""" + revsetlang.getargs(x, 0, 0, _("remotebranches takes no arguments")) + return _revsetutil(repo, subset, x, ('remotebranches',)) + +@revsetpredicate('remotebookmarks()') +def remotebmarksrevset(repo, subset, x): + """All changesets which have bookmarks on remotes.""" + revsetlang.getargs(x, 0, 0, _("remotebookmarks takes no arguments")) + return _revsetutil(repo, subset, x, ('remotebookmarks',)) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/share.py --- a/hgext/share.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/share.py Sat Feb 24 17:49:10 2018 -0600 @@ -52,9 +52,6 @@ util, ) -repository = hg.repository -parseurl = hg.parseurl - cmdtable = {} command = registrar.command(cmdtable) # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for @@ -135,27 +132,9 @@ return False return hg.sharedbookmarks in shared -def _getsrcrepo(repo): - """ - Returns the source repository object for a given shared repository. - If repo is not a shared repository, return None. - """ - if repo.sharedpath == repo.path: - return None - - if util.safehasattr(repo, 'srcrepo') and repo.srcrepo: - return repo.srcrepo - - # the sharedpath always ends in the .hg; we want the path to the repo - source = repo.vfs.split(repo.sharedpath)[0] - srcurl, branches = parseurl(source) - srcrepo = repository(repo.ui, srcurl) - repo.srcrepo = srcrepo - return srcrepo - def getbkfile(orig, repo): if _hassharedbookmarks(repo): - srcrepo = _getsrcrepo(repo) + srcrepo = hg.sharedreposource(repo) if srcrepo is not None: # just orig(srcrepo) doesn't work as expected, because # HG_PENDING refers repo.root. @@ -186,7 +165,7 @@ orig(self, tr) if _hassharedbookmarks(self._repo): - srcrepo = _getsrcrepo(self._repo) + srcrepo = hg.sharedreposource(self._repo) if srcrepo is not None: category = 'share-bookmarks' tr.addpostclose(category, lambda tr: self._writerepo(srcrepo)) @@ -196,6 +175,6 @@ orig(self, repo) if _hassharedbookmarks(self._repo): - srcrepo = _getsrcrepo(self._repo) + srcrepo = hg.sharedreposource(self._repo) if srcrepo is not None: orig(self, srcrepo) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/shelve.py --- a/hgext/shelve.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/shelve.py Sat Feb 24 17:49:10 2018 -0600 @@ -271,7 +271,7 @@ "activebook": activebook or cls._noactivebook } scmutil.simplekeyvaluefile(repo.vfs, cls._filename)\ - .write(info, firstline=str(cls._version)) + .write(info, firstline=("%d" % cls._version)) @classmethod def clear(cls, repo): @@ -619,7 +619,7 @@ repo.vfs.rename('unshelverebasestate', 'rebasestate') try: rebase.rebase(ui, repo, **{ - 'abort' : True + r'abort' : True }) except Exception: repo.vfs.rename('rebasestate', 'unshelverebasestate') @@ -648,7 +648,7 @@ ui.pushbuffer(True) cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(), *pathtofiles(repo, files), - **{'no_backup': True}) + **{r'no_backup': True}) ui.popbuffer() def restorebranch(ui, repo, branchtorestore): @@ -681,7 +681,7 @@ repo.vfs.rename('unshelverebasestate', 'rebasestate') try: rebase.rebase(ui, repo, **{ - 'continue' : True + r'continue' : True }) except Exception: repo.vfs.rename('rebasestate', 'unshelverebasestate') @@ -744,10 +744,10 @@ ui.status(_('rebasing shelved changes\n')) try: rebase.rebase(ui, repo, **{ - 'rev': [shelvectx.rev()], - 'dest': str(tmpwctx.rev()), - 'keep': True, - 'tool': opts.get('tool', ''), + r'rev': [shelvectx.rev()], + r'dest': str(tmpwctx.rev()), + r'keep': True, + r'tool': opts.get('tool', ''), }) except error.InterventionRequired: tr.close() diff -r fb39f6a8a864 -r eb73f8a6177e hgext/show.py --- a/hgext/show.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/show.py Sat Feb 24 17:49:10 2018 -0600 @@ -39,6 +39,7 @@ error, formatter, graphmod, + logcmdutil, phases, pycompat, registrar, @@ -125,7 +126,7 @@ ui.write('\n') for name, func in sorted(views.items()): - ui.write(('%s\n') % func.__doc__) + ui.write(('%s\n') % pycompat.sysbytes(func.__doc__)) ui.write('\n') raise error.Abort(_('no view requested'), @@ -148,7 +149,7 @@ elif fn._csettopic: ref = 'show%s' % fn._csettopic spec = formatter.lookuptemplate(ui, ref, template) - displayer = cmdutil.changeset_templater(ui, repo, spec, buffered=True) + displayer = logcmdutil.changesettemplater(ui, repo, spec, buffered=True) return fn(ui, repo, displayer) else: return fn(ui, repo) @@ -409,8 +410,8 @@ revdag = graphmod.dagwalker(repo, revs) ui.setconfig('experimental', 'graphshorten', True) - cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, - props={'nodelen': nodelen}) + logcmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, + props={'nodelen': nodelen}) def extsetup(ui): # Alias `hg ` to `hg show `. diff -r fb39f6a8a864 -r eb73f8a6177e hgext/sparse.py --- a/hgext/sparse.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/sparse.py Sat Feb 24 17:49:10 2018 -0600 @@ -75,12 +75,12 @@ from mercurial.i18n import _ from mercurial import ( - cmdutil, commands, dirstate, error, extensions, hg, + logcmdutil, match as matchmod, pycompat, registrar, @@ -126,7 +126,7 @@ entry[1].append(('', 'sparse', None, "limit to changesets affecting the sparse checkout")) - def _logrevs(orig, repo, opts): + def _initialrevs(orig, repo, opts): revs = orig(repo, opts) if opts.get('sparse'): sparsematch = sparse.matcher(repo) @@ -135,7 +135,7 @@ return any(f for f in ctx.files() if sparsematch(f)) revs = revs.filter(ctxmatch) return revs - extensions.wrapfunction(cmdutil, '_logrevs', _logrevs) + extensions.wrapfunction(logcmdutil, '_initialrevs', _initialrevs) def _clonesparsecmd(orig, ui, repo, *args, **opts): include_pat = opts.get('include') @@ -194,7 +194,11 @@ """ def walk(orig, self, match, subrepos, unknown, ignored, full=True): - match = matchmod.intersectmatchers(match, self._sparsematcher) + # hack to not exclude explicitly-specified paths so that they can + # be warned later on e.g. dirstate.add() + em = matchmod.exact(match._root, match._cwd, match.files()) + sm = matchmod.unionmatcher([self._sparsematcher, em]) + match = matchmod.intersectmatchers(match, sm) return orig(self, match, subrepos, unknown, ignored, full) extensions.wrapfunction(dirstate.dirstate, 'walk', walk) diff -r fb39f6a8a864 -r eb73f8a6177e hgext/strip.py --- a/hgext/strip.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/strip.py Sat Feb 24 17:49:10 2018 -0600 @@ -181,13 +181,10 @@ strippedrevs = revs.union(descendants) roots = revs.difference(descendants) - update = False # if one of the wdir parent is stripped we'll need # to update away to an earlier revision - for p in repo.dirstate.parents(): - if p != nullid and cl.rev(p) in strippedrevs: - update = True - break + update = any(p != nullid and cl.rev(p) in strippedrevs + for p in repo.dirstate.parents()) rootnodes = set(cl.node(r) for r in roots) @@ -215,7 +212,7 @@ # only reset the dirstate for files that would actually change # between the working context and uctx - descendantrevs = repo.revs("%s::." % uctx.rev()) + descendantrevs = repo.revs(b"%d::.", uctx.rev()) changedfiles = [] for rev in descendantrevs: # blindly reset the files, regardless of what actually changed diff -r fb39f6a8a864 -r eb73f8a6177e hgext/transplant.py --- a/hgext/transplant.py Fri Feb 23 17:57:04 2018 -0800 +++ b/hgext/transplant.py Sat Feb 24 17:49:10 2018 -0600 @@ -24,6 +24,7 @@ error, exchange, hg, + logcmdutil, match, merge, node as nodemod, @@ -119,7 +120,8 @@ opener=self.opener) def getcommiteditor(): editform = cmdutil.mergeeditform(repo[None], 'transplant') - return cmdutil.getcommiteditor(editform=editform, **opts) + return cmdutil.getcommiteditor(editform=editform, + **pycompat.strkwargs(opts)) self.getcommiteditor = getcommiteditor def applied(self, repo, node, parent): @@ -160,7 +162,7 @@ tr = repo.transaction('transplant') for rev in revs: node = revmap[rev] - revstr = '%s:%s' % (rev, nodemod.short(node)) + revstr = '%d:%s' % (rev, nodemod.short(node)) if self.applied(repo, node, p1): self.ui.warn(_('skipping already applied revision %s\n') % @@ -194,7 +196,7 @@ skipmerge = False if parents[1] != revlog.nullid: if not opts.get('parent'): - self.ui.note(_('skipping merge changeset %s:%s\n') + self.ui.note(_('skipping merge changeset %d:%s\n') % (rev, nodemod.short(node))) skipmerge = True else: @@ -210,7 +212,7 @@ patchfile = None else: fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-') - fp = os.fdopen(fd, pycompat.sysstr('w')) + fp = os.fdopen(fd, pycompat.sysstr('wb')) gen = patch.diff(source, parent, node, opts=diffopts) for chunk in gen: fp.write(chunk) @@ -258,7 +260,7 @@ self.ui.status(_('filtering %s\n') % patchfile) user, date, msg = (changelog[1], changelog[2], changelog[4]) fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-') - fp = os.fdopen(fd, pycompat.sysstr('w')) + fp = os.fdopen(fd, pycompat.sysstr('wb')) fp.write("# HG changeset patch\n") fp.write("# User %s\n" % user) fp.write("# Date %d %d\n" % date) @@ -273,7 +275,7 @@ }, onerr=error.Abort, errprefix=_('filter failed'), blockedtag='transplant_filter') - user, date, msg = self.parselog(file(headerfile))[1:4] + user, date, msg = self.parselog(open(headerfile, 'rb'))[1:4] finally: os.unlink(headerfile) @@ -309,7 +311,7 @@ p1 = repo.dirstate.p1() p2 = node self.log(user, date, message, p1, p2, merge=merge) - self.ui.write(str(inst) + '\n') + self.ui.write(util.forcebytestr(inst) + '\n') raise TransplantError(_('fix up the working directory and run ' 'hg transplant --continue')) else: @@ -501,7 +503,7 @@ def browserevs(ui, repo, nodes, opts): '''interactively transplant changesets''' - displayer = cmdutil.show_changeset(ui, repo, opts) + displayer = logcmdutil.changesetdisplayer(ui, repo, opts) transplants = [] merges = [] prompt = _('apply changeset? [ynmpcq?]:' @@ -646,6 +648,7 @@ raise error.Abort(_('--all is incompatible with a ' 'revision list')) + opts = pycompat.byteskwargs(opts) checkopts(opts, revs) if not opts.get('log'): diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/archival.py --- a/mercurial/archival.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/archival.py Sat Feb 24 17:49:10 2018 -0600 @@ -21,6 +21,7 @@ error, formatter, match as matchmod, + scmutil, util, vfs as vfsmod, ) @@ -76,29 +77,27 @@ return repo[rev] return repo['null'] +# {tags} on ctx includes local tags and 'tip', with no current way to limit +# that to global tags. Therefore, use {latesttag} as a substitute when +# the distance is 0, since that will be the list of global tags on ctx. +_defaultmetatemplate = br''' +repo: {root} +node: {ifcontains(rev, revset("wdir()"), "{p1node}{dirty}", "{node}")} +branch: {branch|utf8} +{ifeq(latesttagdistance, 0, join(latesttag % "tag: {tag}", "\n"), + separate("\n", + join(latesttag % "latesttag: {tag}", "\n"), + "latesttagdistance: {latesttagdistance}", + "changessincelatesttag: {changessincelatesttag}"))} +'''[1:] # drop leading '\n' + def buildmetadata(ctx): '''build content of .hg_archival.txt''' repo = ctx.repo() - default = ( - r'repo: {root}\n' - r'node: {ifcontains(rev, revset("wdir()"),' - r'"{p1node}{dirty}", "{node}")}\n' - r'branch: {branch|utf8}\n' - - # {tags} on ctx includes local tags and 'tip', with no current way to - # limit that to global tags. Therefore, use {latesttag} as a substitute - # when the distance is 0, since that will be the list of global tags on - # ctx. - r'{ifeq(latesttagdistance, 0, latesttag % "tag: {tag}\n",' - r'"{latesttag % "latesttag: {tag}\n"}' - r'latesttagdistance: {latesttagdistance}\n' - r'changessincelatesttag: {changessincelatesttag}\n")}' - ) - opts = { 'template': repo.ui.config('experimental', 'archivemetatemplate', - default) + _defaultmetatemplate) } out = util.stringio() @@ -219,7 +218,7 @@ dest.tell() except (AttributeError, IOError): dest = tellable(dest) - self.z = zipfile.ZipFile(dest, 'w', + self.z = zipfile.ZipFile(dest, r'w', compress and zipfile.ZIP_DEFLATED or zipfile.ZIP_STORED) @@ -339,6 +338,7 @@ total = len(files) if total: files.sort() + scmutil.fileprefetchhooks(repo, ctx, files) repo.ui.progress(_('archiving'), 0, unit=_('files'), total=total) for i, f in enumerate(files): ff = ctx.flags(f) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/bookmarks.py --- a/mercurial/bookmarks.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/bookmarks.py Sat Feb 24 17:49:10 2018 -0600 @@ -103,30 +103,21 @@ self._aclean = False def __setitem__(self, *args, **kwargs): - msg = ("'bookmarks[name] = node' is deprecated, " - "use 'bookmarks.applychanges'") - self._repo.ui.deprecwarn(msg, '4.3') - self._set(*args, **kwargs) + raise error.ProgrammingError("use 'bookmarks.applychanges' instead") def _set(self, key, value): self._clean = False return dict.__setitem__(self, key, value) def __delitem__(self, key): - msg = ("'del bookmarks[name]' is deprecated, " - "use 'bookmarks.applychanges'") - self._repo.ui.deprecwarn(msg, '4.3') - self._del(key) + raise error.ProgrammingError("use 'bookmarks.applychanges' instead") def _del(self, key): self._clean = False return dict.__delitem__(self, key) def update(self, *others): - msg = ("bookmarks.update(...)' is deprecated, " - "use 'bookmarks.applychanges'") - self._repo.ui.deprecwarn(msg, '4.5') - return dict.update(self, *others) + raise error.ProgrammingError("use 'bookmarks.applychanges' instead") def applychanges(self, repo, tr, changes): """Apply a list of changes to bookmarks @@ -146,12 +137,6 @@ bmchanges[name] = (old, node) self._recordchange(tr) - def recordchange(self, tr): - msg = ("'bookmarks.recorchange' is deprecated, " - "use 'bookmarks.applychanges'") - self._repo.ui.deprecwarn(msg, '4.3') - return self._recordchange(tr) - def _recordchange(self, tr): """record that bookmarks have been changed in a transaction diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/branchmap.py --- a/mercurial/branchmap.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/branchmap.py Sat Feb 24 17:49:10 2018 -0600 @@ -18,6 +18,7 @@ from . import ( encoding, error, + pycompat, scmutil, util, ) @@ -52,18 +53,19 @@ filteredhash=filteredhash) if not partial.validfor(repo): # invalidate the cache - raise ValueError('tip differs') + raise ValueError(r'tip differs') cl = repo.changelog for l in lines: if not l: continue node, state, label = l.split(" ", 2) if state not in 'oc': - raise ValueError('invalid branch state') + raise ValueError(r'invalid branch state') label = encoding.tolocal(label.strip()) node = bin(node) if not cl.hasnode(node): - raise ValueError('node %s does not exist' % hex(node)) + raise ValueError( + r'node %s does not exist' % pycompat.sysstr(hex(node))) partial.setdefault(label, []).append(node) if state == 'c': partial._closednodes.add(node) @@ -73,7 +75,7 @@ if repo.filtername is not None: msg += ' (%s)' % repo.filtername msg += ': %s\n' - repo.ui.debug(msg % inst) + repo.ui.debug(msg % pycompat.bytestr(inst)) partial = None return partial @@ -253,7 +255,8 @@ repo.filtername, len(self), nodecount) except (IOError, OSError, error.Abort) as inst: # Abort may be raised by read only opener, so log and continue - repo.ui.debug("couldn't write branch cache: %s\n" % inst) + repo.ui.debug("couldn't write branch cache: %s\n" % + util.forcebytestr(inst)) def update(self, repo, revgen): """Given a branchhead cache, self, that may have extra nodes or be @@ -375,7 +378,7 @@ self._rbcrevs[:] = data except (IOError, OSError) as inst: repo.ui.debug("couldn't read revision branch cache: %s\n" % - inst) + util.forcebytestr(inst)) # remember number of good records on disk self._rbcrevslen = min(len(self._rbcrevs) // _rbcrecsize, len(repo.changelog)) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/bundle2.py --- a/mercurial/bundle2.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/bundle2.py Sat Feb 24 17:49:10 2018 -0600 @@ -1729,7 +1729,7 @@ extrakwargs = {} targetphase = inpart.params.get('targetphase') if targetphase is not None: - extrakwargs['targetphase'] = int(targetphase) + extrakwargs[r'targetphase'] = int(targetphase) ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2', expectedtotal=nbchangesets, **extrakwargs) if op.reply is not None: @@ -2040,14 +2040,15 @@ allhooks.append(hookargs) for hookargs in allhooks: - op.repo.hook('prepushkey', throw=True, **hookargs) + op.repo.hook('prepushkey', throw=True, + **pycompat.strkwargs(hookargs)) bookstore.applychanges(op.repo, op.gettransaction(), changes) if pushkeycompat: def runhook(): for hookargs in allhooks: - op.repo.hook('pushkey', **hookargs) + op.repo.hook('pushkey', **pycompat.strkwargs(hookargs)) op.repo._afterlock(runhook) elif bookmarksmode == 'records': diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/bundlerepo.py --- a/mercurial/bundlerepo.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/bundlerepo.py Sat Feb 24 17:49:10 2018 -0600 @@ -402,7 +402,7 @@ # manifestlog implementation did not consume the manifests from the # changegroup (ex: it might be consuming trees from a separate bundle2 # part instead). So we need to manually consume it. - if 'filestart' not in self.__dict__: + if r'filestart' not in self.__dict__: self._consumemanifest() return self.filestart diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/cext/base85.c --- a/mercurial/cext/base85.c Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/cext/base85.c Sat Feb 24 17:49:10 2018 -0600 @@ -14,8 +14,9 @@ #include "util.h" -static const char b85chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; +static const char b85chars[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; static char b85dec[256]; static void b85prep(void) @@ -105,25 +106,25 @@ c = b85dec[(int)*text++] - 1; if (c < 0) return PyErr_Format( - PyExc_ValueError, - "bad base85 character at position %d", - (int)i); + PyExc_ValueError, + "bad base85 character at position %d", + (int)i); acc = acc * 85 + c; } if (i++ < len) { c = b85dec[(int)*text++] - 1; if (c < 0) return PyErr_Format( - PyExc_ValueError, - "bad base85 character at position %d", - (int)i); + PyExc_ValueError, + "bad base85 character at position %d", + (int)i); /* overflow detection: 0xffffffff == "|NsC0", * "|NsC" == 0x03030303 */ if (acc > 0x03030303 || (acc *= 85) > 0xffffffff - c) return PyErr_Format( - PyExc_ValueError, - "bad base85 sequence at position %d", - (int)i); + PyExc_ValueError, + "bad base85 sequence at position %d", + (int)i); acc += c; } @@ -145,23 +146,19 @@ static char base85_doc[] = "Base85 Data Encoding"; static PyMethodDef methods[] = { - {"b85encode", b85encode, METH_VARARGS, - "Encode text in base85.\n\n" - "If the second parameter is true, pad the result to a multiple of " - "five characters.\n"}, - {"b85decode", b85decode, METH_VARARGS, "Decode base85 text.\n"}, - {NULL, NULL} + {"b85encode", b85encode, METH_VARARGS, + "Encode text in base85.\n\n" + "If the second parameter is true, pad the result to a multiple of " + "five characters.\n"}, + {"b85decode", b85decode, METH_VARARGS, "Decode base85 text.\n"}, + {NULL, NULL}, }; static const int version = 1; #ifdef IS_PY3K static struct PyModuleDef base85_module = { - PyModuleDef_HEAD_INIT, - "base85", - base85_doc, - -1, - methods + PyModuleDef_HEAD_INIT, "base85", base85_doc, -1, methods, }; PyMODINIT_FUNC PyInit_base85(void) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/cext/bdiff.c --- a/mercurial/cext/bdiff.c Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/cext/bdiff.c Sat Feb 24 17:49:10 2018 -0600 @@ -19,7 +19,6 @@ #include "bitmanipulation.h" #include "util.h" - static PyObject *blocks(PyObject *self, PyObject *args) { PyObject *sa, *sb, *rl = NULL, *m; @@ -82,9 +81,7 @@ _save = PyEval_SaveThread(); lmax = la > lb ? lb : la; - for (ia = sa, ib = sb; - li < lmax && *ia == *ib; - ++li, ++ia, ++ib) + for (ia = sa, ib = sb; li < lmax && *ia == *ib; ++li, ++ia, ++ib) if (*ia == '\n') lcommon = li + 1; /* we can almost add: if (li == lmax) lcommon = li; */ @@ -122,7 +119,8 @@ if (h->a1 != la || h->b1 != lb) { len = bl[h->b1].l - bl[lb].l; putbe32((uint32_t)(al[la].l + lcommon - al->l), rb); - putbe32((uint32_t)(al[h->a1].l + lcommon - al->l), rb + 4); + putbe32((uint32_t)(al[h->a1].l + lcommon - al->l), + rb + 4); putbe32((uint32_t)len, rb + 8); memcpy(rb + 12, bl[lb].l, len); rb += 12 + len; @@ -167,8 +165,8 @@ if (c == ' ' || c == '\t' || c == '\r') { if (!allws && (wlen == 0 || w[wlen - 1] != ' ')) w[wlen++] = ' '; - } else if (c == '\n' && !allws - && wlen > 0 && w[wlen - 1] == ' ') { + } else if (c == '\n' && !allws && wlen > 0 && + w[wlen - 1] == ' ') { w[wlen - 1] = '\n'; } else { w[wlen++] = c; @@ -182,25 +180,70 @@ return result ? result : PyErr_NoMemory(); } +static bool sliceintolist(PyObject *list, Py_ssize_t destidx, + const char *source, Py_ssize_t len) +{ + PyObject *sliced = PyBytes_FromStringAndSize(source, len); + if (sliced == NULL) + return false; + PyList_SET_ITEM(list, destidx, sliced); + return true; +} + +static PyObject *splitnewlines(PyObject *self, PyObject *args) +{ + const char *text; + Py_ssize_t nelts = 0, size, i, start = 0; + PyObject *result = NULL; + + if (!PyArg_ParseTuple(args, "s#", &text, &size)) { + goto abort; + } + if (!size) { + return PyList_New(0); + } + /* This loops to size-1 because if the last byte is a newline, + * we don't want to perform a split there. */ + for (i = 0; i < size - 1; ++i) { + if (text[i] == '\n') { + ++nelts; + } + } + if ((result = PyList_New(nelts + 1)) == NULL) + goto abort; + nelts = 0; + for (i = 0; i < size - 1; ++i) { + if (text[i] == '\n') { + if (!sliceintolist(result, nelts++, text + start, + i - start + 1)) + goto abort; + start = i + 1; + } + } + if (!sliceintolist(result, nelts++, text + start, size - start)) + goto abort; + return result; +abort: + Py_XDECREF(result); + return NULL; +} static char mdiff_doc[] = "Efficient binary diff."; static PyMethodDef methods[] = { - {"bdiff", bdiff, METH_VARARGS, "calculate a binary diff\n"}, - {"blocks", blocks, METH_VARARGS, "find a list of matching lines\n"}, - {"fixws", fixws, METH_VARARGS, "normalize diff whitespaces\n"}, - {NULL, NULL} + {"bdiff", bdiff, METH_VARARGS, "calculate a binary diff\n"}, + {"blocks", blocks, METH_VARARGS, "find a list of matching lines\n"}, + {"fixws", fixws, METH_VARARGS, "normalize diff whitespaces\n"}, + {"splitnewlines", splitnewlines, METH_VARARGS, + "like str.splitlines, but only split on newlines\n"}, + {NULL, NULL}, }; -static const int version = 1; +static const int version = 2; #ifdef IS_PY3K static struct PyModuleDef bdiff_module = { - PyModuleDef_HEAD_INIT, - "bdiff", - mdiff_doc, - -1, - methods + PyModuleDef_HEAD_INIT, "bdiff", mdiff_doc, -1, methods, }; PyMODINIT_FUNC PyInit_bdiff(void) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/cext/charencode.c --- a/mercurial/cext/charencode.c Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/cext/charencode.c Sat Feb 24 17:49:10 2018 -0600 @@ -65,7 +65,6 @@ '\x58', '\x59', '\x5a', /* x-z */ '\x7b', '\x7c', '\x7d', '\x7e', '\x7f' }; -/* clang-format on */ /* 1: no escape, 2: \, 6: \u */ static const uint8_t jsonlentable[256] = { @@ -102,6 +101,7 @@ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', }; +/* clang-format on */ /* * Turn a hex-encoded string into binary. @@ -151,9 +151,8 @@ Py_RETURN_TRUE; } -static inline PyObject *_asciitransform(PyObject *str_obj, - const char table[128], - PyObject *fallback_fn) +static inline PyObject * +_asciitransform(PyObject *str_obj, const char table[128], PyObject *fallback_fn) { char *str, *newstr; Py_ssize_t i, len; @@ -173,12 +172,12 @@ char c = str[i]; if (c & 0x80) { if (fallback_fn != NULL) { - ret = PyObject_CallFunctionObjArgs(fallback_fn, - str_obj, NULL); + ret = PyObject_CallFunctionObjArgs( + fallback_fn, str_obj, NULL); } else { PyObject *err = PyUnicodeDecodeError_Create( - "ascii", str, len, i, (i + 1), - "unexpected code byte"); + "ascii", str, len, i, (i + 1), + "unexpected code byte"); PyErr_SetObject(PyExc_UnicodeDecodeError, err); Py_XDECREF(err); } @@ -220,10 +219,9 @@ Py_ssize_t pos = 0; const char *table; - if (!PyArg_ParseTuple(args, "O!O!O!:make_file_foldmap", - &PyDict_Type, &dmap, - &PyInt_Type, &spec_obj, - &PyFunction_Type, &normcase_fallback)) + if (!PyArg_ParseTuple(args, "O!O!O!:make_file_foldmap", &PyDict_Type, + &dmap, &PyInt_Type, &spec_obj, &PyFunction_Type, + &normcase_fallback)) goto quit; spec = (int)PyInt_AS_LONG(spec_obj); @@ -251,7 +249,7 @@ while (PyDict_Next(dmap, &pos, &k, &v)) { if (!dirstate_tuple_check(v)) { PyErr_SetString(PyExc_TypeError, - "expected a dirstate tuple"); + "expected a dirstate tuple"); goto quit; } @@ -260,10 +258,10 @@ PyObject *normed; if (table != NULL) { normed = _asciitransform(k, table, - normcase_fallback); + normcase_fallback); } else { normed = PyObject_CallFunctionObjArgs( - normcase_fallback, k, NULL); + normcase_fallback, k, NULL); } if (normed == NULL) @@ -292,13 +290,13 @@ char c = buf[i]; if (c & 0x80) { PyErr_SetString(PyExc_ValueError, - "cannot process non-ascii str"); + "cannot process non-ascii str"); return -1; } esclen += jsonparanoidlentable[(unsigned char)c]; if (esclen < 0) { PyErr_SetString(PyExc_MemoryError, - "overflow in jsonescapelen"); + "overflow in jsonescapelen"); return -1; } } @@ -308,7 +306,7 @@ esclen += jsonlentable[(unsigned char)c]; if (esclen < 0) { PyErr_SetString(PyExc_MemoryError, - "overflow in jsonescapelen"); + "overflow in jsonescapelen"); return -1; } } @@ -336,17 +334,17 @@ case '\\': return '\\'; } - return '\0'; /* should not happen */ + return '\0'; /* should not happen */ } /* convert 'origbuf' to JSON-escaped form 'escbuf'; 'origbuf' should only include characters mappable by json(paranoid)lentable */ static void encodejsonescape(char *escbuf, Py_ssize_t esclen, - const char *origbuf, Py_ssize_t origlen, - bool paranoid) + const char *origbuf, Py_ssize_t origlen, + bool paranoid) { const uint8_t *lentable = - (paranoid) ? jsonparanoidlentable : jsonlentable; + (paranoid) ? jsonparanoidlentable : jsonlentable; Py_ssize_t i, j; for (i = 0, j = 0; i < origlen; i++) { @@ -377,15 +375,15 @@ const char *origbuf; Py_ssize_t origlen, esclen; int paranoid; - if (!PyArg_ParseTuple(args, "O!i:jsonescapeu8fast", - &PyBytes_Type, &origstr, ¶noid)) + if (!PyArg_ParseTuple(args, "O!i:jsonescapeu8fast", &PyBytes_Type, + &origstr, ¶noid)) return NULL; origbuf = PyBytes_AS_STRING(origstr); origlen = PyBytes_GET_SIZE(origstr); esclen = jsonescapelen(origbuf, origlen, paranoid); if (esclen < 0) - return NULL; /* unsupported char found or overflow */ + return NULL; /* unsupported char found or overflow */ if (origlen == esclen) { Py_INCREF(origstr); return origstr; @@ -395,7 +393,7 @@ if (!escstr) return NULL; encodejsonescape(PyBytes_AS_STRING(escstr), esclen, origbuf, origlen, - paranoid); + paranoid); return escstr; } diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/cext/charencode.h --- a/mercurial/cext/charencode.h Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/cext/charencode.h Sat Feb 24 17:49:10 2018 -0600 @@ -25,6 +25,7 @@ PyObject *make_file_foldmap(PyObject *self, PyObject *args); PyObject *jsonescapeu8fast(PyObject *self, PyObject *args); +/* clang-format off */ static const int8_t hextable[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -43,6 +44,7 @@ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; +/* clang-format on */ static inline int hexdigit(const char *p, Py_ssize_t off) { diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/cext/diffhelpers.c --- a/mercurial/cext/diffhelpers.c Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/cext/diffhelpers.c Sat Feb 24 17:49:10 2018 -0600 @@ -16,12 +16,11 @@ static char diffhelpers_doc[] = "Efficient diff parsing"; static PyObject *diffhelpers_Error; - /* fixup the last lines of a and b when the patch has no newline at eof */ static void _fix_newline(PyObject *hunk, PyObject *a, PyObject *b) { Py_ssize_t hunksz = PyList_Size(hunk); - PyObject *s = PyList_GET_ITEM(hunk, hunksz-1); + PyObject *s = PyList_GET_ITEM(hunk, hunksz - 1); char *l = PyBytes_AsString(s); Py_ssize_t alen = PyList_Size(a); Py_ssize_t blen = PyList_Size(b); @@ -29,29 +28,28 @@ PyObject *hline; Py_ssize_t sz = PyBytes_GET_SIZE(s); - if (sz > 1 && l[sz-2] == '\r') + if (sz > 1 && l[sz - 2] == '\r') /* tolerate CRLF in last line */ sz -= 1; - hline = PyBytes_FromStringAndSize(l, sz-1); + hline = PyBytes_FromStringAndSize(l, sz - 1); if (!hline) { return; } if (c == ' ' || c == '+') { PyObject *rline = PyBytes_FromStringAndSize(l + 1, sz - 2); - PyList_SetItem(b, blen-1, rline); + PyList_SetItem(b, blen - 1, rline); } if (c == ' ' || c == '-') { Py_INCREF(hline); - PyList_SetItem(a, alen-1, hline); + PyList_SetItem(a, alen - 1, hline); } - PyList_SetItem(hunk, hunksz-1, hline); + PyList_SetItem(hunk, hunksz - 1, hline); } /* python callable form of _fix_newline */ -static PyObject * -fix_newline(PyObject *self, PyObject *args) +static PyObject *fix_newline(PyObject *self, PyObject *args) { PyObject *hunk, *a, *b; if (!PyArg_ParseTuple(args, "OOO", &hunk, &a, &b)) @@ -72,8 +70,7 @@ * The control char from the hunk is saved when inserting into a, but not b * (for performance while deleting files) */ -static PyObject * -addlines(PyObject *self, PyObject *args) +static PyObject *addlines(PyObject *self, PyObject *args) { PyObject *fp, *hunk, *a, *b, *x; @@ -83,8 +80,8 @@ Py_ssize_t todoa, todob; char *s, c; PyObject *l; - if (!PyArg_ParseTuple(args, addlines_format, - &fp, &hunk, &lena, &lenb, &a, &b)) + if (!PyArg_ParseTuple(args, addlines_format, &fp, &hunk, &lena, &lenb, + &a, &b)) return NULL; while (1) { @@ -92,7 +89,7 @@ todob = lenb - PyList_Size(b); num = todoa > todob ? todoa : todob; if (num == 0) - break; + break; for (i = 0; i < num; i++) { x = PyFile_GetLine(fp, 0); s = PyBytes_AsString(x); @@ -131,8 +128,7 @@ * a control char at the start of each line, this char is ignored in the * compare */ -static PyObject * -testhunk(PyObject *self, PyObject *args) +static PyObject *testhunk(PyObject *self, PyObject *args) { PyObject *a, *b; @@ -158,21 +154,16 @@ } static PyMethodDef methods[] = { - {"addlines", addlines, METH_VARARGS, "add lines to a hunk\n"}, - {"fix_newline", fix_newline, METH_VARARGS, "fixup newline counters\n"}, - {"testhunk", testhunk, METH_VARARGS, "test lines in a hunk\n"}, - {NULL, NULL} -}; + {"addlines", addlines, METH_VARARGS, "add lines to a hunk\n"}, + {"fix_newline", fix_newline, METH_VARARGS, "fixup newline counters\n"}, + {"testhunk", testhunk, METH_VARARGS, "test lines in a hunk\n"}, + {NULL, NULL}}; static const int version = 1; #ifdef IS_PY3K static struct PyModuleDef diffhelpers_module = { - PyModuleDef_HEAD_INIT, - "diffhelpers", - diffhelpers_doc, - -1, - methods + PyModuleDef_HEAD_INIT, "diffhelpers", diffhelpers_doc, -1, methods, }; PyMODINIT_FUNC PyInit_diffhelpers(void) @@ -183,8 +174,8 @@ if (m == NULL) return NULL; - diffhelpers_Error = PyErr_NewException("diffhelpers.diffhelpersError", - NULL, NULL); + diffhelpers_Error = + PyErr_NewException("diffhelpers.diffhelpersError", NULL, NULL); Py_INCREF(diffhelpers_Error); PyModule_AddObject(m, "diffhelpersError", diffhelpers_Error); PyModule_AddIntConstant(m, "version", version); @@ -192,13 +183,12 @@ return m; } #else -PyMODINIT_FUNC -initdiffhelpers(void) +PyMODINIT_FUNC initdiffhelpers(void) { PyObject *m; m = Py_InitModule3("diffhelpers", methods, diffhelpers_doc); - diffhelpers_Error = PyErr_NewException("diffhelpers.diffhelpersError", - NULL, NULL); + diffhelpers_Error = + PyErr_NewException("diffhelpers.diffhelpersError", NULL, NULL); PyModule_AddIntConstant(m, "version", version); } #endif diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/cext/mpatch.c --- a/mercurial/cext/mpatch.c Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/cext/mpatch.c Sat Feb 24 17:49:10 2018 -0600 @@ -55,10 +55,10 @@ ssize_t blen; int r; - PyObject *tmp = PyList_GetItem((PyObject*)bins, pos); + PyObject *tmp = PyList_GetItem((PyObject *)bins, pos); if (!tmp) return NULL; - if (PyObject_AsCharBuffer(tmp, &buffer, (Py_ssize_t*)&blen)) + if (PyObject_AsCharBuffer(tmp, &buffer, (Py_ssize_t *)&blen)) return NULL; if ((r = mpatch_decode(buffer, blen, &res)) < 0) { if (!PyErr_Occurred()) @@ -68,8 +68,7 @@ return res; } -static PyObject * -patches(PyObject *self, PyObject *args) +static PyObject *patches(PyObject *self, PyObject *args) { PyObject *text, *bins, *result; struct mpatch_flist *patch; @@ -110,7 +109,14 @@ goto cleanup; } out = PyBytes_AsString(result); - if ((r = mpatch_apply(out, in, inlen, patch)) < 0) { + /* clang-format off */ + { + Py_BEGIN_ALLOW_THREADS + r = mpatch_apply(out, in, inlen, patch); + Py_END_ALLOW_THREADS + } + /* clang-format on */ + if (r < 0) { Py_DECREF(result); result = NULL; } @@ -122,8 +128,7 @@ } /* calculate size of a patched file directly */ -static PyObject * -patchedsize(PyObject *self, PyObject *args) +static PyObject *patchedsize(PyObject *self, PyObject *args) { long orig, start, end, len, outlen = 0, last = 0, pos = 0; Py_ssize_t patchlen; @@ -146,7 +151,8 @@ if (pos != patchlen) { if (!PyErr_Occurred()) - PyErr_SetString(mpatch_Error, "patch cannot be decoded"); + PyErr_SetString(mpatch_Error, + "patch cannot be decoded"); return NULL; } @@ -155,20 +161,16 @@ } static PyMethodDef methods[] = { - {"patches", patches, METH_VARARGS, "apply a series of patches\n"}, - {"patchedsize", patchedsize, METH_VARARGS, "calculed patched size\n"}, - {NULL, NULL} + {"patches", patches, METH_VARARGS, "apply a series of patches\n"}, + {"patchedsize", patchedsize, METH_VARARGS, "calculed patched size\n"}, + {NULL, NULL}, }; static const int version = 1; #ifdef IS_PY3K static struct PyModuleDef mpatch_module = { - PyModuleDef_HEAD_INIT, - "mpatch", - mpatch_doc, - -1, - methods + PyModuleDef_HEAD_INIT, "mpatch", mpatch_doc, -1, methods, }; PyMODINIT_FUNC PyInit_mpatch(void) @@ -179,8 +181,8 @@ if (m == NULL) return NULL; - mpatch_Error = PyErr_NewException("mercurial.cext.mpatch.mpatchError", - NULL, NULL); + mpatch_Error = + PyErr_NewException("mercurial.cext.mpatch.mpatchError", NULL, NULL); Py_INCREF(mpatch_Error); PyModule_AddObject(m, "mpatchError", mpatch_Error); PyModule_AddIntConstant(m, "version", version); @@ -188,13 +190,12 @@ return m; } #else -PyMODINIT_FUNC -initmpatch(void) +PyMODINIT_FUNC initmpatch(void) { PyObject *m; m = Py_InitModule3("mpatch", methods, mpatch_doc); - mpatch_Error = PyErr_NewException("mercurial.cext.mpatch.mpatchError", - NULL, NULL); + mpatch_Error = + PyErr_NewException("mercurial.cext.mpatch.mpatchError", NULL, NULL); PyModule_AddIntConstant(m, "version", version); } #endif diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/cext/pathencode.c --- a/mercurial/cext/pathencode.c Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/cext/pathencode.c Sat Feb 24 17:49:10 2018 -0600 @@ -26,26 +26,26 @@ /* state machine for the fast path */ enum path_state { - START, /* first byte of a path component */ - A, /* "AUX" */ + START, /* first byte of a path component */ + A, /* "AUX" */ AU, - THIRD, /* third of a 3-byte sequence, e.g. "AUX", "NUL" */ - C, /* "CON" or "COMn" */ + THIRD, /* third of a 3-byte sequence, e.g. "AUX", "NUL" */ + C, /* "CON" or "COMn" */ CO, - COMLPT, /* "COM" or "LPT" */ + COMLPT, /* "COM" or "LPT" */ COMLPTn, L, LP, N, NU, - P, /* "PRN" */ + P, /* "PRN" */ PR, - LDOT, /* leading '.' */ - DOT, /* '.' in a non-leading position */ - H, /* ".h" */ - HGDI, /* ".hg", ".d", or ".i" */ + LDOT, /* leading '.' */ + DOT, /* '.' in a non-leading position */ + H, /* ".h" */ + HGDI, /* ".hg", ".d", or ".i" */ SPACE, - DEFAULT /* byte of a path component after the first */ + DEFAULT, /* byte of a path component after the first */ }; /* state machine for dir-encoding */ @@ -53,7 +53,7 @@ DDOT, DH, DHGDI, - DDEFAULT + DDEFAULT, }; static inline int inset(const uint32_t bitset[], char c) @@ -82,7 +82,7 @@ } static inline void hexencode(char *dest, Py_ssize_t *destlen, size_t destsize, - uint8_t c) + uint8_t c) { static const char hexdigit[] = "0123456789abcdef"; @@ -92,14 +92,14 @@ /* 3-byte escape: tilde followed by two hex digits */ static inline void escape3(char *dest, Py_ssize_t *destlen, size_t destsize, - char c) + char c) { charcopy(dest, destlen, destsize, '~'); hexencode(dest, destlen, destsize, c); } -static Py_ssize_t _encodedir(char *dest, size_t destsize, - const char *src, Py_ssize_t len) +static Py_ssize_t _encodedir(char *dest, size_t destsize, const char *src, + Py_ssize_t len) { enum dir_state state = DDEFAULT; Py_ssize_t i = 0, destlen = 0; @@ -126,8 +126,8 @@ if (src[i] == 'g') { state = DHGDI; charcopy(dest, &destlen, destsize, src[i++]); - } - else state = DDEFAULT; + } else + state = DDEFAULT; break; case DHGDI: if (src[i] == '/') { @@ -173,17 +173,15 @@ if (newobj) { assert(PyBytes_Check(newobj)); Py_SIZE(newobj)--; - _encodedir(PyBytes_AS_STRING(newobj), newlen, path, - len + 1); + _encodedir(PyBytes_AS_STRING(newobj), newlen, path, len + 1); } return newobj; } static Py_ssize_t _encode(const uint32_t twobytes[8], const uint32_t onebyte[8], - char *dest, Py_ssize_t destlen, size_t destsize, - const char *src, Py_ssize_t len, - int encodedir) + char *dest, Py_ssize_t destlen, size_t destsize, + const char *src, Py_ssize_t len, int encodedir) { enum path_state state = START; Py_ssize_t i = 0; @@ -237,15 +235,15 @@ if (src[i] == 'u') { state = AU; charcopy(dest, &destlen, destsize, src[i++]); - } - else state = DEFAULT; + } else + state = DEFAULT; break; case AU: if (src[i] == 'x') { state = THIRD; i++; - } - else state = DEFAULT; + } else + state = DEFAULT; break; case THIRD: state = DEFAULT; @@ -264,24 +262,30 @@ if (src[i] == 'o') { state = CO; charcopy(dest, &destlen, destsize, src[i++]); - } - else state = DEFAULT; + } else + state = DEFAULT; break; case CO: if (src[i] == 'm') { state = COMLPT; i++; - } - else if (src[i] == 'n') { + } else if (src[i] == 'n') { state = THIRD; i++; - } - else state = DEFAULT; + } else + state = DEFAULT; break; case COMLPT: switch (src[i]) { - case '1': case '2': case '3': case '4': case '5': - case '6': case '7': case '8': case '9': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': state = COMLPTn; i++; break; @@ -301,8 +305,8 @@ charcopy(dest, &destlen, destsize, src[i - 1]); break; default: - memcopy(dest, &destlen, destsize, - &src[i - 2], 2); + memcopy(dest, &destlen, destsize, &src[i - 2], + 2); break; } break; @@ -310,43 +314,43 @@ if (src[i] == 'p') { state = LP; charcopy(dest, &destlen, destsize, src[i++]); - } - else state = DEFAULT; + } else + state = DEFAULT; break; case LP: if (src[i] == 't') { state = COMLPT; i++; - } - else state = DEFAULT; + } else + state = DEFAULT; break; case N: if (src[i] == 'u') { state = NU; charcopy(dest, &destlen, destsize, src[i++]); - } - else state = DEFAULT; + } else + state = DEFAULT; break; case NU: if (src[i] == 'l') { state = THIRD; i++; - } - else state = DEFAULT; + } else + state = DEFAULT; break; case P: if (src[i] == 'r') { state = PR; charcopy(dest, &destlen, destsize, src[i++]); - } - else state = DEFAULT; + } else + state = DEFAULT; break; case PR: if (src[i] == 'n') { state = THIRD; i++; - } - else state = DEFAULT; + } else + state = DEFAULT; break; case LDOT: switch (src[i]) { @@ -393,18 +397,18 @@ if (src[i] == 'g') { state = HGDI; charcopy(dest, &destlen, destsize, src[i++]); - } - else state = DEFAULT; + } else + state = DEFAULT; break; case HGDI: if (src[i] == '/') { state = START; if (encodedir) memcopy(dest, &destlen, destsize, ".hg", - 3); + 3); charcopy(dest, &destlen, destsize, src[i++]); - } - else state = DEFAULT; + } else + state = DEFAULT; break; case SPACE: switch (src[i]) { @@ -444,19 +448,17 @@ if (inset(onebyte, src[i])) { do { charcopy(dest, &destlen, - destsize, src[i++]); + destsize, src[i++]); } while (i < len && - inset(onebyte, src[i])); - } - else if (inset(twobytes, src[i])) { + inset(onebyte, src[i])); + } else if (inset(twobytes, src[i])) { char c = src[i++]; charcopy(dest, &destlen, destsize, '_'); charcopy(dest, &destlen, destsize, - c == '_' ? '_' : c + 32); - } - else + c == '_' ? '_' : c + 32); + } else escape3(dest, &destlen, destsize, - src[i++]); + src[i++]); break; } break; @@ -466,31 +468,29 @@ return destlen; } -static Py_ssize_t basicencode(char *dest, size_t destsize, - const char *src, Py_ssize_t len) +static Py_ssize_t basicencode(char *dest, size_t destsize, const char *src, + Py_ssize_t len) { - static const uint32_t twobytes[8] = { 0, 0, 0x87fffffe }; + static const uint32_t twobytes[8] = {0, 0, 0x87fffffe}; static const uint32_t onebyte[8] = { - 1, 0x2bff3bfa, 0x68000001, 0x2fffffff, + 1, 0x2bff3bfa, 0x68000001, 0x2fffffff, }; Py_ssize_t destlen = 0; - return _encode(twobytes, onebyte, dest, destlen, destsize, - src, len, 1); + return _encode(twobytes, onebyte, dest, destlen, destsize, src, len, 1); } static const Py_ssize_t maxstorepathlen = 120; -static Py_ssize_t _lowerencode(char *dest, size_t destsize, - const char *src, Py_ssize_t len) +static Py_ssize_t _lowerencode(char *dest, size_t destsize, const char *src, + Py_ssize_t len) { - static const uint32_t onebyte[8] = { - 1, 0x2bfffbfb, 0xe8000001, 0x2fffffff - }; + static const uint32_t onebyte[8] = {1, 0x2bfffbfb, 0xe8000001, + 0x2fffffff}; - static const uint32_t lower[8] = { 0, 0, 0x7fffffe }; + static const uint32_t lower[8] = {0, 0, 0x7fffffe}; Py_ssize_t i, destlen = 0; @@ -524,13 +524,13 @@ } /* See store.py:_auxencode for a description. */ -static Py_ssize_t auxencode(char *dest, size_t destsize, - const char *src, Py_ssize_t len) +static Py_ssize_t auxencode(char *dest, size_t destsize, const char *src, + Py_ssize_t len) { static const uint32_t twobytes[8]; static const uint32_t onebyte[8] = { - ~0U, 0xffff3ffe, ~0U, ~0U, ~0U, ~0U, ~0U, ~0U, + ~0U, 0xffff3ffe, ~0U, ~0U, ~0U, ~0U, ~0U, ~0U, }; return _encode(twobytes, onebyte, dest, 0, destsize, src, len, 0); @@ -590,8 +590,7 @@ break; charcopy(dest, &destlen, destsize, src[i]); p = -1; - } - else if (p < dirprefixlen) + } else if (p < dirprefixlen) charcopy(dest, &destlen, destsize, src[i]); } @@ -622,13 +621,13 @@ slop = maxstorepathlen - used; if (slop > 0) { Py_ssize_t basenamelen = - lastslash >= 0 ? len - lastslash - 2 : len - 1; + lastslash >= 0 ? len - lastslash - 2 : len - 1; if (basenamelen > slop) basenamelen = slop; if (basenamelen > 0) memcopy(dest, &destlen, destsize, &src[lastslash + 1], - basenamelen); + basenamelen); } /* Add hash and suffix. */ @@ -637,7 +636,7 @@ if (lastdot >= 0) memcopy(dest, &destlen, destsize, &src[lastdot], - len - lastdot - 1); + len - lastdot - 1); assert(PyBytes_Check(ret)); Py_SIZE(ret) = destlen; @@ -672,8 +671,8 @@ if (shafunc == NULL) { PyErr_SetString(PyExc_AttributeError, - "module 'hashlib' has no " - "attribute 'sha1'"); + "module 'hashlib' has no " + "attribute 'sha1'"); return -1; } } @@ -690,7 +689,7 @@ if (!PyBytes_Check(hashobj) || PyBytes_GET_SIZE(hashobj) != 20) { PyErr_SetString(PyExc_TypeError, - "result of digest is not a 20-byte hash"); + "result of digest is not a 20-byte hash"); Py_DECREF(hashobj); return -1; } @@ -755,10 +754,9 @@ assert(PyBytes_Check(newobj)); Py_SIZE(newobj)--; basicencode(PyBytes_AS_STRING(newobj), newlen, path, - len + 1); + len + 1); } - } - else + } else newobj = hashencode(path, len + 1); return newobj; diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/changelog.py --- a/mercurial/changelog.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/changelog.py Sat Feb 24 17:49:10 2018 -0600 @@ -20,6 +20,7 @@ from . import ( encoding, error, + pycompat, revlog, util, ) @@ -90,6 +91,11 @@ return self.offset def flush(self): pass + + @property + def closed(self): + return self.fp.closed + def close(self): self.fp.close() @@ -127,6 +133,13 @@ self.offset += len(s) self._end += len(s) + def __enter__(self): + self.fp.__enter__() + return self + + def __exit__(self, *args): + return self.fp.__exit__(*args) + def _divertopener(opener, target): """build an opener that writes in 'target.a' instead of 'target'""" def _divert(name, mode='r', checkambig=False): @@ -420,7 +433,7 @@ self._delaybuf = None self._divert = False # split when we're done - self.checkinlinesize(tr) + self._enforceinlinesize(tr) def _writepending(self, tr): "create a file containing the unfinalized state for pretxnchangegroup" @@ -446,9 +459,9 @@ return False - def checkinlinesize(self, tr, fp=None): + def _enforceinlinesize(self, tr, fp=None): if not self._delayed: - revlog.revlog.checkinlinesize(self, tr, fp) + revlog.revlog._enforceinlinesize(self, tr, fp) def read(self, node): """Obtain data from a parsed changelog revision. @@ -505,8 +518,8 @@ if not user: raise error.RevlogError(_("empty username")) if "\n" in user: - raise error.RevlogError(_("username %s contains a newline") - % repr(user)) + raise error.RevlogError(_("username %r contains a newline") + % pycompat.bytestr(user)) desc = stripdesc(desc) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/cmdutil.py --- a/mercurial/cmdutil.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/cmdutil.py Sat Feb 24 17:49:10 2018 -0600 @@ -8,7 +8,6 @@ from __future__ import absolute_import import errno -import itertools import os import re import tempfile @@ -26,26 +25,23 @@ changelog, copies, crecord as crecordmod, - dagop, dirstateguard, encoding, error, formatter, - graphmod, + logcmdutil, match as matchmod, - mdiff, + merge as mergemod, obsolete, patch, pathutil, pycompat, registrar, revlog, - revset, - revsetlang, rewriteutil, scmutil, smartset, - templatekw, + subrepoutil, templater, util, vfs as vfsmod, @@ -225,7 +221,6 @@ def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts): - from . import merge as mergemod opts = pycompat.byteskwargs(opts) if not ui.interactive(): if cmdsuggest: @@ -562,8 +557,6 @@ return '\n'.join(commentedlines) + '\n' def _conflictsmsg(repo): - # avoid merge cycle - from . import merge as mergemod mergestate = mergemod.mergestate.read(repo) if not mergestate.active(): return @@ -898,65 +891,45 @@ else: return commiteditor -def loglimit(opts): - """get the log limit according to option -l/--limit""" - limit = opts.get('limit') - if limit: - try: - limit = int(limit) - except ValueError: - raise error.Abort(_('limit must be a positive integer')) - if limit <= 0: - raise error.Abort(_('limit must be positive')) - else: - limit = None - return limit - -def makefilename(repo, pat, node, desc=None, +def makefilename(ctx, pat, total=None, seqno=None, revwidth=None, pathname=None): - node_expander = { - 'H': lambda: hex(node), - 'R': lambda: '%d' % repo.changelog.rev(node), - 'h': lambda: short(node), - 'm': lambda: re.sub('[^\w]', '_', desc or '') - } expander = { + 'H': lambda: ctx.hex(), + 'R': lambda: '%d' % ctx.rev(), + 'h': lambda: short(ctx.node()), + 'm': lambda: re.sub('[^\w]', '_', + ctx.description().rstrip().splitlines()[0]), + 'r': lambda: ('%d' % ctx.rev()).zfill(revwidth or 0), '%': lambda: '%', - 'b': lambda: os.path.basename(repo.root), + 'b': lambda: os.path.basename(ctx.repo().root), } - - try: - if node: - expander.update(node_expander) - if node: - expander['r'] = (lambda: - ('%d' % repo.changelog.rev(node)).zfill(revwidth or 0)) - if total is not None: - expander['N'] = lambda: '%d' % total - if seqno is not None: - expander['n'] = lambda: '%d' % seqno - if total is not None and seqno is not None: - expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total))) - if pathname is not None: - expander['s'] = lambda: os.path.basename(pathname) - expander['d'] = lambda: os.path.dirname(pathname) or '.' - expander['p'] = lambda: pathname - - newname = [] - patlen = len(pat) - i = 0 - while i < patlen: + if total is not None: + expander['N'] = lambda: '%d' % total + if seqno is not None: + expander['n'] = lambda: '%d' % seqno + if total is not None and seqno is not None: + expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total))) + if pathname is not None: + expander['s'] = lambda: os.path.basename(pathname) + expander['d'] = lambda: os.path.dirname(pathname) or '.' + expander['p'] = lambda: pathname + + newname = [] + patlen = len(pat) + i = 0 + while i < patlen: + c = pat[i:i + 1] + if c == '%': + i += 1 c = pat[i:i + 1] - if c == '%': - i += 1 - c = pat[i:i + 1] + try: c = expander[c]() - newname.append(c) - i += 1 - return ''.join(newname) - except KeyError as inst: - raise error.Abort(_("invalid format spec '%%%s' in output filename") % - inst.args[0]) + except KeyError: + raise error.Abort(_("invalid format spec '%%%s' in output " + "filename") % c) + newname.append(c) + i += 1 + return ''.join(newname) def isstdiofilename(pat): """True if the given pat looks like a filename denoting stdin/stdout""" @@ -981,19 +954,20 @@ def __exit__(self, exc_type, exc_value, exc_tb): pass -def makefileobj(repo, pat, node=None, desc=None, total=None, +def makefileobj(ctx, pat, total=None, seqno=None, revwidth=None, mode='wb', modemap=None, pathname=None): writable = mode not in ('r', 'rb') if isstdiofilename(pat): + repo = ctx.repo() if writable: fp = repo.ui.fout else: fp = repo.ui.fin return _unclosablefile(fp) - fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname) + fn = makefilename(ctx, pat, total, seqno, revwidth, pathname) if modemap is not None: mode = modemap.get(fn, mode) if mode == 'wb': @@ -1568,9 +1542,7 @@ ctx = repo[rev] fo = None if not fp and fntemplate: - desc_lines = ctx.description().rstrip().split('\n') - desc = desc_lines[0] #Commit always has a first line. - fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc, + fo = makefileobj(ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth, mode='wb', modemap=filemode) dest = fo.name @@ -1583,500 +1555,6 @@ if fo is not None: fo.close() -def diffordiffstat(ui, repo, diffopts, node1, node2, match, - changes=None, stat=False, fp=None, prefix='', - root='', listsubrepos=False, hunksfilterfn=None): - '''show diff or diffstat.''' - if fp is None: - write = ui.write - else: - def write(s, **kw): - fp.write(s) - - if root: - relroot = pathutil.canonpath(repo.root, repo.getcwd(), root) - else: - relroot = '' - if relroot != '': - # XXX relative roots currently don't work if the root is within a - # subrepo - uirelroot = match.uipath(relroot) - relroot += '/' - for matchroot in match.files(): - if not matchroot.startswith(relroot): - ui.warn(_('warning: %s not inside relative root %s\n') % ( - match.uipath(matchroot), uirelroot)) - - if stat: - diffopts = diffopts.copy(context=0, noprefix=False) - width = 80 - if not ui.plain(): - width = ui.termwidth() - chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts, - prefix=prefix, relroot=relroot, - hunksfilterfn=hunksfilterfn) - for chunk, label in patch.diffstatui(util.iterlines(chunks), - width=width): - write(chunk, label=label) - else: - for chunk, label in patch.diffui(repo, node1, node2, match, - changes, opts=diffopts, prefix=prefix, - relroot=relroot, - hunksfilterfn=hunksfilterfn): - write(chunk, label=label) - - if listsubrepos: - ctx1 = repo[node1] - ctx2 = repo[node2] - for subpath, sub in scmutil.itersubrepos(ctx1, ctx2): - tempnode2 = node2 - try: - if node2 is not None: - tempnode2 = ctx2.substate[subpath][1] - except KeyError: - # A subrepo that existed in node1 was deleted between node1 and - # node2 (inclusive). Thus, ctx2's substate won't contain that - # subpath. The best we can do is to ignore it. - tempnode2 = None - submatch = matchmod.subdirmatcher(subpath, match) - sub.diff(ui, diffopts, tempnode2, submatch, changes=changes, - stat=stat, fp=fp, prefix=prefix) - -def _changesetlabels(ctx): - labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()] - if ctx.obsolete(): - labels.append('changeset.obsolete') - if ctx.isunstable(): - labels.append('changeset.unstable') - for instability in ctx.instabilities(): - labels.append('instability.%s' % instability) - return ' '.join(labels) - -class changeset_printer(object): - '''show changeset information when templating not requested.''' - - def __init__(self, ui, repo, matchfn, diffopts, buffered): - self.ui = ui - self.repo = repo - self.buffered = buffered - self.matchfn = matchfn - self.diffopts = diffopts - self.header = {} - self.hunk = {} - self.lastheader = None - self.footer = None - self._columns = templatekw.getlogcolumns() - - def flush(self, ctx): - rev = ctx.rev() - if rev in self.header: - h = self.header[rev] - if h != self.lastheader: - self.lastheader = h - self.ui.write(h) - del self.header[rev] - if rev in self.hunk: - self.ui.write(self.hunk[rev]) - del self.hunk[rev] - - def close(self): - if self.footer: - self.ui.write(self.footer) - - def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None, - **props): - props = pycompat.byteskwargs(props) - if self.buffered: - self.ui.pushbuffer(labeled=True) - self._show(ctx, copies, matchfn, hunksfilterfn, props) - self.hunk[ctx.rev()] = self.ui.popbuffer() - else: - self._show(ctx, copies, matchfn, hunksfilterfn, props) - - def _show(self, ctx, copies, matchfn, hunksfilterfn, props): - '''show a single changeset or file revision''' - changenode = ctx.node() - rev = ctx.rev() - - if self.ui.quiet: - self.ui.write("%s\n" % scmutil.formatchangeid(ctx), - label='log.node') - return - - columns = self._columns - self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx), - label=_changesetlabels(ctx)) - - # branches are shown first before any other names due to backwards - # compatibility - branch = ctx.branch() - # don't show the default branch name - if branch != 'default': - self.ui.write(columns['branch'] % branch, label='log.branch') - - for nsname, ns in self.repo.names.iteritems(): - # branches has special logic already handled above, so here we just - # skip it - if nsname == 'branches': - continue - # we will use the templatename as the color name since those two - # should be the same - for name in ns.names(self.repo, changenode): - self.ui.write(ns.logfmt % name, - label='log.%s' % ns.colorname) - if self.ui.debugflag: - self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase') - for pctx in scmutil.meaningfulparents(self.repo, ctx): - label = 'log.parent changeset.%s' % pctx.phasestr() - self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx), - label=label) - - if self.ui.debugflag and rev is not None: - mnode = ctx.manifestnode() - mrev = self.repo.manifestlog._revlog.rev(mnode) - self.ui.write(columns['manifest'] - % scmutil.formatrevnode(self.ui, mrev, mnode), - label='ui.debug log.manifest') - self.ui.write(columns['user'] % ctx.user(), label='log.user') - self.ui.write(columns['date'] % util.datestr(ctx.date()), - label='log.date') - - if ctx.isunstable(): - instabilities = ctx.instabilities() - self.ui.write(columns['instability'] % ', '.join(instabilities), - label='log.instability') - - elif ctx.obsolete(): - self._showobsfate(ctx) - - self._exthook(ctx) - - if self.ui.debugflag: - files = ctx.p1().status(ctx)[:3] - for key, value in zip(['files', 'files+', 'files-'], files): - if value: - self.ui.write(columns[key] % " ".join(value), - label='ui.debug log.files') - elif ctx.files() and self.ui.verbose: - self.ui.write(columns['files'] % " ".join(ctx.files()), - label='ui.note log.files') - if copies and self.ui.verbose: - copies = ['%s (%s)' % c for c in copies] - self.ui.write(columns['copies'] % ' '.join(copies), - label='ui.note log.copies') - - extra = ctx.extra() - if extra and self.ui.debugflag: - for key, value in sorted(extra.items()): - self.ui.write(columns['extra'] % (key, util.escapestr(value)), - label='ui.debug log.extra') - - description = ctx.description().strip() - if description: - if self.ui.verbose: - self.ui.write(_("description:\n"), - label='ui.note log.description') - self.ui.write(description, - label='ui.note log.description') - self.ui.write("\n\n") - else: - self.ui.write(columns['summary'] % description.splitlines()[0], - label='log.summary') - self.ui.write("\n") - - self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn) - - def _showobsfate(self, ctx): - obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui) - - if obsfate: - for obsfateline in obsfate: - self.ui.write(self._columns['obsolete'] % obsfateline, - label='log.obsfate') - - def _exthook(self, ctx): - '''empty method used by extension as a hook point - ''' - - def showpatch(self, ctx, matchfn, hunksfilterfn=None): - if not matchfn: - matchfn = self.matchfn - if matchfn: - stat = self.diffopts.get('stat') - diff = self.diffopts.get('patch') - diffopts = patch.diffallopts(self.ui, self.diffopts) - node = ctx.node() - prev = ctx.p1().node() - if stat: - diffordiffstat(self.ui, self.repo, diffopts, prev, node, - match=matchfn, stat=True, - hunksfilterfn=hunksfilterfn) - if diff: - if stat: - self.ui.write("\n") - diffordiffstat(self.ui, self.repo, diffopts, prev, node, - match=matchfn, stat=False, - hunksfilterfn=hunksfilterfn) - if stat or diff: - self.ui.write("\n") - -class jsonchangeset(changeset_printer): - '''format changeset information.''' - - def __init__(self, ui, repo, matchfn, diffopts, buffered): - changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered) - self.cache = {} - self._first = True - - def close(self): - if not self._first: - self.ui.write("\n]\n") - else: - self.ui.write("[]\n") - - def _show(self, ctx, copies, matchfn, hunksfilterfn, props): - '''show a single changeset or file revision''' - rev = ctx.rev() - if rev is None: - jrev = jnode = 'null' - else: - jrev = '%d' % rev - jnode = '"%s"' % hex(ctx.node()) - j = encoding.jsonescape - - if self._first: - self.ui.write("[\n {") - self._first = False - else: - self.ui.write(",\n {") - - if self.ui.quiet: - self.ui.write(('\n "rev": %s') % jrev) - self.ui.write((',\n "node": %s') % jnode) - self.ui.write('\n }') - return - - self.ui.write(('\n "rev": %s') % jrev) - self.ui.write((',\n "node": %s') % jnode) - self.ui.write((',\n "branch": "%s"') % j(ctx.branch())) - self.ui.write((',\n "phase": "%s"') % ctx.phasestr()) - self.ui.write((',\n "user": "%s"') % j(ctx.user())) - self.ui.write((',\n "date": [%d, %d]') % ctx.date()) - self.ui.write((',\n "desc": "%s"') % j(ctx.description())) - - self.ui.write((',\n "bookmarks": [%s]') % - ", ".join('"%s"' % j(b) for b in ctx.bookmarks())) - self.ui.write((',\n "tags": [%s]') % - ", ".join('"%s"' % j(t) for t in ctx.tags())) - self.ui.write((',\n "parents": [%s]') % - ", ".join('"%s"' % c.hex() for c in ctx.parents())) - - if self.ui.debugflag: - if rev is None: - jmanifestnode = 'null' - else: - jmanifestnode = '"%s"' % hex(ctx.manifestnode()) - self.ui.write((',\n "manifest": %s') % jmanifestnode) - - self.ui.write((',\n "extra": {%s}') % - ", ".join('"%s": "%s"' % (j(k), j(v)) - for k, v in ctx.extra().items())) - - files = ctx.p1().status(ctx) - self.ui.write((',\n "modified": [%s]') % - ", ".join('"%s"' % j(f) for f in files[0])) - self.ui.write((',\n "added": [%s]') % - ", ".join('"%s"' % j(f) for f in files[1])) - self.ui.write((',\n "removed": [%s]') % - ", ".join('"%s"' % j(f) for f in files[2])) - - elif self.ui.verbose: - self.ui.write((',\n "files": [%s]') % - ", ".join('"%s"' % j(f) for f in ctx.files())) - - if copies: - self.ui.write((',\n "copies": {%s}') % - ", ".join('"%s": "%s"' % (j(k), j(v)) - for k, v in copies)) - - matchfn = self.matchfn - if matchfn: - stat = self.diffopts.get('stat') - diff = self.diffopts.get('patch') - diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True) - node, prev = ctx.node(), ctx.p1().node() - if stat: - self.ui.pushbuffer() - diffordiffstat(self.ui, self.repo, diffopts, prev, node, - match=matchfn, stat=True) - self.ui.write((',\n "diffstat": "%s"') - % j(self.ui.popbuffer())) - if diff: - self.ui.pushbuffer() - diffordiffstat(self.ui, self.repo, diffopts, prev, node, - match=matchfn, stat=False) - self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer())) - - self.ui.write("\n }") - -class changeset_templater(changeset_printer): - '''format changeset information. - - Note: there are a variety of convenience functions to build a - changeset_templater for common cases. See functions such as: - makelogtemplater, show_changeset, buildcommittemplate, or other - functions that use changesest_templater. - ''' - - # Arguments before "buffered" used to be positional. Consider not - # adding/removing arguments before "buffered" to not break callers. - def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None, - buffered=False): - diffopts = diffopts or {} - - changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered) - tres = formatter.templateresources(ui, repo) - self.t = formatter.loadtemplater(ui, tmplspec, - defaults=templatekw.keywords, - resources=tres, - cache=templatekw.defaulttempl) - self._counter = itertools.count() - self.cache = tres['cache'] # shared with _graphnodeformatter() - - self._tref = tmplspec.ref - self._parts = {'header': '', 'footer': '', - tmplspec.ref: tmplspec.ref, - 'docheader': '', 'docfooter': '', - 'separator': ''} - if tmplspec.mapfile: - # find correct templates for current mode, for backward - # compatibility with 'log -v/-q/--debug' using a mapfile - tmplmodes = [ - (True, ''), - (self.ui.verbose, '_verbose'), - (self.ui.quiet, '_quiet'), - (self.ui.debugflag, '_debug'), - ] - for mode, postfix in tmplmodes: - for t in self._parts: - cur = t + postfix - if mode and cur in self.t: - self._parts[t] = cur - else: - partnames = [p for p in self._parts.keys() if p != tmplspec.ref] - m = formatter.templatepartsmap(tmplspec, self.t, partnames) - self._parts.update(m) - - if self._parts['docheader']: - self.ui.write(templater.stringify(self.t(self._parts['docheader']))) - - def close(self): - if self._parts['docfooter']: - if not self.footer: - self.footer = "" - self.footer += templater.stringify(self.t(self._parts['docfooter'])) - return super(changeset_templater, self).close() - - def _show(self, ctx, copies, matchfn, hunksfilterfn, props): - '''show a single changeset or file revision''' - props = props.copy() - props['ctx'] = ctx - props['index'] = index = next(self._counter) - props['revcache'] = {'copies': copies} - props = pycompat.strkwargs(props) - - # write separator, which wouldn't work well with the header part below - # since there's inherently a conflict between header (across items) and - # separator (per item) - if self._parts['separator'] and index > 0: - self.ui.write(templater.stringify(self.t(self._parts['separator']))) - - # write header - if self._parts['header']: - h = templater.stringify(self.t(self._parts['header'], **props)) - if self.buffered: - self.header[ctx.rev()] = h - else: - if self.lastheader != h: - self.lastheader = h - self.ui.write(h) - - # write changeset metadata, then patch if requested - key = self._parts[self._tref] - self.ui.write(templater.stringify(self.t(key, **props))) - self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn) - - if self._parts['footer']: - if not self.footer: - self.footer = templater.stringify( - self.t(self._parts['footer'], **props)) - -def logtemplatespec(tmpl, mapfile): - if mapfile: - return formatter.templatespec('changeset', tmpl, mapfile) - else: - return formatter.templatespec('', tmpl, None) - -def _lookuplogtemplate(ui, tmpl, style): - """Find the template matching the given template spec or style - - See formatter.lookuptemplate() for details. - """ - - # ui settings - if not tmpl and not style: # template are stronger than style - tmpl = ui.config('ui', 'logtemplate') - if tmpl: - return logtemplatespec(templater.unquotestring(tmpl), None) - else: - style = util.expandpath(ui.config('ui', 'style')) - - if not tmpl and style: - mapfile = style - if not os.path.split(mapfile)[0]: - mapname = (templater.templatepath('map-cmdline.' + mapfile) - or templater.templatepath(mapfile)) - if mapname: - mapfile = mapname - return logtemplatespec(None, mapfile) - - if not tmpl: - return logtemplatespec(None, None) - - return formatter.lookuptemplate(ui, 'changeset', tmpl) - -def makelogtemplater(ui, repo, tmpl, buffered=False): - """Create a changeset_templater from a literal template 'tmpl' - byte-string.""" - spec = logtemplatespec(tmpl, None) - return changeset_templater(ui, repo, spec, buffered=buffered) - -def show_changeset(ui, repo, opts, buffered=False): - """show one changeset using template or regular display. - - Display format will be the first non-empty hit of: - 1. option 'template' - 2. option 'style' - 3. [ui] setting 'logtemplate' - 4. [ui] setting 'style' - If all of these values are either the unset or the empty string, - regular display via changeset_printer() is done. - """ - # options - match = None - if opts.get('patch') or opts.get('stat'): - match = scmutil.matchall(repo) - - if opts.get('template') == 'json': - return jsonchangeset(ui, repo, match, opts, buffered) - - spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style')) - - if not spec.ref and not spec.tmpl and not spec.mapfile: - return changeset_printer(ui, repo, match, opts, buffered) - - return changeset_templater(ui, repo, spec, match, opts, buffered) - def showmarker(fm, marker, index=None): """utility function to display obsolescence marker in a readable way @@ -2095,7 +1573,8 @@ fm.write('date', '(%s) ', fm.formatdate(marker.date())) meta = marker.metadata().copy() meta.pop('date', None) - fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', ')) + smeta = util.rapply(pycompat.maybebytestr, meta) + fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', ')) fm.plain('\n') def finddate(ui, repo, date): @@ -2352,7 +1831,7 @@ else: self.revs.discard(value) ctx = change(value) - matches = filter(match, ctx.files()) + matches = [f for f in ctx.files() if match(f)] if matches: fncache[value] = matches self.set.add(value) @@ -2415,394 +1894,6 @@ return iterate() -def _makelogmatcher(repo, revs, pats, opts): - """Build matcher and expanded patterns from log options - - If --follow, revs are the revisions to follow from. - - Returns (match, pats, slowpath) where - - match: a matcher built from the given pats and -I/-X opts - - pats: patterns used (globs are expanded on Windows) - - slowpath: True if patterns aren't as simple as scanning filelogs - """ - # pats/include/exclude are passed to match.match() directly in - # _matchfiles() revset but walkchangerevs() builds its matcher with - # scmutil.match(). The difference is input pats are globbed on - # platforms without shell expansion (windows). - wctx = repo[None] - match, pats = scmutil.matchandpats(wctx, pats, opts) - slowpath = match.anypats() or (not match.always() and opts.get('removed')) - if not slowpath: - follow = opts.get('follow') or opts.get('follow_first') - startctxs = [] - if follow and opts.get('rev'): - startctxs = [repo[r] for r in revs] - for f in match.files(): - if follow and startctxs: - # No idea if the path was a directory at that revision, so - # take the slow path. - if any(f not in c for c in startctxs): - slowpath = True - continue - elif follow and f not in wctx: - # If the file exists, it may be a directory, so let it - # take the slow path. - if os.path.exists(repo.wjoin(f)): - slowpath = True - continue - else: - raise error.Abort(_('cannot follow file not in parent ' - 'revision: "%s"') % f) - filelog = repo.file(f) - if not filelog: - # A zero count may be a directory or deleted file, so - # try to find matching entries on the slow path. - if follow: - raise error.Abort( - _('cannot follow nonexistent file: "%s"') % f) - slowpath = True - - # We decided to fall back to the slowpath because at least one - # of the paths was not a file. Check to see if at least one of them - # existed in history - in that case, we'll continue down the - # slowpath; otherwise, we can turn off the slowpath - if slowpath: - for path in match.files(): - if path == '.' or path in repo.store: - break - else: - slowpath = False - - return match, pats, slowpath - -def _fileancestors(repo, revs, match, followfirst): - fctxs = [] - for r in revs: - ctx = repo[r] - fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match)) - - # When displaying a revision with --patch --follow FILE, we have - # to know which file of the revision must be diffed. With - # --follow, we want the names of the ancestors of FILE in the - # revision, stored in "fcache". "fcache" is populated as a side effect - # of the graph traversal. - fcache = {} - def filematcher(rev): - return scmutil.matchfiles(repo, fcache.get(rev, [])) - - def revgen(): - for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst): - fcache[rev] = [c.path() for c in cs] - yield rev - return smartset.generatorset(revgen(), iterasc=False), filematcher - -def _makenofollowlogfilematcher(repo, pats, opts): - '''hook for extensions to override the filematcher for non-follow cases''' - return None - -_opt2logrevset = { - 'no_merges': ('not merge()', None), - 'only_merges': ('merge()', None), - '_matchfiles': (None, '_matchfiles(%ps)'), - 'date': ('date(%s)', None), - 'branch': ('branch(%s)', '%lr'), - '_patslog': ('filelog(%s)', '%lr'), - 'keyword': ('keyword(%s)', '%lr'), - 'prune': ('ancestors(%s)', 'not %lr'), - 'user': ('user(%s)', '%lr'), -} - -def _makelogrevset(repo, match, pats, slowpath, opts): - """Return a revset string built from log options and file patterns""" - opts = dict(opts) - # follow or not follow? - follow = opts.get('follow') or opts.get('follow_first') - - # branch and only_branch are really aliases and must be handled at - # the same time - opts['branch'] = opts.get('branch', []) + opts.get('only_branch', []) - opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']] - - if slowpath: - # See walkchangerevs() slow path. - # - # pats/include/exclude cannot be represented as separate - # revset expressions as their filtering logic applies at file - # level. For instance "-I a -X b" matches a revision touching - # "a" and "b" while "file(a) and not file(b)" does - # not. Besides, filesets are evaluated against the working - # directory. - matchargs = ['r:', 'd:relpath'] - for p in pats: - matchargs.append('p:' + p) - for p in opts.get('include', []): - matchargs.append('i:' + p) - for p in opts.get('exclude', []): - matchargs.append('x:' + p) - opts['_matchfiles'] = matchargs - elif not follow: - opts['_patslog'] = list(pats) - - expr = [] - for op, val in sorted(opts.iteritems()): - if not val: - continue - if op not in _opt2logrevset: - continue - revop, listop = _opt2logrevset[op] - if revop and '%' not in revop: - expr.append(revop) - elif not listop: - expr.append(revsetlang.formatspec(revop, val)) - else: - if revop: - val = [revsetlang.formatspec(revop, v) for v in val] - expr.append(revsetlang.formatspec(listop, val)) - - if expr: - expr = '(' + ' and '.join(expr) + ')' - else: - expr = None - return expr - -def _logrevs(repo, opts): - """Return the initial set of revisions to be filtered or followed""" - follow = opts.get('follow') or opts.get('follow_first') - if opts.get('rev'): - revs = scmutil.revrange(repo, opts['rev']) - elif follow and repo.dirstate.p1() == nullid: - revs = smartset.baseset() - elif follow: - revs = repo.revs('.') - else: - revs = smartset.spanset(repo) - revs.reverse() - return revs - -def getlogrevs(repo, pats, opts): - """Return (revs, filematcher) where revs is a smartset - - filematcher is a callable taking a revision number and returning a match - objects filtering the files to be detailed when displaying the revision. - """ - follow = opts.get('follow') or opts.get('follow_first') - followfirst = opts.get('follow_first') - limit = loglimit(opts) - revs = _logrevs(repo, opts) - if not revs: - return smartset.baseset(), None - match, pats, slowpath = _makelogmatcher(repo, revs, pats, opts) - filematcher = None - if follow: - if slowpath or match.always(): - revs = dagop.revancestors(repo, revs, followfirst=followfirst) - else: - revs, filematcher = _fileancestors(repo, revs, match, followfirst) - revs.reverse() - if filematcher is None: - filematcher = _makenofollowlogfilematcher(repo, pats, opts) - if filematcher is None: - def filematcher(rev): - return match - - expr = _makelogrevset(repo, match, pats, slowpath, opts) - if opts.get('graph') and opts.get('rev'): - # User-specified revs might be unsorted, but don't sort before - # _makelogrevset because it might depend on the order of revs - if not (revs.isdescending() or revs.istopo()): - revs.sort(reverse=True) - if expr: - matcher = revset.match(None, expr) - revs = matcher(repo, revs) - if limit is not None: - revs = revs.slice(0, limit) - return revs, filematcher - -def _parselinerangelogopt(repo, opts): - """Parse --line-range log option and return a list of tuples (filename, - (fromline, toline)). - """ - linerangebyfname = [] - for pat in opts.get('line_range', []): - try: - pat, linerange = pat.rsplit(',', 1) - except ValueError: - raise error.Abort(_('malformatted line-range pattern %s') % pat) - try: - fromline, toline = map(int, linerange.split(':')) - except ValueError: - raise error.Abort(_("invalid line range for %s") % pat) - msg = _("line range pattern '%s' must match exactly one file") % pat - fname = scmutil.parsefollowlinespattern(repo, None, pat, msg) - linerangebyfname.append( - (fname, util.processlinerange(fromline, toline))) - return linerangebyfname - -def getloglinerangerevs(repo, userrevs, opts): - """Return (revs, filematcher, hunksfilter). - - "revs" are revisions obtained by processing "line-range" log options and - walking block ancestors of each specified file/line-range. - - "filematcher(rev) -> match" is a factory function returning a match object - for a given revision for file patterns specified in --line-range option. - If neither --stat nor --patch options are passed, "filematcher" is None. - - "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function - returning a hunks filtering function. - If neither --stat nor --patch options are passed, "filterhunks" is None. - """ - wctx = repo[None] - - # Two-levels map of "rev -> file ctx -> [line range]". - linerangesbyrev = {} - for fname, (fromline, toline) in _parselinerangelogopt(repo, opts): - if fname not in wctx: - raise error.Abort(_('cannot follow file not in parent ' - 'revision: "%s"') % fname) - fctx = wctx.filectx(fname) - for fctx, linerange in dagop.blockancestors(fctx, fromline, toline): - rev = fctx.introrev() - if rev not in userrevs: - continue - linerangesbyrev.setdefault( - rev, {}).setdefault( - fctx.path(), []).append(linerange) - - filematcher = None - hunksfilter = None - if opts.get('patch') or opts.get('stat'): - - def nofilterhunksfn(fctx, hunks): - return hunks - - def hunksfilter(rev): - fctxlineranges = linerangesbyrev.get(rev) - if fctxlineranges is None: - return nofilterhunksfn - - def filterfn(fctx, hunks): - lineranges = fctxlineranges.get(fctx.path()) - if lineranges is not None: - for hr, lines in hunks: - if hr is None: # binary - yield hr, lines - continue - if any(mdiff.hunkinrange(hr[2:], lr) - for lr in lineranges): - yield hr, lines - else: - for hunk in hunks: - yield hunk - - return filterfn - - def filematcher(rev): - files = list(linerangesbyrev.get(rev, [])) - return scmutil.matchfiles(repo, files) - - revs = sorted(linerangesbyrev, reverse=True) - - return revs, filematcher, hunksfilter - -def _graphnodeformatter(ui, displayer): - spec = ui.config('ui', 'graphnodetemplate') - if not spec: - return templatekw.showgraphnode # fast path for "{graphnode}" - - spec = templater.unquotestring(spec) - tres = formatter.templateresources(ui) - if isinstance(displayer, changeset_templater): - tres['cache'] = displayer.cache # reuse cache of slow templates - templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords, - resources=tres) - def formatnode(repo, ctx): - props = {'ctx': ctx, 'repo': repo, 'revcache': {}} - return templ.render(props) - return formatnode - -def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None, - filematcher=None, props=None): - props = props or {} - formatnode = _graphnodeformatter(ui, displayer) - state = graphmod.asciistate() - styles = state['styles'] - - # only set graph styling if HGPLAIN is not set. - if ui.plain('graph'): - # set all edge styles to |, the default pre-3.8 behaviour - styles.update(dict.fromkeys(styles, '|')) - else: - edgetypes = { - 'parent': graphmod.PARENT, - 'grandparent': graphmod.GRANDPARENT, - 'missing': graphmod.MISSINGPARENT - } - for name, key in edgetypes.items(): - # experimental config: experimental.graphstyle.* - styles[key] = ui.config('experimental', 'graphstyle.%s' % name, - styles[key]) - if not styles[key]: - styles[key] = None - - # experimental config: experimental.graphshorten - state['graphshorten'] = ui.configbool('experimental', 'graphshorten') - - for rev, type, ctx, parents in dag: - char = formatnode(repo, ctx) - copies = None - if getrenamed and ctx.rev(): - copies = [] - for fn in ctx.files(): - rename = getrenamed(fn, ctx.rev()) - if rename: - copies.append((fn, rename[0])) - revmatchfn = None - if filematcher is not None: - revmatchfn = filematcher(ctx.rev()) - edges = edgefn(type, char, state, rev, parents) - firstedge = next(edges) - width = firstedge[2] - displayer.show(ctx, copies=copies, matchfn=revmatchfn, - _graphwidth=width, **pycompat.strkwargs(props)) - lines = displayer.hunk.pop(rev).split('\n') - if not lines[-1]: - del lines[-1] - displayer.flush(ctx) - for type, char, width, coldata in itertools.chain([firstedge], edges): - graphmod.ascii(ui, state, type, char, lines, coldata) - lines = [] - displayer.close() - -def graphlog(ui, repo, revs, filematcher, opts): - # Parameters are identical to log command ones - revdag = graphmod.dagwalker(repo, revs) - - getrenamed = None - if opts.get('copies'): - endrev = None - if opts.get('rev'): - endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1 - getrenamed = templatekw.getrenamedfn(repo, endrev=endrev) - - ui.pager('log') - displayer = show_changeset(ui, repo, opts, buffered=True) - displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed, - filematcher) - -def checkunsupportedgraphflags(pats, opts): - for op in ["newest_first"]: - if op in opts and opts[op]: - raise error.Abort(_("-G/--graph option is incompatible with --%s") - % op.replace("_", "-")) - -def graphrevs(repo, nodes, opts): - limit = loglimit(opts) - nodes.reverse() - if limit is not None: - nodes = nodes[:limit] - return graphmod.nodes(repo, nodes) - def add(ui, repo, match, prefix, explicitonly, **opts): join = lambda f: os.path.join(prefix, f) bad = [] @@ -3071,7 +2162,7 @@ def write(path): filename = None if fntemplate: - filename = makefilename(repo, fntemplate, ctx.node(), + filename = makefilename(ctx, fntemplate, pathname=os.path.join(prefix, path)) # attempt to create the directory if it does not already exist try: @@ -3089,12 +2180,16 @@ mfnode = ctx.manifestnode() try: if mfnode and mfl[mfnode].find(file)[0]: + scmutil.fileprefetchhooks(repo, ctx, [file]) write(file) return 0 except KeyError: pass - for abs in ctx.walk(matcher): + files = [f for f in ctx.walk(matcher)] + scmutil.fileprefetchhooks(repo, ctx, files) + + for abs in files: write(abs) err = 0 @@ -3204,13 +2299,12 @@ # subrepo.precommit(). To minimize the risk of this hack, we do # nothing if .hgsub does not exist. if '.hgsub' in wctx or '.hgsub' in old: - from . import subrepo # avoid cycle: cmdutil -> subrepo -> cmdutil - subs, commitsubs, newsubstate = subrepo.precommit( + subs, commitsubs, newsubstate = subrepoutil.precommit( ui, wctx, wctx._status, matcher) # amend should abort if commitsubrepos is enabled assert not commitsubs if subs: - subrepo.writestate(repo, newsubstate) + subrepoutil.writestate(repo, newsubstate) filestoamend = set(f for f in wctx.files() if matcher(f)) @@ -3398,7 +2492,7 @@ def buildcommittemplate(repo, ctx, subs, extramsg, ref): ui = repo.ui spec = formatter.templatespec(ref, None, None) - t = changeset_templater(ui, repo, spec, None, {}, False) + t = logcmdutil.changesettemplater(ui, repo, spec) t.t.cache.update((k, templater.unquotestring(v)) for k, v in repo.ui.configitems('committemplate')) @@ -3763,7 +2857,15 @@ if not opts.get('dry_run'): needdata = ('revert', 'add', 'undelete') - _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata]) + if _revertprefetch is not _revertprefetchstub: + ui.deprecwarn("'cmdutil._revertprefetch' is deprecated, " + "add a callback to 'scmutil.fileprefetchhooks'", + '4.6', stacklevel=1) + _revertprefetch(repo, ctx, + *[actions[name][0] for name in needdata]) + oplist = [actions[name][0] for name in needdata] + prefetch = scmutil.fileprefetchhooks + prefetch(repo, ctx, [f for sublist in oplist for f in sublist]) _performrevert(repo, parents, ctx, actions, interactive, tobackup) if targetsubs: @@ -3776,8 +2878,11 @@ raise error.Abort("subrepository '%s' does not exist in %s!" % (sub, short(ctx.node()))) -def _revertprefetch(repo, ctx, *files): - """Let extension changing the storage layer prefetch content""" +def _revertprefetchstub(repo, ctx, *files): + """Stub method for detecting extension wrapping of _revertprefetch(), to + issue a deprecation warning.""" + +_revertprefetch = _revertprefetchstub def _performrevert(repo, parents, ctx, actions, interactive=False, tobackup=None): @@ -3791,7 +2896,6 @@ parent, p2 = parents node = ctx.node() excluded_files = [] - matcher_opts = {"exclude": excluded_files} def checkout(f): fc = ctx[f] @@ -3812,7 +2916,7 @@ if choice == 0: repo.dirstate.drop(f) else: - excluded_files.append(repo.wjoin(f)) + excluded_files.append(f) else: repo.dirstate.drop(f) for f in actions['remove'][0]: @@ -3823,7 +2927,7 @@ if choice == 0: doremove(f) else: - excluded_files.append(repo.wjoin(f)) + excluded_files.append(f) else: doremove(f) for f in actions['drop'][0]: @@ -3843,8 +2947,8 @@ newlyaddedandmodifiedfiles = set() if interactive: # Prompt the user for changes to revert - torevert = [repo.wjoin(f) for f in actions['revert'][0]] - m = scmutil.match(ctx, torevert, matcher_opts) + torevert = [f for f in actions['revert'][0] if f not in excluded_files] + m = scmutil.matchfiles(repo, torevert) diffopts = patch.difffeatureopts(repo.ui, whitespace=True) diffopts.nodates = True diffopts.git = True @@ -4025,3 +3129,23 @@ if after[1]: hint = after[0] raise error.Abort(_('no %s in progress') % task, hint=hint) + +class changeset_printer(logcmdutil.changesetprinter): + + def __init__(self, ui, *args, **kwargs): + msg = ("'cmdutil.changeset_printer' is deprecated, " + "use 'logcmdutil.logcmdutil'") + ui.deprecwarn(msg, "4.6") + super(changeset_printer, self).__init__(ui, *args, **kwargs) + +def displaygraph(ui, *args, **kwargs): + msg = ("'cmdutil.displaygraph' is deprecated, " + "use 'logcmdutil.displaygraph'") + ui.deprecwarn(msg, "4.6") + return logcmdutil.displaygraph(ui, *args, **kwargs) + +def show_changeset(ui, *args, **kwargs): + msg = ("'cmdutil.show_changeset' is deprecated, " + "use 'logcmdutil.changesetdisplayer'") + ui.deprecwarn(msg, "4.6") + return logcmdutil.changesetdisplayer(ui, *args, **kwargs) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/commands.py --- a/mercurial/commands.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/commands.py Sat Feb 24 17:49:10 2018 -0600 @@ -41,6 +41,7 @@ help, hg, lock as lockmod, + logcmdutil, merge as mergemod, obsolete, obsutil, @@ -53,12 +54,12 @@ rewriteutil, scmutil, server, - sshserver, streamclone, tags as tagsmod, templatekw, ui as uimod, util, + wireprotoserver, ) release = lockmod.release @@ -336,8 +337,8 @@ ('number', ' ', lambda x: x.fctx.rev(), formatrev), ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex), ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)), - ('file', ' ', lambda x: x.fctx.path(), str), - ('line_number', ':', lambda x: x.lineno, str), + ('file', ' ', lambda x: x.fctx.path(), pycompat.bytestr), + ('line_number', ':', lambda x: x.lineno, pycompat.bytestr), ] fieldnamemap = {'number': 'rev', 'changeset': 'node'} @@ -475,7 +476,7 @@ if not ctx: raise error.Abort(_('no working directory: please specify a revision')) node = ctx.node() - dest = cmdutil.makefilename(repo, dest, node) + dest = cmdutil.makefilename(ctx, dest) if os.path.realpath(dest) == repo.root: raise error.Abort(_('repository root cannot be destination')) @@ -485,11 +486,11 @@ if dest == '-': if kind == 'files': raise error.Abort(_('cannot archive plain files to stdout')) - dest = cmdutil.makefileobj(repo, dest) + dest = cmdutil.makefileobj(ctx, dest) if not prefix: prefix = os.path.basename(repo.root) + '-%h' - prefix = cmdutil.makefilename(repo, prefix, node) + prefix = cmdutil.makefilename(ctx, prefix) match = scmutil.match(ctx, [], opts) archival.archive(repo, dest, node, kind, not opts.get('no_decode'), match, prefix, subrepos=opts.get('subrepos')) @@ -823,7 +824,7 @@ cmdutil.bailifchanged(repo) return hg.clean(repo, node, show_stats=show_stats) - displayer = cmdutil.show_changeset(ui, repo, {}) + displayer = logcmdutil.changesetdisplayer(ui, repo, {}) if command: changesets = 1 @@ -1156,13 +1157,15 @@ def bundle(ui, repo, fname, dest=None, **opts): """create a bundle file - Generate a bundle file containing data to be added to a repository. + Generate a bundle file containing data to be transferred to another + repository. To create a bundle containing all changesets, use -a/--all (or --base null). Otherwise, hg assumes the destination will have all the nodes you specify with --base parameters. Otherwise, hg will assume the repository has all the nodes in destination, or - default-push/default if no destination is specified. + default-push/default if no destination is specified, where destination + is the repository you provide through DEST option. You can change bundle format with the -t/--type option. See :hg:`help bundlespec` for documentation on this format. By default, @@ -1219,7 +1222,7 @@ raise error.Abort(_("--base is incompatible with specifying " "a destination")) common = [repo.lookup(rev) for rev in base] - heads = revs and map(repo.lookup, revs) or None + heads = [repo.lookup(r) for r in revs] if revs else None outgoing = discovery.outgoing(repo, common, heads) else: dest = ui.expandpath(dest or 'default-push', dest or 'default') @@ -1550,7 +1553,7 @@ extra = {} if opts.get('close_branch'): - extra['close'] = 1 + extra['close'] = '1' if not bheads: raise error.Abort(_('can only close branch heads')) @@ -1873,9 +1876,9 @@ diffopts = patch.diffallopts(ui, opts) m = scmutil.match(repo[node2], pats, opts) ui.pager('diff') - cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat, - listsubrepos=opts.get('subrepos'), - root=opts.get('root')) + logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat, + listsubrepos=opts.get('subrepos'), + root=opts.get('root')) @command('^export', [('o', 'output', '', @@ -2647,7 +2650,7 @@ ui.pager('heads') heads = sorted(heads, key=lambda x: -x.rev()) - displayer = cmdutil.show_changeset(ui, repo, opts) + displayer = logcmdutil.changesetdisplayer(ui, repo, opts) for ctx in heads: displayer.show(ctx) displayer.close() @@ -3155,11 +3158,11 @@ """ opts = pycompat.byteskwargs(opts) if opts.get('graph'): - cmdutil.checkunsupportedgraphflags([], opts) + logcmdutil.checkunsupportedgraphflags([], opts) def display(other, chlist, displayer): - revdag = cmdutil.graphrevs(other, chlist, opts) - cmdutil.displaygraph(ui, repo, revdag, displayer, - graphmod.asciiedges) + revdag = logcmdutil.graphrevs(other, chlist, opts) + logcmdutil.displaygraph(ui, repo, revdag, displayer, + graphmod.asciiedges) hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True) return 0 @@ -3414,33 +3417,17 @@ raise error.Abort(_('--line-range requires --follow')) if linerange and pats: + # TODO: take pats as patterns with no line-range filter raise error.Abort( _('FILE arguments are not compatible with --line-range option') ) repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn') - revs, filematcher = cmdutil.getlogrevs(repo, pats, opts) - hunksfilter = None - - if opts.get('graph'): - if linerange: - raise error.Abort(_('graph not supported with line range patterns')) - return cmdutil.graphlog(ui, repo, revs, filematcher, opts) - + revs, differ = logcmdutil.getrevs(repo, pats, opts) if linerange: - revs, lrfilematcher, hunksfilter = cmdutil.getloglinerangerevs( - repo, revs, opts) - - if filematcher is not None and lrfilematcher is not None: - basefilematcher = filematcher - - def filematcher(rev): - files = (basefilematcher(rev).files() - + lrfilematcher(rev).files()) - return scmutil.matchfiles(repo, files) - - elif filematcher is None: - filematcher = lrfilematcher + # TODO: should follow file history from logcmdutil._initialrevs(), + # then filter the result by logcmdutil._makerevset() and --limit + revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts) getrenamed = None if opts.get('copies'): @@ -3450,29 +3437,13 @@ getrenamed = templatekw.getrenamedfn(repo, endrev=endrev) ui.pager('log') - displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True) - for rev in revs: - ctx = repo[rev] - copies = None - if getrenamed is not None and rev: - copies = [] - for fn in ctx.files(): - rename = getrenamed(fn, rev) - if rename: - copies.append((fn, rename[0])) - if filematcher: - revmatchfn = filematcher(ctx.rev()) - else: - revmatchfn = None - if hunksfilter: - revhunksfilter = hunksfilter(rev) - else: - revhunksfilter = None - displayer.show(ctx, copies=copies, matchfn=revmatchfn, - hunksfilterfn=revhunksfilter) - displayer.flush(ctx) - - displayer.close() + displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ, + buffered=True) + if opts.get('graph'): + displayfn = logcmdutil.displaygraphrevs + else: + displayfn = logcmdutil.displayrevs + displayfn(ui, repo, revs, displayer, getrenamed) @command('manifest', [('r', 'rev', '', _('revision to display'), _('REV')), @@ -3523,8 +3494,8 @@ if not node: node = rev - char = {'l': '@', 'x': '*', '': ''} - mode = {'l': '644', 'x': '755', '': '644'} + char = {'l': '@', 'x': '*', '': '', 't': 'd'} + mode = {'l': '644', 'x': '755', '': '644', 't': '755'} if node: repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn') ctx = scmutil.revsingle(repo, node) @@ -3604,7 +3575,7 @@ p2 = repo.lookup(node) nodes = repo.changelog.findmissing(common=[p1], heads=[p2]) - displayer = cmdutil.show_changeset(ui, repo, opts) + displayer = logcmdutil.changesetdisplayer(ui, repo, opts) for node in nodes: displayer.show(repo[node]) displayer.close() @@ -3668,16 +3639,17 @@ """ opts = pycompat.byteskwargs(opts) if opts.get('graph'): - cmdutil.checkunsupportedgraphflags([], opts) + logcmdutil.checkunsupportedgraphflags([], opts) o, other = hg._outgoing(ui, repo, dest, opts) if not o: cmdutil.outgoinghooks(ui, repo, other, opts, o) return - revdag = cmdutil.graphrevs(repo, o, opts) + revdag = logcmdutil.graphrevs(repo, o, opts) ui.pager('outgoing') - displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True) - cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges) + displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True) + logcmdutil.displaygraph(ui, repo, revdag, displayer, + graphmod.asciiedges) cmdutil.outgoinghooks(ui, repo, other, opts, o) return 0 @@ -3752,7 +3724,7 @@ else: p = [cp.node() for cp in ctx.parents()] - displayer = cmdutil.show_changeset(ui, repo, opts) + displayer = logcmdutil.changesetdisplayer(ui, repo, opts) for n in p: if n != nullid: displayer.show(repo[n]) @@ -4043,7 +4015,7 @@ brev = None if checkout: - checkout = str(repo.changelog.rev(checkout)) + checkout = "%d" % repo.changelog.rev(checkout) # order below depends on implementation of # hg.addbranchrevs(). opts['bookmark'] is ignored, @@ -4757,7 +4729,7 @@ if repo is None: raise error.RepoError(_("there is no Mercurial repository here" " (.hg not found)")) - s = sshserver.sshserver(ui, repo) + s = wireprotoserver.sshserver(ui, repo) s.serve_forever() service = server.createservice(ui, repo, opts) @@ -4984,7 +4956,7 @@ # shows a working directory parent *changeset*: # i18n: column positioning for "hg summary" ui.write(_('parent: %d:%s ') % (p.rev(), p), - label=cmdutil._changesetlabels(p)) + label=logcmdutil.changesetlabels(p)) ui.write(' '.join(p.tags()), label='log.tag') if p.bookmarks(): marks.extend(p.bookmarks()) @@ -5406,7 +5378,7 @@ Returns 0 on success. """ opts = pycompat.byteskwargs(opts) - displayer = cmdutil.show_changeset(ui, repo, opts) + displayer = logcmdutil.changesetdisplayer(ui, repo, opts) displayer.show(repo['tip']) displayer.close() diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/config.py --- a/mercurial/config.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/config.py Sat Feb 24 17:49:10 2018 -0600 @@ -154,7 +154,7 @@ if inst.errno != errno.ENOENT: raise error.ParseError(_("cannot include %s (%s)") % (inc, inst.strerror), - "%s:%s" % (src, line)) + "%s:%d" % (src, line)) continue if emptyre.match(l): continue @@ -185,7 +185,7 @@ self._unset.append((section, name)) continue - raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line))) + raise error.ParseError(l.rstrip(), ("%s:%d" % (src, line))) def read(self, path, fp=None, sections=None, remap=None): if not fp: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/configitems.py --- a/mercurial/configitems.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/configitems.py Sat Feb 24 17:49:10 2018 -0600 @@ -538,9 +538,6 @@ coreconfigitem('experimental', 'httppostargs', default=False, ) -coreconfigitem('experimental', 'manifestv2', - default=False, -) coreconfigitem('experimental', 'mergedriver', default=None, ) @@ -556,6 +553,9 @@ coreconfigitem('experimental', 'single-head-per-branch', default=False, ) +coreconfigitem('experimental', 'sshserver.support-v2', + default=False, +) coreconfigitem('experimental', 'spacemovesdown', default=False, ) @@ -574,6 +574,9 @@ coreconfigitem('experimental', 'update.atomic-file', default=False, ) +coreconfigitem('experimental', 'sshpeer.advertise-v2', + default=False, +) coreconfigitem('extensions', '.*', default=None, generic=True, @@ -743,6 +746,16 @@ generic=True, priority=-1, ) +coreconfigitem('merge-tools', br'.*\.mergemarkers$', + default='basic', + generic=True, + priority=-1, +) +coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$', + default=dynamicdefault, # take from ui.mergemarkertemplate + generic=True, + priority=-1, +) coreconfigitem('merge-tools', br'.*\.priority$', default=0, generic=True, diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/context.py --- a/mercurial/context.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/context.py Sat Feb 24 17:49:10 2018 -0600 @@ -46,6 +46,7 @@ scmutil, sparse, subrepo, + subrepoutil, util, ) @@ -173,7 +174,7 @@ @propertycache def substate(self): - return subrepo.state(self, self._repo.ui) + return subrepoutil.state(self, self._repo.ui) def subrev(self, subpath): return self.substate[subpath][1] @@ -206,22 +207,10 @@ """True if the changeset is extinct""" return self.rev() in obsmod.getrevs(self._repo, 'extinct') - def unstable(self): - msg = ("'context.unstable' is deprecated, " - "use 'context.orphan'") - self._repo.ui.deprecwarn(msg, '4.4') - return self.orphan() - def orphan(self): """True if the changeset is not obsolete but it's ancestor are""" return self.rev() in obsmod.getrevs(self._repo, 'orphan') - def bumped(self): - msg = ("'context.bumped' is deprecated, " - "use 'context.phasedivergent'") - self._repo.ui.deprecwarn(msg, '4.4') - return self.phasedivergent() - def phasedivergent(self): """True if the changeset try to be a successor of a public changeset @@ -229,12 +218,6 @@ """ return self.rev() in obsmod.getrevs(self._repo, 'phasedivergent') - def divergent(self): - msg = ("'context.divergent' is deprecated, " - "use 'context.contentdivergent'") - self._repo.ui.deprecwarn(msg, '4.4') - return self.contentdivergent() - def contentdivergent(self): """Is a successors of a changeset with multiple possible successors set @@ -242,33 +225,10 @@ """ return self.rev() in obsmod.getrevs(self._repo, 'contentdivergent') - def troubled(self): - msg = ("'context.troubled' is deprecated, " - "use 'context.isunstable'") - self._repo.ui.deprecwarn(msg, '4.4') - return self.isunstable() - def isunstable(self): """True if the changeset is either unstable, bumped or divergent""" return self.orphan() or self.phasedivergent() or self.contentdivergent() - def troubles(self): - """Keep the old version around in order to avoid breaking extensions - about different return values. - """ - msg = ("'context.troubles' is deprecated, " - "use 'context.instabilities'") - self._repo.ui.deprecwarn(msg, '4.4') - - troubles = [] - if self.orphan(): - troubles.append('orphan') - if self.phasedivergent(): - troubles.append('bumped') - if self.contentdivergent(): - troubles.append('divergent') - return troubles - def instabilities(self): """return the list of instabilities affecting this changeset. @@ -1051,7 +1011,7 @@ # renamed filectx won't have a filelog yet, so set it # from the cache to save time for p in pl: - if not '_filelog' in p.__dict__: + if not r'_filelog' in p.__dict__: p._filelog = getlog(p.path()) return pl diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/copies.py --- a/mercurial/copies.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/copies.py Sat Feb 24 17:49:10 2018 -0600 @@ -123,7 +123,7 @@ t[k] = v # remove criss-crossed copies - for k, v in t.items(): + for k, v in list(t.items()): if k in src and v in dst: del t[k] @@ -685,8 +685,8 @@ # the base and present in the source. # Presence in the base is important to exclude added files, presence in the # source is important to exclude removed files. - missingfiles = filter(lambda f: f not in m1 and f in base and f in c2, - changedfiles) + filt = lambda f: f not in m1 and f in base and f in c2 + missingfiles = [f for f in changedfiles if filt(f)] if missingfiles: basenametofilename = collections.defaultdict(list) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/crecord.py --- a/mercurial/crecord.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/crecord.py Sat Feb 24 17:49:10 2018 -0600 @@ -950,7 +950,7 @@ # preprocess the text, converting tabs to spaces text = text.expandtabs(4) # strip \n, and convert control characters to ^[char] representation - text = re.sub(r'[\x00-\x08\x0a-\x1f]', + text = re.sub(br'[\x00-\x08\x0a-\x1f]', lambda m:'^' + chr(ord(m.group()) + 64), text.strip('\n')) if pair is not None: @@ -1335,7 +1335,7 @@ # temporarily disable printing to windows by printstring patchdisplaystring = self.printitem(item, ignorefolding, recursechildren, towin=False) - numlines = len(patchdisplaystring) / self.xscreensize + numlines = len(patchdisplaystring) // self.xscreensize return numlines def sigwinchhandler(self, n, frame): diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/debugcommands.py --- a/mercurial/debugcommands.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/debugcommands.py Sat Feb 24 17:49:10 2018 -0600 @@ -48,6 +48,7 @@ hg, localrepo, lock as lockmod, + logcmdutil, merge as mergemod, obsolete, obsutil, @@ -162,7 +163,7 @@ if mergeable_file: linesperrev = 2 # make a file with k lines per rev - initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)] + initialmergedlines = ['%d' % i for i in xrange(0, total * linesperrev)] initialmergedlines.append("") tags = [] @@ -1239,16 +1240,19 @@ # editor editor = ui.geteditor() editor = util.expandpath(editor) - fm.write('editor', _("checking commit editor... (%s)\n"), editor) - cmdpath = util.findexe(pycompat.shlexsplit(editor)[0]) + editorbin = pycompat.shlexsplit(editor, posix=not pycompat.iswindows)[0] + if pycompat.iswindows and editorbin[0] == '"' and editorbin[-1] == '"': + editorbin = editorbin[1:-1] + fm.write('editor', _("checking commit editor... (%s)\n"), editorbin) + cmdpath = util.findexe(editorbin) fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound', _(" No commit editor set and can't find %s in PATH\n" " (specify a commit editor in your configuration" - " file)\n"), not cmdpath and editor == 'vi' and editor) + " file)\n"), not cmdpath and editor == 'vi' and editorbin) fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound', _(" Can't find editor '%s' in PATH\n" " (specify a commit editor in your configuration" - " file)\n"), not cmdpath and editor) + " file)\n"), not cmdpath and editorbin) if not cmdpath and editor != 'vi': problems += 1 @@ -1405,7 +1409,7 @@ return h def printrecords(version): - ui.write(('* version %s records\n') % version) + ui.write(('* version %d records\n') % version) if version == 1: records = v1records else: @@ -1692,6 +1696,25 @@ ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files))) ui.write('\n') +@command('debugpeer', [], _('PATH'), norepo=True) +def debugpeer(ui, path): + """establish a connection to a peer repository""" + # Always enable peer request logging. Requires --debug to display + # though. + overrides = { + ('devel', 'debug.peer-request'): True, + } + + with ui.configoverride(overrides): + peer = hg.peer(ui, {}, path) + + local = peer.local() is not None + canpush = peer.canpush() + + ui.write(_('url: %s\n') % peer.url()) + ui.write(_('local: %s\n') % (_('yes') if local else _('no'))) + ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no'))) + @command('debugpickmergetool', [('r', 'rev', '', _('check for files in this revision'), _('REV')), ('', 'changedelete', None, _('emulate merging change and delete')), @@ -2206,7 +2229,7 @@ if not opts['show_revs']: return for c in revs: - ui.write("%s\n" % c) + ui.write("%d\n" % c) @command('debugsetparents', [], _('REV1 [REV2]')) def debugsetparents(ui, repo, rev1, rev2=None): @@ -2336,7 +2359,7 @@ """ # passed to successorssets caching computation from one call to another cache = {} - ctx2str = str + ctx2str = bytes node2str = short for rev in scmutil.revrange(repo, revs): ctx = repo[rev] @@ -2396,7 +2419,7 @@ t = formatter.maketemplater(ui, tmpl, resources=tres) ui.write(t.render(props)) else: - displayer = cmdutil.makelogtemplater(ui, repo, tmpl) + displayer = logcmdutil.maketemplater(ui, repo, tmpl) for r in revs: displayer.show(repo[r], **pycompat.strkwargs(props)) displayer.close() diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/default.d/mergetools.rc --- a/mercurial/default.d/mergetools.rc Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/default.d/mergetools.rc Sat Feb 24 17:49:10 2018 -0600 @@ -1,7 +1,7 @@ # Some default global settings for common merge tools [merge-tools] -kdiff3.args=--auto --L1 base --L2 local --L3 other $base $local $other -o $output +kdiff3.args=--auto --L1 $labelbase --L2 $labellocal --L3 $labelother $base $local $other -o $output kdiff3.regkey=Software\KDiff3 kdiff3.regkeyalt=Software\Wow6432Node\KDiff3 kdiff3.regappend=\kdiff3.exe @@ -26,7 +26,7 @@ gpyfm.gui=True meld.gui=True -meld.args=--label='local' $local --label='merged' $base --label='other' $other -o $output +meld.args=--label=$labellocal $local --label='merged' $base --label=$labelother $other -o $output meld.check=changed meld.diffargs=-a --label=$plabel1 $parent --label=$clabel $child @@ -35,7 +35,7 @@ tkdiff.priority=-8 tkdiff.diffargs=-L $plabel1 $parent -L $clabel $child -xxdiff.args=--show-merged-pane --exit-with-merge-status --title1 local --title2 base --title3 other --merged-filename $output --merge $local $base $other +xxdiff.args=--show-merged-pane --exit-with-merge-status --title1 $labellocal --title2 $labelbase --title3 $labelother --merged-filename $output --merge $local $base $other xxdiff.gui=True xxdiff.priority=-8 xxdiff.diffargs=--title1 $plabel1 $parent --title2 $clabel $child @@ -44,7 +44,7 @@ diffmerge.regkeyalt=Software\Wow6432Node\SourceGear\SourceGear DiffMerge\ diffmerge.regname=Location diffmerge.priority=-7 -diffmerge.args=-nosplash -merge -title1=local -title2=merged -title3=other $local $base $other -result=$output +diffmerge.args=-nosplash -merge -title1=$labellocal -title2=merged -title3=$labelother $local $base $other -result=$output diffmerge.check=changed diffmerge.gui=True diffmerge.diffargs=--nosplash --title1=$plabel1 --title2=$clabel $parent $child @@ -72,7 +72,7 @@ tortoisemerge.priority=-8 tortoisemerge.diffargs=/base:$parent /mine:$child /basename:$plabel1 /minename:$clabel -ecmerge.args=$base $local $other --mode=merge3 --title0=base --title1=local --title2=other --to=$output +ecmerge.args=$base $local $other --mode=merge3 --title0=$labelbase --title1=$labellocal --title2=$labelother --to=$output ecmerge.regkey=Software\Elli\xc3\xa9 Computing\Merge ecmerge.regkeyalt=Software\Wow6432Node\Elli\xc3\xa9 Computing\Merge ecmerge.gui=True @@ -93,7 +93,7 @@ filemergexcode.gui=True ; Windows version of Beyond Compare -beyondcompare3.args=$local $other $base $output /ro /lefttitle=local /centertitle=base /righttitle=other /automerge /reviewconflicts /solo +beyondcompare3.args=$local $other $base $output /ro /lefttitle=$labellocal /centertitle=$labelbase /righttitle=$labelother /automerge /reviewconflicts /solo beyondcompare3.regkey=Software\Scooter Software\Beyond Compare 3 beyondcompare3.regname=ExePath beyondcompare3.gui=True @@ -113,7 +113,7 @@ bcomposx.priority=-1 bcomposx.diffargs=-lro -lefttitle=$plabel1 -righttitle=$clabel -solo -expandall $parent $child -winmerge.args=/e /x /wl /ub /dl other /dr local $other $local $output +winmerge.args=/e /x /wl /ub /dl $labelother /dr $labellocal $other $local $output winmerge.regkey=Software\Thingamahoochie\WinMerge winmerge.regkeyalt=Software\Wow6432Node\Thingamahoochie\WinMerge\ winmerge.regname=Executable diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/dirstate.py --- a/mercurial/dirstate.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/dirstate.py Sat Feb 24 17:49:10 2018 -0600 @@ -99,27 +99,6 @@ # normally, so we don't have a try/finally here on purpose. self._parentwriters -= 1 - def beginparentchange(self): - '''Marks the beginning of a set of changes that involve changing - the dirstate parents. If there is an exception during this time, - the dirstate will not be written when the wlock is released. This - prevents writing an incoherent dirstate where the parent doesn't - match the contents. - ''' - self._ui.deprecwarn('beginparentchange is obsoleted by the ' - 'parentchange context manager.', '4.3') - self._parentwriters += 1 - - def endparentchange(self): - '''Marks the end of a set of changes that involve changing the - dirstate parents. Once all parent changes have been marked done, - the wlock will be free to write the dirstate on release. - ''' - self._ui.deprecwarn('endparentchange is obsoleted by the ' - 'parentchange context manager.', '4.3') - if self._parentwriters > 0: - self._parentwriters -= 1 - def pendingparentchange(self): '''Returns true if the dirstate is in the middle of a set of changes that modify the dirstate parent. @@ -360,7 +339,7 @@ rereads the dirstate. Use localrepo.invalidatedirstate() if you want to check whether the dirstate has changed before rereading it.''' - for a in ("_map", "_branch", "_ignore"): + for a in (r"_map", r"_branch", r"_ignore"): if a in self.__dict__: delattr(self, a) self._lastnormaltime = 0 @@ -808,6 +787,17 @@ else: badfn(ff, encoding.strtolocal(inst.strerror)) + # match.files() may contain explicitly-specified paths that shouldn't + # be taken; drop them from the list of files found. dirsfound/notfound + # aren't filtered here because they will be tested later. + if match.anypats(): + for f in list(results): + if f == '.hg' or f in subrepos: + # keep sentinel to disable further out-of-repo walks + continue + if not match(f): + del results[f] + # Case insensitive filesystems cannot rely on lstat() failing to detect # a case-only rename. Prune the stat object for any file that does not # match the case in the filesystem, if there are multiple files that @@ -1237,9 +1227,12 @@ util.clearcachedproperty(self, "nonnormalset") util.clearcachedproperty(self, "otherparentset") - def iteritems(self): + def items(self): return self._map.iteritems() + # forward for python2,3 compat + iteritems = items + def __len__(self): return len(self._map) @@ -1264,9 +1257,9 @@ def addfile(self, f, oldstate, state, mode, size, mtime): """Add a tracked file to the dirstate.""" - if oldstate in "?r" and "_dirs" in self.__dict__: + if oldstate in "?r" and r"_dirs" in self.__dict__: self._dirs.addpath(f) - if oldstate == "?" and "_alldirs" in self.__dict__: + if oldstate == "?" and r"_alldirs" in self.__dict__: self._alldirs.addpath(f) self._map[f] = dirstatetuple(state, mode, size, mtime) if state != 'n' or mtime == -1: @@ -1282,11 +1275,11 @@ the file's previous state. In the future, we should refactor this to be more explicit about what that state is. """ - if oldstate not in "?r" and "_dirs" in self.__dict__: + if oldstate not in "?r" and r"_dirs" in self.__dict__: self._dirs.delpath(f) - if oldstate == "?" and "_alldirs" in self.__dict__: + if oldstate == "?" and r"_alldirs" in self.__dict__: self._alldirs.addpath(f) - if "filefoldmap" in self.__dict__: + if r"filefoldmap" in self.__dict__: normed = util.normcase(f) self.filefoldmap.pop(normed, None) self._map[f] = dirstatetuple('r', 0, size, 0) @@ -1299,11 +1292,11 @@ """ exists = self._map.pop(f, None) is not None if exists: - if oldstate != "r" and "_dirs" in self.__dict__: + if oldstate != "r" and r"_dirs" in self.__dict__: self._dirs.delpath(f) - if "_alldirs" in self.__dict__: + if r"_alldirs" in self.__dict__: self._alldirs.delpath(f) - if "filefoldmap" in self.__dict__: + if r"filefoldmap" in self.__dict__: normed = util.normcase(f) self.filefoldmap.pop(normed, None) self.nonnormalset.discard(f) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/discovery.py --- a/mercurial/discovery.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/discovery.py Sat Feb 24 17:49:10 2018 -0600 @@ -53,16 +53,11 @@ return treediscovery.findcommonincoming(repo, remote, heads, force) if heads: - allknown = True knownnode = repo.changelog.hasnode # no nodemap until it is filtered - for h in heads: - if not knownnode(h): - allknown = False - break - if allknown: + if all(knownnode(h) for h in heads): return (heads, False, heads) - res = setdiscovery.findcommonheads(repo.ui, repo, remote, + res = setdiscovery.findcommonheads(repo.ui, repo, remote, heads, abortwhenunrelated=not force, ancestorsof=ancestorsof) common, anyinc, srvheads = res diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/dispatch.py --- a/mercurial/dispatch.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/dispatch.py Sat Feb 24 17:49:10 2018 -0600 @@ -477,7 +477,8 @@ if earlyopts: self.badalias = (_("error in definition for alias '%s': %s may " "only be given on the command line") - % (self.name, '/'.join(zip(*earlyopts)[0]))) + % (self.name, '/'.join(pycompat.ziplist(*earlyopts) + [0]))) return self.cmdname = cmd = args.pop(0) self.givenargs = args @@ -821,9 +822,7 @@ if options['verbose'] or options['debug'] or options['quiet']: for opt in ('verbose', 'debug', 'quiet'): - val = str(bool(options[opt])) - if pycompat.ispy3: - val = val.encode('ascii') + val = pycompat.bytestr(bool(options[opt])) for ui_ in uis: ui_.setconfig('ui', opt, val, '--' + opt) @@ -941,9 +940,9 @@ worst = None, ct, '' if ui.config('ui', 'supportcontact') is None: for name, mod in extensions.extensions(): - testedwith = getattr(mod, 'testedwith', '') - if pycompat.ispy3 and isinstance(testedwith, str): - testedwith = testedwith.encode(u'utf-8') + # 'testedwith' should be bytes, but not all extensions are ported + # to py3 and we don't want UnicodeException because of that. + testedwith = util.forcebytestr(getattr(mod, 'testedwith', '')) report = getattr(mod, 'buglink', _('the extension author.')) if not testedwith.strip(): # We found an untested extension. It's likely the culprit. @@ -978,11 +977,7 @@ bugtracker = _("https://mercurial-scm.org/wiki/BugTracker") warning = (_("** unknown exception encountered, " "please report by visiting\n** ") + bugtracker + '\n') - if pycompat.ispy3: - sysversion = sys.version.encode(u'utf-8') - else: - sysversion = sys.version - sysversion = sysversion.replace('\n', '') + sysversion = pycompat.sysbytes(sys.version).replace('\n', '') warning += ((_("** Python %s\n") % sysversion) + (_("** Mercurial Distributed SCM (version %s)\n") % util.version()) + @@ -997,6 +992,7 @@ this function returns False, ignored otherwise. """ warning = _exceptionwarning(ui) - ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc()) + ui.log("commandexception", "%s\n%s\n", warning, + pycompat.sysbytes(traceback.format_exc())) ui.warn(warning) return False # re-raise the exception diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/extensions.py --- a/mercurial/extensions.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/extensions.py Sat Feb 24 17:49:10 2018 -0600 @@ -122,6 +122,18 @@ if ui.debugflag: ui.traceback() +def _rejectunicode(name, xs): + if isinstance(xs, (list, set, tuple)): + for x in xs: + _rejectunicode(name, x) + elif isinstance(xs, dict): + for k, v in xs.items(): + _rejectunicode(name, k) + _rejectunicode(b'%s.%s' % (name, util.forcebytestr(k)), v) + elif isinstance(xs, type(u'')): + raise error.ProgrammingError(b"unicode %r found in %s" % (xs, name), + hint="use b'' to make it byte string") + # attributes set by registrar.command _cmdfuncattrs = ('norepo', 'optionalrepo', 'inferrepo') @@ -134,19 +146,22 @@ "registrar.command to register '%s'" % c, '4.6') missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)] if not missing: - for option in e[1]: - default = option[2] - if isinstance(default, type(u'')): - raise error.ProgrammingError( - "option '%s.%s' has a unicode default value" - % (c, option[1]), - hint=("change the %s.%s default value to a " - "non-unicode string" % (c, option[1]))) continue raise error.ProgrammingError( 'missing attributes: %s' % ', '.join(missing), hint="use @command decorator to register '%s'" % c) +def _validatetables(ui, mod): + """Sanity check for loadable tables provided by extension module""" + for t in ['cmdtable', 'colortable', 'configtable']: + _rejectunicode(t, getattr(mod, t, {})) + for t in ['filesetpredicate', 'internalmerge', 'revsetpredicate', + 'templatefilter', 'templatefunc', 'templatekeyword']: + o = getattr(mod, t, None) + if o: + _rejectunicode(t, o._table) + _validatecmdtable(ui, getattr(mod, 'cmdtable', {})) + def load(ui, name, path): if name.startswith('hgext.') or name.startswith('hgext/'): shortname = name[6:] @@ -168,7 +183,7 @@ ui.warn(_('(third party extension %s requires version %s or newer ' 'of Mercurial; disabling)\n') % (shortname, minver)) return - _validatecmdtable(ui, getattr(mod, 'cmdtable', {})) + _validatetables(ui, mod) _extensions[shortname] = mod _order.append(shortname) @@ -195,11 +210,7 @@ try: extsetup(ui) except TypeError: - # Try to use getfullargspec (Python 3) first, and fall - # back to getargspec only if it doesn't exist so as to - # avoid warnings. - if getattr(inspect, 'getfullargspec', - getattr(inspect, 'getargspec'))(extsetup).args: + if pycompat.getargspec(extsetup).args: raise extsetup() # old extsetup with no ui argument except Exception as inst: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/fancyopts.py --- a/mercurial/fancyopts.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/fancyopts.py Sat Feb 24 17:49:10 2018 -0600 @@ -7,6 +7,7 @@ from __future__ import absolute_import +import abc import functools from .i18n import _ @@ -201,6 +202,64 @@ parsedargs.extend(args[pos:]) return parsedopts, parsedargs +class customopt(object): + """Manage defaults and mutations for any type of opt.""" + + __metaclass__ = abc.ABCMeta + + def __init__(self, defaultvalue): + self.defaultvalue = defaultvalue + + def _isboolopt(self): + return False + + @abc.abstractmethod + def newstate(self, oldstate, newparam, abort): + """Adds newparam to oldstate and returns the new state. + + On failure, abort can be called with a string error message.""" + +class _simpleopt(customopt): + def _isboolopt(self): + return isinstance(self.defaultvalue, (bool, type(None))) + + def newstate(self, oldstate, newparam, abort): + return newparam + +class _callableopt(customopt): + def __init__(self, callablefn): + self.callablefn = callablefn + super(_callableopt, self).__init__(None) + + def newstate(self, oldstate, newparam, abort): + return self.callablefn(newparam) + +class _listopt(customopt): + def newstate(self, oldstate, newparam, abort): + oldstate.append(newparam) + return oldstate + +class _intopt(customopt): + def newstate(self, oldstate, newparam, abort): + try: + return int(newparam) + except ValueError: + abort(_('expected int')) + +def _defaultopt(default): + """Returns a default opt implementation, given a default value.""" + + if isinstance(default, customopt): + return default + elif callable(default): + return _callableopt(default) + elif isinstance(default, list): + return _listopt(default[:]) + elif type(default) is type(1): + return _intopt(default) + else: + return _simpleopt(default) + def fancyopts(args, options, state, gnu=False, early=False, optaliases=None): """ read args, parse options, and store options in state @@ -220,6 +279,7 @@ list - parameter string is added to a list integer - parameter strings is stored as int function - call function with parameter + customopt - subclass of 'customopt' optaliases is a mapping from a canonical option name to a list of additional long options. This exists for preserving backward compatibility @@ -250,18 +310,13 @@ argmap['-' + short] = name for n in onames: argmap['--' + n] = name - defmap[name] = default + defmap[name] = _defaultopt(default) # copy defaults to state - if isinstance(default, list): - state[name] = default[:] - elif callable(default): - state[name] = None - else: - state[name] = default + state[name] = defmap[name].defaultvalue # does it take a parameter? - if not (default is None or default is True or default is False): + if not defmap[name]._isboolopt(): if short: short += ':' onames = [n + '=' for n in onames] @@ -301,21 +356,13 @@ boolval = False name = argmap[opt] obj = defmap[name] - t = type(obj) - if callable(obj): - state[name] = defmap[name](val) - elif t is type(1): - try: - state[name] = int(val) - except ValueError: - raise error.Abort(_('invalid value %r for option %s, ' - 'expected int') % (val, opt)) - elif t is type(''): - state[name] = val - elif t is type([]): - state[name].append(val) - elif t is type(None) or t is type(False): + if obj._isboolopt(): state[name] = boolval + else: + def abort(s): + raise error.Abort( + _('invalid value %r for option %s, %s') % (val, opt, s)) + state[name] = defmap[name].newstate(state[name], val, abort) # return unparsed args return args diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/filemerge.py --- a/mercurial/filemerge.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/filemerge.py Sat Feb 24 17:49:10 2018 -0600 @@ -513,6 +513,11 @@ b, c = _maketempfiles(repo, fco, fca) try: out = "" + mylabel, otherlabel = labels[:2] + if len(labels) >= 3: + baselabel = labels[2] + else: + baselabel = 'base' env = {'HG_FILE': fcd.path(), 'HG_MY_NODE': short(mynode), 'HG_OTHER_NODE': str(fco.changectx()), @@ -520,6 +525,9 @@ 'HG_MY_ISLINK': 'l' in fcd.flags(), 'HG_OTHER_ISLINK': 'l' in fco.flags(), 'HG_BASE_ISLINK': 'l' in fca.flags(), + 'HG_MY_LABEL': mylabel, + 'HG_OTHER_LABEL': otherlabel, + 'HG_BASE_LABEL': baselabel, } ui = repo.ui @@ -528,8 +536,10 @@ # read input from backup, write to original out = a a = repo.wvfs.join(back.path()) - replace = {'local': a, 'base': b, 'other': c, 'output': out} - args = util.interpolate(r'\$', replace, args, + replace = {'local': a, 'base': b, 'other': c, 'output': out, + 'labellocal': mylabel, 'labelother': otherlabel, + 'labelbase': baselabel} + args = util.interpolate(br'\$', replace, args, lambda s: util.shellquote(util.localpath(s))) cmd = toolpath + ' ' + args if _toolbool(ui, tool, "gui"): @@ -566,7 +576,7 @@ _defaultconflictlabels = ['local', 'other'] -def _formatlabels(repo, fcd, fco, fca, labels): +def _formatlabels(repo, fcd, fco, fca, labels, tool=None): """Formats the given labels using the conflict marker template. Returns a list of formatted labels. @@ -577,6 +587,8 @@ ui = repo.ui template = ui.config('ui', 'mergemarkertemplate') + if tool is not None: + template = _toolstr(ui, tool, 'mergemarkertemplate', template) template = templater.unquotestring(template) tres = formatter.templateresources(ui, repo) tmpl = formatter.maketemplater(ui, template, defaults=templatekw.keywords, @@ -706,6 +718,7 @@ mergetype = func.mergetype onfailure = func.onfailure precheck = func.precheck + isexternal = False else: if wctx.isinmemory(): func = _xmergeimm @@ -714,6 +727,7 @@ mergetype = fullmerge onfailure = _("merging %s failed!\n") precheck = None + isexternal = True toolconf = tool, toolpath, binary, symlink @@ -743,19 +757,42 @@ files = (None, None, None, back) r = 1 try: - markerstyle = ui.config('ui', 'mergemarkers') + internalmarkerstyle = ui.config('ui', 'mergemarkers') + if isexternal: + markerstyle = _toolstr(ui, tool, 'mergemarkers') + else: + markerstyle = internalmarkerstyle + if not labels: labels = _defaultconflictlabels + formattedlabels = labels if markerstyle != 'basic': - labels = _formatlabels(repo, fcd, fco, fca, labels) + formattedlabels = _formatlabels(repo, fcd, fco, fca, labels, + tool=tool) if premerge and mergetype == fullmerge: - r = _premerge(repo, fcd, fco, fca, toolconf, files, labels=labels) + # conflict markers generated by premerge will use 'detailed' + # settings if either ui.mergemarkers or the tool's mergemarkers + # setting is 'detailed'. This way tools can have basic labels in + # space-constrained areas of the UI, but still get full information + # in conflict markers if premerge is 'keep' or 'keep-merge3'. + premergelabels = labels + labeltool = None + if markerstyle != 'basic': + # respect 'tool's mergemarkertemplate (which defaults to + # ui.mergemarkertemplate) + labeltool = tool + if internalmarkerstyle != 'basic' or markerstyle != 'basic': + premergelabels = _formatlabels(repo, fcd, fco, fca, + premergelabels, tool=labeltool) + + r = _premerge(repo, fcd, fco, fca, toolconf, files, + labels=premergelabels) # complete if premerge successful (r is 0) return not r, r, False needcheck, r, deleted = func(repo, mynode, orig, fcd, fco, fca, - toolconf, files, labels=labels) + toolconf, files, labels=formattedlabels) if needcheck: r = _check(repo, r, ui, tool, fcd, files) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/formatter.py --- a/mercurial/formatter.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/formatter.py Sat Feb 24 17:49:10 2018 -0600 @@ -291,7 +291,7 @@ self._out = out self._out.write("%s = [\n" % self._topic) def _showitem(self): - self._out.write(" " + repr(self._item) + ",\n") + self._out.write(' %s,\n' % pycompat.byterepr(self._item)) def end(self): baseformatter.end(self) self._out.write("]\n") diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/graphmod.py --- a/mercurial/graphmod.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/graphmod.py Sat Feb 24 17:49:10 2018 -0600 @@ -454,7 +454,7 @@ if any(len(char) > 1 for char in edgemap.values()): # limit drawing an edge to the first or last N lines of the current # section the rest of the edge is drawn like a parent line. - parent = state['styles'][PARENT][-1] + parent = state['styles'][PARENT][-1:] def _drawgp(char, i): # should a grandparent character be drawn for this line? if len(char) < 2: @@ -463,7 +463,7 @@ # either skip first num lines or take last num lines, based on sign return -num <= i if num < 0 else (len(lines) - i) <= num for i, line in enumerate(lines): - line[:] = [c[-1] if _drawgp(c, i) else parent for c in line] + line[:] = [c[-1:] if _drawgp(c, i) else parent for c in line] edgemap.update( (e, (c if len(c) < 2 else parent)) for e, c in edgemap.items()) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/help.py --- a/mercurial/help.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/help.py Sat Feb 24 17:49:10 2018 -0600 @@ -62,7 +62,8 @@ rst = loaddoc('extensions')(ui).splitlines(True) rst.extend(listexts( _('enabled extensions:'), extensions.enabled(), showdeprecated=True)) - rst.extend(listexts(_('disabled extensions:'), extensions.disabled())) + rst.extend(listexts(_('disabled extensions:'), extensions.disabled(), + showdeprecated=ui.verbose)) doc = ''.join(rst) return doc @@ -149,7 +150,7 @@ doclines = docs.splitlines() if doclines: summary = doclines[0] - cmdname = cmd.partition('|')[0].lstrip('^') + cmdname = cmdutil.parsealiases(cmd)[0] if filtercmd(ui, cmdname, kw, docs): continue results['commands'].append((cmdname, summary)) @@ -169,7 +170,7 @@ continue for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems(): if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])): - cmdname = cmd.partition('|')[0].lstrip('^') + cmdname = cmdutil.parsealiases(cmd)[0] cmddoc = pycompat.getdoc(entry[0]) if cmddoc: cmddoc = gettext(cmddoc).splitlines()[0] @@ -327,7 +328,7 @@ # py3k fix: except vars can't be used outside the scope of the # except block, nor can be used inside a lambda. python issue4617 prefix = inst.args[0] - select = lambda c: c.lstrip('^').startswith(prefix) + select = lambda c: cmdutil.parsealiases(c)[0].startswith(prefix) rst = helplist(select) return rst @@ -418,15 +419,18 @@ h = {} cmds = {} for c, e in commands.table.iteritems(): - f = c.partition("|")[0] - if select and not select(f): + fs = cmdutil.parsealiases(c) + f = fs[0] + p = '' + if c.startswith("^"): + p = '^' + if select and not select(p + f): continue if (not select and name != 'shortlist' and e[0].__module__ != commands.__name__): continue - if name == "shortlist" and not f.startswith("^"): + if name == "shortlist" and not p: continue - f = f.lstrip("^") doc = pycompat.getdoc(e[0]) if filtercmd(ui, f, name, doc): continue @@ -434,7 +438,7 @@ if not doc: doc = _("(no help text available)") h[f] = doc.splitlines()[0].rstrip() - cmds[f] = c.lstrip("^") + cmds[f] = '|'.join(fs) rst = [] if not h: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/help/config.txt --- a/mercurial/help/config.txt Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/help/config.txt Sat Feb 24 17:49:10 2018 -0600 @@ -1363,13 +1363,18 @@ ``args`` The arguments to pass to the tool executable. You can refer to the files being merged as well as the output file through these - variables: ``$base``, ``$local``, ``$other``, ``$output``. The meaning - of ``$local`` and ``$other`` can vary depending on which action is being - performed. During and update or merge, ``$local`` represents the original - state of the file, while ``$other`` represents the commit you are updating - to or the commit you are merging with. During a rebase ``$local`` - represents the destination of the rebase, and ``$other`` represents the - commit being rebased. + variables: ``$base``, ``$local``, ``$other``, ``$output``. + + The meaning of ``$local`` and ``$other`` can vary depending on which action is + being performed. During an update or merge, ``$local`` represents the original + state of the file, while ``$other`` represents the commit you are updating to or + the commit you are merging with. During a rebase, ``$local`` represents the + destination of the rebase, and ``$other`` represents the commit being rebased. + + Some operations define custom labels to assist with identifying the revisions, + accessible via ``$labellocal``, ``$labelother``, and ``$labelbase``. If custom + labels are not available, these will be ``local``, ``other``, and ``base``, + respectively. (default: ``$local $base $other``) ``premerge`` @@ -1405,6 +1410,21 @@ ``gui`` This tool requires a graphical interface to run. (default: False) +``mergemarkers`` + Controls whether the labels passed via ``$labellocal``, ``$labelother``, and + ``$labelbase`` are ``detailed`` (respecting ``mergemarkertemplate``) or + ``basic``. If ``premerge`` is ``keep`` or ``keep-merge3``, the conflict + markers generated during premerge will be ``detailed`` if either this option or + the corresponding option in the ``[ui]`` section is ``detailed``. + (default: ``basic``) + +``mergemarkertemplate`` + This setting can be used to override ``mergemarkertemplate`` from the ``[ui]`` + section on a per-tool basis; this applies to the ``$label``-prefixed variables + and to the conflict markers that are generated if ``premerge`` is ``keep` or + ``keep-merge3``. See the corresponding variable in ``[ui]`` for more + information. + .. container:: windows ``regkey`` @@ -2120,6 +2140,8 @@ markers is different from the encoding of the merged files, serious problems may occur. + Can be overridden per-merge-tool, see the ``[merge-tools]`` section. + ``origbackuppath`` The path to a directory used to store generated .orig files. If the path is not a directory, one will be created. If set, files stored in this diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/help/internals/requirements.txt --- a/mercurial/help/internals/requirements.txt Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/help/internals/requirements.txt Sat Feb 24 17:49:10 2018 -0600 @@ -1,4 +1,3 @@ - Repositories contain a file (``.hg/requires``) containing a list of features/capabilities that are *required* for clients to interface with the repository. This file has been present in Mercurial since @@ -105,8 +104,10 @@ Denotes that version 2 of manifests are being used. Support for this requirement was added in Mercurial 3.4 (released -May 2015). The requirement is currently experimental and is disabled -by default. +May 2015). The new format failed to meet expectations and support +for the format and requirement were removed in Mercurial 4.6 +(released May 2018) since the feature never graduated frome experiment +status. treemanifest ============ diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/help/internals/wireprotocol.txt --- a/mercurial/help/internals/wireprotocol.txt Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/help/internals/wireprotocol.txt Sat Feb 24 17:49:10 2018 -0600 @@ -10,11 +10,43 @@ The protocol is synchronous and does not support multiplexing (concurrent commands). -Transport Protocols -=================== +Handshake +========= + +It is required or common for clients to perform a *handshake* when connecting +to a server. The handshake serves the following purposes: + +* Negotiating protocol/transport level options +* Allows the client to learn about server capabilities to influence + future requests +* Ensures the underlying transport channel is in a *clean* state -HTTP Transport --------------- +An important goal of the handshake is to allow clients to use more modern +wire protocol features. By default, clients must assume they are talking +to an old version of Mercurial server (possibly even the very first +implementation). So, clients should not attempt to call or utilize modern +wire protocol features until they have confirmation that the server +supports them. The handshake implementation is designed to allow both +ends to utilize the latest set of features and capabilities with as +few round trips as possible. + +The handshake mechanism varies by transport and protocol and is documented +in the sections below. + +HTTP Protocol +============= + +Handshake +--------- + +The client sends a ``capabilities`` command request (``?cmd=capabilities``) +as soon as HTTP requests may be issued. + +The server responds with a capabilities string, which the client parses to +learn about the server's abilities. + +HTTP Version 1 Transport +------------------------ Commands are issued as HTTP/1.0 or HTTP/1.1 requests. Commands are sent to the base URL of the repository with the command name sent in @@ -112,11 +144,175 @@ ``application/mercurial-0.*`` media type and the HTTP response is typically using *chunked transfer* (``Transfer-Encoding: chunked``). -SSH Transport -============= +SSH Protocol +============ + +Handshake +--------- + +For all clients, the handshake consists of the client sending 1 or more +commands to the server using version 1 of the transport. Servers respond +to commands they know how to respond to and send an empty response (``0\n``) +for unknown commands (per standard behavior of version 1 of the transport). +Clients then typically look for a response to the newest sent command to +determine which transport version to use and what the available features for +the connection and server are. + +Preceding any response from client-issued commands, the server may print +non-protocol output. It is common for SSH servers to print banners, message +of the day announcements, etc when clients connect. It is assumed that any +such *banner* output will precede any Mercurial server output. So clients +must be prepared to handle server output on initial connect that isn't +in response to any client-issued command and doesn't conform to Mercurial's +wire protocol. This *banner* output should only be on stdout. However, +some servers may send output on stderr. + +Pre 0.9.1 clients issue a ``between`` command with the ``pairs`` argument +having the value +``0000000000000000000000000000000000000000-0000000000000000000000000000000000000000``. + +The ``between`` command has been supported since the original Mercurial +SSH server. Requesting the empty range will return a ``\n`` string response, +which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline +followed by the value, which happens to be a newline). + +For pre 0.9.1 clients and all servers, the exchange looks like:: + + c: between\n + c: pairs 81\n + c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + s: 1\n + s: \n + +0.9.1+ clients send a ``hello`` command (with no arguments) before the +``between`` command. The response to this command allows clients to +discover server capabilities and settings. + +An example exchange between 0.9.1+ clients and a ``hello`` aware server looks +like:: + + c: hello\n + c: between\n + c: pairs 81\n + c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + s: 324\n + s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n + s: 1\n + s: \n + +And a similar scenario but with servers sending a banner on connect:: + + c: hello\n + c: between\n + c: pairs 81\n + c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + s: welcome to the server\n + s: if you find any issues, email someone@somewhere.com\n + s: 324\n + s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n + s: 1\n + s: \n + +Note that output from the ``hello`` command is terminated by a ``\n``. This is +part of the response payload and not part of the wire protocol adding a newline +after responses. In other words, the length of the response contains the +trailing ``\n``. + +Clients supporting version 2 of the SSH transport send a line beginning +with ``upgrade`` before the ``hello`` and ``between`` commands. The line +(which isn't a well-formed command line because it doesn't consist of a +single command name) serves to both communicate the client's intent to +switch to transport version 2 (transports are version 1 by default) as +well as to advertise the client's transport-level capabilities so the +server may satisfy that request immediately. + +The upgrade line has the form: -The SSH transport is a custom text-based protocol suitable for use over any -bi-directional stream transport. It is most commonly used with SSH. + upgrade + +That is the literal string ``upgrade`` followed by a space, followed by +a randomly generated string, followed by a space, followed by a string +denoting the client's transport capabilities. + +The token can be anything. However, a random UUID is recommended. (Use +of version 4 UUIDs is recommended because version 1 UUIDs can leak the +client's MAC address.) + +The transport capabilities string is a URL/percent encoded string +containing key-value pairs defining the client's transport-level +capabilities. The following capabilities are defined: + +proto + A comma-delimited list of transport protocol versions the client + supports. e.g. ``ssh-v2``. + +If the server does not recognize the ``upgrade`` line, it should issue +an empty response and continue processing the ``hello`` and ``between`` +commands. Here is an example handshake between a version 2 aware client +and a non version 2 aware server: + + c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2 + c: hello\n + c: between\n + c: pairs 81\n + c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + s: 0\n + s: 324\n + s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n + s: 1\n + s: \n + +(The initial ``0\n`` line from the server indicates an empty response to +the unknown ``upgrade ..`` command/line.) + +If the server recognizes the ``upgrade`` line and is willing to satisfy that +upgrade request, it replies to with a payload of the following form: + + upgraded \n + +This line is the literal string ``upgraded``, a space, the token that was +specified by the client in its ``upgrade ...`` request line, a space, and the +name of the transport protocol that was chosen by the server. The transport +name MUST match one of the names the client specified in the ``proto`` field +of its ``upgrade ...`` request line. + +If a server issues an ``upgraded`` response, it MUST also read and ignore +the lines associated with the ``hello`` and ``between`` command requests +that were issued by the server. It is assumed that the negotiated transport +will respond with equivalent requested information following the transport +handshake. + +All data following the ``\n`` terminating the ``upgraded`` line is the +domain of the negotiated transport. It is common for the data immediately +following to contain additional metadata about the state of the transport and +the server. However, this isn't strictly speaking part of the transport +handshake and isn't covered by this section. + +Here is an example handshake between a version 2 aware client and a version +2 aware server: + + c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2 + c: hello\n + c: between\n + c: pairs 81\n + c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n + s: + +The client-issued token that is echoed in the response provides a more +resilient mechanism for differentiating *banner* output from Mercurial +output. In version 1, properly formatted banner output could get confused +for Mercurial server output. By submitting a randomly generated token +that is then present in the response, the client can look for that token +in response lines and have reasonable certainty that the line did not +originate from a *banner* message. + +SSH Version 1 Transport +----------------------- + +The SSH transport (version 1) is a custom text-based protocol suitable for +use over any bi-directional stream transport. It is most commonly used with +SSH. A SSH transport server can be started with ``hg serve --stdio``. The stdin, stderr, and stdout file descriptors of the started process are used to exchange @@ -174,6 +370,31 @@ The server terminates if it receives an empty command (a ``\n`` character). +SSH Version 2 Transport +----------------------- + +**Experimental** + +Version 2 of the SSH transport behaves identically to version 1 of the SSH +transport with the exception of handshake semantics. See above for how +version 2 of the SSH transport is negotiated. + +Immediately following the ``upgraded`` line signaling a switch to version +2 of the SSH protocol, the server automatically sends additional details +about the capabilities of the remote server. This has the form: + + \n + capabilities: ...\n + +e.g. + + s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n + s: 240\n + s: capabilities: known getbundle batch ...\n + +Following capabilities advertisement, the peers communicate using version +1 of the SSH transport. + Capabilities ============ @@ -463,53 +684,6 @@ reflects the priority/preference of that type, where the first value is the most preferred type. -Handshake Protocol -================== - -While not explicitly required, it is common for clients to perform a -*handshake* when connecting to a server. The handshake accomplishes 2 things: - -* Obtaining capabilities and other server features -* Flushing extra server output (e.g. SSH servers may print extra text - when connecting that may confuse the wire protocol) - -This isn't a traditional *handshake* as far as network protocols go because -there is no persistent state as a result of the handshake: the handshake is -simply the issuing of commands and commands are stateless. - -The canonical clients perform a capabilities lookup at connection establishment -time. This is because clients must assume a server only supports the features -of the original Mercurial server implementation until proven otherwise (from -advertised capabilities). Nearly every server running today supports features -that weren't present in the original Mercurial server implementation. Rather -than wait for a client to perform functionality that needs to consult -capabilities, it issues the lookup at connection start to avoid any delay later. - -For HTTP servers, the client sends a ``capabilities`` command request as -soon as the connection is established. The server responds with a capabilities -string, which the client parses. - -For SSH servers, the client sends the ``hello`` command (no arguments) -and a ``between`` command with the ``pairs`` argument having the value -``0000000000000000000000000000000000000000-0000000000000000000000000000000000000000``. - -The ``between`` command has been supported since the original Mercurial -server. Requesting the empty range will return a ``\n`` string response, -which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline -followed by the value, which happens to be a newline). - -The ``hello`` command was later introduced. Servers supporting it will issue -a response to that command before sending the ``1\n\n`` response to the -``between`` command. Servers not supporting ``hello`` will send an empty -response (``0\n``). - -In addition to the expected output from the ``hello`` and ``between`` commands, -servers may also send other output, such as *message of the day (MOTD)* -announcements. Clients assume servers will send this output before the -Mercurial server replies to the client-issued commands. So any server output -not conforming to the expected command responses is assumed to be not related -to Mercurial and can be ignored. - Content Negotiation =================== @@ -519,8 +693,8 @@ well-defined response type and only certain commands needed to support functionality like compression. -Currently, only the HTTP transport supports content negotiation at the protocol -layer. +Currently, only the HTTP version 1 transport supports content negotiation +at the protocol layer. HTTP requests advertise supported response formats via the ``X-HgProto-`` request header, where ```` is an integer starting at 1 allowing the logical @@ -662,6 +836,8 @@ This command does not accept any arguments. Return type is a ``string``. +This command was introduced in Mercurial 0.9.1 (released July 2006). + changegroup ----------- @@ -737,7 +913,7 @@ Boolean indicating whether phases data is requested. The return type on success is a ``stream`` where the value is bundle. -On the HTTP transport, the response is zlib compressed. +On the HTTP version 1 transport, the response is zlib compressed. If an error occurs, a generic error response can be sent. @@ -779,6 +955,8 @@ This command does not accept any arguments. The return type is a ``string``. +This command was introduced in Mercurial 0.9.1 (released July 2006). + listkeys -------- @@ -838,13 +1016,14 @@ The return type is a ``string``. The value depends on the transport protocol. -The SSH transport sends a string encoded integer followed by a newline -(``\n``) which indicates operation result. The server may send additional -output on the ``stderr`` stream that should be displayed to the user. +The SSH version 1 transport sends a string encoded integer followed by a +newline (``\n``) which indicates operation result. The server may send +additional output on the ``stderr`` stream that should be displayed to the +user. -The HTTP transport sends a string encoded integer followed by a newline -followed by additional server output that should be displayed to the user. -This may include output from hooks, etc. +The HTTP version 1 transport sends a string encoded integer followed by a +newline followed by additional server output that should be displayed to +the user. This may include output from hooks, etc. The integer result varies by namespace. ``0`` means an error has occurred and there should be additional output to display to the user. @@ -908,18 +1087,18 @@ The encoding of the ``push response`` type varies by transport. -For the SSH transport, this type is composed of 2 ``string`` responses: an -empty response (``0\n``) followed by the integer result value. e.g. -``1\n2``. So the full response might be ``0\n1\n2``. +For the SSH version 1 transport, this type is composed of 2 ``string`` +responses: an empty response (``0\n``) followed by the integer result value. +e.g. ``1\n2``. So the full response might be ``0\n1\n2``. -For the HTTP transport, the response is a ``string`` type composed of an -integer result value followed by a newline (``\n``) followed by string +For the HTTP version 1 transport, the response is a ``string`` type composed +of an integer result value followed by a newline (``\n``) followed by string content holding server output that should be displayed on the client (output hooks, etc). In some cases, the server may respond with a ``bundle2`` bundle. In this -case, the response type is ``stream``. For the HTTP transport, the response -is zlib compressed. +case, the response type is ``stream``. For the HTTP version 1 transport, the +response is zlib compressed. The server may also respond with a generic error type, which contains a string indicating the failure. diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/hg.py --- a/mercurial/hg.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/hg.py Sat Feb 24 17:49:10 2018 -0600 @@ -31,6 +31,7 @@ httppeer, localrepo, lock, + logcmdutil, logexchange, merge as mergemod, node, @@ -201,6 +202,24 @@ return '' return os.path.basename(os.path.normpath(path)) +def sharedreposource(repo): + """Returns repository object for source repository of a shared repo. + + If repo is not a shared repository, returns None. + """ + if repo.sharedpath == repo.path: + return None + + if util.safehasattr(repo, 'srcrepo') and repo.srcrepo: + return repo.srcrepo + + # the sharedpath always ends in the .hg; we want the path to the repo + source = repo.vfs.split(repo.sharedpath)[0] + srcurl, branches = parseurl(source) + srcrepo = repository(repo.ui, srcurl) + repo.srcrepo = srcrepo + return srcrepo + def share(ui, source, dest=None, update=True, bookmarks=True, defaultpath=None, relative=False): '''create a shared repository''' @@ -213,7 +232,7 @@ else: dest = ui.expandpath(dest) - if isinstance(source, str): + if isinstance(source, bytes): origsource = ui.expandpath(source) source, branches = parseurl(origsource) srcrepo = repository(ui, source) @@ -885,7 +904,8 @@ ui.status(_("no changes found\n")) return subreporecurse() ui.pager('incoming') - displayer = cmdutil.show_changeset(ui, other, opts, buffered) + displayer = logcmdutil.changesetdisplayer(ui, other, opts, + buffered=buffered) displaychlist(other, chlist, displayer) displayer.close() finally: @@ -904,7 +924,7 @@ return ret def display(other, chlist, displayer): - limit = cmdutil.loglimit(opts) + limit = logcmdutil.getlimit(opts) if opts.get('newest_first'): chlist.reverse() count = 0 @@ -949,7 +969,7 @@ ret = min(ret, sub.outgoing(ui, dest, opts)) return ret - limit = cmdutil.loglimit(opts) + limit = logcmdutil.getlimit(opts) o, other = _outgoing(ui, repo, dest, opts) if not o: cmdutil.outgoinghooks(ui, repo, other, opts, o) @@ -958,7 +978,7 @@ if opts.get('newest_first'): o.reverse() ui.pager('outgoing') - displayer = cmdutil.show_changeset(ui, repo, opts) + displayer = logcmdutil.changesetdisplayer(ui, repo, opts) count = 0 for n in o: if limit is not None and count >= limit: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/hgweb/common.py --- a/mercurial/hgweb/common.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/hgweb/common.py Sat Feb 24 17:49:10 2018 -0600 @@ -45,7 +45,7 @@ authentication info). Return if op allowed, else raise an ErrorResponse exception.''' - user = req.env.get('REMOTE_USER') + user = req.env.get(r'REMOTE_USER') deny_read = hgweb.configlist('web', 'deny_read') if deny_read and (not user or ismember(hgweb.repo.ui, user, deny_read)): @@ -61,7 +61,7 @@ return # enforce that you can only push using POST requests - if req.env['REQUEST_METHOD'] != 'POST': + if req.env[r'REQUEST_METHOD'] != r'POST': msg = 'push requires POST request' raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg) @@ -185,7 +185,7 @@ if stripecount and offset: # account for offset, e.g. due to building the list in reverse count = (stripecount + offset) % stripecount - parity = (stripecount + offset) / stripecount & 1 + parity = (stripecount + offset) // stripecount & 1 else: count = 0 parity = 0 diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/hgweb/hgweb_mod.py Sat Feb 24 17:49:10 2018 -0600 @@ -36,10 +36,10 @@ templater, ui as uimod, util, + wireprotoserver, ) from . import ( - protocol, webcommands, webutil, wsgicgi, @@ -63,8 +63,6 @@ def getstyle(req, configfn, templatepath): fromreq = req.form.get('style', [None])[0] - if fromreq is not None: - fromreq = pycompat.sysbytes(fromreq) styles = ( fromreq, configfn('web', 'style'), @@ -357,31 +355,21 @@ query = req.env[r'QUERY_STRING'].partition(r'&')[0] query = query.partition(r';')[0] - # process this if it's a protocol request - # protocol bits don't need to create any URLs - # and the clients always use the old URL structure + # Route it to a wire protocol handler if it looks like a wire protocol + # request. + protohandler = wireprotoserver.parsehttprequest(rctx.repo, req, query) - cmd = pycompat.sysbytes(req.form.get(r'cmd', [r''])[0]) - if protocol.iscmd(cmd): + if protohandler: + cmd = protohandler['cmd'] try: if query: raise ErrorResponse(HTTP_NOT_FOUND) if cmd in perms: self.check_perm(rctx, req, perms[cmd]) - return protocol.call(rctx.repo, req, cmd) except ErrorResponse as inst: - # A client that sends unbundle without 100-continue will - # break if we respond early. - if (cmd == 'unbundle' and - (req.env.get('HTTP_EXPECT', - '').lower() != '100-continue') or - req.env.get('X-HgHttp2', '')): - req.drain() - else: - req.headers.append((r'Connection', r'Close')) - req.respond(inst, protocol.HGTYPE, - body='0\n%s\n' % inst) - return '' + return protohandler['handleerror'](inst) + + return protohandler['dispatch']() # translate user-visible url structure to internal structure @@ -417,6 +405,8 @@ if fn.endswith(ext): req.form['node'] = [fn[:-len(ext)]] req.form['type'] = [type_] + else: + cmd = pycompat.sysbytes(req.form.get(r'cmd', [r''])[0]) # process the web interface request @@ -451,20 +441,20 @@ except (error.LookupError, error.RepoLookupError) as err: req.respond(HTTP_NOT_FOUND, ctype) - msg = str(err) + msg = pycompat.bytestr(err) if (util.safehasattr(err, 'name') and not isinstance(err, error.ManifestLookupError)): msg = 'revision not found: %s' % err.name return tmpl('error', error=msg) except (error.RepoError, error.RevlogError) as inst: req.respond(HTTP_SERVER_ERROR, ctype) - return tmpl('error', error=str(inst)) + return tmpl('error', error=pycompat.bytestr(inst)) except ErrorResponse as inst: req.respond(inst, ctype) if inst.code == HTTP_NOT_MODIFIED: # Not allowed to return a body on a 304 return [''] - return tmpl('error', error=str(inst)) + return tmpl('error', error=pycompat.bytestr(inst)) def check_perm(self, rctx, req, op): for permhook in permhooks: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/hgweb/protocol.py --- a/mercurial/hgweb/protocol.py Fri Feb 23 17:57:04 2018 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,201 +0,0 @@ -# -# Copyright 21 May 2005 - (c) 2005 Jake Edge -# Copyright 2005-2007 Matt Mackall -# -# This software may be used and distributed according to the terms of the -# GNU General Public License version 2 or any later version. - -from __future__ import absolute_import - -import cgi -import struct - -from .common import ( - HTTP_OK, -) - -from .. import ( - error, - pycompat, - util, - wireproto, -) -stringio = util.stringio - -urlerr = util.urlerr -urlreq = util.urlreq - -HGTYPE = 'application/mercurial-0.1' -HGTYPE2 = 'application/mercurial-0.2' -HGERRTYPE = 'application/hg-error' - -def decodevaluefromheaders(req, headerprefix): - """Decode a long value from multiple HTTP request headers. - - Returns the value as a bytes, not a str. - """ - chunks = [] - i = 1 - prefix = headerprefix.upper().replace(r'-', r'_') - while True: - v = req.env.get(r'HTTP_%s_%d' % (prefix, i)) - if v is None: - break - chunks.append(pycompat.bytesurl(v)) - i += 1 - - return ''.join(chunks) - -class webproto(wireproto.abstractserverproto): - def __init__(self, req, ui): - self.req = req - self.response = '' - self.ui = ui - self.name = 'http' - - def getargs(self, args): - knownargs = self._args() - data = {} - keys = args.split() - for k in keys: - if k == '*': - star = {} - for key in knownargs.keys(): - if key != 'cmd' and key not in keys: - star[key] = knownargs[key][0] - data['*'] = star - else: - data[k] = knownargs[k][0] - return [data[k] for k in keys] - def _args(self): - args = self.req.form.copy() - if pycompat.ispy3: - args = {k.encode('ascii'): [v.encode('ascii') for v in vs] - for k, vs in args.items()} - postlen = int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0)) - if postlen: - args.update(cgi.parse_qs( - self.req.read(postlen), keep_blank_values=True)) - return args - - argvalue = decodevaluefromheaders(self.req, r'X-HgArg') - args.update(cgi.parse_qs(argvalue, keep_blank_values=True)) - return args - def getfile(self, fp): - length = int(self.req.env[r'CONTENT_LENGTH']) - # If httppostargs is used, we need to read Content-Length - # minus the amount that was consumed by args. - length -= int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0)) - for s in util.filechunkiter(self.req, limit=length): - fp.write(s) - def redirect(self): - self.oldio = self.ui.fout, self.ui.ferr - self.ui.ferr = self.ui.fout = stringio() - def restore(self): - val = self.ui.fout.getvalue() - self.ui.ferr, self.ui.fout = self.oldio - return val - - def _client(self): - return 'remote:%s:%s:%s' % ( - self.req.env.get('wsgi.url_scheme') or 'http', - urlreq.quote(self.req.env.get('REMOTE_HOST', '')), - urlreq.quote(self.req.env.get('REMOTE_USER', ''))) - - def responsetype(self, prefer_uncompressed): - """Determine the appropriate response type and compression settings. - - Returns a tuple of (mediatype, compengine, engineopts). - """ - # Determine the response media type and compression engine based - # on the request parameters. - protocaps = decodevaluefromheaders(self.req, r'X-HgProto').split(' ') - - if '0.2' in protocaps: - # All clients are expected to support uncompressed data. - if prefer_uncompressed: - return HGTYPE2, util._noopengine(), {} - - # Default as defined by wire protocol spec. - compformats = ['zlib', 'none'] - for cap in protocaps: - if cap.startswith('comp='): - compformats = cap[5:].split(',') - break - - # Now find an agreed upon compression format. - for engine in wireproto.supportedcompengines(self.ui, self, - util.SERVERROLE): - if engine.wireprotosupport().name in compformats: - opts = {} - level = self.ui.configint('server', - '%slevel' % engine.name()) - if level is not None: - opts['level'] = level - - return HGTYPE2, engine, opts - - # No mutually supported compression format. Fall back to the - # legacy protocol. - - # Don't allow untrusted settings because disabling compression or - # setting a very high compression level could lead to flooding - # the server's network or CPU. - opts = {'level': self.ui.configint('server', 'zliblevel')} - return HGTYPE, util.compengines['zlib'], opts - -def iscmd(cmd): - return cmd in wireproto.commands - -def call(repo, req, cmd): - p = webproto(req, repo.ui) - - def genversion2(gen, engine, engineopts): - # application/mercurial-0.2 always sends a payload header - # identifying the compression engine. - name = engine.wireprotosupport().name - assert 0 < len(name) < 256 - yield struct.pack('B', len(name)) - yield name - - for chunk in gen: - yield chunk - - rsp = wireproto.dispatch(repo, p, cmd) - if isinstance(rsp, bytes): - req.respond(HTTP_OK, HGTYPE, body=rsp) - return [] - elif isinstance(rsp, wireproto.streamres_legacy): - gen = rsp.gen - req.respond(HTTP_OK, HGTYPE) - return gen - elif isinstance(rsp, wireproto.streamres): - gen = rsp.gen - - # This code for compression should not be streamres specific. It - # is here because we only compress streamres at the moment. - mediatype, engine, engineopts = p.responsetype(rsp.prefer_uncompressed) - gen = engine.compressstream(gen, engineopts) - - if mediatype == HGTYPE2: - gen = genversion2(gen, engine, engineopts) - - req.respond(HTTP_OK, mediatype) - return gen - elif isinstance(rsp, wireproto.pushres): - val = p.restore() - rsp = '%d\n%s' % (rsp.res, val) - req.respond(HTTP_OK, HGTYPE, body=rsp) - return [] - elif isinstance(rsp, wireproto.pusherr): - # drain the incoming bundle - req.drain() - p.restore() - rsp = '0\n%s\n' % rsp.res - req.respond(HTTP_OK, HGTYPE, body=rsp) - return [] - elif isinstance(rsp, wireproto.ooberror): - rsp = rsp.message - req.respond(HTTP_OK, HGERRTYPE, body=rsp) - return [] - raise error.ProgrammingError('hgweb.protocol internal failure', rsp) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/hgweb/request.py Sat Feb 24 17:49:10 2018 -0600 @@ -115,13 +115,14 @@ self.headers = [(k, v) for (k, v) in self.headers if k in ('Date', 'ETag', 'Expires', 'Cache-Control', 'Vary')] - status = statusmessage(status.code, str(status)) + status = statusmessage(status.code, pycompat.bytestr(status)) elif status == 200: status = '200 Script output follows' elif isinstance(status, int): status = statusmessage(status) - self.server_write = self._start_response(status, self.headers) + self.server_write = self._start_response( + pycompat.sysstr(status), self.headers) self._start_response = None self.headers = [] if body is not None: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/hgweb/server.py --- a/mercurial/hgweb/server.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/hgweb/server.py Sat Feb 24 17:49:10 2018 -0600 @@ -273,7 +273,7 @@ def openlog(opt, default): if opt and opt != '-': - return open(opt, 'a') + return open(opt, 'ab') return default class MercurialHTTPServer(_mixin, httpservermod.httpserver, object): diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/hgweb/webcommands.py Sat Feb 24 17:49:10 2018 -0600 @@ -542,7 +542,7 @@ emptydirs = [] h = dirs[d] while isinstance(h, dict) and len(h) == 1: - k, v = h.items()[0] + k, v = next(iter(h.items())) if v: emptydirs.append(k) h = v @@ -561,7 +561,7 @@ fentries=filelist, dentries=dirlist, archives=web.archivelist(hex(node)), - **webutil.commonentry(web.repo, ctx)) + **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))) @webcommand('tags') def tags(web, req, tmpl): @@ -1116,7 +1116,7 @@ msg = 'Archive type not allowed: %s' % type_ raise ErrorResponse(HTTP_FORBIDDEN, msg) - reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame)) + reponame = re.sub(br"\W+", "-", os.path.basename(web.reponame)) cnode = web.repo.lookup(key) arch_version = key if cnode == key or key == 'tip': @@ -1403,7 +1403,7 @@ try: doc = helpmod.help_(u, commands, topic, subtopic=subtopic) - except error.UnknownCommand: + except error.Abort: raise ErrorResponse(HTTP_NOT_FOUND) return tmpl('help', topic=topicname, doc=doc) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/hgweb/webutil.py --- a/mercurial/hgweb/webutil.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/hgweb/webutil.py Sat Feb 24 17:49:10 2018 -0600 @@ -347,7 +347,7 @@ try: return util.processlinerange(fromline, toline) except error.ParseError as exc: - raise ErrorResponse(HTTP_BAD_REQUEST, str(exc)) + raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc)) def formatlinerange(fromline, toline): return '%d:%d' % (fromline + 1, toline) @@ -619,14 +619,14 @@ websubdefs += repo.ui.configitems('interhg') for key, pattern in websubdefs: # grab the delimiter from the character after the "s" - unesc = pattern[1] + unesc = pattern[1:2] delim = re.escape(unesc) # identify portions of the pattern, taking care to avoid escaped # delimiters. the replace format and flags are optional, but # delimiters are required. match = re.match( - r'^s%s(.+)(?:(?<=\\\\)|(? 0: - httpheader = self.capable('httpheader') - if httpheader: - headersize = int(httpheader.split(',', 1)[0]) + elif args: + # Calling self.capable() can infinite loop if we are calling + # "capabilities". But that command should never accept wire + # protocol arguments. So this should never happen. + assert cmd != 'capabilities' + httpheader = self.capable('httpheader') + if httpheader: + headersize = int(httpheader.split(',', 1)[0]) + + # Send arguments via HTTP headers. if headersize > 0: # The headers can typically carry more data than the URL. encargs = urlreq.urlencode(sorted(args.items())) @@ -278,8 +280,10 @@ headersize): headers[header] = value varyheaders.append(header) + # Send arguments via query string (Mercurial <1.9). else: q += sorted(args.items()) + qs = '?%s' % urlreq.urlencode(q) cu = "%s%s" % (self._url, qs) size = 0 @@ -330,8 +334,8 @@ req = self._requestbuilder(pycompat.strurl(cu), data, headers) if data is not None: - self.ui.debug("sending %s bytes\n" % size) - req.add_unredirected_header('Content-Length', '%d' % size) + self.ui.debug("sending %d bytes\n" % size) + req.add_unredirected_header(r'Content-Length', r'%d' % size) try: resp = self._openurl(req) except urlerr.httperror as inst: @@ -430,7 +434,7 @@ tempname = bundle2.writebundle(self.ui, cg, None, type) fp = httpconnection.httpsendfile(self.ui, tempname, "rb") - headers = {'Content-Type': 'application/mercurial-0.1'} + headers = {r'Content-Type': r'application/mercurial-0.1'} try: r = self._call(cmd, data=fp, headers=headers, **args) @@ -461,7 +465,7 @@ fh.close() # start http push fp_ = httpconnection.httpsendfile(self.ui, filename, "rb") - headers = {'Content-Type': 'application/mercurial-0.1'} + headers = {r'Content-Type': r'application/mercurial-0.1'} return self._callstream(cmd, data=fp_, headers=headers, **args) finally: if fp_ is not None: @@ -476,28 +480,17 @@ def _abort(self, exception): raise exception -class httpspeer(httppeer): - def __init__(self, ui, path): - if not url.has_https: - raise error.Abort(_('Python support for SSL and HTTPS ' - 'is not installed')) - httppeer.__init__(self, ui, path) - def instance(ui, path, create): if create: raise error.Abort(_('cannot create new http repository')) try: - if path.startswith('https:'): - inst = httpspeer(ui, path) - else: - inst = httppeer(ui, path) - try: - # Try to do useful work when checking compatibility. - # Usually saves a roundtrip since we want the caps anyway. - inst._fetchcaps() - except error.RepoError: - # No luck, try older compatibility check. - inst.between([(nullid, nullid)]) + if path.startswith('https:') and not url.has_https: + raise error.Abort(_('Python support for SSL and HTTPS ' + 'is not installed')) + + inst = httppeer(ui, path) + inst._fetchcaps() + return inst except error.RepoError as httpexception: try: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/keepalive.py --- a/mercurial/keepalive.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/keepalive.py Sat Feb 24 17:49:10 2018 -0600 @@ -324,11 +324,11 @@ h.putrequest( req.get_method(), urllibcompat.getselector(req), **pycompat.strkwargs(skipheaders)) - if 'content-type' not in headers: - h.putheader('Content-type', - 'application/x-www-form-urlencoded') - if 'content-length' not in headers: - h.putheader('Content-length', '%d' % len(data)) + if r'content-type' not in headers: + h.putheader(r'Content-type', + r'application/x-www-form-urlencoded') + if r'content-length' not in headers: + h.putheader(r'Content-length', r'%d' % len(data)) else: h.putrequest( req.get_method(), urllibcompat.getselector(req), diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/localrepo.py --- a/mercurial/localrepo.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/localrepo.py Sat Feb 24 17:49:10 2018 -0600 @@ -9,7 +9,6 @@ import errno import hashlib -import inspect import os import random import time @@ -57,7 +56,7 @@ scmutil, sparse, store, - subrepo, + subrepoutil, tags as tagsmod, transaction, txnutil, @@ -304,11 +303,15 @@ class localrepository(object): + # obsolete experimental requirements: + # - manifestv2: An experimental new manifest format that allowed + # for stem compression of long paths. Experiment ended up not + # being successful (repository sizes went up due to worse delta + # chains), and the code was deleted in 4.6. supportedformats = { 'revlogv1', 'generaldelta', 'treemanifest', - 'manifestv2', REVLOGV2_REQUIREMENT, } _basesupported = supportedformats | { @@ -323,7 +326,6 @@ 'revlogv1', 'generaldelta', 'treemanifest', - 'manifestv2', } # a list of (ui, featureset) functions. @@ -1068,7 +1070,7 @@ if not fn: fn = lambda s, c, **kwargs: util.filter(s, c) # Wrap old filters not supporting keyword arguments - if not inspect.getargspec(fn)[2]: + if not pycompat.getargspec(fn)[2]: oldfn = fn fn = lambda s, c, **kwargs: oldfn(s, c) l.append((mf, fn, params)) @@ -1332,7 +1334,7 @@ """To be run if transaction is aborted """ reporef().hook('txnabort', throw=False, txnname=desc, - **tr2.hookargs) + **pycompat.strkwargs(tr2.hookargs)) tr.addabort('txnabort-hook', txnaborthook) # avoid eager cache invalidation. in-memory data should be identical # to stored data if transaction has no error. @@ -1574,7 +1576,8 @@ def _refreshfilecachestats(self, tr): """Reload stats of cached files so that they are flagged as valid""" for k, ce in self._filecache.items(): - if k == 'dirstate' or k not in self.__dict__: + k = pycompat.sysstr(k) + if k == r'dirstate' or k not in self.__dict__: continue ce.refresh() @@ -1832,7 +1835,7 @@ status.modified.extend(status.clean) # mq may commit clean files # check subrepos - subs, commitsubs, newstate = subrepo.precommit( + subs, commitsubs, newstate = subrepoutil.precommit( self.ui, wctx, status, match, force=force) # make sure all explicit patterns are matched @@ -1869,10 +1872,10 @@ for s in sorted(commitsubs): sub = wctx.sub(s) self.ui.status(_('committing subrepository %s\n') % - subrepo.subrelpath(sub)) + subrepoutil.subrelpath(sub)) sr = sub.commit(cctx._text, user, date) newstate[s] = (newstate[s][0], sr) - subrepo.writestate(self, newstate) + subrepoutil.writestate(self, newstate) p1, p2 = self.dirstate.parents() hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '') @@ -1982,7 +1985,7 @@ self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1, parent2=xp2) # set the new commit is proper phase - targetphase = subrepo.newcommitphase(self.ui, ctx) + targetphase = subrepoutil.newcommitphase(self.ui, ctx) if targetphase: # retract boundary do not alter parent changeset. # if a parent have higher the resulting phase will @@ -2047,15 +2050,6 @@ # tag cache retrieval" case to work. self.invalidate() - def walk(self, match, node=None): - ''' - walk recursively through the directory tree or a given - changeset, finding all files matched by the match - function - ''' - self.ui.deprecwarn('use repo[node].walk instead of repo.walk', '4.3') - return self[node].walk(match) - def status(self, node1='.', node2=None, match=None, ignored=False, clean=False, unknown=False, listsubrepos=False): @@ -2270,8 +2264,6 @@ requirements.add('generaldelta') if ui.configbool('experimental', 'treemanifest'): requirements.add('treemanifest') - if ui.configbool('experimental', 'manifestv2'): - requirements.add('manifestv2') revlogv2 = ui.config('experimental', 'revlogv2') if revlogv2 == 'enable-unstable-format-and-corrupt-my-data': diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/lock.py --- a/mercurial/lock.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/lock.py Sat Feb 24 17:49:10 2018 -0600 @@ -30,9 +30,7 @@ confidence. Typically it's just hostname. On modern linux, we include an extra Linux-specific pid namespace identifier. """ - result = socket.gethostname() - if pycompat.ispy3: - result = result.encode(pycompat.sysstr(encoding.encoding), 'replace') + result = encoding.strtolocal(socket.gethostname()) if pycompat.sysplatform.startswith('linux'): try: result += '/%x' % os.stat('/proc/self/ns/pid').st_ino @@ -86,9 +84,9 @@ l.delay = delay if l.delay: if 0 <= warningidx <= l.delay: - ui.warn(_("got lock after %s seconds\n") % l.delay) + ui.warn(_("got lock after %d seconds\n") % l.delay) else: - ui.debug("got lock after %s seconds\n" % l.delay) + ui.debug("got lock after %d seconds\n" % l.delay) if l.acquirefn: l.acquirefn() return l diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/logcmdutil.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/logcmdutil.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,931 @@ +# logcmdutil.py - utility for log-like commands +# +# Copyright 2005-2007 Matt Mackall +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +import itertools +import os + +from .i18n import _ +from .node import ( + hex, + nullid, +) + +from . import ( + dagop, + encoding, + error, + formatter, + graphmod, + match as matchmod, + mdiff, + patch, + pathutil, + pycompat, + revset, + revsetlang, + scmutil, + smartset, + templatekw, + templater, + util, +) + +def getlimit(opts): + """get the log limit according to option -l/--limit""" + limit = opts.get('limit') + if limit: + try: + limit = int(limit) + except ValueError: + raise error.Abort(_('limit must be a positive integer')) + if limit <= 0: + raise error.Abort(_('limit must be positive')) + else: + limit = None + return limit + +def diffordiffstat(ui, repo, diffopts, node1, node2, match, + changes=None, stat=False, fp=None, prefix='', + root='', listsubrepos=False, hunksfilterfn=None): + '''show diff or diffstat.''' + if root: + relroot = pathutil.canonpath(repo.root, repo.getcwd(), root) + else: + relroot = '' + if relroot != '': + # XXX relative roots currently don't work if the root is within a + # subrepo + uirelroot = match.uipath(relroot) + relroot += '/' + for matchroot in match.files(): + if not matchroot.startswith(relroot): + ui.warn(_('warning: %s not inside relative root %s\n') % ( + match.uipath(matchroot), uirelroot)) + + if stat: + diffopts = diffopts.copy(context=0, noprefix=False) + width = 80 + if not ui.plain(): + width = ui.termwidth() + + chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts, + prefix=prefix, relroot=relroot, + hunksfilterfn=hunksfilterfn) + + if fp is not None or ui.canwritewithoutlabels(): + out = fp or ui + if stat: + chunks = [patch.diffstat(util.iterlines(chunks), width=width)] + for chunk in util.filechunkiter(util.chunkbuffer(chunks)): + out.write(chunk) + else: + if stat: + chunks = patch.diffstatui(util.iterlines(chunks), width=width) + else: + chunks = patch.difflabel(lambda chunks, **kwargs: chunks, chunks, + opts=diffopts) + if ui.canbatchlabeledwrites(): + def gen(): + for chunk, label in chunks: + yield ui.label(chunk, label=label) + for chunk in util.filechunkiter(util.chunkbuffer(gen())): + ui.write(chunk) + else: + for chunk, label in chunks: + ui.write(chunk, label=label) + + if listsubrepos: + ctx1 = repo[node1] + ctx2 = repo[node2] + for subpath, sub in scmutil.itersubrepos(ctx1, ctx2): + tempnode2 = node2 + try: + if node2 is not None: + tempnode2 = ctx2.substate[subpath][1] + except KeyError: + # A subrepo that existed in node1 was deleted between node1 and + # node2 (inclusive). Thus, ctx2's substate won't contain that + # subpath. The best we can do is to ignore it. + tempnode2 = None + submatch = matchmod.subdirmatcher(subpath, match) + sub.diff(ui, diffopts, tempnode2, submatch, changes=changes, + stat=stat, fp=fp, prefix=prefix) + +class changesetdiffer(object): + """Generate diff of changeset with pre-configured filtering functions""" + + def _makefilematcher(self, ctx): + return scmutil.matchall(ctx.repo()) + + def _makehunksfilter(self, ctx): + return None + + def showdiff(self, ui, ctx, diffopts, stat=False): + repo = ctx.repo() + node = ctx.node() + prev = ctx.p1().node() + diffordiffstat(ui, repo, diffopts, prev, node, + match=self._makefilematcher(ctx), stat=stat, + hunksfilterfn=self._makehunksfilter(ctx)) + +def changesetlabels(ctx): + labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()] + if ctx.obsolete(): + labels.append('changeset.obsolete') + if ctx.isunstable(): + labels.append('changeset.unstable') + for instability in ctx.instabilities(): + labels.append('instability.%s' % instability) + return ' '.join(labels) + +class changesetprinter(object): + '''show changeset information when templating not requested.''' + + def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False): + self.ui = ui + self.repo = repo + self.buffered = buffered + self._differ = differ or changesetdiffer() + self.diffopts = diffopts or {} + self.header = {} + self.hunk = {} + self.lastheader = None + self.footer = None + self._columns = templatekw.getlogcolumns() + + def flush(self, ctx): + rev = ctx.rev() + if rev in self.header: + h = self.header[rev] + if h != self.lastheader: + self.lastheader = h + self.ui.write(h) + del self.header[rev] + if rev in self.hunk: + self.ui.write(self.hunk[rev]) + del self.hunk[rev] + + def close(self): + if self.footer: + self.ui.write(self.footer) + + def show(self, ctx, copies=None, **props): + props = pycompat.byteskwargs(props) + if self.buffered: + self.ui.pushbuffer(labeled=True) + self._show(ctx, copies, props) + self.hunk[ctx.rev()] = self.ui.popbuffer() + else: + self._show(ctx, copies, props) + + def _show(self, ctx, copies, props): + '''show a single changeset or file revision''' + changenode = ctx.node() + rev = ctx.rev() + + if self.ui.quiet: + self.ui.write("%s\n" % scmutil.formatchangeid(ctx), + label='log.node') + return + + columns = self._columns + self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx), + label=changesetlabels(ctx)) + + # branches are shown first before any other names due to backwards + # compatibility + branch = ctx.branch() + # don't show the default branch name + if branch != 'default': + self.ui.write(columns['branch'] % branch, label='log.branch') + + for nsname, ns in self.repo.names.iteritems(): + # branches has special logic already handled above, so here we just + # skip it + if nsname == 'branches': + continue + # we will use the templatename as the color name since those two + # should be the same + for name in ns.names(self.repo, changenode): + self.ui.write(ns.logfmt % name, + label='log.%s' % ns.colorname) + if self.ui.debugflag: + self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase') + for pctx in scmutil.meaningfulparents(self.repo, ctx): + label = 'log.parent changeset.%s' % pctx.phasestr() + self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx), + label=label) + + if self.ui.debugflag and rev is not None: + mnode = ctx.manifestnode() + mrev = self.repo.manifestlog._revlog.rev(mnode) + self.ui.write(columns['manifest'] + % scmutil.formatrevnode(self.ui, mrev, mnode), + label='ui.debug log.manifest') + self.ui.write(columns['user'] % ctx.user(), label='log.user') + self.ui.write(columns['date'] % util.datestr(ctx.date()), + label='log.date') + + if ctx.isunstable(): + instabilities = ctx.instabilities() + self.ui.write(columns['instability'] % ', '.join(instabilities), + label='log.instability') + + elif ctx.obsolete(): + self._showobsfate(ctx) + + self._exthook(ctx) + + if self.ui.debugflag: + files = ctx.p1().status(ctx)[:3] + for key, value in zip(['files', 'files+', 'files-'], files): + if value: + self.ui.write(columns[key] % " ".join(value), + label='ui.debug log.files') + elif ctx.files() and self.ui.verbose: + self.ui.write(columns['files'] % " ".join(ctx.files()), + label='ui.note log.files') + if copies and self.ui.verbose: + copies = ['%s (%s)' % c for c in copies] + self.ui.write(columns['copies'] % ' '.join(copies), + label='ui.note log.copies') + + extra = ctx.extra() + if extra and self.ui.debugflag: + for key, value in sorted(extra.items()): + self.ui.write(columns['extra'] % (key, util.escapestr(value)), + label='ui.debug log.extra') + + description = ctx.description().strip() + if description: + if self.ui.verbose: + self.ui.write(_("description:\n"), + label='ui.note log.description') + self.ui.write(description, + label='ui.note log.description') + self.ui.write("\n\n") + else: + self.ui.write(columns['summary'] % description.splitlines()[0], + label='log.summary') + self.ui.write("\n") + + self._showpatch(ctx) + + def _showobsfate(self, ctx): + obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui) + + if obsfate: + for obsfateline in obsfate: + self.ui.write(self._columns['obsolete'] % obsfateline, + label='log.obsfate') + + def _exthook(self, ctx): + '''empty method used by extension as a hook point + ''' + + def _showpatch(self, ctx): + stat = self.diffopts.get('stat') + diff = self.diffopts.get('patch') + diffopts = patch.diffallopts(self.ui, self.diffopts) + if stat: + self._differ.showdiff(self.ui, ctx, diffopts, stat=True) + if stat and diff: + self.ui.write("\n") + if diff: + self._differ.showdiff(self.ui, ctx, diffopts, stat=False) + if stat or diff: + self.ui.write("\n") + +class jsonchangeset(changesetprinter): + '''format changeset information.''' + + def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False): + changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered) + self.cache = {} + self._first = True + + def close(self): + if not self._first: + self.ui.write("\n]\n") + else: + self.ui.write("[]\n") + + def _show(self, ctx, copies, props): + '''show a single changeset or file revision''' + rev = ctx.rev() + if rev is None: + jrev = jnode = 'null' + else: + jrev = '%d' % rev + jnode = '"%s"' % hex(ctx.node()) + j = encoding.jsonescape + + if self._first: + self.ui.write("[\n {") + self._first = False + else: + self.ui.write(",\n {") + + if self.ui.quiet: + self.ui.write(('\n "rev": %s') % jrev) + self.ui.write((',\n "node": %s') % jnode) + self.ui.write('\n }') + return + + self.ui.write(('\n "rev": %s') % jrev) + self.ui.write((',\n "node": %s') % jnode) + self.ui.write((',\n "branch": "%s"') % j(ctx.branch())) + self.ui.write((',\n "phase": "%s"') % ctx.phasestr()) + self.ui.write((',\n "user": "%s"') % j(ctx.user())) + self.ui.write((',\n "date": [%d, %d]') % ctx.date()) + self.ui.write((',\n "desc": "%s"') % j(ctx.description())) + + self.ui.write((',\n "bookmarks": [%s]') % + ", ".join('"%s"' % j(b) for b in ctx.bookmarks())) + self.ui.write((',\n "tags": [%s]') % + ", ".join('"%s"' % j(t) for t in ctx.tags())) + self.ui.write((',\n "parents": [%s]') % + ", ".join('"%s"' % c.hex() for c in ctx.parents())) + + if self.ui.debugflag: + if rev is None: + jmanifestnode = 'null' + else: + jmanifestnode = '"%s"' % hex(ctx.manifestnode()) + self.ui.write((',\n "manifest": %s') % jmanifestnode) + + self.ui.write((',\n "extra": {%s}') % + ", ".join('"%s": "%s"' % (j(k), j(v)) + for k, v in ctx.extra().items())) + + files = ctx.p1().status(ctx) + self.ui.write((',\n "modified": [%s]') % + ", ".join('"%s"' % j(f) for f in files[0])) + self.ui.write((',\n "added": [%s]') % + ", ".join('"%s"' % j(f) for f in files[1])) + self.ui.write((',\n "removed": [%s]') % + ", ".join('"%s"' % j(f) for f in files[2])) + + elif self.ui.verbose: + self.ui.write((',\n "files": [%s]') % + ", ".join('"%s"' % j(f) for f in ctx.files())) + + if copies: + self.ui.write((',\n "copies": {%s}') % + ", ".join('"%s": "%s"' % (j(k), j(v)) + for k, v in copies)) + + stat = self.diffopts.get('stat') + diff = self.diffopts.get('patch') + diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True) + if stat: + self.ui.pushbuffer() + self._differ.showdiff(self.ui, ctx, diffopts, stat=True) + self.ui.write((',\n "diffstat": "%s"') + % j(self.ui.popbuffer())) + if diff: + self.ui.pushbuffer() + self._differ.showdiff(self.ui, ctx, diffopts, stat=False) + self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer())) + + self.ui.write("\n }") + +class changesettemplater(changesetprinter): + '''format changeset information. + + Note: there are a variety of convenience functions to build a + changesettemplater for common cases. See functions such as: + maketemplater, changesetdisplayer, buildcommittemplate, or other + functions that use changesest_templater. + ''' + + # Arguments before "buffered" used to be positional. Consider not + # adding/removing arguments before "buffered" to not break callers. + def __init__(self, ui, repo, tmplspec, differ=None, diffopts=None, + buffered=False): + changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered) + tres = formatter.templateresources(ui, repo) + self.t = formatter.loadtemplater(ui, tmplspec, + defaults=templatekw.keywords, + resources=tres, + cache=templatekw.defaulttempl) + self._counter = itertools.count() + self.cache = tres['cache'] # shared with _graphnodeformatter() + + self._tref = tmplspec.ref + self._parts = {'header': '', 'footer': '', + tmplspec.ref: tmplspec.ref, + 'docheader': '', 'docfooter': '', + 'separator': ''} + if tmplspec.mapfile: + # find correct templates for current mode, for backward + # compatibility with 'log -v/-q/--debug' using a mapfile + tmplmodes = [ + (True, ''), + (self.ui.verbose, '_verbose'), + (self.ui.quiet, '_quiet'), + (self.ui.debugflag, '_debug'), + ] + for mode, postfix in tmplmodes: + for t in self._parts: + cur = t + postfix + if mode and cur in self.t: + self._parts[t] = cur + else: + partnames = [p for p in self._parts.keys() if p != tmplspec.ref] + m = formatter.templatepartsmap(tmplspec, self.t, partnames) + self._parts.update(m) + + if self._parts['docheader']: + self.ui.write(templater.stringify(self.t(self._parts['docheader']))) + + def close(self): + if self._parts['docfooter']: + if not self.footer: + self.footer = "" + self.footer += templater.stringify(self.t(self._parts['docfooter'])) + return super(changesettemplater, self).close() + + def _show(self, ctx, copies, props): + '''show a single changeset or file revision''' + props = props.copy() + props['ctx'] = ctx + props['index'] = index = next(self._counter) + props['revcache'] = {'copies': copies} + props = pycompat.strkwargs(props) + + # write separator, which wouldn't work well with the header part below + # since there's inherently a conflict between header (across items) and + # separator (per item) + if self._parts['separator'] and index > 0: + self.ui.write(templater.stringify(self.t(self._parts['separator']))) + + # write header + if self._parts['header']: + h = templater.stringify(self.t(self._parts['header'], **props)) + if self.buffered: + self.header[ctx.rev()] = h + else: + if self.lastheader != h: + self.lastheader = h + self.ui.write(h) + + # write changeset metadata, then patch if requested + key = self._parts[self._tref] + self.ui.write(templater.stringify(self.t(key, **props))) + self._showpatch(ctx) + + if self._parts['footer']: + if not self.footer: + self.footer = templater.stringify( + self.t(self._parts['footer'], **props)) + +def templatespec(tmpl, mapfile): + if mapfile: + return formatter.templatespec('changeset', tmpl, mapfile) + else: + return formatter.templatespec('', tmpl, None) + +def _lookuptemplate(ui, tmpl, style): + """Find the template matching the given template spec or style + + See formatter.lookuptemplate() for details. + """ + + # ui settings + if not tmpl and not style: # template are stronger than style + tmpl = ui.config('ui', 'logtemplate') + if tmpl: + return templatespec(templater.unquotestring(tmpl), None) + else: + style = util.expandpath(ui.config('ui', 'style')) + + if not tmpl and style: + mapfile = style + if not os.path.split(mapfile)[0]: + mapname = (templater.templatepath('map-cmdline.' + mapfile) + or templater.templatepath(mapfile)) + if mapname: + mapfile = mapname + return templatespec(None, mapfile) + + if not tmpl: + return templatespec(None, None) + + return formatter.lookuptemplate(ui, 'changeset', tmpl) + +def maketemplater(ui, repo, tmpl, buffered=False): + """Create a changesettemplater from a literal template 'tmpl' + byte-string.""" + spec = templatespec(tmpl, None) + return changesettemplater(ui, repo, spec, buffered=buffered) + +def changesetdisplayer(ui, repo, opts, differ=None, buffered=False): + """show one changeset using template or regular display. + + Display format will be the first non-empty hit of: + 1. option 'template' + 2. option 'style' + 3. [ui] setting 'logtemplate' + 4. [ui] setting 'style' + If all of these values are either the unset or the empty string, + regular display via changesetprinter() is done. + """ + postargs = (differ, opts, buffered) + if opts.get('template') == 'json': + return jsonchangeset(ui, repo, *postargs) + + spec = _lookuptemplate(ui, opts.get('template'), opts.get('style')) + + if not spec.ref and not spec.tmpl and not spec.mapfile: + return changesetprinter(ui, repo, *postargs) + + return changesettemplater(ui, repo, spec, *postargs) + +def _makematcher(repo, revs, pats, opts): + """Build matcher and expanded patterns from log options + + If --follow, revs are the revisions to follow from. + + Returns (match, pats, slowpath) where + - match: a matcher built from the given pats and -I/-X opts + - pats: patterns used (globs are expanded on Windows) + - slowpath: True if patterns aren't as simple as scanning filelogs + """ + # pats/include/exclude are passed to match.match() directly in + # _matchfiles() revset but walkchangerevs() builds its matcher with + # scmutil.match(). The difference is input pats are globbed on + # platforms without shell expansion (windows). + wctx = repo[None] + match, pats = scmutil.matchandpats(wctx, pats, opts) + slowpath = match.anypats() or (not match.always() and opts.get('removed')) + if not slowpath: + follow = opts.get('follow') or opts.get('follow_first') + startctxs = [] + if follow and opts.get('rev'): + startctxs = [repo[r] for r in revs] + for f in match.files(): + if follow and startctxs: + # No idea if the path was a directory at that revision, so + # take the slow path. + if any(f not in c for c in startctxs): + slowpath = True + continue + elif follow and f not in wctx: + # If the file exists, it may be a directory, so let it + # take the slow path. + if os.path.exists(repo.wjoin(f)): + slowpath = True + continue + else: + raise error.Abort(_('cannot follow file not in parent ' + 'revision: "%s"') % f) + filelog = repo.file(f) + if not filelog: + # A zero count may be a directory or deleted file, so + # try to find matching entries on the slow path. + if follow: + raise error.Abort( + _('cannot follow nonexistent file: "%s"') % f) + slowpath = True + + # We decided to fall back to the slowpath because at least one + # of the paths was not a file. Check to see if at least one of them + # existed in history - in that case, we'll continue down the + # slowpath; otherwise, we can turn off the slowpath + if slowpath: + for path in match.files(): + if path == '.' or path in repo.store: + break + else: + slowpath = False + + return match, pats, slowpath + +def _fileancestors(repo, revs, match, followfirst): + fctxs = [] + for r in revs: + ctx = repo[r] + fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match)) + + # When displaying a revision with --patch --follow FILE, we have + # to know which file of the revision must be diffed. With + # --follow, we want the names of the ancestors of FILE in the + # revision, stored in "fcache". "fcache" is populated as a side effect + # of the graph traversal. + fcache = {} + def filematcher(ctx): + return scmutil.matchfiles(repo, fcache.get(ctx.rev(), [])) + + def revgen(): + for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst): + fcache[rev] = [c.path() for c in cs] + yield rev + return smartset.generatorset(revgen(), iterasc=False), filematcher + +def _makenofollowfilematcher(repo, pats, opts): + '''hook for extensions to override the filematcher for non-follow cases''' + return None + +_opt2logrevset = { + 'no_merges': ('not merge()', None), + 'only_merges': ('merge()', None), + '_matchfiles': (None, '_matchfiles(%ps)'), + 'date': ('date(%s)', None), + 'branch': ('branch(%s)', '%lr'), + '_patslog': ('filelog(%s)', '%lr'), + 'keyword': ('keyword(%s)', '%lr'), + 'prune': ('ancestors(%s)', 'not %lr'), + 'user': ('user(%s)', '%lr'), +} + +def _makerevset(repo, match, pats, slowpath, opts): + """Return a revset string built from log options and file patterns""" + opts = dict(opts) + # follow or not follow? + follow = opts.get('follow') or opts.get('follow_first') + + # branch and only_branch are really aliases and must be handled at + # the same time + opts['branch'] = opts.get('branch', []) + opts.get('only_branch', []) + opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']] + + if slowpath: + # See walkchangerevs() slow path. + # + # pats/include/exclude cannot be represented as separate + # revset expressions as their filtering logic applies at file + # level. For instance "-I a -X b" matches a revision touching + # "a" and "b" while "file(a) and not file(b)" does + # not. Besides, filesets are evaluated against the working + # directory. + matchargs = ['r:', 'd:relpath'] + for p in pats: + matchargs.append('p:' + p) + for p in opts.get('include', []): + matchargs.append('i:' + p) + for p in opts.get('exclude', []): + matchargs.append('x:' + p) + opts['_matchfiles'] = matchargs + elif not follow: + opts['_patslog'] = list(pats) + + expr = [] + for op, val in sorted(opts.iteritems()): + if not val: + continue + if op not in _opt2logrevset: + continue + revop, listop = _opt2logrevset[op] + if revop and '%' not in revop: + expr.append(revop) + elif not listop: + expr.append(revsetlang.formatspec(revop, val)) + else: + if revop: + val = [revsetlang.formatspec(revop, v) for v in val] + expr.append(revsetlang.formatspec(listop, val)) + + if expr: + expr = '(' + ' and '.join(expr) + ')' + else: + expr = None + return expr + +def _initialrevs(repo, opts): + """Return the initial set of revisions to be filtered or followed""" + follow = opts.get('follow') or opts.get('follow_first') + if opts.get('rev'): + revs = scmutil.revrange(repo, opts['rev']) + elif follow and repo.dirstate.p1() == nullid: + revs = smartset.baseset() + elif follow: + revs = repo.revs('.') + else: + revs = smartset.spanset(repo) + revs.reverse() + return revs + +def getrevs(repo, pats, opts): + """Return (revs, differ) where revs is a smartset + + differ is a changesetdiffer with pre-configured file matcher. + """ + follow = opts.get('follow') or opts.get('follow_first') + followfirst = opts.get('follow_first') + limit = getlimit(opts) + revs = _initialrevs(repo, opts) + if not revs: + return smartset.baseset(), None + match, pats, slowpath = _makematcher(repo, revs, pats, opts) + filematcher = None + if follow: + if slowpath or match.always(): + revs = dagop.revancestors(repo, revs, followfirst=followfirst) + else: + revs, filematcher = _fileancestors(repo, revs, match, followfirst) + revs.reverse() + if filematcher is None: + filematcher = _makenofollowfilematcher(repo, pats, opts) + if filematcher is None: + def filematcher(ctx): + return match + + expr = _makerevset(repo, match, pats, slowpath, opts) + if opts.get('graph') and opts.get('rev'): + # User-specified revs might be unsorted, but don't sort before + # _makerevset because it might depend on the order of revs + if not (revs.isdescending() or revs.istopo()): + revs.sort(reverse=True) + if expr: + matcher = revset.match(None, expr) + revs = matcher(repo, revs) + if limit is not None: + revs = revs.slice(0, limit) + + differ = changesetdiffer() + differ._makefilematcher = filematcher + return revs, differ + +def _parselinerangeopt(repo, opts): + """Parse --line-range log option and return a list of tuples (filename, + (fromline, toline)). + """ + linerangebyfname = [] + for pat in opts.get('line_range', []): + try: + pat, linerange = pat.rsplit(',', 1) + except ValueError: + raise error.Abort(_('malformatted line-range pattern %s') % pat) + try: + fromline, toline = map(int, linerange.split(':')) + except ValueError: + raise error.Abort(_("invalid line range for %s") % pat) + msg = _("line range pattern '%s' must match exactly one file") % pat + fname = scmutil.parsefollowlinespattern(repo, None, pat, msg) + linerangebyfname.append( + (fname, util.processlinerange(fromline, toline))) + return linerangebyfname + +def getlinerangerevs(repo, userrevs, opts): + """Return (revs, differ). + + "revs" are revisions obtained by processing "line-range" log options and + walking block ancestors of each specified file/line-range. + + "differ" is a changesetdiffer with pre-configured file matcher and hunks + filter. + """ + wctx = repo[None] + + # Two-levels map of "rev -> file ctx -> [line range]". + linerangesbyrev = {} + for fname, (fromline, toline) in _parselinerangeopt(repo, opts): + if fname not in wctx: + raise error.Abort(_('cannot follow file not in parent ' + 'revision: "%s"') % fname) + fctx = wctx.filectx(fname) + for fctx, linerange in dagop.blockancestors(fctx, fromline, toline): + rev = fctx.introrev() + if rev not in userrevs: + continue + linerangesbyrev.setdefault( + rev, {}).setdefault( + fctx.path(), []).append(linerange) + + def nofilterhunksfn(fctx, hunks): + return hunks + + def hunksfilter(ctx): + fctxlineranges = linerangesbyrev.get(ctx.rev()) + if fctxlineranges is None: + return nofilterhunksfn + + def filterfn(fctx, hunks): + lineranges = fctxlineranges.get(fctx.path()) + if lineranges is not None: + for hr, lines in hunks: + if hr is None: # binary + yield hr, lines + continue + if any(mdiff.hunkinrange(hr[2:], lr) + for lr in lineranges): + yield hr, lines + else: + for hunk in hunks: + yield hunk + + return filterfn + + def filematcher(ctx): + files = list(linerangesbyrev.get(ctx.rev(), [])) + return scmutil.matchfiles(repo, files) + + revs = sorted(linerangesbyrev, reverse=True) + + differ = changesetdiffer() + differ._makefilematcher = filematcher + differ._makehunksfilter = hunksfilter + return revs, differ + +def _graphnodeformatter(ui, displayer): + spec = ui.config('ui', 'graphnodetemplate') + if not spec: + return templatekw.showgraphnode # fast path for "{graphnode}" + + spec = templater.unquotestring(spec) + tres = formatter.templateresources(ui) + if isinstance(displayer, changesettemplater): + tres['cache'] = displayer.cache # reuse cache of slow templates + templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords, + resources=tres) + def formatnode(repo, ctx): + props = {'ctx': ctx, 'repo': repo, 'revcache': {}} + return templ.render(props) + return formatnode + +def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None, props=None): + props = props or {} + formatnode = _graphnodeformatter(ui, displayer) + state = graphmod.asciistate() + styles = state['styles'] + + # only set graph styling if HGPLAIN is not set. + if ui.plain('graph'): + # set all edge styles to |, the default pre-3.8 behaviour + styles.update(dict.fromkeys(styles, '|')) + else: + edgetypes = { + 'parent': graphmod.PARENT, + 'grandparent': graphmod.GRANDPARENT, + 'missing': graphmod.MISSINGPARENT + } + for name, key in edgetypes.items(): + # experimental config: experimental.graphstyle.* + styles[key] = ui.config('experimental', 'graphstyle.%s' % name, + styles[key]) + if not styles[key]: + styles[key] = None + + # experimental config: experimental.graphshorten + state['graphshorten'] = ui.configbool('experimental', 'graphshorten') + + for rev, type, ctx, parents in dag: + char = formatnode(repo, ctx) + copies = None + if getrenamed and ctx.rev(): + copies = [] + for fn in ctx.files(): + rename = getrenamed(fn, ctx.rev()) + if rename: + copies.append((fn, rename[0])) + edges = edgefn(type, char, state, rev, parents) + firstedge = next(edges) + width = firstedge[2] + displayer.show(ctx, copies=copies, + _graphwidth=width, **pycompat.strkwargs(props)) + lines = displayer.hunk.pop(rev).split('\n') + if not lines[-1]: + del lines[-1] + displayer.flush(ctx) + for type, char, width, coldata in itertools.chain([firstedge], edges): + graphmod.ascii(ui, state, type, char, lines, coldata) + lines = [] + displayer.close() + +def displaygraphrevs(ui, repo, revs, displayer, getrenamed): + revdag = graphmod.dagwalker(repo, revs) + displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed) + +def displayrevs(ui, repo, revs, displayer, getrenamed): + for rev in revs: + ctx = repo[rev] + copies = None + if getrenamed is not None and rev: + copies = [] + for fn in ctx.files(): + rename = getrenamed(fn, rev) + if rename: + copies.append((fn, rename[0])) + displayer.show(ctx, copies=copies) + displayer.flush(ctx) + displayer.close() + +def checkunsupportedgraphflags(pats, opts): + for op in ["newest_first"]: + if op in opts and opts[op]: + raise error.Abort(_("-G/--graph option is incompatible with --%s") + % op.replace("_", "-")) + +def graphrevs(repo, nodes, opts): + limit = getlimit(opts) + nodes.reverse() + if limit is not None: + nodes = nodes[:limit] + return graphmod.nodes(repo, nodes) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/logexchange.py --- a/mercurial/logexchange.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/logexchange.py Sat Feb 24 17:49:10 2018 -0600 @@ -11,6 +11,7 @@ from .node import hex from . import ( + util, vfs as vfsmod, ) @@ -94,6 +95,30 @@ finally: wlock.release() +def activepath(repo, remote): + """returns remote path""" + local = None + # is the remote a local peer + local = remote.local() + + # determine the remote path from the repo, if possible; else just + # use the string given to us + rpath = remote + if local: + rpath = remote._repo.root + elif not isinstance(remote, str): + rpath = remote._url + + # represent the remotepath with user defined path name if exists + for path, url in repo.ui.configitems('paths'): + # remove auth info from user defined url + url = util.removeauth(url) + if url == rpath: + rpath = path + break + + return rpath + def pullremotenames(localrepo, remoterepo): """ pulls bookmarks and branches information of the remote repo during a @@ -101,7 +126,7 @@ localrepo is our local repository remoterepo is the peer instance """ - remotepath = remoterepo.url() + remotepath = activepath(localrepo, remoterepo) bookmarks = remoterepo.listkeys('bookmarks') # on a push, we don't want to keep obsolete heads since # they won't show up as heads on the next pull, so we diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/lsprof.py --- a/mercurial/lsprof.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/lsprof.py Sat Feb 24 17:49:10 2018 -0600 @@ -27,7 +27,7 @@ def __init__(self, data): self.data = data - def sort(self, crit="inlinetime"): + def sort(self, crit=r"inlinetime"): """XXX docstring""" # profiler_entries isn't defined when running under PyPy. if profiler_entry: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/mail.py --- a/mercurial/mail.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/mail.py Sat Feb 24 17:49:10 2018 -0600 @@ -20,6 +20,7 @@ from . import ( encoding, error, + pycompat, sslutil, util, ) @@ -186,7 +187,7 @@ def codec2iana(cs): '''''' - cs = email.charset.Charset(cs).input_charset.lower() + cs = pycompat.sysbytes(email.charset.Charset(cs).input_charset.lower()) # "latin1" normalizes to "iso8859-1", standard calls for "iso-8859-1" if cs.startswith("iso") and not cs.startswith("iso-"): @@ -205,7 +206,7 @@ return mimetextqp(s, subtype, 'us-ascii') for charset in cs: try: - s.decode(charset) + s.decode(pycompat.sysstr(charset)) return mimetextqp(s, subtype, codec2iana(charset)) except UnicodeDecodeError: pass @@ -218,7 +219,7 @@ ''' cs = email.charset.Charset(charset) msg = email.message.Message() - msg.set_type('text/' + subtype) + msg.set_type(pycompat.sysstr('text/' + subtype)) for line in body.splitlines(): if len(line) > 950: @@ -287,13 +288,13 @@ addr = addr.encode('ascii') except UnicodeDecodeError: raise error.Abort(_('invalid local address: %s') % addr) - return email.Utils.formataddr((name, addr)) + return email.utils.formataddr((name, addr)) def addressencode(ui, address, charsets=None, display=False): '''Turns address into RFC-2047 compliant header.''' if display or not address: return address or '' - name, addr = email.Utils.parseaddr(address) + name, addr = email.utils.parseaddr(address) return _addressencode(ui, name, addr, charsets) def addrlistencode(ui, addrs, charsets=None, display=False): @@ -304,7 +305,7 @@ return [a.strip() for a in addrs if a.strip()] result = [] - for name, addr in email.Utils.getaddresses(addrs): + for name, addr in email.utils.getaddresses(addrs): if name or addr: result.append(_addressencode(ui, name, addr, charsets)) return result diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/manifest.py --- a/mercurial/manifest.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/manifest.py Sat Feb 24 17:49:10 2018 -0600 @@ -9,7 +9,6 @@ import heapq import itertools -import os import struct from .i18n import _ @@ -28,7 +27,7 @@ parsers = policy.importmod(r'parsers') propertycache = util.propertycache -def _parsev1(data): +def _parse(data): # This method does a little bit of excessive-looking # precondition checking. This is so that the behavior of this # class exactly matches its C counterpart to try and help @@ -47,43 +46,7 @@ else: yield f, bin(n), '' -def _parsev2(data): - metadataend = data.find('\n') - # Just ignore metadata for now - pos = metadataend + 1 - prevf = '' - while pos < len(data): - end = data.find('\n', pos + 1) # +1 to skip stem length byte - if end == -1: - raise ValueError('Manifest ended with incomplete file entry.') - stemlen = ord(data[pos:pos + 1]) - items = data[pos + 1:end].split('\0') - f = prevf[:stemlen] + items[0] - if prevf > f: - raise ValueError('Manifest entries not in sorted order.') - fl = items[1] - # Just ignore metadata (items[2:] for now) - n = data[end + 1:end + 21] - yield f, n, fl - pos = end + 22 - prevf = f - -def _parse(data): - """Generates (path, node, flags) tuples from a manifest text""" - if data.startswith('\0'): - return iter(_parsev2(data)) - else: - return iter(_parsev1(data)) - -def _text(it, usemanifestv2): - """Given an iterator over (path, node, flags) tuples, returns a manifest - text""" - if usemanifestv2: - return _textv2(it) - else: - return _textv1(it) - -def _textv1(it): +def _text(it): files = [] lines = [] _hex = revlog.hex @@ -96,19 +59,6 @@ _checkforbidden(files) return ''.join(lines) -def _textv2(it): - files = [] - lines = ['\0\n'] - prevf = '' - for f, n, fl in it: - files.append(f) - stem = os.path.commonprefix([prevf, f]) - stemlen = min(len(stem), 255) - lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n)) - prevf = f - _checkforbidden(files) - return ''.join(lines) - class lazymanifestiter(object): def __init__(self, lm): self.pos = 0 @@ -414,13 +364,7 @@ class manifestdict(object): def __init__(self, data=''): - if data.startswith('\0'): - #_lazymanifest can not parse v2 - self._lm = _lazymanifest('') - for f, n, fl in _parsev2(data): - self._lm[f] = n, fl - else: - self._lm = _lazymanifest(data) + self._lm = _lazymanifest(data) def __getitem__(self, key): return self._lm[key][0] @@ -589,12 +533,9 @@ def iterentries(self): return self._lm.iterentries() - def text(self, usemanifestv2=False): - if usemanifestv2: - return _textv2(self._lm.iterentries()) - else: - # use (probably) native version for v1 - return self._lm.text() + def text(self): + # most likely uses native version + return self._lm.text() def fastdelta(self, base, changes): """Given a base manifest text as a bytearray and a list of changes @@ -755,6 +696,12 @@ size += m.__len__() return size + def __nonzero__(self): + # Faster than "__len() != 0" since it avoids loading sub-manifests + return not self._isempty() + + __bool__ = __nonzero__ + def _isempty(self): self._load() # for consistency; already loaded by all callers return (not self._files and (not self._dirs or @@ -954,7 +901,7 @@ else: files.update(m1.iterkeys()) - for fn in t1._files.iterkeys(): + for fn in t1._files: if fn not in t2._files: files.add(t1._subpath(fn)) @@ -1013,7 +960,7 @@ # yield this dir's files and walk its submanifests self._load() - for p in sorted(self._dirs.keys() + self._files.keys()): + for p in sorted(list(self._dirs) + list(self._files)): if p in self._files: fullp = self._subpath(p) if match(fullp): @@ -1132,12 +1079,12 @@ if fl: self._flags[f] = fl - def text(self, usemanifestv2=False): + def text(self): """Get the full data of this manifest as a bytestring.""" self._load() - return _text(self.iterentries(), usemanifestv2) + return _text(self.iterentries()) - def dirtext(self, usemanifestv2=False): + def dirtext(self): """Get the full data of this directory as a bytestring. Make sure that any submanifests have been written first, so their nodeids are correct. """ @@ -1145,7 +1092,7 @@ flags = self.flags dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs] files = [(f, self._files[f], flags(f)) for f in self._files] - return _text(sorted(dirs + files), usemanifestv2) + return _text(sorted(dirs + files)) def read(self, gettext, readsubtree): def _load_for_read(s): @@ -1202,15 +1149,12 @@ # stacks of commits, the number can go up, hence the config knob below. cachesize = 4 optiontreemanifest = False - usemanifestv2 = False opts = getattr(opener, 'options', None) if opts is not None: cachesize = opts.get('manifestcachesize', cachesize) optiontreemanifest = opts.get('treemanifest', False) - usemanifestv2 = opts.get('manifestv2', usemanifestv2) self._treeondisk = optiontreemanifest or treemanifest - self._usemanifestv2 = usemanifestv2 self._fulltextcache = util.lrucachedict(cachesize) @@ -1245,19 +1189,18 @@ self._fulltextcache.clear() self._dirlogcache = {'': self} - def dirlog(self, dir): - if dir: + def dirlog(self, d): + if d: assert self._treeondisk - if dir not in self._dirlogcache: - mfrevlog = manifestrevlog(self.opener, dir, + if d not in self._dirlogcache: + mfrevlog = manifestrevlog(self.opener, d, self._dirlogcache, treemanifest=self._treeondisk) - self._dirlogcache[dir] = mfrevlog - return self._dirlogcache[dir] + self._dirlogcache[d] = mfrevlog + return self._dirlogcache[d] def add(self, m, transaction, link, p1, p2, added, removed, readtree=None): - if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta') - and not self._usemanifestv2): + if p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta'): # If our first parent is in the manifest cache, we can # compute a delta here using properties we know about the # manifest up-front, which may save time later for the @@ -1284,7 +1227,7 @@ n = self._addtree(m, transaction, link, m1, m2, readtree) arraytext = None else: - text = m.text(self._usemanifestv2) + text = m.text() n = self.addrevision(text, transaction, link, p1, p2) arraytext = bytearray(text) @@ -1303,13 +1246,13 @@ sublog.add(subm, transaction, link, subp1, subp2, None, None, readtree=readtree) m.writesubtrees(m1, m2, writesubtree) - text = m.dirtext(self._usemanifestv2) + text = m.dirtext() n = None if self._dir != '': # Double-check whether contents are unchanged to one parent - if text == m1.dirtext(self._usemanifestv2): + if text == m1.dirtext(): n = m1.node() - elif text == m2.dirtext(self._usemanifestv2): + elif text == m2.dirtext(): n = m2.node() if not n: @@ -1487,19 +1430,6 @@ Changing the value of `shallow` has no effect on flat manifests. ''' revlog = self._revlog() - if revlog._usemanifestv2: - # Need to perform a slow delta - r0 = revlog.deltaparent(revlog.rev(self._node)) - m0 = self._manifestlog[revlog.node(r0)].read() - m1 = self.read() - md = manifestdict() - for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems(): - if n1: - md[f] = n1 - if fl1: - md.setflag(f, fl1) - return md - r = revlog.rev(self._node) d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r)) return manifestdict(d) @@ -1602,7 +1532,7 @@ its 't' flag. ''' revlog = self._revlog() - if shallow and not revlog._usemanifestv2: + if shallow: r = revlog.rev(self._node) d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r)) return manifestdict(d) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/match.py --- a/mercurial/match.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/match.py Sat Feb 24 17:49:10 2018 -0600 @@ -13,6 +13,7 @@ from .i18n import _ from . import ( + encoding, error, pathutil, util, @@ -345,7 +346,7 @@ return 'all' def __repr__(self): - return '' + return r'' class nevermatcher(basematcher): '''Matches nothing.''' @@ -368,7 +369,7 @@ return False def __repr__(self): - return '' + return r'' class patternmatcher(basematcher): @@ -397,6 +398,7 @@ def prefix(self): return self._prefix + @encoding.strmethod def __repr__(self): return ('' % self._pats) @@ -424,6 +426,7 @@ any(parentdir in self._roots for parentdir in util.finddirs(dir))) + @encoding.strmethod def __repr__(self): return ('' % self._pats) @@ -452,6 +455,7 @@ def isexact(self): return True + @encoding.strmethod def __repr__(self): return ('' % self._files) @@ -492,6 +496,7 @@ def isexact(self): return self._m1.isexact() + @encoding.strmethod def __repr__(self): return ('' % (self._m1, self._m2)) @@ -558,6 +563,7 @@ def isexact(self): return self._m1.isexact() or self._m2.isexact() + @encoding.strmethod def __repr__(self): return ('' % (self._m1, self._m2)) @@ -638,6 +644,7 @@ def prefix(self): return self._matcher.prefix() and not self._always + @encoding.strmethod def __repr__(self): return ('' % (self._path, self._matcher)) @@ -671,6 +678,7 @@ r |= v return r + @encoding.strmethod def __repr__(self): return ('' % self._matchers) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/mdiff.py --- a/mercurial/mdiff.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/mdiff.py Sat Feb 24 17:49:10 2018 -0600 @@ -19,6 +19,8 @@ util, ) +_missing_newline_marker = "\\ No newline at end of file\n" + bdiff = policy.importmod(r'bdiff') mpatch = policy.importmod(r'mpatch') @@ -27,16 +29,7 @@ patches = mpatch.patches patchedsize = mpatch.patchedsize textdiff = bdiff.bdiff - -def splitnewlines(text): - '''like str.splitlines, but only split on newlines.''' - lines = [l + '\n' for l in text.split('\n')] - if lines: - if lines[-1] == '\n': - lines.pop() - else: - lines[-1] = lines[-1][:-1] - return lines +splitnewlines = bdiff.splitnewlines class diffopts(object): '''context is the number of context lines @@ -234,13 +227,15 @@ yield s, type yield s1, '=' -def unidiff(a, ad, b, bd, fn1, fn2, opts=defaultopts): +def unidiff(a, ad, b, bd, fn1, fn2, binary, opts=defaultopts): """Return a unified diff as a (headers, hunks) tuple. If the diff is not null, `headers` is a list with unified diff header lines "--- " and "+++ " and `hunks` is a generator yielding (hunkrange, hunklines) coming from _unidiff(). Otherwise, `headers` and `hunks` are empty. + + Set binary=True if either a or b should be taken as a binary file. """ def datetag(date, fn=None): if not opts.git and not opts.nodates: @@ -264,18 +259,13 @@ fn1 = util.pconvert(fn1) fn2 = util.pconvert(fn2) - def checknonewline(lines): - for text in lines: - if text[-1:] != '\n': - text += "\n\ No newline at end of file\n" - yield text - - if not opts.text and (util.binary(a) or util.binary(b)): + if binary: if a and b and len(a) == len(b) and a == b: return sentinel headerlines = [] hunks = (None, ['Binary file %s has changed\n' % fn1]), elif not a: + without_newline = not b.endswith('\n') b = splitnewlines(b) if a is None: l1 = '--- /dev/null%s' % datetag(epoch) @@ -286,8 +276,12 @@ size = len(b) hunkrange = (0, 0, 1, size) hunklines = ["@@ -0,0 +1,%d @@\n" % size] + ["+" + e for e in b] - hunks = (hunkrange, checknonewline(hunklines)), + if without_newline: + hunklines[-1] += '\n' + hunklines.append(_missing_newline_marker) + hunks = (hunkrange, hunklines), elif not b: + without_newline = not a.endswith('\n') a = splitnewlines(a) l1 = "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1)) if b is None: @@ -298,24 +292,19 @@ size = len(a) hunkrange = (1, size, 0, 0) hunklines = ["@@ -1,%d +0,0 @@\n" % size] + ["-" + e for e in a] - hunks = (hunkrange, checknonewline(hunklines)), + if without_newline: + hunklines[-1] += '\n' + hunklines.append(_missing_newline_marker) + hunks = (hunkrange, hunklines), else: - diffhunks = _unidiff(a, b, opts=opts) - try: - hunkrange, hunklines = next(diffhunks) - except StopIteration: + hunks = _unidiff(a, b, opts=opts) + if not next(hunks): return sentinel headerlines = [ "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1)), "+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2)), ] - def rewindhunks(): - yield hunkrange, checknonewline(hunklines) - for hr, hl in diffhunks: - yield hr, checknonewline(hl) - - hunks = rewindhunks() return headerlines, hunks @@ -327,6 +316,8 @@ form the '@@ -s1,l1 +s2,l2 @@' header and `hunklines` is a list of lines of the hunk combining said header followed by line additions and deletions. + + The hunks are prefixed with a bool. """ l1 = splitnewlines(t1) l2 = splitnewlines(t2) @@ -377,6 +368,26 @@ + delta + [' ' + l1[x] for x in xrange(a2, aend)] ) + # If either file ends without a newline and the last line of + # that file is part of a hunk, a marker is printed. If the + # last line of both files is identical and neither ends in + # a newline, print only one marker. That's the only case in + # which the hunk can end in a shared line without a newline. + skip = False + if not t1.endswith('\n') and astart + alen == len(l1) + 1: + for i in xrange(len(hunklines) - 1, -1, -1): + if hunklines[i].startswith(('-', ' ')): + if hunklines[i].startswith(' '): + skip = True + hunklines[i] += '\n' + hunklines.insert(i + 1, _missing_newline_marker) + break + if not skip and not t2.endswith('\n') and bstart + blen == len(l2) + 1: + for i in xrange(len(hunklines) - 1, -1, -1): + if hunklines[i].startswith('+'): + hunklines[i] += '\n' + hunklines.insert(i + 1, _missing_newline_marker) + break yield hunkrange, hunklines # bdiff.blocks gives us the matching sequences in the files. The loop @@ -385,6 +396,7 @@ # hunk = None ignoredlines = 0 + has_hunks = False for s, stype in allblocks(t1, t2, opts, l1, l2): a1, a2, b1, b2 = s if stype != '!': @@ -411,6 +423,9 @@ astart = hunk[1] bstart = hunk[3] else: + if not has_hunks: + has_hunks = True + yield True for x in yieldhunk(hunk): yield x if prev: @@ -427,17 +442,22 @@ delta[len(delta):] = ['+' + x for x in new] if hunk: + if not has_hunks: + has_hunks = True + yield True for x in yieldhunk(hunk): yield x + elif not has_hunks: + yield False def b85diff(to, tn): '''print base85-encoded binary diff''' def fmtline(line): l = len(line) if l <= 26: - l = chr(ord('A') + l - 1) + l = pycompat.bytechr(ord('A') + l - 1) else: - l = chr(l - 26 + ord('a') - 1) + l = pycompat.bytechr(l - 26 + ord('a') - 1) return '%c%s\n' % (l, util.b85encode(line, True)) def chunk(text, csize=52): diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/merge.py --- a/mercurial/merge.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/merge.py Sat Feb 24 17:49:10 2018 -0600 @@ -25,13 +25,12 @@ from . import ( copies, error, - extensions, filemerge, match as matchmod, obsutil, pycompat, scmutil, - subrepo, + subrepoutil, util, worker, ) @@ -974,14 +973,14 @@ # Rename all local conflicting files that have not been deleted. for p in localconflicts: if p not in deletedfiles: - ctxname = str(wctx).rstrip('+') + ctxname = bytes(wctx).rstrip('+') pnew = util.safename(p, ctxname, wctx, set(actions.keys())) actions[pnew] = ('pr', (p,), "local path conflict") actions[p] = ('p', (pnew, 'l'), "path conflict") if remoteconflicts: # Check if all files in the conflicting directories have been removed. - ctxname = str(mctx).rstrip('+') + ctxname = bytes(mctx).rstrip('+') for f, p in _filesindirs(repo, mf, remoteconflicts): if f not in deletedfiles: m, args, msg = actions[p] @@ -1186,8 +1185,9 @@ def _resolvetrivial(repo, wctx, mctx, ancestor, actions): """Resolves false conflicts where the nodeid changed but the content remained the same.""" - - for f, (m, args, msg) in actions.items(): + # We force a copy of actions.items() because we're going to mutate + # actions as we resolve trivial conflicts. + for f, (m, args, msg) in list(actions.items()): if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]): # local did change but ended up with same content actions[f] = 'r', None, "prompt same" @@ -1386,6 +1386,16 @@ if i > 0: yield i, f +def _prefetchfiles(repo, ctx, actions): + """Invoke ``scmutil.fileprefetchhooks()`` for the files relevant to the dict + of merge actions. ``ctx`` is the context being merged in.""" + + # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they + # don't touch the context to be merged in. 'cd' is skipped, because + # changed/deleted never resolves to something from the remote side. + oplist = [actions[a] for a in 'g dc dg m'.split()] + prefetch = scmutil.fileprefetchhooks + prefetch(repo, ctx, [f for sublist in oplist for f, args, msg in sublist]) def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None): """apply the merge action list to the working directory @@ -1397,6 +1407,8 @@ describes how many files were affected by the update. """ + _prefetchfiles(repo, mctx, actions) + updated, merged, removed = 0, 0, 0 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels) moves = [] @@ -1445,7 +1457,7 @@ z = 0 if [a for a in actions['r'] if a[0] == '.hgsubstate']: - subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels) + subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels) # record path conflicts for f, args, msg in actions['p']: @@ -1495,7 +1507,7 @@ updated = len(actions['g']) if [a for a in actions['g'] if a[0] == '.hgsubstate']: - subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels) + subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels) # forget (manifest only, just log it) (must come first) for f, args, msg in actions['f']: @@ -1583,8 +1595,8 @@ z += 1 progress(_updating, z, item=f, total=numupdates, unit=_files) if f == '.hgsubstate': # subrepo states need updating - subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), - overwrite, labels) + subrepoutil.submerge(repo, wctx, mctx, wctx.ancestor(mctx), + overwrite, labels) continue wctx[f].audit() complete, r = ms.preresolve(f, wctx) @@ -1835,7 +1847,7 @@ else: pas = [p1.ancestor(p2, warn=branchmerge)] - fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2) + fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2) ### check phase if not overwrite: @@ -1913,7 +1925,7 @@ # Prompt and create actions. Most of this is in the resolve phase # already, but we can't handle .hgsubstate in filemerge or - # subrepo.submerge yet so we have to keep prompting for it. + # subrepoutil.submerge yet so we have to keep prompting for it. if '.hgsubstate' in actionbyfile: f = '.hgsubstate' m, args, msg = actionbyfile[f] @@ -1992,6 +2004,8 @@ fsmonitorthreshold = repo.ui.configint('fsmonitor', 'warn_update_file_count') try: + # avoid cycle: extensions -> cmdutil -> merge + from . import extensions extensions.find('fsmonitor') fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off' # We intentionally don't look at whether fsmonitor has disabled diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/narrowspec.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/narrowspec.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,204 @@ +# narrowspec.py - methods for working with a narrow view of a repository +# +# 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. + +from __future__ import absolute_import + +import errno + +from .i18n import _ +from . import ( + error, + hg, + match as matchmod, + util, +) + +FILENAME = 'narrowspec' + +def _parsestoredpatterns(text): + """Parses the narrowspec format that's stored on disk.""" + patlist = None + includepats = [] + excludepats = [] + for l in text.splitlines(): + if l == '[includes]': + if patlist is None: + patlist = includepats + else: + raise error.Abort(_('narrowspec includes section must appear ' + 'at most once, before excludes')) + elif l == '[excludes]': + if patlist is not excludepats: + patlist = excludepats + else: + raise error.Abort(_('narrowspec excludes section must appear ' + 'at most once')) + else: + patlist.append(l) + + return set(includepats), set(excludepats) + +def parseserverpatterns(text): + """Parses the narrowspec format that's returned by the server.""" + includepats = set() + excludepats = set() + + # We get one entry per line, in the format " ". + # It's OK for value to contain other spaces. + for kp in (l.split(' ', 1) for l in text.splitlines()): + if len(kp) != 2: + raise error.Abort(_('Invalid narrowspec pattern line: "%s"') % kp) + key = kp[0] + pat = kp[1] + if key == 'include': + includepats.add(pat) + elif key == 'exclude': + excludepats.add(pat) + else: + raise error.Abort(_('Invalid key "%s" in server response') % key) + + return includepats, excludepats + +def normalizesplitpattern(kind, pat): + """Returns the normalized version of a pattern and kind. + + Returns a tuple with the normalized kind and normalized pattern. + """ + pat = pat.rstrip('/') + _validatepattern(pat) + return kind, pat + +def _numlines(s): + """Returns the number of lines in s, including ending empty lines.""" + # We use splitlines because it is Unicode-friendly and thus Python 3 + # compatible. However, it does not count empty lines at the end, so trick + # it by adding a character at the end. + return len((s + 'x').splitlines()) + +def _validatepattern(pat): + """Validates the pattern and aborts if it is invalid. + + Patterns are stored in the narrowspec as newline-separated + POSIX-style bytestring paths. There's no escaping. + """ + + # We use newlines as separators in the narrowspec file, so don't allow them + # in patterns. + if _numlines(pat) > 1: + raise error.Abort(_('newlines are not allowed in narrowspec paths')) + + components = pat.split('/') + if '.' in components or '..' in components: + raise error.Abort(_('"." and ".." are not allowed in narrowspec paths')) + +def normalizepattern(pattern, defaultkind='path'): + """Returns the normalized version of a text-format pattern. + + If the pattern has no kind, the default will be added. + """ + kind, pat = matchmod._patsplit(pattern, defaultkind) + return '%s:%s' % normalizesplitpattern(kind, pat) + +def parsepatterns(pats): + """Parses a list of patterns into a typed pattern set.""" + return set(normalizepattern(p) for p in pats) + +def format(includes, excludes): + output = '[includes]\n' + for i in sorted(includes - excludes): + output += i + '\n' + output += '[excludes]\n' + for e in sorted(excludes): + output += e + '\n' + return output + +def match(root, include=None, exclude=None): + if not include: + # Passing empty include and empty exclude to matchmod.match() + # gives a matcher that matches everything, so explicitly use + # the nevermatcher. + return matchmod.never(root, '') + return matchmod.match(root, '', [], include=include or [], + exclude=exclude or []) + +def needsexpansion(includes): + return [i for i in includes if i.startswith('include:')] + +def load(repo): + if repo.shared(): + repo = hg.sharedreposource(repo) + try: + spec = repo.vfs.read(FILENAME) + except IOError as e: + # Treat "narrowspec does not exist" the same as "narrowspec file exists + # and is empty". + if e.errno == errno.ENOENT: + # Without this the next call to load will use the cached + # non-existence of the file, which can cause some odd issues. + repo.invalidate(clearfilecache=True) + return set(), set() + raise + return _parsestoredpatterns(spec) + +def save(repo, includepats, excludepats): + spec = format(includepats, excludepats) + if repo.shared(): + repo = hg.sharedreposource(repo) + repo.vfs.write(FILENAME, spec) + +def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes): + r""" Restricts the patterns according to repo settings, + results in a logical AND operation + + :param req_includes: requested includes + :param req_excludes: requested excludes + :param repo_includes: repo includes + :param repo_excludes: repo excludes + :return: include patterns, exclude patterns, and invalid include patterns. + + >>> restrictpatterns({'f1','f2'}, {}, ['f1'], []) + (set(['f1']), {}, []) + >>> restrictpatterns({'f1'}, {}, ['f1','f2'], []) + (set(['f1']), {}, []) + >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], []) + (set(['f1/fc1']), {}, []) + >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], []) + ([], set(['path:.']), []) + >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], []) + (set(['f2/fc2']), {}, []) + >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], []) + ([], set(['path:.']), []) + >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], []) + (set(['f1/$non_exitent_var']), {}, []) + """ + res_excludes = set(req_excludes) + res_excludes.update(repo_excludes) + invalid_includes = [] + if not req_includes: + res_includes = set(repo_includes) + elif 'path:.' not in repo_includes: + res_includes = [] + for req_include in req_includes: + req_include = util.expandpath(util.normpath(req_include)) + if req_include in repo_includes: + res_includes.append(req_include) + continue + valid = False + for repo_include in repo_includes: + if req_include.startswith(repo_include + '/'): + valid = True + res_includes.append(req_include) + break + if not valid: + invalid_includes.append(req_include) + if len(res_includes) == 0: + res_excludes = {'path:.'} + else: + res_includes = set(res_includes) + else: + res_includes = set(req_includes) + return res_includes, res_excludes, invalid_includes diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/node.py --- a/mercurial/node.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/node.py Sat Feb 24 17:49:10 2018 -0600 @@ -11,7 +11,14 @@ # This ugly style has a noticeable effect in manifest parsing hex = binascii.hexlify -bin = binascii.unhexlify +# Adapt to Python 3 API changes. If this ends up showing up in +# profiles, we can use this version only on Python 3, and forward +# binascii.unhexlify like we used to on Python 2. +def bin(s): + try: + return binascii.unhexlify(s) + except binascii.Error as e: + raise TypeError(e) nullrev = -1 nullid = b"\0" * 20 diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/obsolete.py --- a/mercurial/obsolete.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/obsolete.py Sat Feb 24 17:49:10 2018 -0600 @@ -506,13 +506,6 @@ for mark in markers: successors.setdefault(mark[0], set()).add(mark) -def _addprecursors(*args, **kwargs): - msg = ("'obsolete._addprecursors' is deprecated, " - "use 'obsolete._addpredecessors'") - util.nouideprecwarn(msg, '4.4') - - return _addpredecessors(*args, **kwargs) - @util.nogc def _addpredecessors(predecessors, markers): for mark in markers: @@ -570,7 +563,7 @@ return len(self._all) def __nonzero__(self): - if not self._cached('_all'): + if not self._cached(r'_all'): try: return self.svfs.stat('obsstore').st_size > 1 except OSError as inst: @@ -700,14 +693,6 @@ _addsuccessors(successors, self._all) return successors - @property - def precursors(self): - msg = ("'obsstore.precursors' is deprecated, " - "use 'obsstore.predecessors'") - util.nouideprecwarn(msg, '4.4') - - return self.predecessors - @propertycache def predecessors(self): predecessors = {} @@ -727,11 +712,11 @@ markers = list(markers) # to allow repeated iteration self._data = self._data + rawdata self._all.extend(markers) - if self._cached('successors'): + if self._cached(r'successors'): _addsuccessors(self.successors, markers) - if self._cached('predecessors'): + if self._cached(r'predecessors'): _addpredecessors(self.predecessors, markers) - if self._cached('children'): + if self._cached(r'children'): _addchildren(self.children, markers) _checkinvalidmarkers(markers) @@ -843,42 +828,6 @@ repo.invalidatevolatilesets() return True -# keep compatibility for the 4.3 cycle -def allprecursors(obsstore, nodes, ignoreflags=0): - movemsg = 'obsolete.allprecursors moved to obsutil.allprecursors' - util.nouideprecwarn(movemsg, '4.3') - return obsutil.allprecursors(obsstore, nodes, ignoreflags) - -def allsuccessors(obsstore, nodes, ignoreflags=0): - movemsg = 'obsolete.allsuccessors moved to obsutil.allsuccessors' - util.nouideprecwarn(movemsg, '4.3') - return obsutil.allsuccessors(obsstore, nodes, ignoreflags) - -def marker(repo, data): - movemsg = 'obsolete.marker moved to obsutil.marker' - repo.ui.deprecwarn(movemsg, '4.3') - return obsutil.marker(repo, data) - -def getmarkers(repo, nodes=None, exclusive=False): - movemsg = 'obsolete.getmarkers moved to obsutil.getmarkers' - repo.ui.deprecwarn(movemsg, '4.3') - return obsutil.getmarkers(repo, nodes=nodes, exclusive=exclusive) - -def exclusivemarkers(repo, nodes): - movemsg = 'obsolete.exclusivemarkers moved to obsutil.exclusivemarkers' - repo.ui.deprecwarn(movemsg, '4.3') - return obsutil.exclusivemarkers(repo, nodes) - -def foreground(repo, nodes): - movemsg = 'obsolete.foreground moved to obsutil.foreground' - repo.ui.deprecwarn(movemsg, '4.3') - return obsutil.foreground(repo, nodes) - -def successorssets(repo, initialnode, cache=None): - movemsg = 'obsolete.successorssets moved to obsutil.successorssets' - repo.ui.deprecwarn(movemsg, '4.3') - return obsutil.successorssets(repo, initialnode, cache=cache) - # mapping of 'set-name' -> cachefuncs = {} def cachefor(name): @@ -933,14 +882,6 @@ obs = set(r for r in notpublic if isobs(getnode(r))) return obs -@cachefor('unstable') -def _computeunstableset(repo): - msg = ("'unstable' volatile set is deprecated, " - "use 'orphan'") - repo.ui.deprecwarn(msg, '4.4') - - return _computeorphanset(repo) - @cachefor('orphan') def _computeorphanset(repo): """the set of non obsolete revisions with obsolete parents""" @@ -969,14 +910,6 @@ """the set of obsolete parents without non obsolete descendants""" return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended') -@cachefor('bumped') -def _computebumpedset(repo): - msg = ("'bumped' volatile set is deprecated, " - "use 'phasedivergent'") - repo.ui.deprecwarn(msg, '4.4') - - return _computephasedivergentset(repo) - @cachefor('phasedivergent') def _computephasedivergentset(repo): """the set of revs trying to obsolete public revisions""" @@ -1000,14 +933,6 @@ break # Next draft! return bumped -@cachefor('divergent') -def _computedivergentset(repo): - msg = ("'divergent' volatile set is deprecated, " - "use 'contentdivergent'") - repo.ui.deprecwarn(msg, '4.4') - - return _computecontentdivergentset(repo) - @cachefor('contentdivergent') def _computecontentdivergentset(repo): """the set of rev that compete to be the final successors of some revision. diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/obsutil.py --- a/mercurial/obsutil.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/obsutil.py Sat Feb 24 17:49:10 2018 -0600 @@ -33,12 +33,6 @@ return False return self._data == other._data - def precnode(self): - msg = ("'marker.precnode' is deprecated, " - "use 'marker.prednode'") - util.nouideprecwarn(msg, '4.4') - return self.prednode() - def prednode(self): """Predecessor changeset node identifier""" return self._data[0] @@ -106,15 +100,6 @@ else: stack.append(precnodeid) -def allprecursors(*args, **kwargs): - """ (DEPRECATED) - """ - msg = ("'obsutil.allprecursors' is deprecated, " - "use 'obsutil.allpredecessors'") - util.nouideprecwarn(msg, '4.4') - - return allpredecessors(*args, **kwargs) - def allpredecessors(obsstore, nodes, ignoreflags=0): """Yield node for every precursors of . @@ -421,10 +406,10 @@ # Check if other meta has changed changeextra = changectx.extra().items() - ctxmeta = filter(metanotblacklisted, changeextra) + ctxmeta = list(filter(metanotblacklisted, changeextra)) sourceextra = source.extra().items() - srcmeta = filter(metanotblacklisted, sourceextra) + srcmeta = list(filter(metanotblacklisted, sourceextra)) if ctxmeta != srcmeta: effects |= METACHANGED diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/patch.py --- a/mercurial/patch.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/patch.py Sat Feb 24 17:49:10 2018 -0600 @@ -12,7 +12,6 @@ import copy import difflib import email -import email.parser as emailparser import errno import hashlib import os @@ -109,7 +108,7 @@ cur.append(line) c = chunk(cur) - m = emailparser.Parser().parse(c) + m = pycompat.emailparser().parse(c) if not m.is_multipart(): yield msgfp(m) else: @@ -216,9 +215,9 @@ data = {} fd, tmpname = tempfile.mkstemp(prefix='hg-patch-') - tmpfp = os.fdopen(fd, pycompat.sysstr('w')) + tmpfp = os.fdopen(fd, pycompat.sysstr('wb')) try: - msg = emailparser.Parser().parse(fileobj) + msg = pycompat.emailparser().parse(fileobj) subject = msg['Subject'] and mail.headdecode(msg['Subject']) data['user'] = msg['From'] and mail.headdecode(msg['From']) @@ -242,7 +241,7 @@ ok_types = ('text/plain', 'text/x-diff', 'text/x-patch') message = '' for part in msg.walk(): - content_type = part.get_content_type() + content_type = pycompat.bytestr(part.get_content_type()) ui.debug('Content-Type: %s\n' % content_type) if content_type not in ok_types: continue @@ -1451,7 +1450,7 @@ dec = [] line = getline(lr, self.hunk) while len(line) > 1: - l = line[0] + l = line[0:1] if l <= 'Z' and l >= 'A': l = ord(l) - ord('A') + 1 else: @@ -1460,7 +1459,7 @@ dec.append(util.b85decode(line[1:])[:l]) except ValueError as e: raise PatchError(_('could not decode "%s" binary patch: %s') - % (self._fname, str(e))) + % (self._fname, util.forcebytestr(e))) line = getline(lr, self.hunk) text = zlib.decompress(''.join(dec)) if len(text) != size: @@ -2342,7 +2341,7 @@ if hunksfilterfn is not None: # If the file has been removed, fctx2 is None; but this should # not occur here since we catch removed files early in - # cmdutil.getloglinerangerevs() for 'hg log -L'. + # logcmdutil.getlinerangerevs() for 'hg log -L'. assert fctx2 is not None, \ 'fctx2 unexpectly None in diff hunks filtering' hunks = hunksfilterfn(fctx2, hunks) @@ -2698,8 +2697,10 @@ if opts.git or losedatafn: flag2 = ctx2.flags(f2) # if binary is True, output "summary" or "base85", but not "text diff" - binary = not opts.text and any(f.isbinary() - for f in [fctx1, fctx2] if f is not None) + if opts.text: + binary = False + else: + binary = any(f.isbinary() for f in [fctx1, fctx2] if f is not None) if losedatafn and not opts.git: if (binary or @@ -2789,7 +2790,8 @@ uheaders, hunks = mdiff.unidiff(content1, date1, content2, date2, - path1, path2, opts=opts) + path1, path2, + binary=binary, opts=opts) header.extend(uheaders) yield fctx1, fctx2, header, hunks diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/policy.py --- a/mercurial/policy.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/policy.py Sat Feb 24 17:49:10 2018 -0600 @@ -71,7 +71,7 @@ # keep in sync with "version" in C modules _cextversions = { (r'cext', r'base85'): 1, - (r'cext', r'bdiff'): 1, + (r'cext', r'bdiff'): 2, (r'cext', r'diffhelpers'): 1, (r'cext', r'mpatch'): 1, (r'cext', r'osutil'): 3, diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/posix.py --- a/mercurial/posix.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/posix.py Sat Feb 24 17:49:10 2018 -0600 @@ -113,7 +113,7 @@ if l: if not stat.S_ISLNK(s): # switch file to link - fp = open(f) + fp = open(f, 'rb') data = fp.read() fp.close() unlink(f) @@ -121,7 +121,7 @@ os.symlink(data, f) except OSError: # failed to make a link, rewrite file - fp = open(f, "w") + fp = open(f, "wb") fp.write(data) fp.close() # no chmod needed at this point @@ -130,7 +130,7 @@ # switch link to file data = os.readlink(f) unlink(f) - fp = open(f, "w") + fp = open(f, "wb") fp.write(data) fp.close() s = 0o666 & ~umask # avoid restatting for chmod diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/progress.py --- a/mercurial/progress.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/progress.py Sat Feb 24 17:49:10 2018 -0600 @@ -119,8 +119,7 @@ add = topic elif indicator == 'number': if total: - add = ('% ' + str(len(str(total))) + - 's/%s') % (pos, total) + add = b'%*d/%d' % (len(str(total)), pos, total) else: add = str(pos) elif indicator.startswith('item') and item: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/pure/base85.py --- a/mercurial/pure/base85.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/pure/base85.py Sat Feb 24 17:49:10 2018 -0600 @@ -9,8 +9,10 @@ import struct -_b85chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ - "abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~" +from .. import pycompat + +_b85chars = pycompat.bytestr("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef" + "ghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~") _b85chars2 = [(a + b) for a in _b85chars for b in _b85chars] _b85dec = {} @@ -51,6 +53,7 @@ out = [] for i in range(0, len(text), 5): chunk = text[i:i + 5] + chunk = pycompat.bytestr(chunk) acc = 0 for j, c in enumerate(chunk): try: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/pure/bdiff.py --- a/mercurial/pure/bdiff.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/pure/bdiff.py Sat Feb 24 17:49:10 2018 -0600 @@ -90,3 +90,13 @@ text = re.sub('[ \t\r]+', ' ', text) text = text.replace(' \n', '\n') return text + +def splitnewlines(text): + '''like str.splitlines, but only split on newlines.''' + lines = [l + '\n' for l in text.split('\n')] + if lines: + if lines[-1] == '\n': + lines.pop() + else: + lines[-1] = lines[-1][:-1] + return lines diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/pycompat.py --- a/mercurial/pycompat.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/pycompat.py Sat Feb 24 17:49:10 2018 -0600 @@ -11,6 +11,7 @@ from __future__ import absolute_import import getopt +import inspect import os import shlex import sys @@ -65,6 +66,7 @@ maplist = lambda *args: list(map(*args)) ziplist = lambda *args: list(zip(*args)) rawinput = input + getargspec = inspect.getfullargspec # TODO: .buffer might not exist if std streams were replaced; we'll need # a silly wrapper to make a bytes stream backed by a unicode one. @@ -83,12 +85,13 @@ sysargv = list(map(os.fsencode, sys.argv)) bytechr = struct.Struct('>B').pack + byterepr = b'%r'.__mod__ class bytestr(bytes): """A bytes which mostly acts as a Python 2 str >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1) - (b'', b'foo', b'ascii', b'1') + ('', 'foo', 'ascii', '1') >>> s = bytestr(b'foo') >>> assert s is bytestr(s) @@ -98,7 +101,7 @@ ... def __bytes__(self): ... return b'bytes' >>> bytestr(bytesable()) - b'bytes' + 'bytes' There's no implicit conversion from non-ascii str as its encoding is unknown: @@ -154,10 +157,19 @@ def __iter__(self): return iterbytestr(bytes.__iter__(self)) + def __repr__(self): + return bytes.__repr__(self)[1:] # drop b'' + def iterbytestr(s): """Iterate bytes as if it were a str object of Python 2""" return map(bytechr, s) + def maybebytestr(s): + """Promote bytes to bytestr""" + if isinstance(s, bytes): + return bytestr(s) + return s + def sysbytes(s): """Convert an internal str (e.g. keyword, __doc__) back to bytes @@ -249,21 +261,27 @@ return dic # TODO: handle shlex.shlex(). - def shlexsplit(s): + def shlexsplit(s, comments=False, posix=True): """ Takes bytes argument, convert it to str i.e. unicodes, pass that into shlex.split(), convert the returned value to bytes and return that for Python 3 compatibility as shelx.split() don't accept bytes on Python 3. """ - ret = shlex.split(s.decode('latin-1')) + ret = shlex.split(s.decode('latin-1'), comments, posix) return [a.encode('latin-1') for a in ret] + def emailparser(*args, **kwargs): + import email.parser + return email.parser.BytesParser(*args, **kwargs) + else: import cStringIO bytechr = chr + byterepr = repr bytestr = str iterbytestr = iter + maybebytestr = identity sysbytes = identity sysstr = identity strurl = identity @@ -316,6 +334,11 @@ maplist = map ziplist = zip rawinput = raw_input + getargspec = inspect.getargspec + + def emailparser(*args, **kwargs): + import email.parser + return email.parser.Parser(*args, **kwargs) isjython = sysplatform.startswith('java') diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/revlog.py --- a/mercurial/revlog.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/revlog.py Sat Feb 24 17:49:10 2018 -0600 @@ -13,8 +13,8 @@ from __future__ import absolute_import -import binascii import collections +import contextlib import errno import hashlib import heapq @@ -621,13 +621,12 @@ indexdata = '' self._initempty = True try: - f = self.opener(self.indexfile) - if (mmapindexthreshold is not None and - self.opener.fstat(f).st_size >= mmapindexthreshold): - indexdata = util.buffer(util.mmapread(f)) - else: - indexdata = f.read() - f.close() + with self._indexfp() as f: + if (mmapindexthreshold is not None and + self.opener.fstat(f).st_size >= mmapindexthreshold): + indexdata = util.buffer(util.mmapread(f)) + else: + indexdata = f.read() if len(indexdata) > 0: v = versionformat_unpack(indexdata[:4])[0] self._initempty = False @@ -682,6 +681,32 @@ def _compressor(self): return util.compengines[self._compengine].revlogcompressor() + def _indexfp(self, mode='r'): + """file object for the revlog's index file""" + args = {r'mode': mode} + if mode != 'r': + args[r'checkambig'] = self._checkambig + if mode == 'w': + args[r'atomictemp'] = True + return self.opener(self.indexfile, **args) + + def _datafp(self, mode='r'): + """file object for the revlog's data file""" + return self.opener(self.datafile, mode=mode) + + @contextlib.contextmanager + def _datareadfp(self, existingfp=None): + """file object suitable to read data""" + if existingfp is not None: + yield existingfp + else: + if self._inline: + func = self._indexfp + else: + func = self._datafp + with func() as fp: + yield fp + def tip(self): return self.node(len(self.index) - 2) def __contains__(self, rev): @@ -1404,7 +1429,7 @@ if maybewdir: raise error.WdirUnsupported return None - except (TypeError, binascii.Error): + except TypeError: pass def lookup(self, id): @@ -1490,15 +1515,6 @@ Returns a str or buffer of raw byte data. """ - if df is not None: - closehandle = False - else: - if self._inline: - df = self.opener(self.indexfile) - else: - df = self.opener(self.datafile) - closehandle = True - # Cache data both forward and backward around the requested # data, in a fixed size window. This helps speed up operations # involving reading the revlog backwards. @@ -1506,10 +1522,9 @@ realoffset = offset & ~(cachesize - 1) reallength = (((offset + length + cachesize) & ~(cachesize - 1)) - realoffset) - df.seek(realoffset) - d = df.read(reallength) - if closehandle: - df.close() + with self._datareadfp(df) as df: + df.seek(realoffset) + d = df.read(reallength) self._cachesegment(realoffset, d) if offset != realoffset or reallength != length: return util.buffer(d, offset - realoffset, length) @@ -1818,7 +1833,7 @@ raise RevlogError(_("integrity check failed on %s:%s") % (self.indexfile, pycompat.bytestr(revornode))) - def checkinlinesize(self, tr, fp=None): + def _enforceinlinesize(self, tr, fp=None): """Check if the revlog is too big for inline and convert if so. This should be called after revisions are added to the revlog. If the @@ -1847,24 +1862,20 @@ fp.flush() fp.close() - df = self.opener(self.datafile, 'w') - try: + with self._datafp('w') as df: for r in self: df.write(self._getsegmentforrevs(r, r)[1]) - finally: - df.close() - fp = self.opener(self.indexfile, 'w', atomictemp=True, - checkambig=self._checkambig) - self.version &= ~FLAG_INLINE_DATA - self._inline = False - for i in self: - e = self._io.packentry(self.index[i], self.node, self.version, i) - fp.write(e) + with self._indexfp('w') as fp: + self.version &= ~FLAG_INLINE_DATA + self._inline = False + io = self._io + for i in self: + e = io.packentry(self.index[i], self.node, self.version, i) + fp.write(e) - # if we don't call close, the temp file will never replace the - # real index - fp.close() + # the temp file replace the real index when we exit the context + # manager tr.replace(self.indexfile, trindex * self._io.size) self._chunkclear() @@ -1923,8 +1934,8 @@ """ dfh = None if not self._inline: - dfh = self.opener(self.datafile, "a+") - ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig) + dfh = self._datafp("a+") + ifh = self._indexfp("a+") try: return self._addrevision(node, rawtext, transaction, link, p1, p2, flags, cachedelta, ifh, dfh, @@ -2099,7 +2110,7 @@ if alwayscache and rawtext is None: rawtext = deltacomputer._buildtext(revinfo, fh) - if type(rawtext) == str: # only accept immutable objects + if type(rawtext) == bytes: # only accept immutable objects self._cache = (node, curr, rawtext) self._chainbasecache[curr] = chainbase return node @@ -2133,7 +2144,7 @@ ifh.write(entry) ifh.write(data[0]) ifh.write(data[1]) - self.checkinlinesize(transaction, ifh) + self._enforceinlinesize(transaction, ifh) def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None): """ @@ -2153,7 +2164,7 @@ end = 0 if r: end = self.end(r - 1) - ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig) + ifh = self._indexfp("a+") isize = r * self._io.size if self._inline: transaction.add(self.indexfile, end + isize, r) @@ -2161,7 +2172,7 @@ else: transaction.add(self.indexfile, isize, r) transaction.add(self.datafile, end) - dfh = self.opener(self.datafile, "a+") + dfh = self._datafp("a+") def flush(): if dfh: dfh.flush() @@ -2224,9 +2235,8 @@ # addrevision switched from inline to conventional # reopen the index ifh.close() - dfh = self.opener(self.datafile, "a+") - ifh = self.opener(self.indexfile, "a+", - checkambig=self._checkambig) + dfh = self._datafp("a+") + ifh = self._indexfp("a+") finally: if dfh: dfh.close() @@ -2328,10 +2338,9 @@ expected = max(0, self.end(len(self) - 1)) try: - f = self.opener(self.datafile) - f.seek(0, 2) - actual = f.tell() - f.close() + with self._datafp() as f: + f.seek(0, 2) + actual = f.tell() dd = actual - expected except IOError as inst: if inst.errno != errno.ENOENT: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/revset.py --- a/mercurial/revset.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/revset.py Sat Feb 24 17:49:10 2018 -0600 @@ -105,6 +105,9 @@ pass return None +def _sortedb(xs): + return sorted(util.rapply(pycompat.maybebytestr, xs)) + # operator methods def stringset(repo, subset, x, order): @@ -507,15 +510,7 @@ b.add(getbranch(r)) c = s.__contains__ return subset.filter(lambda r: c(r) or getbranch(r) in b, - condrepr=lambda: '' % sorted(b)) - -@predicate('bumped()', safe=True) -def bumped(repo, subset, x): - msg = ("'bumped()' is deprecated, " - "use 'phasedivergent()'") - repo.ui.deprecwarn(msg, '4.4') - - return phasedivergent(repo, subset, x) + condrepr=lambda: '' % _sortedb(b)) @predicate('phasedivergent()', safe=True) def phasedivergent(repo, subset, x): @@ -768,15 +763,7 @@ src = _getrevsource(repo, r) return subset.filter(dests.__contains__, - condrepr=lambda: '' % sorted(dests)) - -@predicate('divergent()', safe=True) -def divergent(repo, subset, x): - msg = ("'divergent()' is deprecated, " - "use 'contentdivergent()'") - repo.ui.deprecwarn(msg, '4.4') - - return contentdivergent(repo, subset, x) + condrepr=lambda: '' % _sortedb(dests)) @predicate('contentdivergent()', safe=True) def contentdivergent(repo, subset, x): @@ -1854,7 +1841,7 @@ keyflags = [] for k in keys.split(): fk = k - reverse = (k[0] == '-') + reverse = (k.startswith('-')) if reverse: k = k[1:] if k not in _sortkeyfuncs and k != 'topo': @@ -2031,14 +2018,6 @@ def tagged(repo, subset, x): return tag(repo, subset, x) -@predicate('unstable()', safe=True) -def unstable(repo, subset, x): - msg = ("'unstable()' is deprecated, " - "use 'orphan()'") - repo.ui.deprecwarn(msg, '4.4') - - return orphan(repo, subset, x) - @predicate('orphan()', safe=True) def orphan(repo, subset, x): """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/revsetlang.py --- a/mercurial/revsetlang.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/revsetlang.py Sat Feb 24 17:49:10 2018 -0600 @@ -635,7 +635,7 @@ "root(_list('a\\\\x00b\\\\x00c\\\\x00d'))" >>> formatspec(b'sort(%r, %ps)', b':', [b'desc', b'user']) "sort((:), 'desc', 'user')" - >>> formatspec('%ls', ['a', "'"]) + >>> formatspec(b'%ls', [b'a', b"'"]) "_list('a\\\\x00\\\\'')" ''' expr = pycompat.bytestr(expr) @@ -717,13 +717,13 @@ def gethashlikesymbols(tree): """returns the list of symbols of the tree that look like hashes - >>> gethashlikesymbols(('dagrange', ('symbol', '3'), ('symbol', 'abe3ff'))) + >>> gethashlikesymbols(parse(b'3::abe3ff')) ['3', 'abe3ff'] - >>> gethashlikesymbols(('func', ('symbol', 'precursors'), ('symbol', '.'))) + >>> gethashlikesymbols(parse(b'precursors(.)')) [] - >>> gethashlikesymbols(('func', ('symbol', 'precursors'), ('symbol', '34'))) + >>> gethashlikesymbols(parse(b'precursors(34)')) ['34'] - >>> gethashlikesymbols(('symbol', 'abe3ffZ')) + >>> gethashlikesymbols(parse(b'abe3ffZ')) [] """ if not tree: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/scmutil.py --- a/mercurial/scmutil.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/scmutil.py Sat Feb 24 17:49:10 2018 -0600 @@ -215,7 +215,7 @@ ui.warn(_("(is your Python install correct?)\n")) except IOError as inst: if util.safehasattr(inst, "code"): - ui.warn(_("abort: %s\n") % inst) + ui.warn(_("abort: %s\n") % util.forcebytestr(inst)) elif util.safehasattr(inst, "reason"): try: # usually it is in the form (errno, strerror) reason = inst.reason.args[1] @@ -267,6 +267,8 @@ raise error.Abort(_("cannot use an integer as a name")) except ValueError: pass + if lbl.strip() != lbl: + raise error.Abort(_("leading or trailing whitespace in name %r") % lbl) def checkfilename(f): '''Check that the filename f is an acceptable filename for a tracked file''' @@ -355,12 +357,8 @@ samestat = getattr(os.path, 'samestat', None) if followsym and samestat is not None: def adddir(dirlst, dirname): - match = False dirstat = os.stat(dirname) - for lstdirstat in dirlst: - if samestat(dirstat, lstdirstat): - match = True - break + match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst) if not match: dirlst.append(dirstat) return not match @@ -411,7 +409,7 @@ def formatchangeid(ctx): """Format changectx as '{rev}:{node|formatnode}', which is the default - template provided by cmdutil.changeset_templater""" + template provided by logcmdutil.changesettemplater""" repo = ctx.repo() return formatrevnode(repo.ui, intrev(ctx), binnode(ctx)) @@ -885,7 +883,7 @@ missings = [] for r in requirements: if r not in supported: - if not r or not r[0].isalnum(): + if not r or not r[0:1].isalnum(): raise error.RequirementError(_(".hg/requires file is corrupt")) missings.append(r) missings.sort() @@ -1196,7 +1194,7 @@ if k == self.firstlinekey: e = "key name '%s' is reserved" % self.firstlinekey raise error.ProgrammingError(e) - if not k[0].isalpha(): + if not k[0:1].isalpha(): e = "keys must start with a letter in a key-value file" raise error.ProgrammingError(e) if not k.isalnum(): @@ -1222,6 +1220,11 @@ 'unbundle', ] +# a list of (repo, ctx, files) functions called by various commands to allow +# extensions to ensure the corresponding files are available locally, before the +# command uses them. +fileprefetchhooks = util.hooks() + # A marker that tells the evolve extension to suppress its own reporting _reportstroubledchangesets = True diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/setdiscovery.py --- a/mercurial/setdiscovery.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/setdiscovery.py Sat Feb 24 17:49:10 2018 -0600 @@ -130,7 +130,7 @@ sample = set(random.sample(sample, desiredlen)) return sample -def findcommonheads(ui, local, remote, +def findcommonheads(ui, local, remote, heads=None, initialsamplesize=100, fullsamplesize=200, abortwhenunrelated=True, @@ -155,11 +155,15 @@ sample = _limitsample(ownheads, initialsamplesize) # indices between sample and externalized version must match sample = list(sample) - batch = remote.iterbatch() - batch.heads() - batch.known(dag.externalizeall(sample)) - batch.submit() - srvheadhashes, yesno = batch.results() + if heads: + srvheadhashes = heads + yesno = remote.known(dag.externalizeall(sample)) + else: + batch = remote.iterbatch() + batch.heads() + batch.known(dag.externalizeall(sample)) + batch.submit() + srvheadhashes, yesno = batch.results() if cl.tip() == nullid: if srvheadhashes != [nullid]: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/smartset.py --- a/mercurial/smartset.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/smartset.py Sat Feb 24 17:49:10 2018 -0600 @@ -8,7 +8,9 @@ from __future__ import absolute_import from . import ( + encoding, error, + pycompat, util, ) @@ -19,7 +21,7 @@ type(r) example ======== ================================= tuple ('', other) - str '' + bytes '' callable lambda: '' % sorted(b) object other ======== ================================= @@ -27,13 +29,16 @@ if r is None: return '' elif isinstance(r, tuple): - return r[0] % r[1:] - elif isinstance(r, str): + return r[0] % util.rapply(pycompat.maybebytestr, r[1:]) + elif isinstance(r, bytes): return r elif callable(r): return r() else: - return repr(r) + return pycompat.byterepr(r) + +def _typename(o): + return pycompat.sysbytes(type(o).__name__).lstrip('_') class abstractsmartset(object): @@ -306,7 +311,7 @@ self._istopo = False def __len__(self): - if '_list' in self.__dict__: + if r'_list' in self.__dict__: return len(self._list) else: return len(self._set) @@ -384,6 +389,7 @@ s._ascending = self._ascending return s + @encoding.strmethod def __repr__(self): d = {None: '', False: '-', True: '+'}[self._ascending] s = _formatsetrepr(self._datarepr) @@ -394,8 +400,8 @@ # We fallback to the sorted version for a stable output. if self._ascending is not None: l = self._asclist - s = repr(l) - return '<%s%s %s>' % (type(self).__name__, d, s) + s = pycompat.byterepr(l) + return '<%s%s %s>' % (_typename(self), d, s) class filteredset(abstractsmartset): """Duck type for baseset class which iterates lazily over the revisions in @@ -505,12 +511,13 @@ pass return x + @encoding.strmethod def __repr__(self): - xs = [repr(self._subset)] + xs = [pycompat.byterepr(self._subset)] s = _formatsetrepr(self._condrepr) if s: xs.append(s) - return '<%s %s>' % (type(self).__name__, ', '.join(xs)) + return '<%s %s>' % (_typename(self), ', '.join(xs)) def _iterordered(ascending, iter1, iter2): """produce an ordered iteration from two iterators with the same order @@ -755,9 +762,10 @@ self.reverse() return val + @encoding.strmethod def __repr__(self): d = {None: '', False: '-', True: '+'}[self._ascending] - return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2) + return '<%s%s %r, %r>' % (_typename(self), d, self._r1, self._r2) class generatorset(abstractsmartset): """Wrap a generator for lazy iteration @@ -918,9 +926,10 @@ return self.last() return next(it(), None) + @encoding.strmethod def __repr__(self): d = {False: '-', True: '+'}[self._ascending] - return '<%s%s>' % (type(self).__name__.lstrip('_'), d) + return '<%s%s>' % (_typename(self), d) class _generatorsetasc(generatorset): """Special case of generatorset optimized for ascending generators.""" @@ -1087,10 +1096,10 @@ y = max(self._end - start, self._start) return _spanset(x, y, self._ascending, self._hiddenrevs) + @encoding.strmethod def __repr__(self): d = {False: '-', True: '+'}[self._ascending] - return '<%s%s %d:%d>' % (type(self).__name__.lstrip('_'), d, - self._start, self._end) + return '<%s%s %d:%d>' % (_typename(self), d, self._start, self._end) class fullreposet(_spanset): """a set containing all revisions in the repo @@ -1123,7 +1132,7 @@ def prettyformat(revs): lines = [] - rs = repr(revs) + rs = pycompat.byterepr(revs) p = 0 while p < len(rs): q = rs.find('<', p + 1) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/sshpeer.py --- a/mercurial/sshpeer.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/sshpeer.py Sat Feb 24 17:49:10 2018 -0600 @@ -8,6 +8,7 @@ from __future__ import absolute_import import re +import uuid from .i18n import _ from . import ( @@ -15,6 +16,7 @@ pycompat, util, wireproto, + wireprotoserver, ) def _serverquote(s): @@ -63,8 +65,11 @@ (This will only wait for data if the setup is supported by `util.poll`) """ - if getattr(self._main, 'hasbuffer', False): # getattr for classic pipe - return (True, True) # main has data, assume side is worth poking at. + if (isinstance(self._main, util.bufferedinputpipe) and + self._main.hasbuffer): + # Main has data. Assume side is worth poking at. + return True, True + fds = [self._main.fileno(), self._side.fileno()] try: act = util.poll(fds) @@ -114,43 +119,253 @@ def flush(self): return self._main.flush() -class sshpeer(wireproto.wirepeer): - def __init__(self, ui, path, create=False): - self._url = path - self._ui = ui - self._pipeo = self._pipei = self._pipee = None +def _cleanuppipes(ui, pipei, pipeo, pipee): + """Clean up pipes used by an SSH connection.""" + if pipeo: + pipeo.close() + if pipei: + pipei.close() + + if pipee: + # Try to read from the err descriptor until EOF. + try: + for l in pipee: + ui.status(_('remote: '), l) + except (IOError, ValueError): + pass + + pipee.close() + +def _makeconnection(ui, sshcmd, args, remotecmd, path, sshenv=None): + """Create an SSH connection to a server. + + Returns a tuple of (process, stdin, stdout, stderr) for the + spawned process. + """ + cmd = '%s %s %s' % ( + sshcmd, + args, + util.shellquote('%s -R %s serve --stdio' % ( + _serverquote(remotecmd), _serverquote(path)))) + + ui.debug('running %s\n' % cmd) + cmd = util.quotecommand(cmd) + + # no buffer allow the use of 'select' + # feel free to remove buffering and select usage when we ultimately + # move to threading. + stdin, stdout, stderr, proc = util.popen4(cmd, bufsize=0, env=sshenv) + + return proc, stdin, stdout, stderr + +def _performhandshake(ui, stdin, stdout, stderr): + def badresponse(): + # Flush any output on stderr. + _forwardoutput(ui, stderr) + + msg = _('no suitable response from remote hg') + hint = ui.config('ui', 'ssherrorhint') + raise error.RepoError(msg, hint=hint) - u = util.url(path, parsequery=False, parsefragment=False) - if u.scheme != 'ssh' or not u.host or u.path is None: - self._abort(error.RepoError(_("couldn't parse location %s") % path)) + # The handshake consists of sending wire protocol commands in reverse + # order of protocol implementation and then sniffing for a response + # to one of them. + # + # Those commands (from oldest to newest) are: + # + # ``between`` + # Asks for the set of revisions between a pair of revisions. Command + # present in all Mercurial server implementations. + # + # ``hello`` + # Instructs the server to advertise its capabilities. Introduced in + # Mercurial 0.9.1. + # + # ``upgrade`` + # Requests upgrade from default transport protocol version 1 to + # a newer version. Introduced in Mercurial 4.6 as an experimental + # feature. + # + # The ``between`` command is issued with a request for the null + # range. If the remote is a Mercurial server, this request will + # generate a specific response: ``1\n\n``. This represents the + # wire protocol encoded value for ``\n``. We look for ``1\n\n`` + # in the output stream and know this is the response to ``between`` + # and we're at the end of our handshake reply. + # + # The response to the ``hello`` command will be a line with the + # length of the value returned by that command followed by that + # value. If the server doesn't support ``hello`` (which should be + # rare), that line will be ``0\n``. Otherwise, the value will contain + # RFC 822 like lines. Of these, the ``capabilities:`` line contains + # the capabilities of the server. + # + # The ``upgrade`` command isn't really a command in the traditional + # sense of version 1 of the transport because it isn't using the + # proper mechanism for formatting insteads: instead, it just encodes + # arguments on the line, delimited by spaces. + # + # The ``upgrade`` line looks like ``upgrade ``. + # If the server doesn't support protocol upgrades, it will reply to + # this line with ``0\n``. Otherwise, it emits an + # ``upgraded `` line to both stdout and stderr. + # Content immediately following this line describes additional + # protocol and server state. + # + # In addition to the responses to our command requests, the server + # may emit "banner" output on stdout. SSH servers are allowed to + # print messages to stdout on login. Issuing commands on connection + # allows us to flush this banner output from the server by scanning + # for output to our well-known ``between`` command. Of course, if + # the banner contains ``1\n\n``, this will throw off our detection. - util.checksafessh(path) + requestlog = ui.configbool('devel', 'debug.peer-request') + + # Generate a random token to help identify responses to version 2 + # upgrade request. + token = pycompat.sysbytes(str(uuid.uuid4())) + upgradecaps = [ + ('proto', wireprotoserver.SSHV2), + ] + upgradecaps = util.urlreq.urlencode(upgradecaps) - if u.passwd is not None: - self._abort(error.RepoError(_("password in URL not supported"))) + try: + pairsarg = '%s-%s' % ('0' * 40, '0' * 40) + handshake = [ + 'hello\n', + 'between\n', + 'pairs %d\n' % len(pairsarg), + pairsarg, + ] + + # Request upgrade to version 2 if configured. + if ui.configbool('experimental', 'sshpeer.advertise-v2'): + ui.debug('sending upgrade request: %s %s\n' % (token, upgradecaps)) + handshake.insert(0, 'upgrade %s %s\n' % (token, upgradecaps)) - self._user = u.user - self._host = u.host - self._port = u.port - self._path = u.path or '.' + if requestlog: + ui.debug('devel-peer-request: hello\n') + ui.debug('sending hello command\n') + if requestlog: + ui.debug('devel-peer-request: between\n') + ui.debug('devel-peer-request: pairs: %d bytes\n' % len(pairsarg)) + ui.debug('sending between command\n') + + stdin.write(''.join(handshake)) + stdin.flush() + except IOError: + badresponse() + + # Assume version 1 of wire protocol by default. + protoname = wireprotoserver.SSHV1 + reupgraded = re.compile(b'^upgraded %s (.*)$' % re.escape(token)) + + lines = ['', 'dummy'] + max_noise = 500 + while lines[-1] and max_noise: + try: + l = stdout.readline() + _forwardoutput(ui, stderr) - sshcmd = self.ui.config("ui", "ssh") - remotecmd = self.ui.config("ui", "remotecmd") - sshaddenv = dict(self.ui.configitems("sshenv")) - sshenv = util.shellenviron(sshaddenv) + # Look for reply to protocol upgrade request. It has a token + # in it, so there should be no false positives. + m = reupgraded.match(l) + if m: + protoname = m.group(1) + ui.debug('protocol upgraded to %s\n' % protoname) + # If an upgrade was handled, the ``hello`` and ``between`` + # requests are ignored. The next output belongs to the + # protocol, so stop scanning lines. + break + + # Otherwise it could be a banner, ``0\n`` response if server + # doesn't support upgrade. + + if lines[-1] == '1\n' and l == '\n': + break + if l: + ui.debug('remote: ', l) + lines.append(l) + max_noise -= 1 + except IOError: + badresponse() + else: + badresponse() + + caps = set() - args = util.sshargs(sshcmd, self._host, self._user, self._port) + # For version 1, we should see a ``capabilities`` line in response to the + # ``hello`` command. + if protoname == wireprotoserver.SSHV1: + for l in reversed(lines): + # Look for response to ``hello`` command. Scan from the back so + # we don't misinterpret banner output as the command reply. + if l.startswith('capabilities:'): + caps.update(l[:-1].split(':')[1].split()) + break + elif protoname == wireprotoserver.SSHV2: + # We see a line with number of bytes to follow and then a value + # looking like ``capabilities: *``. + line = stdout.readline() + try: + valuelen = int(line) + except ValueError: + badresponse() + + capsline = stdout.read(valuelen) + if not capsline.startswith('capabilities: '): + badresponse() + + ui.debug('remote: %s\n' % capsline) + + caps.update(capsline.split(':')[1].split()) + # Trailing newline. + stdout.read(1) - if create: - cmd = '%s %s %s' % (sshcmd, args, - util.shellquote("%s init %s" % - (_serverquote(remotecmd), _serverquote(self._path)))) - ui.debug('running %s\n' % cmd) - res = ui.system(cmd, blockedtag='sshpeer', environ=sshenv) - if res != 0: - self._abort(error.RepoError(_("could not create remote repo"))) + # Error if we couldn't find capabilities, this means: + # + # 1. Remote isn't a Mercurial server + # 2. Remote is a <0.9.1 Mercurial server + # 3. Remote is a future Mercurial server that dropped ``hello`` + # and other attempted handshake mechanisms. + if not caps: + badresponse() + + # Flush any output on stderr before proceeding. + _forwardoutput(ui, stderr) + + return protoname, caps + +class sshv1peer(wireproto.wirepeer): + def __init__(self, ui, url, proc, stdin, stdout, stderr, caps): + """Create a peer from an existing SSH connection. - self._validaterepo(sshcmd, args, remotecmd, sshenv) + ``proc`` is a handle on the underlying SSH process. + ``stdin``, ``stdout``, and ``stderr`` are handles on the stdio + pipes for that process. + ``caps`` is a set of capabilities supported by the remote. + """ + self._url = url + self._ui = ui + # self._subprocess is unused. Keeping a handle on the process + # holds a reference and prevents it from being garbage collected. + self._subprocess = proc + + # And we hook up our "doublepipe" wrapper to allow querying + # stderr any time we perform I/O. + stdout = doublepipe(ui, util.bufferedinputpipe(stdout), stderr) + stdin = doublepipe(ui, stdin, stderr) + + self._pipeo = stdin + self._pipei = stdout + self._pipee = stderr + self._caps = caps + + # Commands that have a "framed" response where the first line of the + # response contains the length of that response. + _FRAMED_COMMANDS = { + 'batch', + } # Begin of _basepeer interface. @@ -182,64 +397,6 @@ # End of _basewirecommands interface. - def _validaterepo(self, sshcmd, args, remotecmd, sshenv=None): - # cleanup up previous run - self._cleanup() - - cmd = '%s %s %s' % (sshcmd, args, - util.shellquote("%s -R %s serve --stdio" % - (_serverquote(remotecmd), _serverquote(self._path)))) - self.ui.debug('running %s\n' % cmd) - cmd = util.quotecommand(cmd) - - # while self._subprocess isn't used, having it allows the subprocess to - # to clean up correctly later - # - # no buffer allow the use of 'select' - # feel free to remove buffering and select usage when we ultimately - # move to threading. - sub = util.popen4(cmd, bufsize=0, env=sshenv) - self._pipeo, self._pipei, self._pipee, self._subprocess = sub - - self._pipei = util.bufferedinputpipe(self._pipei) - self._pipei = doublepipe(self.ui, self._pipei, self._pipee) - self._pipeo = doublepipe(self.ui, self._pipeo, self._pipee) - - def badresponse(): - msg = _("no suitable response from remote hg") - hint = self.ui.config("ui", "ssherrorhint") - self._abort(error.RepoError(msg, hint=hint)) - - try: - # skip any noise generated by remote shell - self._callstream("hello") - r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40))) - except IOError: - badresponse() - - lines = ["", "dummy"] - max_noise = 500 - while lines[-1] and max_noise: - try: - l = r.readline() - self._readerr() - if lines[-1] == "1\n" and l == "\n": - break - if l: - self.ui.debug("remote: ", l) - lines.append(l) - max_noise -= 1 - except IOError: - badresponse() - else: - badresponse() - - self._caps = set() - for l in reversed(lines): - if l.startswith("capabilities:"): - self._caps.update(l[:-1].split(":")[1].split()) - break - def _readerr(self): _forwardoutput(self.ui, self._pipee) @@ -248,41 +405,11 @@ raise exception def _cleanup(self): - if self._pipeo is None: - return - self._pipeo.close() - self._pipei.close() - try: - # read the error descriptor until EOF - for l in self._pipee: - self.ui.status(_("remote: "), l) - except (IOError, ValueError): - pass - self._pipee.close() + _cleanuppipes(self.ui, self._pipei, self._pipeo, self._pipee) __del__ = _cleanup - def _submitbatch(self, req): - rsp = self._callstream("batch", cmds=wireproto.encodebatchcmds(req)) - available = self._getamount() - # TODO this response parsing is probably suboptimal for large - # batches with large responses. - toread = min(available, 1024) - work = rsp.read(toread) - available -= toread - chunk = work - while chunk: - while ';' in work: - one, work = work.split(';', 1) - yield wireproto.unescapearg(one) - toread = min(available, 1024) - chunk = rsp.read(toread) - available -= toread - work += chunk - yield wireproto.unescapearg(work) - - def _callstream(self, cmd, **args): - args = pycompat.byteskwargs(args) + def _sendrequest(self, cmd, args, framed=False): if (self.ui.debugflag and self.ui.configbool('devel', 'debug.peer-request')): dbg = self.ui.debug @@ -316,35 +443,63 @@ self._pipeo.write(v) self._pipeo.flush() + # We know exactly how many bytes are in the response. So return a proxy + # around the raw output stream that allows reading exactly this many + # bytes. Callers then can read() without fear of overrunning the + # response. + if framed: + amount = self._getamount() + return util.cappedreader(self._pipei, amount) + return self._pipei + def _callstream(self, cmd, **args): + args = pycompat.byteskwargs(args) + return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS) + def _callcompressable(self, cmd, **args): - return self._callstream(cmd, **args) + args = pycompat.byteskwargs(args) + return self._sendrequest(cmd, args, framed=cmd in self._FRAMED_COMMANDS) def _call(self, cmd, **args): - self._callstream(cmd, **args) - return self._recv() + args = pycompat.byteskwargs(args) + return self._sendrequest(cmd, args, framed=True).read() def _callpush(self, cmd, fp, **args): + # The server responds with an empty frame if the client should + # continue submitting the payload. r = self._call(cmd, **args) if r: return '', r + + # The payload consists of frames with content followed by an empty + # frame. for d in iter(lambda: fp.read(4096), ''): - self._send(d) - self._send("", flush=True) - r = self._recv() + self._writeframed(d) + self._writeframed("", flush=True) + + # In case of success, there is an empty frame and a frame containing + # the integer result (as a string). + # In case of error, there is a non-empty frame containing the error. + r = self._readframed() if r: return '', r - return self._recv(), '' + return self._readframed(), '' def _calltwowaystream(self, cmd, fp, **args): + # The server responds with an empty frame if the client should + # continue submitting the payload. r = self._call(cmd, **args) if r: # XXX needs to be made better raise error.Abort(_('unexpected remote reply: %s') % r) + + # The payload consists of frames with content followed by an empty + # frame. for d in iter(lambda: fp.read(4096), ''): - self._send(d) - self._send("", flush=True) + self._writeframed(d) + self._writeframed("", flush=True) + return self._pipei def _getamount(self): @@ -359,10 +514,10 @@ except ValueError: self._abort(error.ResponseError(_("unexpected response:"), l)) - def _recv(self): + def _readframed(self): return self._pipei.read(self._getamount()) - def _send(self, data, flush=False): + def _writeframed(self, data, flush=False): self._pipeo.write("%d\n" % len(data)) if data: self._pipeo.write(data) @@ -370,4 +525,57 @@ self._pipeo.flush() self._readerr() -instance = sshpeer +class sshv2peer(sshv1peer): + """A peer that speakers version 2 of the transport protocol.""" + # Currently version 2 is identical to version 1 post handshake. + # And handshake is performed before the peer is instantiated. So + # we need no custom code. + +def instance(ui, path, create): + """Create an SSH peer. + + The returned object conforms to the ``wireproto.wirepeer`` interface. + """ + u = util.url(path, parsequery=False, parsefragment=False) + if u.scheme != 'ssh' or not u.host or u.path is None: + raise error.RepoError(_("couldn't parse location %s") % path) + + util.checksafessh(path) + + if u.passwd is not None: + raise error.RepoError(_('password in URL not supported')) + + sshcmd = ui.config('ui', 'ssh') + remotecmd = ui.config('ui', 'remotecmd') + sshaddenv = dict(ui.configitems('sshenv')) + sshenv = util.shellenviron(sshaddenv) + remotepath = u.path or '.' + + args = util.sshargs(sshcmd, u.host, u.user, u.port) + + if create: + cmd = '%s %s %s' % (sshcmd, args, + util.shellquote('%s init %s' % + (_serverquote(remotecmd), _serverquote(remotepath)))) + ui.debug('running %s\n' % cmd) + res = ui.system(cmd, blockedtag='sshpeer', environ=sshenv) + if res != 0: + raise error.RepoError(_('could not create remote repo')) + + proc, stdin, stdout, stderr = _makeconnection(ui, sshcmd, args, remotecmd, + remotepath, sshenv) + + try: + protoname, caps = _performhandshake(ui, stdin, stdout, stderr) + except Exception: + _cleanuppipes(ui, stdout, stdin, stderr) + raise + + if protoname == wireprotoserver.SSHV1: + return sshv1peer(ui, path, proc, stdin, stdout, stderr, caps) + elif protoname == wireprotoserver.SSHV2: + return sshv2peer(ui, path, proc, stdin, stdout, stderr, caps) + else: + _cleanuppipes(ui, stdout, stdin, stderr) + raise error.RepoError(_('unknown version of SSH protocol: %s') % + protoname) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/sshserver.py --- a/mercurial/sshserver.py Fri Feb 23 17:57:04 2018 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,131 +0,0 @@ -# sshserver.py - ssh protocol server support for mercurial -# -# Copyright 2005-2007 Matt Mackall -# Copyright 2006 Vadim Gelfer -# -# This software may be used and distributed according to the terms of the -# GNU General Public License version 2 or any later version. - -from __future__ import absolute_import - -import sys - -from .i18n import _ -from . import ( - encoding, - error, - hook, - util, - wireproto, -) - -class sshserver(wireproto.abstractserverproto): - def __init__(self, ui, repo): - self.ui = ui - self.repo = repo - self.lock = None - self.fin = ui.fin - self.fout = ui.fout - self.name = 'ssh' - - hook.redirect(True) - ui.fout = repo.ui.fout = ui.ferr - - # Prevent insertion/deletion of CRs - util.setbinary(self.fin) - util.setbinary(self.fout) - - def getargs(self, args): - data = {} - keys = args.split() - for n in xrange(len(keys)): - argline = self.fin.readline()[:-1] - arg, l = argline.split() - if arg not in keys: - raise error.Abort(_("unexpected parameter %r") % arg) - if arg == '*': - star = {} - for k in xrange(int(l)): - argline = self.fin.readline()[:-1] - arg, l = argline.split() - val = self.fin.read(int(l)) - star[arg] = val - data['*'] = star - else: - val = self.fin.read(int(l)) - data[arg] = val - return [data[k] for k in keys] - - def getarg(self, name): - return self.getargs(name)[0] - - def getfile(self, fpout): - self.sendresponse('') - count = int(self.fin.readline()) - while count: - fpout.write(self.fin.read(count)) - count = int(self.fin.readline()) - - def redirect(self): - pass - - def sendresponse(self, v): - self.fout.write("%d\n" % len(v)) - self.fout.write(v) - self.fout.flush() - - def sendstream(self, source): - write = self.fout.write - for chunk in source.gen: - write(chunk) - self.fout.flush() - - def sendpushresponse(self, rsp): - self.sendresponse('') - self.sendresponse(str(rsp.res)) - - def sendpusherror(self, rsp): - self.sendresponse(rsp.res) - - def sendooberror(self, rsp): - self.ui.ferr.write('%s\n-\n' % rsp.message) - self.ui.ferr.flush() - self.fout.write('\n') - self.fout.flush() - - def serve_forever(self): - try: - while self.serve_one(): - pass - finally: - if self.lock is not None: - self.lock.release() - sys.exit(0) - - handlers = { - str: sendresponse, - wireproto.streamres: sendstream, - wireproto.streamres_legacy: sendstream, - wireproto.pushres: sendpushresponse, - wireproto.pusherr: sendpusherror, - wireproto.ooberror: sendooberror, - } - - def serve_one(self): - cmd = self.fin.readline()[:-1] - if cmd and cmd in wireproto.commands: - rsp = wireproto.dispatch(self.repo, self, cmd) - self.handlers[rsp.__class__](self, rsp) - elif cmd: - impl = getattr(self, 'do_' + cmd, None) - if impl: - r = impl() - if r is not None: - self.sendresponse(r) - else: - self.sendresponse("") - return cmd != '' - - def _client(self): - client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] - return 'remote:ssh:' + client diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/subrepo.py --- a/mercurial/subrepo.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/subrepo.py Sat Feb 24 17:49:10 2018 -0600 @@ -1,4 +1,4 @@ -# subrepo.py - sub-repository handling for Mercurial +# subrepo.py - sub-repository classes and factory # # Copyright 2009-2010 Matt Mackall # @@ -19,30 +19,30 @@ import tarfile import xml.dom.minidom - from .i18n import _ from . import ( cmdutil, - config, encoding, error, exchange, - filemerge, + logcmdutil, match as matchmod, node, pathutil, phases, pycompat, scmutil, + subrepoutil, util, vfs as vfsmod, ) hg = None +reporelpath = subrepoutil.reporelpath +subrelpath = subrepoutil.subrelpath +_abssource = subrepoutil._abssource propertycache = util.propertycache -nullstate = ('', '', 'empty') - def _expandedabspath(path): ''' get a path or url and if it is a path expand it and return an absolute path @@ -80,284 +80,6 @@ return res return decoratedmethod -def state(ctx, ui): - """return a state dict, mapping subrepo paths configured in .hgsub - to tuple: (source from .hgsub, revision from .hgsubstate, kind - (key in types dict)) - """ - p = config.config() - repo = ctx.repo() - def read(f, sections=None, remap=None): - if f in ctx: - try: - data = ctx[f].data() - except IOError as err: - if err.errno != errno.ENOENT: - raise - # handle missing subrepo spec files as removed - ui.warn(_("warning: subrepo spec file \'%s\' not found\n") % - repo.pathto(f)) - return - p.parse(f, data, sections, remap, read) - else: - raise error.Abort(_("subrepo spec file \'%s\' not found") % - repo.pathto(f)) - if '.hgsub' in ctx: - read('.hgsub') - - for path, src in ui.configitems('subpaths'): - p.set('subpaths', path, src, ui.configsource('subpaths', path)) - - rev = {} - if '.hgsubstate' in ctx: - try: - for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()): - l = l.lstrip() - if not l: - continue - try: - revision, path = l.split(" ", 1) - except ValueError: - raise error.Abort(_("invalid subrepository revision " - "specifier in \'%s\' line %d") - % (repo.pathto('.hgsubstate'), (i + 1))) - rev[path] = revision - except IOError as err: - if err.errno != errno.ENOENT: - raise - - def remap(src): - for pattern, repl in p.items('subpaths'): - # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub - # does a string decode. - repl = util.escapestr(repl) - # However, we still want to allow back references to go - # through unharmed, so we turn r'\\1' into r'\1'. Again, - # extra escapes are needed because re.sub string decodes. - repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl) - try: - src = re.sub(pattern, repl, src, 1) - except re.error as e: - raise error.Abort(_("bad subrepository pattern in %s: %s") - % (p.source('subpaths', pattern), e)) - return src - - state = {} - for path, src in p[''].items(): - kind = 'hg' - if src.startswith('['): - if ']' not in src: - raise error.Abort(_('missing ] in subrepository source')) - kind, src = src.split(']', 1) - kind = kind[1:] - src = src.lstrip() # strip any extra whitespace after ']' - - if not util.url(src).isabs(): - parent = _abssource(repo, abort=False) - if parent: - parent = util.url(parent) - parent.path = posixpath.join(parent.path or '', src) - parent.path = posixpath.normpath(parent.path) - joined = str(parent) - # Remap the full joined path and use it if it changes, - # else remap the original source. - remapped = remap(joined) - if remapped == joined: - src = remap(src) - else: - src = remapped - - src = remap(src) - state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind) - - return state - -def writestate(repo, state): - """rewrite .hgsubstate in (outer) repo with these subrepo states""" - lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state) - if state[s][1] != nullstate[1]] - repo.wwrite('.hgsubstate', ''.join(lines), '') - -def submerge(repo, wctx, mctx, actx, overwrite, labels=None): - """delegated from merge.applyupdates: merging of .hgsubstate file - in working context, merging context and ancestor context""" - if mctx == actx: # backwards? - actx = wctx.p1() - s1 = wctx.substate - s2 = mctx.substate - sa = actx.substate - sm = {} - - repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx)) - - def debug(s, msg, r=""): - if r: - r = "%s:%s:%s" % r - repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r)) - - promptssrc = filemerge.partextras(labels) - for s, l in sorted(s1.iteritems()): - prompts = None - a = sa.get(s, nullstate) - ld = l # local state with possible dirty flag for compares - if wctx.sub(s).dirty(): - ld = (l[0], l[1] + "+") - if wctx == actx: # overwrite - a = ld - - prompts = promptssrc.copy() - prompts['s'] = s - if s in s2: - r = s2[s] - if ld == r or r == a: # no change or local is newer - sm[s] = l - continue - elif ld == a: # other side changed - debug(s, "other changed, get", r) - wctx.sub(s).get(r, overwrite) - sm[s] = r - elif ld[0] != r[0]: # sources differ - prompts['lo'] = l[0] - prompts['ro'] = r[0] - if repo.ui.promptchoice( - _(' subrepository sources for %(s)s differ\n' - 'use (l)ocal%(l)s source (%(lo)s)' - ' or (r)emote%(o)s source (%(ro)s)?' - '$$ &Local $$ &Remote') % prompts, 0): - debug(s, "prompt changed, get", r) - wctx.sub(s).get(r, overwrite) - sm[s] = r - elif ld[1] == a[1]: # local side is unchanged - debug(s, "other side changed, get", r) - wctx.sub(s).get(r, overwrite) - sm[s] = r - else: - debug(s, "both sides changed") - srepo = wctx.sub(s) - prompts['sl'] = srepo.shortid(l[1]) - prompts['sr'] = srepo.shortid(r[1]) - option = repo.ui.promptchoice( - _(' subrepository %(s)s diverged (local revision: %(sl)s, ' - 'remote revision: %(sr)s)\n' - '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?' - '$$ &Merge $$ &Local $$ &Remote') - % prompts, 0) - if option == 0: - wctx.sub(s).merge(r) - sm[s] = l - debug(s, "merge with", r) - elif option == 1: - sm[s] = l - debug(s, "keep local subrepo revision", l) - else: - wctx.sub(s).get(r, overwrite) - sm[s] = r - debug(s, "get remote subrepo revision", r) - elif ld == a: # remote removed, local unchanged - debug(s, "remote removed, remove") - wctx.sub(s).remove() - elif a == nullstate: # not present in remote or ancestor - debug(s, "local added, keep") - sm[s] = l - continue - else: - if repo.ui.promptchoice( - _(' local%(l)s changed subrepository %(s)s' - ' which remote%(o)s removed\n' - 'use (c)hanged version or (d)elete?' - '$$ &Changed $$ &Delete') % prompts, 0): - debug(s, "prompt remove") - wctx.sub(s).remove() - - for s, r in sorted(s2.items()): - prompts = None - if s in s1: - continue - elif s not in sa: - debug(s, "remote added, get", r) - mctx.sub(s).get(r) - sm[s] = r - elif r != sa[s]: - prompts = promptssrc.copy() - prompts['s'] = s - if repo.ui.promptchoice( - _(' remote%(o)s changed subrepository %(s)s' - ' which local%(l)s removed\n' - 'use (c)hanged version or (d)elete?' - '$$ &Changed $$ &Delete') % prompts, 0) == 0: - debug(s, "prompt recreate", r) - mctx.sub(s).get(r) - sm[s] = r - - # record merged .hgsubstate - writestate(repo, sm) - return sm - -def precommit(ui, wctx, status, match, force=False): - """Calculate .hgsubstate changes that should be applied before committing - - Returns (subs, commitsubs, newstate) where - - subs: changed subrepos (including dirty ones) - - commitsubs: dirty subrepos which the caller needs to commit recursively - - newstate: new state dict which the caller must write to .hgsubstate - - This also updates the given status argument. - """ - subs = [] - commitsubs = set() - newstate = wctx.substate.copy() - - # only manage subrepos and .hgsubstate if .hgsub is present - if '.hgsub' in wctx: - # we'll decide whether to track this ourselves, thanks - for c in status.modified, status.added, status.removed: - if '.hgsubstate' in c: - c.remove('.hgsubstate') - - # compare current state to last committed state - # build new substate based on last committed state - oldstate = wctx.p1().substate - for s in sorted(newstate.keys()): - if not match(s): - # ignore working copy, use old state if present - if s in oldstate: - newstate[s] = oldstate[s] - continue - if not force: - raise error.Abort( - _("commit with new subrepo %s excluded") % s) - dirtyreason = wctx.sub(s).dirtyreason(True) - if dirtyreason: - if not ui.configbool('ui', 'commitsubrepos'): - raise error.Abort(dirtyreason, - hint=_("use --subrepos for recursive commit")) - subs.append(s) - commitsubs.add(s) - else: - bs = wctx.sub(s).basestate() - newstate[s] = (newstate[s][0], bs, newstate[s][2]) - if oldstate.get(s, (None, None, None))[1] != bs: - subs.append(s) - - # check for removed subrepos - for p in wctx.parents(): - r = [s for s in p.substate if s not in newstate] - subs += [s for s in r if match(s)] - if subs: - if (not match('.hgsub') and - '.hgsub' in (wctx.modified() + wctx.added())): - raise error.Abort(_("can't commit subrepos without .hgsub")) - status.modified.insert(0, '.hgsubstate') - - elif '.hgsub' in status.removed: - # clean up .hgsubstate when .hgsub is removed - if ('.hgsubstate' in wctx and - '.hgsubstate' not in (status.modified + status.added + - status.removed)): - status.removed.insert(0, '.hgsubstate') - - return subs, commitsubs, newstate - def _updateprompt(ui, sub, dirty, local, remote): if dirty: msg = (_(' subrepository sources for %s differ\n' @@ -372,64 +94,6 @@ % (subrelpath(sub), local, remote)) return ui.promptchoice(msg, 0) -def reporelpath(repo): - """return path to this (sub)repo as seen from outermost repo""" - parent = repo - while util.safehasattr(parent, '_subparent'): - parent = parent._subparent - return repo.root[len(pathutil.normasprefix(parent.root)):] - -def subrelpath(sub): - """return path to this subrepo as seen from outermost repo""" - return sub._relpath - -def _abssource(repo, push=False, abort=True): - """return pull/push path of repo - either based on parent repo .hgsub info - or on the top repo config. Abort or return None if no source found.""" - if util.safehasattr(repo, '_subparent'): - source = util.url(repo._subsource) - if source.isabs(): - return bytes(source) - source.path = posixpath.normpath(source.path) - parent = _abssource(repo._subparent, push, abort=False) - if parent: - parent = util.url(util.pconvert(parent)) - parent.path = posixpath.join(parent.path or '', source.path) - parent.path = posixpath.normpath(parent.path) - return bytes(parent) - else: # recursion reached top repo - path = None - if util.safehasattr(repo, '_subtoppath'): - path = repo._subtoppath - elif push and repo.ui.config('paths', 'default-push'): - path = repo.ui.config('paths', 'default-push') - elif repo.ui.config('paths', 'default'): - path = repo.ui.config('paths', 'default') - elif repo.shared(): - # chop off the .hg component to get the default path form. This has - # already run through vfsmod.vfs(..., realpath=True), so it doesn't - # have problems with 'C:' - return os.path.dirname(repo.sharedpath) - if path: - # issue5770: 'C:\' and 'C:' are not equivalent paths. The former is - # as expected: an absolute path to the root of the C: drive. The - # latter is a relative path, and works like so: - # - # C:\>cd C:\some\path - # C:\>D: - # D:\>python -c "import os; print os.path.abspath('C:')" - # C:\some\path - # - # D:\>python -c "import os; print os.path.abspath('C:relative')" - # C:\some\path\relative - if util.hasdriveletter(path): - if len(path) == 2 or path[2:3] not in br'\/': - path = os.path.abspath(path) - return path - - if abort: - raise error.Abort(_("default path for subrepository not found")) - def _sanitize(ui, vfs, ignore): for dirname, dirs, names in vfs.walk(): for i, d in enumerate(dirs): @@ -508,37 +172,6 @@ subrev = "0" * 40 return types[state[2]](pctx, path, (state[0], subrev), True) -def newcommitphase(ui, ctx): - commitphase = phases.newcommitphase(ui) - substate = getattr(ctx, "substate", None) - if not substate: - return commitphase - check = ui.config('phases', 'checksubrepos') - if check not in ('ignore', 'follow', 'abort'): - raise error.Abort(_('invalid phases.checksubrepos configuration: %s') - % (check)) - if check == 'ignore': - return commitphase - maxphase = phases.public - maxsub = None - for s in sorted(substate): - sub = ctx.sub(s) - subphase = sub.phase(substate[s][1]) - if maxphase < subphase: - maxphase = subphase - maxsub = s - if commitphase < maxphase: - if check == 'abort': - raise error.Abort(_("can't commit in %s phase" - " conflicting %s from subrepository %s") % - (phases.phasenames[commitphase], - phases.phasenames[maxphase], maxsub)) - ui.warn(_("warning: changes are committed in" - " %s phase from subrepository %s\n") % - (phases.phasenames[maxphase], maxsub)) - return maxphase - return commitphase - # subrepo classes need to implement the following abstract class: class abstractsubrepo(object): @@ -907,10 +540,10 @@ # in hex format if node2 is not None: node2 = node.bin(node2) - cmdutil.diffordiffstat(ui, self._repo, diffopts, - node1, node2, match, - prefix=posixpath.join(prefix, self._path), - listsubrepos=True, **opts) + logcmdutil.diffordiffstat(ui, self._repo, diffopts, + node1, node2, match, + prefix=posixpath.join(prefix, self._path), + listsubrepos=True, **opts) except error.RepoLookupError as inst: self.ui.warn(_('warning: error "%s" in subrepository "%s"\n') % (inst, subrelpath(self))) @@ -918,9 +551,13 @@ @annotatesubrepoerror def archive(self, archiver, prefix, match=None, decode=True): self._get(self._state + ('hg',)) - total = abstractsubrepo.archive(self, archiver, prefix, match) + files = self.files() + if match: + files = [f for f in files if match(f)] rev = self._state[1] ctx = self._repo[rev] + scmutil.fileprefetchhooks(self._repo, ctx, files) + total = abstractsubrepo.archive(self, archiver, prefix, match) for subpath in ctx.substate: s = subrepo(ctx, subpath, True) submatch = matchmod.subdirmatcher(subpath, match) @@ -2005,8 +1642,7 @@ # TODO: add support for non-plain formatter (see cmdutil.cat()) for f in match.files(): output = self._gitcommand(["show", "%s:%s" % (rev, f)]) - fp = cmdutil.makefileobj(self._subparent, fntemplate, - self._ctx.node(), + fp = cmdutil.makefileobj(self._ctx, fntemplate, pathname=self.wvfs.reljoin(prefix, f)) fp.write(output) fp.close() diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/subrepoutil.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/subrepoutil.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,392 @@ +# subrepoutil.py - sub-repository operations and substate handling +# +# Copyright 2009-2010 Matt Mackall +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +import errno +import os +import posixpath +import re + +from .i18n import _ +from . import ( + config, + error, + filemerge, + pathutil, + phases, + util, +) + +nullstate = ('', '', 'empty') + +def state(ctx, ui): + """return a state dict, mapping subrepo paths configured in .hgsub + to tuple: (source from .hgsub, revision from .hgsubstate, kind + (key in types dict)) + """ + p = config.config() + repo = ctx.repo() + def read(f, sections=None, remap=None): + if f in ctx: + try: + data = ctx[f].data() + except IOError as err: + if err.errno != errno.ENOENT: + raise + # handle missing subrepo spec files as removed + ui.warn(_("warning: subrepo spec file \'%s\' not found\n") % + repo.pathto(f)) + return + p.parse(f, data, sections, remap, read) + else: + raise error.Abort(_("subrepo spec file \'%s\' not found") % + repo.pathto(f)) + if '.hgsub' in ctx: + read('.hgsub') + + for path, src in ui.configitems('subpaths'): + p.set('subpaths', path, src, ui.configsource('subpaths', path)) + + rev = {} + if '.hgsubstate' in ctx: + try: + for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()): + l = l.lstrip() + if not l: + continue + try: + revision, path = l.split(" ", 1) + except ValueError: + raise error.Abort(_("invalid subrepository revision " + "specifier in \'%s\' line %d") + % (repo.pathto('.hgsubstate'), (i + 1))) + rev[path] = revision + except IOError as err: + if err.errno != errno.ENOENT: + raise + + def remap(src): + for pattern, repl in p.items('subpaths'): + # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub + # does a string decode. + repl = util.escapestr(repl) + # However, we still want to allow back references to go + # through unharmed, so we turn r'\\1' into r'\1'. Again, + # extra escapes are needed because re.sub string decodes. + repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl) + try: + src = re.sub(pattern, repl, src, 1) + except re.error as e: + raise error.Abort(_("bad subrepository pattern in %s: %s") + % (p.source('subpaths', pattern), e)) + return src + + state = {} + for path, src in p[''].items(): + kind = 'hg' + if src.startswith('['): + if ']' not in src: + raise error.Abort(_('missing ] in subrepository source')) + kind, src = src.split(']', 1) + kind = kind[1:] + src = src.lstrip() # strip any extra whitespace after ']' + + if not util.url(src).isabs(): + parent = _abssource(repo, abort=False) + if parent: + parent = util.url(parent) + parent.path = posixpath.join(parent.path or '', src) + parent.path = posixpath.normpath(parent.path) + joined = str(parent) + # Remap the full joined path and use it if it changes, + # else remap the original source. + remapped = remap(joined) + if remapped == joined: + src = remap(src) + else: + src = remapped + + src = remap(src) + state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind) + + return state + +def writestate(repo, state): + """rewrite .hgsubstate in (outer) repo with these subrepo states""" + lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state) + if state[s][1] != nullstate[1]] + repo.wwrite('.hgsubstate', ''.join(lines), '') + +def submerge(repo, wctx, mctx, actx, overwrite, labels=None): + """delegated from merge.applyupdates: merging of .hgsubstate file + in working context, merging context and ancestor context""" + if mctx == actx: # backwards? + actx = wctx.p1() + s1 = wctx.substate + s2 = mctx.substate + sa = actx.substate + sm = {} + + repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx)) + + def debug(s, msg, r=""): + if r: + r = "%s:%s:%s" % r + repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r)) + + promptssrc = filemerge.partextras(labels) + for s, l in sorted(s1.iteritems()): + prompts = None + a = sa.get(s, nullstate) + ld = l # local state with possible dirty flag for compares + if wctx.sub(s).dirty(): + ld = (l[0], l[1] + "+") + if wctx == actx: # overwrite + a = ld + + prompts = promptssrc.copy() + prompts['s'] = s + if s in s2: + r = s2[s] + if ld == r or r == a: # no change or local is newer + sm[s] = l + continue + elif ld == a: # other side changed + debug(s, "other changed, get", r) + wctx.sub(s).get(r, overwrite) + sm[s] = r + elif ld[0] != r[0]: # sources differ + prompts['lo'] = l[0] + prompts['ro'] = r[0] + if repo.ui.promptchoice( + _(' subrepository sources for %(s)s differ\n' + 'use (l)ocal%(l)s source (%(lo)s)' + ' or (r)emote%(o)s source (%(ro)s)?' + '$$ &Local $$ &Remote') % prompts, 0): + debug(s, "prompt changed, get", r) + wctx.sub(s).get(r, overwrite) + sm[s] = r + elif ld[1] == a[1]: # local side is unchanged + debug(s, "other side changed, get", r) + wctx.sub(s).get(r, overwrite) + sm[s] = r + else: + debug(s, "both sides changed") + srepo = wctx.sub(s) + prompts['sl'] = srepo.shortid(l[1]) + prompts['sr'] = srepo.shortid(r[1]) + option = repo.ui.promptchoice( + _(' subrepository %(s)s diverged (local revision: %(sl)s, ' + 'remote revision: %(sr)s)\n' + '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?' + '$$ &Merge $$ &Local $$ &Remote') + % prompts, 0) + if option == 0: + wctx.sub(s).merge(r) + sm[s] = l + debug(s, "merge with", r) + elif option == 1: + sm[s] = l + debug(s, "keep local subrepo revision", l) + else: + wctx.sub(s).get(r, overwrite) + sm[s] = r + debug(s, "get remote subrepo revision", r) + elif ld == a: # remote removed, local unchanged + debug(s, "remote removed, remove") + wctx.sub(s).remove() + elif a == nullstate: # not present in remote or ancestor + debug(s, "local added, keep") + sm[s] = l + continue + else: + if repo.ui.promptchoice( + _(' local%(l)s changed subrepository %(s)s' + ' which remote%(o)s removed\n' + 'use (c)hanged version or (d)elete?' + '$$ &Changed $$ &Delete') % prompts, 0): + debug(s, "prompt remove") + wctx.sub(s).remove() + + for s, r in sorted(s2.items()): + prompts = None + if s in s1: + continue + elif s not in sa: + debug(s, "remote added, get", r) + mctx.sub(s).get(r) + sm[s] = r + elif r != sa[s]: + prompts = promptssrc.copy() + prompts['s'] = s + if repo.ui.promptchoice( + _(' remote%(o)s changed subrepository %(s)s' + ' which local%(l)s removed\n' + 'use (c)hanged version or (d)elete?' + '$$ &Changed $$ &Delete') % prompts, 0) == 0: + debug(s, "prompt recreate", r) + mctx.sub(s).get(r) + sm[s] = r + + # record merged .hgsubstate + writestate(repo, sm) + return sm + +def precommit(ui, wctx, status, match, force=False): + """Calculate .hgsubstate changes that should be applied before committing + + Returns (subs, commitsubs, newstate) where + - subs: changed subrepos (including dirty ones) + - commitsubs: dirty subrepos which the caller needs to commit recursively + - newstate: new state dict which the caller must write to .hgsubstate + + This also updates the given status argument. + """ + subs = [] + commitsubs = set() + newstate = wctx.substate.copy() + + # only manage subrepos and .hgsubstate if .hgsub is present + if '.hgsub' in wctx: + # we'll decide whether to track this ourselves, thanks + for c in status.modified, status.added, status.removed: + if '.hgsubstate' in c: + c.remove('.hgsubstate') + + # compare current state to last committed state + # build new substate based on last committed state + oldstate = wctx.p1().substate + for s in sorted(newstate.keys()): + if not match(s): + # ignore working copy, use old state if present + if s in oldstate: + newstate[s] = oldstate[s] + continue + if not force: + raise error.Abort( + _("commit with new subrepo %s excluded") % s) + dirtyreason = wctx.sub(s).dirtyreason(True) + if dirtyreason: + if not ui.configbool('ui', 'commitsubrepos'): + raise error.Abort(dirtyreason, + hint=_("use --subrepos for recursive commit")) + subs.append(s) + commitsubs.add(s) + else: + bs = wctx.sub(s).basestate() + newstate[s] = (newstate[s][0], bs, newstate[s][2]) + if oldstate.get(s, (None, None, None))[1] != bs: + subs.append(s) + + # check for removed subrepos + for p in wctx.parents(): + r = [s for s in p.substate if s not in newstate] + subs += [s for s in r if match(s)] + if subs: + if (not match('.hgsub') and + '.hgsub' in (wctx.modified() + wctx.added())): + raise error.Abort(_("can't commit subrepos without .hgsub")) + status.modified.insert(0, '.hgsubstate') + + elif '.hgsub' in status.removed: + # clean up .hgsubstate when .hgsub is removed + if ('.hgsubstate' in wctx and + '.hgsubstate' not in (status.modified + status.added + + status.removed)): + status.removed.insert(0, '.hgsubstate') + + return subs, commitsubs, newstate + +def reporelpath(repo): + """return path to this (sub)repo as seen from outermost repo""" + parent = repo + while util.safehasattr(parent, '_subparent'): + parent = parent._subparent + return repo.root[len(pathutil.normasprefix(parent.root)):] + +def subrelpath(sub): + """return path to this subrepo as seen from outermost repo""" + return sub._relpath + +def _abssource(repo, push=False, abort=True): + """return pull/push path of repo - either based on parent repo .hgsub info + or on the top repo config. Abort or return None if no source found.""" + if util.safehasattr(repo, '_subparent'): + source = util.url(repo._subsource) + if source.isabs(): + return bytes(source) + source.path = posixpath.normpath(source.path) + parent = _abssource(repo._subparent, push, abort=False) + if parent: + parent = util.url(util.pconvert(parent)) + parent.path = posixpath.join(parent.path or '', source.path) + parent.path = posixpath.normpath(parent.path) + return bytes(parent) + else: # recursion reached top repo + path = None + if util.safehasattr(repo, '_subtoppath'): + path = repo._subtoppath + elif push and repo.ui.config('paths', 'default-push'): + path = repo.ui.config('paths', 'default-push') + elif repo.ui.config('paths', 'default'): + path = repo.ui.config('paths', 'default') + elif repo.shared(): + # chop off the .hg component to get the default path form. This has + # already run through vfsmod.vfs(..., realpath=True), so it doesn't + # have problems with 'C:' + return os.path.dirname(repo.sharedpath) + if path: + # issue5770: 'C:\' and 'C:' are not equivalent paths. The former is + # as expected: an absolute path to the root of the C: drive. The + # latter is a relative path, and works like so: + # + # C:\>cd C:\some\path + # C:\>D: + # D:\>python -c "import os; print os.path.abspath('C:')" + # C:\some\path + # + # D:\>python -c "import os; print os.path.abspath('C:relative')" + # C:\some\path\relative + if util.hasdriveletter(path): + if len(path) == 2 or path[2:3] not in br'\/': + path = os.path.abspath(path) + return path + + if abort: + raise error.Abort(_("default path for subrepository not found")) + +def newcommitphase(ui, ctx): + commitphase = phases.newcommitphase(ui) + substate = getattr(ctx, "substate", None) + if not substate: + return commitphase + check = ui.config('phases', 'checksubrepos') + if check not in ('ignore', 'follow', 'abort'): + raise error.Abort(_('invalid phases.checksubrepos configuration: %s') + % (check)) + if check == 'ignore': + return commitphase + maxphase = phases.public + maxsub = None + for s in sorted(substate): + sub = ctx.sub(s) + subphase = sub.phase(substate[s][1]) + if maxphase < subphase: + maxphase = subphase + maxsub = s + if commitphase < maxphase: + if check == 'abort': + raise error.Abort(_("can't commit in %s phase" + " conflicting %s from subrepository %s") % + (phases.phasenames[commitphase], + phases.phasenames[maxphase], maxsub)) + ui.warn(_("warning: changes are committed in" + " %s phase from subrepository %s\n") % + (phases.phasenames[maxphase], maxsub)) + return maxphase + return commitphase diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/tags.py --- a/mercurial/tags.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/tags.py Sat Feb 24 17:49:10 2018 -0600 @@ -244,7 +244,7 @@ # remove tags pointing to invalid nodes cl = repo.changelog - for t in filetags.keys(): + for t in list(filetags): try: cl.rev(filetags[t][0]) except (LookupError, ValueError): @@ -739,7 +739,7 @@ entry = bytearray(prefix + fnode) self._raw[offset:offset + _fnodesrecsize] = entry # self._dirtyoffset could be None. - self._dirtyoffset = min(self._dirtyoffset, offset) or 0 + self._dirtyoffset = min(self._dirtyoffset or 0, offset or 0) def write(self): """Perform all necessary writes to cache file. @@ -783,6 +783,6 @@ except (IOError, OSError) as inst: repo.ui.log('tagscache', "couldn't write cache/%s: %s\n" % ( - _fnodescachefile, inst)) + _fnodescachefile, util.forcebytestr(inst))) finally: lock.release() diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/templatefilters.py --- a/mercurial/templatefilters.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/templatefilters.py Sat Feb 24 17:49:10 2018 -0600 @@ -100,6 +100,13 @@ """List or text. Returns the length as an integer.""" return len(i) +@templatefilter('dirname') +def dirname(path): + """Any text. Treats the text as a path, and strips the last + component of the path after splitting by the path separator. + """ + return os.path.dirname(path) + @templatefilter('domain') def domain(author): """Any text. Finds the first string that looks like an email diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/templatekw.py --- a/mercurial/templatekw.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/templatekw.py Sat Feb 24 17:49:10 2018 -0600 @@ -192,11 +192,15 @@ def one(v, tag=name): try: vmapping.update(v) - except (AttributeError, ValueError): + # Python 2 raises ValueError if the type of v is wrong. Python + # 3 raises TypeError. + except (AttributeError, TypeError, ValueError): try: + # Python 2 raises ValueError trying to destructure an e.g. + # bytes. Python 3 raises TypeError. for a, b in v: vmapping[a] = b - except ValueError: + except (TypeError, ValueError): vmapping[name] = v return templ(tag, **pycompat.strkwargs(vmapping)) lastname = 'last_' + name @@ -722,6 +726,11 @@ lambda x: {'ctx': repo[x], 'revcache': {}}, lambda x: scmutil.formatchangeid(repo[x])) +@templatekeyword('reporoot') +def showreporoot(repo, **args): + """String. The root directory of the current repository.""" + return repo.root + @templatekeyword("successorssets") def showsuccessorssets(repo, ctx, **args): """Returns a string of sets of successors for a changectx. Format used @@ -893,17 +902,6 @@ """Integer. The width of the current terminal.""" return repo.ui.termwidth() -@templatekeyword('troubles') -def showtroubles(repo, **args): - """List of strings. Evolution troubles affecting the changeset. - (DEPRECATED) - """ - msg = ("'troubles' is deprecated, " - "use 'instabilities'") - repo.ui.deprecwarn(msg, '4.4') - - return showinstabilities(repo=repo, **args) - @templatekeyword('instabilities') def showinstabilities(**args): """List of strings. Evolution instabilities affecting the changeset. @@ -917,7 +915,7 @@ def showverbosity(ui, **args): """String. The current output verbosity in 'debug', 'quiet', 'verbose', or ''.""" - # see cmdutil.changeset_templater for priority of these flags + # see logcmdutil.changesettemplater for priority of these flags if ui.debugflag: return 'debug' elif ui.quiet: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/templater.py --- a/mercurial/templater.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/templater.py Sat Feb 24 17:49:10 2018 -0600 @@ -161,6 +161,39 @@ ([('string', 'foo\\')], 6) """ parsed = [] + for typ, val, pos in _scantemplate(tmpl, start, stop, quote): + if typ == 'string': + parsed.append((typ, val)) + elif typ == 'template': + parsed.append(val) + elif typ == 'end': + return parsed, pos + else: + raise error.ProgrammingError('unexpected type: %s' % typ) + raise error.ProgrammingError('unterminated scanning of template') + +def scantemplate(tmpl): + """Scan (type, start, end) positions of outermost elements in template + + >>> list(scantemplate(b'foo{bar}"baz')) + [('string', 0, 3), ('template', 3, 8), ('string', 8, 12)] + >>> list(scantemplate(b'outer{"inner"}outer')) + [('string', 0, 5), ('template', 5, 14), ('string', 14, 19)] + >>> list(scantemplate(b'foo\\{escaped}')) + [('string', 0, 5), ('string', 5, 13)] + """ + last = None + for typ, val, pos in _scantemplate(tmpl, 0, len(tmpl)): + if last: + yield last + (pos,) + if typ == 'end': + return + else: + last = (typ, pos) + raise error.ProgrammingError('unterminated scanning of template') + +def _scantemplate(tmpl, start, stop, quote=''): + """Parse template string into chunks of strings and template expressions""" sepchars = '{' + quote pos = start p = parser.parser(elements) @@ -168,29 +201,30 @@ n = min((tmpl.find(c, pos, stop) for c in sepchars), key=lambda n: (n < 0, n)) if n < 0: - parsed.append(('string', parser.unescapestr(tmpl[pos:stop]))) + yield ('string', parser.unescapestr(tmpl[pos:stop]), pos) pos = stop break c = tmpl[n:n + 1] bs = (n - pos) - len(tmpl[pos:n].rstrip('\\')) if bs % 2 == 1: # escaped (e.g. '\{', '\\\{', but not '\\{') - parsed.append(('string', parser.unescapestr(tmpl[pos:n - 1]) + c)) + yield ('string', parser.unescapestr(tmpl[pos:n - 1]) + c, pos) pos = n + 1 continue if n > pos: - parsed.append(('string', parser.unescapestr(tmpl[pos:n]))) + yield ('string', parser.unescapestr(tmpl[pos:n]), pos) if c == quote: - return parsed, n + 1 + yield ('end', None, n + 1) + return parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, '}')) if not tmpl.endswith('}', n + 1, pos): raise error.ParseError(_("invalid token"), pos) - parsed.append(parseres) + yield ('template', parseres, n) if quote: raise error.ParseError(_("unterminated string"), start) - return parsed, pos + yield ('end', None, pos) def _unnesttemplatelist(tree): """Expand list of templates to node tuple diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/templates/gitweb/changeset.tmpl --- a/mercurial/templates/gitweb/changeset.tmpl Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/templates/gitweb/changeset.tmpl Sat Feb 24 17:49:10 2018 -0600 @@ -44,7 +44,7 @@ changeset {rev} {node|short} -{if(obsolete, 'obsolete{succsandmarkers%obsfateentry}')} +{if(obsolete, succsandmarkers%obsfateentry)} {ifeq(count(parent), '2', parent%changesetparentdiff, parent%changesetparent)} {child%changesetchild} diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/templates/gitweb/map --- a/mercurial/templates/gitweb/map Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/templates/gitweb/map Sat Feb 24 17:49:10 2018 -0600 @@ -275,7 +275,13 @@ obsfatesuccessors = '{if(successors, ' as ')}{successors%successorlink}' obsfateverb = '{obsfateverb(successors, markers)}' obsfateoperations = '{if(obsfateoperations(markers), ' using {join(obsfateoperations(markers), ', ')}')}' -obsfateentry = '{obsfateverb}{obsfateoperations}{obsfatesuccessors}' +obsfateusers = '{if(obsfateusers(markers), ' by {join(obsfateusers(markers)%'{user|obfuscate}', ', ')}')}' +obsfatedate = '{if(obsfatedate(markers), ' {ifeq(min(obsfatedate(markers)), max(obsfatedate(markers)), '{min(obsfatedate(markers))|rfc822date}', 'between {min(obsfatedate(markers))|rfc822date} and {max(obsfatedate(markers))|rfc822date}')}')}' +obsfateentry = ' + + obsolete + {obsfateverb}{obsfateoperations}{obsfatesuccessors}{obsfateusers}{obsfatedate} + ' shortlogentry = ' {date|rfc822date} diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/templates/monoblue/changeset.tmpl --- a/mercurial/templates/monoblue/changeset.tmpl Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/templates/monoblue/changeset.tmpl Sat Feb 24 17:49:10 2018 -0600 @@ -48,7 +48,7 @@ {branch%changesetbranch}
changeset {rev}
{node|short}
- {if(obsolete, '
obsolete
{succsandmarkers%obsfateentry}
')} + {if(obsolete, succsandmarkers%obsfateentry)} {ifeq(count(parent), '2', parent%changesetparentdiff, parent%changesetparent)} {child%changesetchild} diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/templates/monoblue/map --- a/mercurial/templates/monoblue/map Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/templates/monoblue/map Sat Feb 24 17:49:10 2018 -0600 @@ -233,7 +233,11 @@ obsfatesuccessors = '{if(successors, ' as ')}{successors%successorlink}' obsfateverb = '{obsfateverb(successors, markers)}' obsfateoperations = '{if(obsfateoperations(markers), ' using {join(obsfateoperations(markers), ', ')}')}' -obsfateentry = '{obsfateverb}{obsfateoperations}{obsfatesuccessors}' +obsfateusers = '{if(obsfateusers(markers), ' by {join(obsfateusers(markers)%'{user|obfuscate}', ', ')}')}' +obsfatedate = '{if(obsfatedate(markers), ' {ifeq(min(obsfatedate(markers)), max(obsfatedate(markers)), '{min(obsfatedate(markers))|rfc822date}', 'between {min(obsfatedate(markers))|rfc822date} and {max(obsfatedate(markers))|rfc822date}')}')}' +obsfateentry = ' +
obsolete
+
{obsfateverb}{obsfateoperations}{obsfatesuccessors}{obsfateusers}{obsfatedate}
' shortlogentry = ' {date|rfc822date} diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/templates/paper/changeset.tmpl --- a/mercurial/templates/paper/changeset.tmpl Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/templates/paper/changeset.tmpl Sat Feb 24 17:49:10 2018 -0600 @@ -51,7 +51,7 @@ {if(obsolete, ' obsolete - {succsandmarkers%obsfateentry} + {join(succsandmarkers%obsfateentry, '
\n')} ')} parents diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/templates/paper/map --- a/mercurial/templates/paper/map Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/templates/paper/map Sat Feb 24 17:49:10 2018 -0600 @@ -213,7 +213,9 @@ obsfatesuccessors = '{if(successors, ' as ')}{successors%successorlink}' obsfateverb = '{obsfateverb(successors, markers)}' obsfateoperations = '{if(obsfateoperations(markers), ' using {join(obsfateoperations(markers), ', ')}')}' -obsfateentry = '{obsfateverb}{obsfateoperations}{obsfatesuccessors}' +obsfateusers = '{if(obsfateusers(markers), ' by {join(obsfateusers(markers)%'{user|obfuscate}', ', ')}')}' +obsfatedate = '{if(obsfatedate(markers), ' {ifeq(min(obsfatedate(markers)), max(obsfatedate(markers)), '{min(obsfatedate(markers))|rfc822date}', 'between {min(obsfatedate(markers))|rfc822date} and {max(obsfatedate(markers))|rfc822date}')}')}' +obsfateentry = '{obsfateverb}{obsfateoperations}{obsfatesuccessors}{obsfateusers}{obsfatedate}' filediffparent = ' diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/templates/spartan/changelogentry.tmpl --- a/mercurial/templates/spartan/changelogentry.tmpl Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/templates/spartan/changelogentry.tmpl Sat Feb 24 17:49:10 2018 -0600 @@ -22,10 +22,7 @@ phase: {phase|escape} ')} - {if(obsolete, ' - obsolete: - {succsandmarkers%obsfateentry} - ')} + {if(obsolete, succsandmarkers%obsfateentry)} {ifeq(count(instabilities), '0', '', ' instabilities: {instabilities%"{instability} "|escape} diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/templates/spartan/changeset.tmpl --- a/mercurial/templates/spartan/changeset.tmpl Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/templates/spartan/changeset.tmpl Sat Feb 24 17:49:10 2018 -0600 @@ -37,10 +37,7 @@ phase: {phase|escape} ')} -{if(obsolete, ' - obsolete: - {succsandmarkers%obsfateentry} -')} +{if(obsolete, succsandmarkers%obsfateentry)} {ifeq(count(instabilities), '0', '', ' instabilities: {instabilities%"{instability} "|escape} diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/templates/spartan/map --- a/mercurial/templates/spartan/map Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/templates/spartan/map Sat Feb 24 17:49:10 2018 -0600 @@ -170,7 +170,13 @@ obsfatesuccessors = '{if(successors, ' as ')}{successors%successorlink}' obsfateverb = '{obsfateverb(successors, markers)}' obsfateoperations = '{if(obsfateoperations(markers), ' using {join(obsfateoperations(markers), ', ')}')}' -obsfateentry = '{obsfateverb}{obsfateoperations}{obsfatesuccessors}' +obsfateusers = '{if(obsfateusers(markers), ' by {join(obsfateusers(markers)%'{user|obfuscate}', ', ')}')}' +obsfatedate = '{if(obsfatedate(markers), ' {ifeq(min(obsfatedate(markers)), max(obsfatedate(markers)), '{min(obsfatedate(markers))|rfc822date}', 'between {min(obsfatedate(markers))|rfc822date} and {max(obsfatedate(markers))|rfc822date}')}')}' +obsfateentry = ' + + obsolete: + {obsfateverb}{obsfateoperations}{obsfatesuccessors}{obsfateusers}{obsfatedate} + ' filediffparent = ' parent {rev}: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/templates/static/style-gitweb.css --- a/mercurial/templates/static/style-gitweb.css Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/templates/static/style-gitweb.css Sat Feb 24 17:49:10 2018 -0600 @@ -29,7 +29,7 @@ div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; } div.log_body { padding:8px 8px 8px 150px; } .age { white-space:nowrap; } -span.age { position:relative; float:left; width:142px; font-style:italic; } +a.title span.age { position:relative; float:left; width:142px; font-style:italic; } div.log_link { padding:0px 8px; font-size:10px; font-family:sans-serif; font-style:normal; diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/transaction.py --- a/mercurial/transaction.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/transaction.py Sat Feb 24 17:49:10 2018 -0600 @@ -612,7 +612,7 @@ lines = fp.readlines() if lines: ver = lines[0][:-1] - if ver == str(version): + if ver == (b'%d' % version): for line in lines[1:]: if line: # Shave off the trailing newline diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/ui.py --- a/mercurial/ui.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/ui.py Sat Feb 24 17:49:10 2018 -0600 @@ -45,7 +45,7 @@ if not c.isalnum()) # The config knobs that will be altered (if unset) by ui.tweakdefaults. -tweakrc = """ +tweakrc = b""" [ui] # The rollback command is dangerous. As a rule, don't use it. rollback = False @@ -148,14 +148,10 @@ } def _maybestrurl(maybebytes): - if maybebytes is None: - return None - return pycompat.strurl(maybebytes) + return util.rapply(pycompat.strurl, maybebytes) def _maybebytesurl(maybestr): - if maybestr is None: - return None - return pycompat.bytesurl(maybestr) + return util.rapply(pycompat.bytesurl, maybestr) class httppasswordmgrdbproxy(object): """Delays loading urllib2 until it's needed.""" @@ -168,18 +164,14 @@ return self._mgr def add_password(self, realm, uris, user, passwd): - if isinstance(uris, tuple): - uris = tuple(_maybestrurl(u) for u in uris) - else: - uris = _maybestrurl(uris) return self._get_mgr().add_password( - _maybestrurl(realm), uris, + _maybestrurl(realm), _maybestrurl(uris), _maybestrurl(user), _maybestrurl(passwd)) def find_user_password(self, realm, uri): - return tuple(_maybebytesurl(v) for v in - self._get_mgr().find_user_password(_maybestrurl(realm), - _maybestrurl(uri))) + mgr = self._get_mgr() + return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm), + _maybestrurl(uri))) def _catchterm(*args): raise error.SignalInterrupt @@ -506,7 +498,7 @@ and default != itemdefault): msg = ("specifying a mismatched default value for a registered " "config item: '%s.%s' '%s'") - msg %= (section, name, default) + msg %= (section, name, pycompat.bytestr(default)) self.develwarn(msg, 2, 'warn-config-default') for s, n in alternates: @@ -816,8 +808,8 @@ hint=_("use 'hg config --edit' " 'to set your username')) if "\n" in user: - raise error.Abort(_("username %s contains a newline\n") - % repr(user)) + raise error.Abort(_("username %r contains a newline\n") + % pycompat.bytestr(user)) return user def shortuser(self, user): @@ -878,6 +870,17 @@ return "".join(self._buffers.pop()) + def canwritewithoutlabels(self): + '''check if write skips the label''' + if self._buffers and not self._bufferapplylabels: + return True + return self._colormode is None + + def canbatchlabeledwrites(self): + '''check if write calls with labels are batchable''' + # Windows color printing is special, see ``write``. + return self._colormode != 'win32' + def write(self, *args, **opts): '''write args to output @@ -894,13 +897,17 @@ "cmdname.type" is recommended. For example, status issues a label of "status.modified" for modified files. ''' - if self._buffers and not opts.get(r'prompt', False): + if self._buffers: if self._bufferapplylabels: label = opts.get(r'label', '') self._buffers[-1].extend(self.label(a, label) for a in args) else: self._buffers[-1].extend(args) - elif self._colormode == 'win32': + else: + self._writenobuf(*args, **opts) + + def _writenobuf(self, *args, **opts): + if self._colormode == 'win32': # windows color printing is its own can of crab, defer to # the color module and that is it. color.win32print(self, self._write, *args, **opts) @@ -916,8 +923,7 @@ # opencode timeblockedsection because this is a critical path starttime = util.timer() try: - for a in msgs: - self.fout.write(a) + self.fout.write(''.join(msgs)) except IOError as err: raise error.StdioError(err) finally: @@ -1255,7 +1261,7 @@ return i - def _readline(self, prompt=''): + def _readline(self): if self._isatty(self.fin): try: # magically add command line editing support, where @@ -1267,11 +1273,6 @@ except Exception: pass - # call write() so output goes through subclassed implementation - # e.g. color extension on Windows - self.write(prompt, prompt=True) - self.flush() - # prompt ' ' must exist; otherwise readline may delete entire line # - http://bugs.python.org/issue12833 with self.timeblockedsection('stdio'): @@ -1290,8 +1291,10 @@ if not self.interactive(): self.write(msg, ' ', default or '', "\n") return default + self._writenobuf(msg, label='ui.prompt') + self.flush() try: - r = self._readline(self.label(msg, 'ui.prompt')) + r = self._readline() if not r: r = default if self.configbool('ui', 'promptecho'): @@ -1509,11 +1512,7 @@ ''.join(exconly)) else: output = traceback.format_exception(exc[0], exc[1], exc[2]) - data = r''.join(output) - if pycompat.ispy3: - enc = pycompat.sysstr(encoding.encoding) - data = data.encode(enc, errors=r'replace') - self.write_err(data) + self.write_err(encoding.strtolocal(r''.join(output))) return self.tracebackflag or force def geteditor(self): @@ -1621,13 +1620,15 @@ else: curframe = inspect.currentframe() calframe = inspect.getouterframes(curframe, 2) - self.write_err('%s at: %s:%s (%s)\n' - % ((msg,) + calframe[stacklevel][1:4])) - self.log('develwarn', '%s at: %s:%s (%s)\n', - msg, *calframe[stacklevel][1:4]) + fname, lineno, fmsg = calframe[stacklevel][1:4] + fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg) + self.write_err('%s at: %s:%d (%s)\n' + % (msg, fname, lineno, fmsg)) + self.log('develwarn', '%s at: %s:%d (%s)\n', + msg, fname, lineno, fmsg) curframe = calframe = None # avoid cycles - def deprecwarn(self, msg, version): + def deprecwarn(self, msg, version, stacklevel=2): """issue a deprecation warning - msg: message explaining what is deprecated and how to upgrade, @@ -1638,7 +1639,7 @@ return msg += ("\n(compatibility will be dropped after Mercurial-%s," " update your code.)") % version - self.develwarn(msg, stacklevel=2, config='deprec-warn') + self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn') def exportableenviron(self): """The environment variables that are safe to export, e.g. through diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/upgrade.py --- a/mercurial/upgrade.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/upgrade.py Sat Feb 24 17:49:10 2018 -0600 @@ -46,7 +46,6 @@ return { # The upgrade code does not yet support these experimental features. # This is an artificial limitation. - 'manifestv2', 'treemanifest', # This was a precursor to generaldelta and was never enabled by default. # It should (hopefully) not exist in the wild. diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/url.py --- a/mercurial/url.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/url.py Sat Feb 24 17:49:10 2018 -0600 @@ -71,10 +71,10 @@ u.query = None if not self.ui.interactive(): raise error.Abort(_('http authorization required for %s') % - util.hidepassword(str(u))) + util.hidepassword(bytes(u))) self.ui.write(_("http authorization required for %s\n") % - util.hidepassword(str(u))) + util.hidepassword(bytes(u))) self.ui.write(_("realm: %s\n") % realm) if user: self.ui.write(_("user: %s\n") % user) @@ -124,10 +124,9 @@ else: self.no_list = no_list - proxyurl = str(proxy) + proxyurl = bytes(proxy) proxies = {'http': proxyurl, 'https': proxyurl} - ui.debug('proxying through http://%s:%s\n' % - (proxy.host, proxy.port)) + ui.debug('proxying through %s\n' % util.hidepassword(proxyurl)) else: proxies = {} diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/urllibcompat.py --- a/mercurial/urllibcompat.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/urllibcompat.py Sat Feb 24 17:49:10 2018 -0600 @@ -47,6 +47,7 @@ "urlparse", "urlunparse", )) + urlreq._registeralias(urllib.parse, "parse_qs", "parseqs") urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote") import urllib.request urlreq._registeraliases(urllib.request, ( @@ -157,6 +158,7 @@ "urlparse", "urlunparse", )) + urlreq._registeralias(urlparse, "parse_qs", "parseqs") urlerr._registeraliases(urllib2, ( "HTTPError", "URLError", diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/util.py --- a/mercurial/util.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/util.py Sat Feb 24 17:49:10 2018 -0600 @@ -183,6 +183,39 @@ def safehasattr(thing, attr): return getattr(thing, attr, _notset) is not _notset +def _rapply(f, xs): + if xs is None: + # assume None means non-value of optional data + return xs + if isinstance(xs, (list, set, tuple)): + return type(xs)(_rapply(f, x) for x in xs) + if isinstance(xs, dict): + return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items()) + return f(xs) + +def rapply(f, xs): + """Apply function recursively to every item preserving the data structure + + >>> def f(x): + ... return 'f(%s)' % x + >>> rapply(f, None) is None + True + >>> rapply(f, 'a') + 'f(a)' + >>> rapply(f, {'a'}) == {'f(a)'} + True + >>> rapply(f, ['a', 'b', None, {'c': 'd'}, []]) + ['f(a)', 'f(b)', None, {'f(c)': 'f(d)'}, []] + + >>> xs = [object()] + >>> rapply(pycompat.identity, xs) is xs + True + """ + if f is pycompat.identity: + # fast path mainly for py2 + return xs + return _rapply(f, xs) + def bytesinput(fin, fout, *args, **kwargs): sin, sout = sys.stdin, sys.stdout try: @@ -220,7 +253,7 @@ if _dowarn: msg += ("\n(compatibility will be dropped after Mercurial-%s," " update your code.)") % version - warnings.warn(msg, DeprecationWarning, stacklevel + 1) + warnings.warn(pycompat.sysstr(msg), DeprecationWarning, stacklevel + 1) DIGESTS = { 'md5': hashlib.md5, @@ -1947,6 +1980,35 @@ limit -= len(s) yield s +class cappedreader(object): + """A file object proxy that allows reading up to N bytes. + + Given a source file object, instances of this type allow reading up to + N bytes from that source file object. Attempts to read past the allowed + limit are treated as EOF. + + It is assumed that I/O is not performed on the original file object + in addition to I/O that is performed by this instance. If there is, + state tracking will get out of sync and unexpected results will ensue. + """ + def __init__(self, fh, limit): + """Allow reading up to bytes from .""" + self._fh = fh + self._left = limit + + def read(self, n=-1): + if not self._left: + return b'' + + if n < 0: + n = self._left + + data = self._fh.read(min(n, self._left)) + self._left -= len(data) + assert self._left >= 0 + + return data + def makedate(timestamp=None): '''Return a unix timestamp (or the current time) as a (unixtime, offset) tuple based off the local timezone.''' @@ -2394,7 +2456,7 @@ def uirepr(s): # Avoid double backslash in Windows path repr() - return repr(s).replace('\\\\', '\\') + return pycompat.byterepr(pycompat.bytestr(s)).replace(b'\\\\', b'\\') # delay import of textwrap def MBTextWrapper(**kwargs): @@ -2684,7 +2746,7 @@ pass try: - return socket.getservbyname(port) + return socket.getservbyname(pycompat.sysstr(port)) except socket.error: raise Abort(_("no port number associated with service '%s'") % port) @@ -3126,7 +3188,7 @@ results.append(hook(*args)) return results -def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0): +def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%d', depth=0): '''Yields lines for a nicely formatted stacktrace. Skips the 'skip' last entries, then return the last 'depth' entries. Each file+linenumber is formatted according to fileline. @@ -3138,7 +3200,7 @@ Not be used in production code but very convenient while developing. ''' - entries = [(fileline % (fn, ln), func) + entries = [(fileline % (pycompat.sysbytes(fn), ln), pycompat.sysbytes(func)) for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1] ][-depth:] if entries: diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/verify.py --- a/mercurial/verify.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/verify.py Sat Feb 24 17:49:10 2018 -0600 @@ -60,6 +60,7 @@ def err(self, linkrev, msg, filename=None): if linkrev is not None: self.badrevs.add(linkrev) + linkrev = "%d" % linkrev else: linkrev = '?' msg = "%s: %s" % (linkrev, msg) @@ -455,12 +456,7 @@ if rp: if lr is not None and ui.verbose: ctx = lrugetctx(lr) - found = False - for pctx in ctx.parents(): - if rp[0] in pctx: - found = True - break - if not found: + if not any(rp[0] in pctx for pctx in ctx.parents()): self.warn(_("warning: copy source of '%s' not" " in parents of %s") % (f, ctx)) fl2 = repo.file(rp[0]) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/wireproto.py --- a/mercurial/wireproto.py Fri Feb 23 17:57:04 2018 -0800 +++ b/mercurial/wireproto.py Sat Feb 24 17:49:10 2018 -0600 @@ -31,56 +31,24 @@ repository, streamclone, util, + wireprototypes, ) urlerr = util.urlerr urlreq = util.urlreq +bytesresponse = wireprototypes.bytesresponse +ooberror = wireprototypes.ooberror +pushres = wireprototypes.pushres +pusherr = wireprototypes.pusherr +streamres = wireprototypes.streamres +streamres_legacy = wireprototypes.streamreslegacy + bundle2requiredmain = _('incompatible Mercurial client; bundle2 required') bundle2requiredhint = _('see https://www.mercurial-scm.org/wiki/' 'IncompatibleClient') bundle2required = '%s\n(%s)\n' % (bundle2requiredmain, bundle2requiredhint) -class abstractserverproto(object): - """abstract class that summarizes the protocol API - - Used as reference and documentation. - """ - - def getargs(self, args): - """return the value for arguments in - - returns a list of values (same order as )""" - raise NotImplementedError() - - def getfile(self, fp): - """write the whole content of a file into a file like object - - The file is in the form:: - - (\n)+0\n - - chunk size is the ascii version of the int. - """ - raise NotImplementedError() - - def redirect(self): - """may setup interception for stdout and stderr - - See also the `restore` method.""" - raise NotImplementedError() - - # If the `redirect` function does install interception, the `restore` - # function MUST be defined. If interception is not used, this function - # MUST NOT be defined. - # - # left commented here on purpose - # - #def restore(self): - # """reinstall previous stdout and stderr and return intercepted stdout - # """ - # raise NotImplementedError() - class remoteiterbatcher(peer.iterbatcher): def __init__(self, remote): super(remoteiterbatcher, self).__init__() @@ -517,58 +485,6 @@ # server side # wire protocol command can either return a string or one of these classes. -class streamres(object): - """wireproto reply: binary stream - - The call was successful and the result is a stream. - - Accepts a generator containing chunks of data to be sent to the client. - - ``prefer_uncompressed`` indicates that the data is expected to be - uncompressable and that the stream should therefore use the ``none`` - engine. - """ - def __init__(self, gen=None, prefer_uncompressed=False): - self.gen = gen - self.prefer_uncompressed = prefer_uncompressed - -class streamres_legacy(object): - """wireproto reply: uncompressed binary stream - - The call was successful and the result is a stream. - - Accepts a generator containing chunks of data to be sent to the client. - - Like ``streamres``, but sends an uncompressed data for "version 1" clients - using the application/mercurial-0.1 media type. - """ - def __init__(self, gen=None): - self.gen = gen - -class pushres(object): - """wireproto reply: success with simple integer return - - The call was successful and returned an integer contained in `self.res`. - """ - def __init__(self, res): - self.res = res - -class pusherr(object): - """wireproto reply: failure - - The call failed. The `self.res` attribute contains the error message. - """ - def __init__(self, res): - self.res = res - -class ooberror(object): - """wireproto reply: failure of a batch of operation - - Something failed during a batch call. The error message is stored in - `self.message`. - """ - def __init__(self, message): - self.message = message def getdispatchrepo(repo, proto, command): """Obtain the repo used for processing wire protocol commands. @@ -625,7 +541,7 @@ return ui.configbool('server', 'bundle1') -def supportedcompengines(ui, proto, role): +def supportedcompengines(ui, role): """Obtain the list of supported compression engines for a request.""" assert role in (util.CLIENTROLE, util.SERVERROLE) @@ -674,13 +590,81 @@ return compengines -# list of commands -commands = {} +class commandentry(object): + """Represents a declared wire protocol command.""" + def __init__(self, func, args=''): + self.func = func + self.args = args + + def _merge(self, func, args): + """Merge this instance with an incoming 2-tuple. + + This is called when a caller using the old 2-tuple API attempts + to replace an instance. The incoming values are merged with + data not captured by the 2-tuple and a new instance containing + the union of the two objects is returned. + """ + return commandentry(func, args) + + # Old code treats instances as 2-tuples. So expose that interface. + def __iter__(self): + yield self.func + yield self.args + + def __getitem__(self, i): + if i == 0: + return self.func + elif i == 1: + return self.args + else: + raise IndexError('can only access elements 0 and 1') + +class commanddict(dict): + """Container for registered wire protocol commands. + + It behaves like a dict. But __setitem__ is overwritten to allow silent + coercion of values from 2-tuples for API compatibility. + """ + def __setitem__(self, k, v): + if isinstance(v, commandentry): + pass + # Cast 2-tuples to commandentry instances. + elif isinstance(v, tuple): + if len(v) != 2: + raise ValueError('command tuples must have exactly 2 elements') + + # It is common for extensions to wrap wire protocol commands via + # e.g. ``wireproto.commands[x] = (newfn, args)``. Because callers + # doing this aren't aware of the new API that uses objects to store + # command entries, we automatically merge old state with new. + if k in self: + v = self[k]._merge(v[0], v[1]) + else: + v = commandentry(v[0], v[1]) + else: + raise ValueError('command entries must be commandentry instances ' + 'or 2-tuples') + + return super(commanddict, self).__setitem__(k, v) + + def commandavailable(self, command, proto): + """Determine if a command is available for the requested protocol.""" + # For now, commands are available for all protocols. So do a simple + # membership test. + return command in self + +commands = commanddict() def wireprotocommand(name, args=''): - """decorator for wire protocol command""" + """Decorator to declare a wire protocol command. + + ``name`` is the name of the wire protocol command being provided. + + ``args`` is a space-delimited list of named arguments that the command + accepts. ``*`` is a special value that says to accept all arguments. + """ def register(func): - commands[name] = (func, args) + commands[name] = commandentry(func, args) return func return register @@ -713,8 +697,15 @@ result = func(repo, proto) if isinstance(result, ooberror): return result + + # For now, all batchable commands must return bytesresponse or + # raw bytes (for backwards compatibility). + assert isinstance(result, (bytesresponse, bytes)) + if isinstance(result, bytesresponse): + result = result.data res.append(escapearg(result)) - return ';'.join(res) + + return bytesresponse(';'.join(res)) @wireprotocommand('between', 'pairs') def between(repo, proto, pairs): @@ -722,7 +713,8 @@ r = [] for b in repo.between(pairs): r.append(encodelist(b) + "\n") - return "".join(r) + + return bytesresponse(''.join(r)) @wireprotocommand('branchmap') def branchmap(repo, proto): @@ -732,7 +724,8 @@ branchname = urlreq.quote(encoding.fromlocal(branch)) branchnodes = encodelist(nodes) heads.append('%s %s' % (branchname, branchnodes)) - return '\n'.join(heads) + + return bytesresponse('\n'.join(heads)) @wireprotocommand('branches', 'nodes') def branches(repo, proto, nodes): @@ -740,7 +733,8 @@ r = [] for b in repo.branches(nodes): r.append(encodelist(b) + "\n") - return "".join(r) + + return bytesresponse(''.join(r)) @wireprotocommand('clonebundles', '') def clonebundles(repo, proto): @@ -752,7 +746,7 @@ depending on the request. e.g. you could advertise URLs for the closest data center given the client's IP address. """ - return repo.vfs.tryread('clonebundles.manifest') + return bytesresponse(repo.vfs.tryread('clonebundles.manifest')) wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey', 'known', 'getbundle', 'unbundlehash', 'batch'] @@ -784,7 +778,7 @@ caps.append('bundle2=' + urlreq.quote(capsblob)) caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority)) - if proto.name == 'http': + if proto.name == 'http-v1': caps.append('httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen')) if repo.ui.configbool('experimental', 'httppostargs'): @@ -794,7 +788,7 @@ # FUTURE advertise minrx and mintx after consulting config option caps.append('httpmediatype=0.1rx,0.1tx,0.2tx') - compengines = supportedcompengines(repo.ui, proto, util.SERVERROLE) + compengines = supportedcompengines(repo.ui, util.SERVERROLE) if compengines: comptypes = ','.join(urlreq.quote(e.wireprotosupport().name) for e in compengines) @@ -806,7 +800,7 @@ # `_capabilities` instead. @wireprotocommand('capabilities') def capabilities(repo, proto): - return ' '.join(_capabilities(repo, proto)) + return bytesresponse(' '.join(_capabilities(repo, proto))) @wireprotocommand('changegroup', 'roots') def changegroup(repo, proto, roots): @@ -831,7 +825,8 @@ def debugwireargs(repo, proto, one, two, others): # only accept optional args from the known set opts = options('debugwireargs', ['three', 'four'], others) - return repo.debugwireargs(one, two, **pycompat.strkwargs(opts)) + return bytesresponse(repo.debugwireargs(one, two, + **pycompat.strkwargs(opts))) @wireprotocommand('getbundle', '*') def getbundle(repo, proto, others): @@ -857,7 +852,7 @@ if not bundle1allowed(repo, 'pull'): if not exchange.bundle2requested(opts.get('bundlecaps')): - if proto.name == 'http': + if proto.name == 'http-v1': return ooberror(bundle2required) raise error.Abort(bundle2requiredmain, hint=bundle2requiredhint) @@ -883,12 +878,12 @@ except error.Abort as exc: # cleanly forward Abort error to the client if not exchange.bundle2requested(opts.get('bundlecaps')): - if proto.name == 'http': - return ooberror(str(exc) + '\n') + if proto.name == 'http-v1': + return ooberror(pycompat.bytestr(exc) + '\n') raise # cannot do better for bundle1 + ssh # bundle2 request expect a bundle2 reply bundler = bundle2.bundle20(repo.ui) - manargs = [('message', str(exc))] + manargs = [('message', pycompat.bytestr(exc))] advargs = [] if exc.hint is not None: advargs.append(('hint', exc.hint)) @@ -902,23 +897,27 @@ @wireprotocommand('heads') def heads(repo, proto): h = repo.heads() - return encodelist(h) + "\n" + return bytesresponse(encodelist(h) + '\n') @wireprotocommand('hello') def hello(repo, proto): - '''the hello command returns a set of lines describing various - interesting things about the server, in an RFC822-like format. - Currently the only one defined is "capabilities", which - consists of a line in the form: + """Called as part of SSH handshake to obtain server info. + + Returns a list of lines describing interesting things about the + server, in an RFC822-like format. - capabilities: space separated list of tokens - ''' - return "capabilities: %s\n" % (capabilities(repo, proto)) + Currently, the only one defined is ``capabilities``, which consists of a + line of space separated tokens describing server abilities: + + capabilities: + """ + caps = capabilities(repo, proto).data + return bytesresponse('capabilities: %s\n' % caps) @wireprotocommand('listkeys', 'namespace') def listkeys(repo, proto, namespace): d = repo.listkeys(encoding.tolocal(namespace)).items() - return pushkeymod.encodekeys(d) + return bytesresponse(pushkeymod.encodekeys(d)) @wireprotocommand('lookup', 'key') def lookup(repo, proto, key): @@ -928,13 +927,14 @@ r = c.hex() success = 1 except Exception as inst: - r = str(inst) + r = util.forcebytestr(inst) success = 0 - return "%d %s\n" % (success, r) + return bytesresponse('%d %s\n' % (success, r)) @wireprotocommand('known', 'nodes *') def known(repo, proto, nodes, others): - return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes))) + v = ''.join(b and '1' or '0' for b in repo.known(decodelist(nodes))) + return bytesresponse(v) @wireprotocommand('pushkey', 'namespace key old new') def pushkey(repo, proto, namespace, key, old, new): @@ -950,23 +950,12 @@ else: new = encoding.tolocal(new) # normal path - if util.safehasattr(proto, 'restore'): - - proto.redirect() + with proto.mayberedirectstdio() as output: + r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key), + encoding.tolocal(old), new) or False - try: - r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key), - encoding.tolocal(old), new) or False - except error.Abort: - r = False - - output = proto.restore() - - return '%s\n%s' % (int(r), output) - - r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key), - encoding.tolocal(old), new) - return '%s\n' % int(r) + output = output.getvalue() if output else '' + return bytesresponse('%s\n%s' % (int(r), output)) @wireprotocommand('stream_out') def stream(repo, proto): @@ -980,97 +969,99 @@ def unbundle(repo, proto, heads): their_heads = decodelist(heads) - try: - proto.redirect() - - exchange.check_heads(repo, their_heads, 'preparing changes') - - # write bundle data to temporary file because it can be big - fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') - fp = os.fdopen(fd, pycompat.sysstr('wb+')) - r = 0 + with proto.mayberedirectstdio() as output: try: - proto.getfile(fp) - fp.seek(0) - gen = exchange.readbundle(repo.ui, fp, None) - if (isinstance(gen, changegroupmod.cg1unpacker) - and not bundle1allowed(repo, 'push')): - if proto.name == 'http': - # need to special case http because stderr do not get to - # the http client on failed push so we need to abuse some - # other error type to make sure the message get to the - # user. - return ooberror(bundle2required) - raise error.Abort(bundle2requiredmain, - hint=bundle2requiredhint) + exchange.check_heads(repo, their_heads, 'preparing changes') - r = exchange.unbundle(repo, gen, their_heads, 'serve', - proto._client()) - if util.safehasattr(r, 'addpart'): - # The return looks streamable, we are in the bundle2 case and - # should return a stream. - return streamres_legacy(gen=r.getchunks()) - return pushres(r) - - finally: - fp.close() - os.unlink(tempname) - - except (error.BundleValueError, error.Abort, error.PushRaced) as exc: - # handle non-bundle2 case first - if not getattr(exc, 'duringunbundle2', False): + # write bundle data to temporary file because it can be big + fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') + fp = os.fdopen(fd, pycompat.sysstr('wb+')) + r = 0 try: - raise - except error.Abort: - # The old code we moved used util.stderr directly. - # We did not change it to minimise code change. - # This need to be moved to something proper. - # Feel free to do it. - util.stderr.write("abort: %s\n" % exc) - if exc.hint is not None: - util.stderr.write("(%s)\n" % exc.hint) - return pushres(0) - except error.PushRaced: - return pusherr(str(exc)) + proto.forwardpayload(fp) + fp.seek(0) + gen = exchange.readbundle(repo.ui, fp, None) + if (isinstance(gen, changegroupmod.cg1unpacker) + and not bundle1allowed(repo, 'push')): + if proto.name == 'http-v1': + # need to special case http because stderr do not get to + # the http client on failed push so we need to abuse + # some other error type to make sure the message get to + # the user. + return ooberror(bundle2required) + raise error.Abort(bundle2requiredmain, + hint=bundle2requiredhint) - bundler = bundle2.bundle20(repo.ui) - for out in getattr(exc, '_bundle2salvagedoutput', ()): - bundler.addpart(out) - try: - try: - raise - except error.PushkeyFailed as exc: - # check client caps - remotecaps = getattr(exc, '_replycaps', None) - if (remotecaps is not None - and 'pushkey' not in remotecaps.get('error', ())): - # no support remote side, fallback to Abort handler. + r = exchange.unbundle(repo, gen, their_heads, 'serve', + proto.client()) + if util.safehasattr(r, 'addpart'): + # The return looks streamable, we are in the bundle2 case + # and should return a stream. + return streamres_legacy(gen=r.getchunks()) + return pushres(r, output.getvalue() if output else '') + + finally: + fp.close() + os.unlink(tempname) + + except (error.BundleValueError, error.Abort, error.PushRaced) as exc: + # handle non-bundle2 case first + if not getattr(exc, 'duringunbundle2', False): + try: raise - part = bundler.newpart('error:pushkey') - part.addparam('in-reply-to', exc.partid) - if exc.namespace is not None: - part.addparam('namespace', exc.namespace, mandatory=False) - if exc.key is not None: - part.addparam('key', exc.key, mandatory=False) - if exc.new is not None: - part.addparam('new', exc.new, mandatory=False) - if exc.old is not None: - part.addparam('old', exc.old, mandatory=False) - if exc.ret is not None: - part.addparam('ret', exc.ret, mandatory=False) - except error.BundleValueError as exc: - errpart = bundler.newpart('error:unsupportedcontent') - if exc.parttype is not None: - errpart.addparam('parttype', exc.parttype) - if exc.params: - errpart.addparam('params', '\0'.join(exc.params)) - except error.Abort as exc: - manargs = [('message', str(exc))] - advargs = [] - if exc.hint is not None: - advargs.append(('hint', exc.hint)) - bundler.addpart(bundle2.bundlepart('error:abort', - manargs, advargs)) - except error.PushRaced as exc: - bundler.newpart('error:pushraced', [('message', str(exc))]) - return streamres_legacy(gen=bundler.getchunks()) + except error.Abort: + # The old code we moved used util.stderr directly. + # We did not change it to minimise code change. + # This need to be moved to something proper. + # Feel free to do it. + util.stderr.write("abort: %s\n" % exc) + if exc.hint is not None: + util.stderr.write("(%s)\n" % exc.hint) + return pushres(0, output.getvalue() if output else '') + except error.PushRaced: + return pusherr(str(exc), + output.getvalue() if output else '') + + bundler = bundle2.bundle20(repo.ui) + for out in getattr(exc, '_bundle2salvagedoutput', ()): + bundler.addpart(out) + try: + try: + raise + except error.PushkeyFailed as exc: + # check client caps + remotecaps = getattr(exc, '_replycaps', None) + if (remotecaps is not None + and 'pushkey' not in remotecaps.get('error', ())): + # no support remote side, fallback to Abort handler. + raise + part = bundler.newpart('error:pushkey') + part.addparam('in-reply-to', exc.partid) + if exc.namespace is not None: + part.addparam('namespace', exc.namespace, + mandatory=False) + if exc.key is not None: + part.addparam('key', exc.key, mandatory=False) + if exc.new is not None: + part.addparam('new', exc.new, mandatory=False) + if exc.old is not None: + part.addparam('old', exc.old, mandatory=False) + if exc.ret is not None: + part.addparam('ret', exc.ret, mandatory=False) + except error.BundleValueError as exc: + errpart = bundler.newpart('error:unsupportedcontent') + if exc.parttype is not None: + errpart.addparam('parttype', exc.parttype) + if exc.params: + errpart.addparam('params', '\0'.join(exc.params)) + except error.Abort as exc: + manargs = [('message', util.forcebytestr(exc))] + advargs = [] + if exc.hint is not None: + advargs.append(('hint', exc.hint)) + bundler.addpart(bundle2.bundlepart('error:abort', + manargs, advargs)) + except error.PushRaced as exc: + bundler.newpart('error:pushraced', + [('message', util.forcebytestr(exc))]) + return streamres_legacy(gen=bundler.getchunks()) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/wireprotoserver.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/wireprotoserver.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,605 @@ +# Copyright 21 May 2005 - (c) 2005 Jake Edge +# Copyright 2005-2007 Matt Mackall +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +import contextlib +import struct +import sys + +from .i18n import _ +from . import ( + encoding, + error, + hook, + pycompat, + util, + wireproto, + wireprototypes, +) + +stringio = util.stringio + +urlerr = util.urlerr +urlreq = util.urlreq + +HTTP_OK = 200 + +HGTYPE = 'application/mercurial-0.1' +HGTYPE2 = 'application/mercurial-0.2' +HGERRTYPE = 'application/hg-error' + +# Names of the SSH protocol implementations. +SSHV1 = 'ssh-v1' +# This is advertised over the wire. Incremental the counter at the end +# to reflect BC breakages. +SSHV2 = 'exp-ssh-v2-0001' + +def decodevaluefromheaders(req, headerprefix): + """Decode a long value from multiple HTTP request headers. + + Returns the value as a bytes, not a str. + """ + chunks = [] + i = 1 + prefix = headerprefix.upper().replace(r'-', r'_') + while True: + v = req.env.get(r'HTTP_%s_%d' % (prefix, i)) + if v is None: + break + chunks.append(pycompat.bytesurl(v)) + i += 1 + + return ''.join(chunks) + +class httpv1protocolhandler(wireprototypes.baseprotocolhandler): + def __init__(self, req, ui): + self._req = req + self._ui = ui + + @property + def name(self): + return 'http-v1' + + def getargs(self, args): + knownargs = self._args() + data = {} + keys = args.split() + for k in keys: + if k == '*': + star = {} + for key in knownargs.keys(): + if key != 'cmd' and key not in keys: + star[key] = knownargs[key][0] + data['*'] = star + else: + data[k] = knownargs[k][0] + return [data[k] for k in keys] + + def _args(self): + args = util.rapply(pycompat.bytesurl, self._req.form.copy()) + postlen = int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0)) + if postlen: + args.update(urlreq.parseqs( + self._req.read(postlen), keep_blank_values=True)) + return args + + argvalue = decodevaluefromheaders(self._req, r'X-HgArg') + args.update(urlreq.parseqs(argvalue, keep_blank_values=True)) + return args + + def forwardpayload(self, fp): + if r'HTTP_CONTENT_LENGTH' in self._req.env: + length = int(self._req.env[r'HTTP_CONTENT_LENGTH']) + else: + length = int(self._req.env[r'CONTENT_LENGTH']) + # If httppostargs is used, we need to read Content-Length + # minus the amount that was consumed by args. + length -= int(self._req.env.get(r'HTTP_X_HGARGS_POST', 0)) + for s in util.filechunkiter(self._req, limit=length): + fp.write(s) + + @contextlib.contextmanager + def mayberedirectstdio(self): + oldout = self._ui.fout + olderr = self._ui.ferr + + out = util.stringio() + + try: + self._ui.fout = out + self._ui.ferr = out + yield out + finally: + self._ui.fout = oldout + self._ui.ferr = olderr + + def client(self): + return 'remote:%s:%s:%s' % ( + self._req.env.get('wsgi.url_scheme') or 'http', + urlreq.quote(self._req.env.get('REMOTE_HOST', '')), + urlreq.quote(self._req.env.get('REMOTE_USER', ''))) + +# This method exists mostly so that extensions like remotefilelog can +# disable a kludgey legacy method only over http. As of early 2018, +# there are no other known users, so with any luck we can discard this +# hook if remotefilelog becomes a first-party extension. +def iscmd(cmd): + return cmd in wireproto.commands + +def parsehttprequest(repo, req, query): + """Parse the HTTP request for a wire protocol request. + + If the current request appears to be a wire protocol request, this + function returns a dict with details about that request, including + an ``abstractprotocolserver`` instance suitable for handling the + request. Otherwise, ``None`` is returned. + + ``req`` is a ``wsgirequest`` instance. + """ + # HTTP version 1 wire protocol requests are denoted by a "cmd" query + # string parameter. If it isn't present, this isn't a wire protocol + # request. + if r'cmd' not in req.form: + return None + + cmd = pycompat.sysbytes(req.form[r'cmd'][0]) + + # The "cmd" request parameter is used by both the wire protocol and hgweb. + # While not all wire protocol commands are available for all transports, + # if we see a "cmd" value that resembles a known wire protocol command, we + # route it to a protocol handler. This is better than routing possible + # wire protocol requests to hgweb because it prevents hgweb from using + # known wire protocol commands and it is less confusing for machine + # clients. + if not iscmd(cmd): + return None + + proto = httpv1protocolhandler(req, repo.ui) + + return { + 'cmd': cmd, + 'proto': proto, + 'dispatch': lambda: _callhttp(repo, req, proto, cmd), + 'handleerror': lambda ex: _handlehttperror(ex, req, cmd), + } + +def _httpresponsetype(ui, req, prefer_uncompressed): + """Determine the appropriate response type and compression settings. + + Returns a tuple of (mediatype, compengine, engineopts). + """ + # Determine the response media type and compression engine based + # on the request parameters. + protocaps = decodevaluefromheaders(req, r'X-HgProto').split(' ') + + if '0.2' in protocaps: + # All clients are expected to support uncompressed data. + if prefer_uncompressed: + return HGTYPE2, util._noopengine(), {} + + # Default as defined by wire protocol spec. + compformats = ['zlib', 'none'] + for cap in protocaps: + if cap.startswith('comp='): + compformats = cap[5:].split(',') + break + + # Now find an agreed upon compression format. + for engine in wireproto.supportedcompengines(ui, util.SERVERROLE): + if engine.wireprotosupport().name in compformats: + opts = {} + level = ui.configint('server', '%slevel' % engine.name()) + if level is not None: + opts['level'] = level + + return HGTYPE2, engine, opts + + # No mutually supported compression format. Fall back to the + # legacy protocol. + + # Don't allow untrusted settings because disabling compression or + # setting a very high compression level could lead to flooding + # the server's network or CPU. + opts = {'level': ui.configint('server', 'zliblevel')} + return HGTYPE, util.compengines['zlib'], opts + +def _callhttp(repo, req, proto, cmd): + def genversion2(gen, engine, engineopts): + # application/mercurial-0.2 always sends a payload header + # identifying the compression engine. + name = engine.wireprotosupport().name + assert 0 < len(name) < 256 + yield struct.pack('B', len(name)) + yield name + + for chunk in gen: + yield chunk + + rsp = wireproto.dispatch(repo, proto, cmd) + + if not wireproto.commands.commandavailable(cmd, proto): + req.respond(HTTP_OK, HGERRTYPE, + body=_('requested wire protocol command is not available ' + 'over HTTP')) + return [] + + if isinstance(rsp, bytes): + req.respond(HTTP_OK, HGTYPE, body=rsp) + return [] + elif isinstance(rsp, wireprototypes.bytesresponse): + req.respond(HTTP_OK, HGTYPE, body=rsp.data) + return [] + elif isinstance(rsp, wireprototypes.streamreslegacy): + gen = rsp.gen + req.respond(HTTP_OK, HGTYPE) + return gen + elif isinstance(rsp, wireprototypes.streamres): + gen = rsp.gen + + # This code for compression should not be streamres specific. It + # is here because we only compress streamres at the moment. + mediatype, engine, engineopts = _httpresponsetype( + repo.ui, req, rsp.prefer_uncompressed) + gen = engine.compressstream(gen, engineopts) + + if mediatype == HGTYPE2: + gen = genversion2(gen, engine, engineopts) + + req.respond(HTTP_OK, mediatype) + return gen + elif isinstance(rsp, wireprototypes.pushres): + rsp = '%d\n%s' % (rsp.res, rsp.output) + req.respond(HTTP_OK, HGTYPE, body=rsp) + return [] + elif isinstance(rsp, wireprototypes.pusherr): + # This is the httplib workaround documented in _handlehttperror(). + req.drain() + + rsp = '0\n%s\n' % rsp.res + req.respond(HTTP_OK, HGTYPE, body=rsp) + return [] + elif isinstance(rsp, wireprototypes.ooberror): + rsp = rsp.message + req.respond(HTTP_OK, HGERRTYPE, body=rsp) + return [] + raise error.ProgrammingError('hgweb.protocol internal failure', rsp) + +def _handlehttperror(e, req, cmd): + """Called when an ErrorResponse is raised during HTTP request processing.""" + + # Clients using Python's httplib are stateful: the HTTP client + # won't process an HTTP response until all request data is + # sent to the server. The intent of this code is to ensure + # we always read HTTP request data from the client, thus + # ensuring httplib transitions to a state that allows it to read + # the HTTP response. In other words, it helps prevent deadlocks + # on clients using httplib. + + if (req.env[r'REQUEST_METHOD'] == r'POST' and + # But not if Expect: 100-continue is being used. + (req.env.get('HTTP_EXPECT', + '').lower() != '100-continue') or + # Or the non-httplib HTTP library is being advertised by + # the client. + req.env.get('X-HgHttp2', '')): + req.drain() + else: + req.headers.append((r'Connection', r'Close')) + + # TODO This response body assumes the failed command was + # "unbundle." That assumption is not always valid. + req.respond(e, HGTYPE, body='0\n%s\n' % pycompat.bytestr(e)) + + return '' + +def _sshv1respondbytes(fout, value): + """Send a bytes response for protocol version 1.""" + fout.write('%d\n' % len(value)) + fout.write(value) + fout.flush() + +def _sshv1respondstream(fout, source): + write = fout.write + for chunk in source.gen: + write(chunk) + fout.flush() + +def _sshv1respondooberror(fout, ferr, rsp): + ferr.write(b'%s\n-\n' % rsp) + ferr.flush() + fout.write(b'\n') + fout.flush() + +class sshv1protocolhandler(wireprototypes.baseprotocolhandler): + """Handler for requests services via version 1 of SSH protocol.""" + def __init__(self, ui, fin, fout): + self._ui = ui + self._fin = fin + self._fout = fout + + @property + def name(self): + return SSHV1 + + def getargs(self, args): + data = {} + keys = args.split() + for n in xrange(len(keys)): + argline = self._fin.readline()[:-1] + arg, l = argline.split() + if arg not in keys: + raise error.Abort(_("unexpected parameter %r") % arg) + if arg == '*': + star = {} + for k in xrange(int(l)): + argline = self._fin.readline()[:-1] + arg, l = argline.split() + val = self._fin.read(int(l)) + star[arg] = val + data['*'] = star + else: + val = self._fin.read(int(l)) + data[arg] = val + return [data[k] for k in keys] + + def forwardpayload(self, fpout): + # We initially send an empty response. This tells the client it is + # OK to start sending data. If a client sees any other response, it + # interprets it as an error. + _sshv1respondbytes(self._fout, b'') + + # The file is in the form: + # + # \n + # ... + # 0\n + count = int(self._fin.readline()) + while count: + fpout.write(self._fin.read(count)) + count = int(self._fin.readline()) + + @contextlib.contextmanager + def mayberedirectstdio(self): + yield None + + def client(self): + client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0] + return 'remote:ssh:' + client + +class sshv2protocolhandler(sshv1protocolhandler): + """Protocol handler for version 2 of the SSH protocol.""" + +def _runsshserver(ui, repo, fin, fout): + # This function operates like a state machine of sorts. The following + # states are defined: + # + # protov1-serving + # Server is in protocol version 1 serving mode. Commands arrive on + # new lines. These commands are processed in this state, one command + # after the other. + # + # protov2-serving + # Server is in protocol version 2 serving mode. + # + # upgrade-initial + # The server is going to process an upgrade request. + # + # upgrade-v2-filter-legacy-handshake + # The protocol is being upgraded to version 2. The server is expecting + # the legacy handshake from version 1. + # + # upgrade-v2-finish + # The upgrade to version 2 of the protocol is imminent. + # + # shutdown + # The server is shutting down, possibly in reaction to a client event. + # + # And here are their transitions: + # + # protov1-serving -> shutdown + # When server receives an empty request or encounters another + # error. + # + # protov1-serving -> upgrade-initial + # An upgrade request line was seen. + # + # upgrade-initial -> upgrade-v2-filter-legacy-handshake + # Upgrade to version 2 in progress. Server is expecting to + # process a legacy handshake. + # + # upgrade-v2-filter-legacy-handshake -> shutdown + # Client did not fulfill upgrade handshake requirements. + # + # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish + # Client fulfilled version 2 upgrade requirements. Finishing that + # upgrade. + # + # upgrade-v2-finish -> protov2-serving + # Protocol upgrade to version 2 complete. Server can now speak protocol + # version 2. + # + # protov2-serving -> protov1-serving + # Ths happens by default since protocol version 2 is the same as + # version 1 except for the handshake. + + state = 'protov1-serving' + proto = sshv1protocolhandler(ui, fin, fout) + protoswitched = False + + while True: + if state == 'protov1-serving': + # Commands are issued on new lines. + request = fin.readline()[:-1] + + # Empty lines signal to terminate the connection. + if not request: + state = 'shutdown' + continue + + # It looks like a protocol upgrade request. Transition state to + # handle it. + if request.startswith(b'upgrade '): + if protoswitched: + _sshv1respondooberror(fout, ui.ferr, + b'cannot upgrade protocols multiple ' + b'times') + state = 'shutdown' + continue + + state = 'upgrade-initial' + continue + + available = wireproto.commands.commandavailable(request, proto) + + # This command isn't available. Send an empty response and go + # back to waiting for a new command. + if not available: + _sshv1respondbytes(fout, b'') + continue + + rsp = wireproto.dispatch(repo, proto, request) + + if isinstance(rsp, bytes): + _sshv1respondbytes(fout, rsp) + elif isinstance(rsp, wireprototypes.bytesresponse): + _sshv1respondbytes(fout, rsp.data) + elif isinstance(rsp, wireprototypes.streamres): + _sshv1respondstream(fout, rsp) + elif isinstance(rsp, wireprototypes.streamreslegacy): + _sshv1respondstream(fout, rsp) + elif isinstance(rsp, wireprototypes.pushres): + _sshv1respondbytes(fout, b'') + _sshv1respondbytes(fout, b'%d' % rsp.res) + elif isinstance(rsp, wireprototypes.pusherr): + _sshv1respondbytes(fout, rsp.res) + elif isinstance(rsp, wireprototypes.ooberror): + _sshv1respondooberror(fout, ui.ferr, rsp.message) + else: + raise error.ProgrammingError('unhandled response type from ' + 'wire protocol command: %s' % rsp) + + # For now, protocol version 2 serving just goes back to version 1. + elif state == 'protov2-serving': + state = 'protov1-serving' + continue + + elif state == 'upgrade-initial': + # We should never transition into this state if we've switched + # protocols. + assert not protoswitched + assert proto.name == SSHV1 + + # Expected: upgrade + # If we get something else, the request is malformed. It could be + # from a future client that has altered the upgrade line content. + # We treat this as an unknown command. + try: + token, caps = request.split(b' ')[1:] + except ValueError: + _sshv1respondbytes(fout, b'') + state = 'protov1-serving' + continue + + # Send empty response if we don't support upgrading protocols. + if not ui.configbool('experimental', 'sshserver.support-v2'): + _sshv1respondbytes(fout, b'') + state = 'protov1-serving' + continue + + try: + caps = urlreq.parseqs(caps) + except ValueError: + _sshv1respondbytes(fout, b'') + state = 'protov1-serving' + continue + + # We don't see an upgrade request to protocol version 2. Ignore + # the upgrade request. + wantedprotos = caps.get(b'proto', [b''])[0] + if SSHV2 not in wantedprotos: + _sshv1respondbytes(fout, b'') + state = 'protov1-serving' + continue + + # It looks like we can honor this upgrade request to protocol 2. + # Filter the rest of the handshake protocol request lines. + state = 'upgrade-v2-filter-legacy-handshake' + continue + + elif state == 'upgrade-v2-filter-legacy-handshake': + # Client should have sent legacy handshake after an ``upgrade`` + # request. Expected lines: + # + # hello + # between + # pairs 81 + # 0000...-0000... + + ok = True + for line in (b'hello', b'between', b'pairs 81'): + request = fin.readline()[:-1] + + if request != line: + _sshv1respondooberror(fout, ui.ferr, + b'malformed handshake protocol: ' + b'missing %s' % line) + ok = False + state = 'shutdown' + break + + if not ok: + continue + + request = fin.read(81) + if request != b'%s-%s' % (b'0' * 40, b'0' * 40): + _sshv1respondooberror(fout, ui.ferr, + b'malformed handshake protocol: ' + b'missing between argument value') + state = 'shutdown' + continue + + state = 'upgrade-v2-finish' + continue + + elif state == 'upgrade-v2-finish': + # Send the upgrade response. + fout.write(b'upgraded %s %s\n' % (token, SSHV2)) + servercaps = wireproto.capabilities(repo, proto) + rsp = b'capabilities: %s' % servercaps.data + fout.write(b'%d\n%s\n' % (len(rsp), rsp)) + fout.flush() + + proto = sshv2protocolhandler(ui, fin, fout) + protoswitched = True + + state = 'protov2-serving' + continue + + elif state == 'shutdown': + break + + else: + raise error.ProgrammingError('unhandled ssh server state: %s' % + state) + +class sshserver(object): + def __init__(self, ui, repo): + self._ui = ui + self._repo = repo + self._fin = ui.fin + self._fout = ui.fout + + hook.redirect(True) + ui.fout = repo.ui.fout = ui.ferr + + # Prevent insertion/deletion of CRs + util.setbinary(self._fin) + util.setbinary(self._fout) + + def serve_forever(self): + _runsshserver(self._ui, self._repo, self._fin, self._fout) + sys.exit(0) diff -r fb39f6a8a864 -r eb73f8a6177e mercurial/wireprototypes.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/wireprototypes.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,117 @@ +# Copyright 2018 Gregory Szorc +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from __future__ import absolute_import + +import abc + +class bytesresponse(object): + """A wire protocol response consisting of raw bytes.""" + def __init__(self, data): + self.data = data + +class ooberror(object): + """wireproto reply: failure of a batch of operation + + Something failed during a batch call. The error message is stored in + `self.message`. + """ + def __init__(self, message): + self.message = message + +class pushres(object): + """wireproto reply: success with simple integer return + + The call was successful and returned an integer contained in `self.res`. + """ + def __init__(self, res, output): + self.res = res + self.output = output + +class pusherr(object): + """wireproto reply: failure + + The call failed. The `self.res` attribute contains the error message. + """ + def __init__(self, res, output): + self.res = res + self.output = output + +class streamres(object): + """wireproto reply: binary stream + + The call was successful and the result is a stream. + + Accepts a generator containing chunks of data to be sent to the client. + + ``prefer_uncompressed`` indicates that the data is expected to be + uncompressable and that the stream should therefore use the ``none`` + engine. + """ + def __init__(self, gen=None, prefer_uncompressed=False): + self.gen = gen + self.prefer_uncompressed = prefer_uncompressed + +class streamreslegacy(object): + """wireproto reply: uncompressed binary stream + + The call was successful and the result is a stream. + + Accepts a generator containing chunks of data to be sent to the client. + + Like ``streamres``, but sends an uncompressed data for "version 1" clients + using the application/mercurial-0.1 media type. + """ + def __init__(self, gen=None): + self.gen = gen + +class baseprotocolhandler(object): + """Abstract base class for wire protocol handlers. + + A wire protocol handler serves as an interface between protocol command + handlers and the wire protocol transport layer. Protocol handlers provide + methods to read command arguments, redirect stdio for the duration of + the request, handle response types, etc. + """ + + __metaclass__ = abc.ABCMeta + + @abc.abstractproperty + def name(self): + """The name of the protocol implementation. + + Used for uniquely identifying the transport type. + """ + + @abc.abstractmethod + def getargs(self, args): + """return the value for arguments in + + returns a list of values (same order as )""" + + @abc.abstractmethod + def forwardpayload(self, fp): + """Read the raw payload and forward to a file. + + The payload is read in full before the function returns. + """ + + @abc.abstractmethod + def mayberedirectstdio(self): + """Context manager to possibly redirect stdio. + + The context manager yields a file-object like object that receives + stdout and stderr output when the context manager is active. Or it + yields ``None`` if no I/O redirection occurs. + + The intent of this context manager is to capture stdio output + so it may be sent in the response. Some transports support streaming + stdio to the client in real time. For these transports, stdio output + won't be captured. + """ + + @abc.abstractmethod + def client(self): + """Returns a string representation of this client (as bytes).""" diff -r fb39f6a8a864 -r eb73f8a6177e setup.py --- a/setup.py Fri Feb 23 17:57:04 2018 -0800 +++ b/setup.py Sat Feb 24 17:49:10 2018 -0600 @@ -812,7 +812,8 @@ 'mercurial.thirdparty.attr', 'hgext', 'hgext.convert', 'hgext.fsmonitor', 'hgext.fsmonitor.pywatchman', 'hgext.highlight', - 'hgext.largefiles', 'hgext.lfs', 'hgext.zeroconf', 'hgext3rd', + 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow', + 'hgext.zeroconf', 'hgext3rd', 'hgdemandimport'] common_depends = ['mercurial/bitmanipulation.h', diff -r fb39f6a8a864 -r eb73f8a6177e tests/badserverext.py --- a/tests/badserverext.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/badserverext.py Sat Feb 24 17:49:10 2018 -0600 @@ -44,16 +44,16 @@ configtable = {} configitem = registrar.configitem(configtable) -configitem('badserver', 'closeafteraccept', +configitem(b'badserver', b'closeafteraccept', default=False, ) -configitem('badserver', 'closeafterrecvbytes', +configitem(b'badserver', b'closeafterrecvbytes', default=0, ) -configitem('badserver', 'closeaftersendbytes', +configitem(b'badserver', b'closeaftersendbytes', default=0, ) -configitem('badserver', 'closebeforeaccept', +configitem(b'badserver', b'closebeforeaccept', default=False, ) diff -r fb39f6a8a864 -r eb73f8a6177e tests/common-pattern.py --- a/tests/common-pattern.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/common-pattern.py Sat Feb 24 17:49:10 2018 -0600 @@ -69,8 +69,8 @@ br'$USUAL_BUNDLE2_CAPS_SERVER$' ), # HTTP log dates - (br' - - \[\d\d/.../2\d\d\d \d\d:\d\d:\d\d] "GET', - br' - - [$LOGDATE$] "GET' + (br' - - \[\d\d/.../2\d\d\d \d\d:\d\d:\d\d] "(GET|PUT|POST)', + lambda m: br' - - [$LOGDATE$] "' + m.group(1) ), # Windows has an extra '/' in the following lines that get globbed away: # pushing to file:/*/$TESTTMP/r2 (glob) diff -r fb39f6a8a864 -r eb73f8a6177e tests/dummyssh --- a/tests/dummyssh Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/dummyssh Sat Feb 24 17:49:10 2018 -0600 @@ -15,8 +15,8 @@ log = open("dummylog", "ab") log.write(b"Got arguments") for i, arg in enumerate(sys.argv[1:]): - log.write(b" %d:%s" % (i + 1, arg)) -log.write("\n") + log.write(b" %d:%s" % (i + 1, arg.encode('latin1'))) +log.write(b"\n") log.close() hgcmd = sys.argv[2] if os.name == 'nt': diff -r fb39f6a8a864 -r eb73f8a6177e tests/f --- a/tests/f Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/f Sat Feb 24 17:49:10 2018 -0600 @@ -25,6 +25,7 @@ from __future__ import absolute_import +import binascii import glob import hashlib import optparse @@ -58,46 +59,47 @@ facts = [] if isfile: if opts.type: - facts.append('file') + facts.append(b'file') if any((opts.hexdump, opts.dump, opts.md5, opts.sha1, opts.sha256)): content = open(f, 'rb').read() elif islink: if opts.type: - facts.append('link') + facts.append(b'link') content = os.readlink(f) elif isstdin: content = getattr(sys.stdin, 'buffer', sys.stdin).read() if opts.size: - facts.append('size=%s' % len(content)) + facts.append(b'size=%d' % len(content)) elif isdir: if opts.recurse or opts.type: dirfiles = glob.glob(f + '/*') - facts.append('directory with %s files' % len(dirfiles)) + facts.append(b'directory with %d files' % len(dirfiles)) elif opts.type: - facts.append('type unknown') + facts.append(b'type unknown') if not isstdin: stat = os.lstat(f) if opts.size and not isdir: - facts.append('size=%s' % stat.st_size) + facts.append(b'size=%d' % stat.st_size) if opts.mode and not islink: - facts.append('mode=%o' % (stat.st_mode & 0o777)) + facts.append(b'mode=%o' % (stat.st_mode & 0o777)) if opts.links: - facts.append('links=%s' % stat.st_nlink) + facts.append(b'links=%s' % stat.st_nlink) if opts.newer: # mtime might be in whole seconds so newer file might be same if stat.st_mtime >= os.stat(opts.newer).st_mtime: - facts.append('newer than %s' % opts.newer) + facts.append(b'newer than %s' % opts.newer) else: - facts.append('older than %s' % opts.newer) + facts.append(b'older than %s' % opts.newer) if opts.md5 and content is not None: h = hashlib.md5(content) - facts.append('md5=%s' % h.hexdigest()[:opts.bytes]) + facts.append(b'md5=%s' % binascii.hexlify(h.digest())[:opts.bytes]) if opts.sha1 and content is not None: h = hashlib.sha1(content) - facts.append('sha1=%s' % h.hexdigest()[:opts.bytes]) + facts.append(b'sha1=%s' % binascii.hexlify(h.digest())[:opts.bytes]) if opts.sha256 and content is not None: h = hashlib.sha256(content) - facts.append('sha256=%s' % h.hexdigest()[:opts.bytes]) + facts.append(b'sha256=%s' % + binascii.hexlify(h.digest())[:opts.bytes]) if isstdin: outfile.write(b', '.join(facts) + b'\n') elif facts: diff -r fb39f6a8a864 -r eb73f8a6177e tests/fakedirstatewritetime.py --- a/tests/fakedirstatewritetime.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/fakedirstatewritetime.py Sat Feb 24 17:49:10 2018 -0600 @@ -19,7 +19,7 @@ configtable = {} configitem = registrar.configitem(configtable) -configitem('fakedirstatewritetime', 'fakenow', +configitem(b'fakedirstatewritetime', b'fakenow', default=None, ) @@ -29,7 +29,7 @@ # execute what original parsers.pack_dirstate should do actually # for consistency actualnow = int(now) - for f, e in dmap.iteritems(): + for f, e in dmap.items(): if e[0] == 'n' and e[3] == actualnow: e = parsers.dirstatetuple(e[0], e[1], e[2], -1) dmap[f] = e @@ -39,7 +39,7 @@ def fakewrite(ui, func): # fake "now" of 'pack_dirstate' only if it is invoked while 'func' - fakenow = ui.config('fakedirstatewritetime', 'fakenow') + fakenow = ui.config(b'fakedirstatewritetime', b'fakenow') if not fakenow: # Execute original one, if fakenow isn't configured. This is # useful to prevent subrepos from executing replaced one, @@ -49,7 +49,7 @@ # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy - fakenow = util.parsedate(fakenow, ['%Y%m%d%H%M'])[0] + fakenow = util.parsedate(fakenow, [b'%Y%m%d%H%M'])[0] orig_pack_dirstate = parsers.pack_dirstate orig_dirstate_getfsnow = dirstate._getfsnow diff -r fb39f6a8a864 -r eb73f8a6177e tests/fakemergerecord.py --- a/tests/fakemergerecord.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/fakemergerecord.py Sat Feb 24 17:49:10 2018 -0600 @@ -12,15 +12,15 @@ cmdtable = {} command = registrar.command(cmdtable) -@command('fakemergerecord', - [('X', 'mandatory', None, 'add a fake mandatory record'), - ('x', 'advisory', None, 'add a fake advisory record')], '') +@command(b'fakemergerecord', + [(b'X', b'mandatory', None, b'add a fake mandatory record'), + (b'x', b'advisory', None, b'add a fake advisory record')], '') def fakemergerecord(ui, repo, *pats, **opts): with repo.wlock(): ms = merge.mergestate.read(repo) records = ms._makerecords() - if opts.get('mandatory'): - records.append(('X', 'mandatory record')) - if opts.get('advisory'): - records.append(('x', 'advisory record')) + if opts.get(b'mandatory'): + records.append((b'X', b'mandatory record')) + if opts.get(b'advisory'): + records.append((b'x', b'advisory record')) ms._writerecords(records) diff -r fb39f6a8a864 -r eb73f8a6177e tests/fakepatchtime.py --- a/tests/fakepatchtime.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/fakepatchtime.py Sat Feb 24 17:49:10 2018 -0600 @@ -13,24 +13,24 @@ configtable = {} configitem = registrar.configitem(configtable) -configitem('fakepatchtime', 'fakenow', +configitem(b'fakepatchtime', b'fakenow', default=None, ) def internalpatch(orig, ui, repo, patchobj, strip, - prefix='', files=None, - eolmode='strict', similarity=0): + prefix=b'', files=None, + eolmode=b'strict', similarity=0): if files is None: files = set() r = orig(ui, repo, patchobj, strip, prefix=prefix, files=files, eolmode=eolmode, similarity=similarity) - fakenow = ui.config('fakepatchtime', 'fakenow') + fakenow = ui.config(b'fakepatchtime', b'fakenow') if fakenow: # parsing 'fakenow' in YYYYmmddHHMM format makes comparison between # 'fakenow' value and 'touch -t YYYYmmddHHMM' argument easy - fakenow = util.parsedate(fakenow, ['%Y%m%d%H%M'])[0] + fakenow = util.parsedate(fakenow, [b'%Y%m%d%H%M'])[0] for f in files: repo.wvfs.utime(f, (fakenow, fakenow)) diff -r fb39f6a8a864 -r eb73f8a6177e tests/flagprocessorext.py --- a/tests/flagprocessorext.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/flagprocessorext.py Sat Feb 24 17:49:10 2018 -0600 @@ -45,14 +45,14 @@ def supportedoutgoingversions(orig, repo): versions = orig(repo) - versions.discard('01') - versions.discard('02') - versions.add('03') + versions.discard(b'01') + versions.discard(b'02') + versions.add(b'03') return versions def allsupportedversions(orig, ui): versions = orig(ui) - versions.add('03') + versions.add(b'03') return versions def noopaddrevision(orig, self, text, transaction, link, p1, p2, @@ -106,7 +106,7 @@ # Teach exchange to use changegroup 3 for k in exchange._bundlespeccgversions.keys(): - exchange._bundlespeccgversions[k] = '03' + exchange._bundlespeccgversions[k] = b'03' # Add wrappers for addrevision, responsible to set flags depending on the # revision data contents. diff -r fb39f6a8a864 -r eb73f8a6177e tests/generate-working-copy-states.py --- a/tests/generate-working-copy-states.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/generate-working-copy-states.py Sat Feb 24 17:49:10 2018 -0600 @@ -42,12 +42,12 @@ def generatestates(maxchangesets, parentcontents): depth = len(parentcontents) if depth == maxchangesets + 1: - for tracked in ('untracked', 'tracked'): - filename = "_".join([(content is None and 'missing' or content) for - content in parentcontents]) + "-" + tracked + for tracked in (b'untracked', b'tracked'): + filename = b"_".join([(content is None and b'missing' or content) + for content in parentcontents]) + b"-" + tracked yield (filename, parentcontents) else: - for content in ({None, 'content' + str(depth + 1)} | + for content in ({None, b'content' + (b"%d" % (depth + 1))} | set(parentcontents)): for combination in generatestates(maxchangesets, parentcontents + [content]): @@ -71,7 +71,7 @@ if depth == 'wc': # Make sure there is content so the file gets written and can be # tracked. It will be deleted outside of this script. - content.append((filename, states[maxchangesets] or 'TOBEDELETED')) + content.append((filename, states[maxchangesets] or b'TOBEDELETED')) else: content.append((filename, states[int(depth) - 1])) else: @@ -82,7 +82,7 @@ for filename, data in content: if data is not None: f = open(filename, 'wb') - f.write(data + '\n') + f.write(data + b'\n') f.close() elif os.path.exists(filename): os.remove(filename) diff -r fb39f6a8a864 -r eb73f8a6177e tests/get-with-headers.py --- a/tests/get-with-headers.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/get-with-headers.py Sat Feb 24 17:49:10 2018 -0600 @@ -76,7 +76,7 @@ if args.bodyfile: bodyfh = open(args.bodyfile, 'wb') else: - bodyfh = sys.stdout + bodyfh = getattr(sys.stdout, 'buffer', sys.stdout) # Pretty print JSON. This also has the beneficial side-effect # of verifying emitted JSON is well-formed. diff -r fb39f6a8a864 -r eb73f8a6177e tests/logexceptions.py --- a/tests/logexceptions.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/logexceptions.py Sat Feb 24 17:49:10 2018 -0600 @@ -65,6 +65,7 @@ primaryframe, hgframe, hgline, + ui.environ[b'TESTNAME'].decode('utf-8', 'replace'), ] fh.write(b'\0'.join(p.encode('utf-8', 'replace') for p in parts)) diff -r fb39f6a8a864 -r eb73f8a6177e tests/mockblackbox.py --- a/tests/mockblackbox.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/mockblackbox.py Sat Feb 24 17:49:10 2018 -0600 @@ -5,7 +5,7 @@ # XXX: we should probably offer a devel option to do this in blackbox directly def getuser(): - return 'bob' + return b'bob' def getpid(): return 5000 diff -r fb39f6a8a864 -r eb73f8a6177e tests/narrow-library.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/narrow-library.sh Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,8 @@ +cat >> $HGRCPATH < +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +# This extension replaces the SSH server started via `hg serve --stdio`. +# The server behaves differently depending on environment variables. + +from __future__ import absolute_import + +from mercurial import ( + error, + extensions, + registrar, + sshpeer, + wireproto, + wireprotoserver, +) + +configtable = {} +configitem = registrar.configitem(configtable) + +configitem(b'sshpeer', b'mode', default=None) +configitem(b'sshpeer', b'handshake-mode', default=None) + +class bannerserver(wireprotoserver.sshserver): + """Server that sends a banner to stdout.""" + def serve_forever(self): + for i in range(10): + self._fout.write(b'banner: line %d\n' % i) + + super(bannerserver, self).serve_forever() + +class prehelloserver(wireprotoserver.sshserver): + """Tests behavior when connecting to <0.9.1 servers. + + The ``hello`` wire protocol command was introduced in Mercurial + 0.9.1. Modern clients send the ``hello`` command when connecting + to SSH servers. This mock server tests behavior of the handshake + when ``hello`` is not supported. + """ + def serve_forever(self): + l = self._fin.readline() + assert l == b'hello\n' + # Respond to unknown commands with an empty reply. + wireprotoserver._sshv1respondbytes(self._fout, b'') + l = self._fin.readline() + assert l == b'between\n' + proto = wireprotoserver.sshv1protocolhandler(self._ui, self._fin, + self._fout) + rsp = wireproto.dispatch(self._repo, proto, b'between') + wireprotoserver._sshv1respondbytes(self._fout, rsp.data) + + super(prehelloserver, self).serve_forever() + +def performhandshake(orig, ui, stdin, stdout, stderr): + """Wrapped version of sshpeer._performhandshake to send extra commands.""" + mode = ui.config(b'sshpeer', b'handshake-mode') + if mode == b'pre-no-args': + ui.debug(b'sending no-args command\n') + stdin.write(b'no-args\n') + stdin.flush() + return orig(ui, stdin, stdout, stderr) + elif mode == b'pre-multiple-no-args': + ui.debug(b'sending unknown1 command\n') + stdin.write(b'unknown1\n') + ui.debug(b'sending unknown2 command\n') + stdin.write(b'unknown2\n') + ui.debug(b'sending unknown3 command\n') + stdin.write(b'unknown3\n') + stdin.flush() + return orig(ui, stdin, stdout, stderr) + else: + raise error.ProgrammingError(b'unknown HANDSHAKECOMMANDMODE: %s' % + mode) + +def extsetup(ui): + # It's easier for tests to define the server behavior via environment + # variables than config options. This is because `hg serve --stdio` + # has to be invoked with a certain form for security reasons and + # `dummyssh` can't just add `--config` flags to the command line. + servermode = ui.environ.get(b'SSHSERVERMODE') + + if servermode == b'banner': + wireprotoserver.sshserver = bannerserver + elif servermode == b'no-hello': + wireprotoserver.sshserver = prehelloserver + elif servermode: + raise error.ProgrammingError(b'unknown server mode: %s' % servermode) + + peermode = ui.config(b'sshpeer', b'mode') + + if peermode == b'extra-handshake-commands': + extensions.wrapfunction(sshpeer, '_performhandshake', performhandshake) + elif peermode: + raise error.ProgrammingError(b'unknown peer mode: %s' % peermode) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-abort-checkin.t --- a/tests/test-abort-checkin.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-abort-checkin.t Sat Feb 24 17:49:10 2018 -0600 @@ -1,9 +1,9 @@ $ cat > abortcommit.py < from mercurial import error > def hook(**args): - > raise error.Abort("no commits allowed") + > raise error.Abort(b"no commits allowed") > def reposetup(ui, repo): - > repo.ui.setconfig("hooks", "pretxncommit.nocommits", hook) + > repo.ui.setconfig(b"hooks", b"pretxncommit.nocommits", hook) > EOF $ abspath=`pwd`/abortcommit.py diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-add.t --- a/tests/test-add.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-add.t Sat Feb 24 17:49:10 2018 -0600 @@ -146,6 +146,13 @@ M a ? a.orig +excluded file shouldn't be added even if it is explicitly specified + + $ hg add a.orig -X '*.orig' + $ hg st + M a + ? a.orig + Forgotten file can be added back (as either clean or modified) $ hg forget b diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-alias.t --- a/tests/test-alias.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-alias.t Sat Feb 24 17:49:10 2018 -0600 @@ -548,12 +548,12 @@ > from mercurial import cmdutil, commands, registrar > cmdtable = {} > command = registrar.command(cmdtable) - > @command('expandalias') + > @command(b'expandalias') > def expandalias(ui, repo, name): > alias = cmdutil.findcmd(name, commands.table)[1][0] - > ui.write('%s args: %s\n' % (name, ' '.join(alias.args))) + > ui.write(b'%s args: %s\n' % (name, b' '.join(alias.args))) > os.environ['COUNT'] = '2' - > ui.write('%s args: %s (with COUNT=2)\n' % (name, ' '.join(alias.args))) + > ui.write(b'%s args: %s (with COUNT=2)\n' % (name, b' '.join(alias.args))) > EOF $ cat >> $HGRCPATH <<'EOF' diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-annotate.py --- a/tests/test-annotate.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-annotate.py Sat Feb 24 17:49:10 2018 -0600 @@ -27,7 +27,7 @@ def decorate(text, rev): return ([annotateline(fctx=rev, lineno=i) - for i in xrange(1, text.count(b'\n') + 1)], + for i in range(1, text.count(b'\n') + 1)], text) # Basic usage @@ -36,17 +36,17 @@ p1ann = decorate(p1data, p1fctx) p1ann = _annotatepair([oldann], p1fctx, p1ann, False, diffopts) self.assertEqual(p1ann[0], [ - annotateline('old', 1), - annotateline('old', 2), - annotateline('p1', 3), + annotateline(b'old', 1), + annotateline(b'old', 2), + annotateline(b'p1', 3), ]) p2ann = decorate(p2data, p2fctx) p2ann = _annotatepair([oldann], p2fctx, p2ann, False, diffopts) self.assertEqual(p2ann[0], [ - annotateline('old', 1), - annotateline('p2', 2), - annotateline('p2', 3), + annotateline(b'old', 1), + annotateline(b'p2', 2), + annotateline(b'p2', 3), ]) # Test with multiple parents (note the difference caused by ordering) @@ -55,22 +55,22 @@ childann = _annotatepair([p1ann, p2ann], childfctx, childann, False, diffopts) self.assertEqual(childann[0], [ - annotateline('old', 1), - annotateline('c', 2), - annotateline('p2', 2), - annotateline('c', 4), - annotateline('p2', 3), + annotateline(b'old', 1), + annotateline(b'c', 2), + annotateline(b'p2', 2), + annotateline(b'c', 4), + annotateline(b'p2', 3), ]) childann = decorate(childdata, childfctx) childann = _annotatepair([p2ann, p1ann], childfctx, childann, False, diffopts) self.assertEqual(childann[0], [ - annotateline('old', 1), - annotateline('c', 2), - annotateline('p1', 3), - annotateline('c', 4), - annotateline('p2', 3), + annotateline(b'old', 1), + annotateline(b'c', 2), + annotateline(b'p1', 3), + annotateline(b'c', 4), + annotateline(b'p2', 3), ]) # Test with skipchild (note the difference caused by ordering) @@ -79,24 +79,24 @@ childann = _annotatepair([p1ann, p2ann], childfctx, childann, True, diffopts) self.assertEqual(childann[0], [ - annotateline('old', 1), - annotateline('old', 2, True), + annotateline(b'old', 1), + annotateline(b'old', 2, True), # note that this line was carried over from earlier so it is *not* # marked skipped - annotateline('p2', 2), - annotateline('p2', 2, True), - annotateline('p2', 3), + annotateline(b'p2', 2), + annotateline(b'p2', 2, True), + annotateline(b'p2', 3), ]) childann = decorate(childdata, childfctx) childann = _annotatepair([p2ann, p1ann], childfctx, childann, True, diffopts) self.assertEqual(childann[0], [ - annotateline('old', 1), - annotateline('old', 2, True), - annotateline('p1', 3), - annotateline('p1', 3, True), - annotateline('p2', 3), + annotateline(b'old', 1), + annotateline(b'old', 2, True), + annotateline(b'p1', 3), + annotateline(b'p1', 3, True), + annotateline(b'p2', 3), ]) if __name__ == '__main__': diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-arbitraryfilectx.t --- a/tests/test-arbitraryfilectx.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-arbitraryfilectx.t Sat Feb 24 17:49:10 2018 -0600 @@ -5,11 +5,11 @@ > from mercurial import commands, context, registrar > cmdtable = {} > command = registrar.command(cmdtable) - > @command(b'eval', [], 'hg eval CMD') + > @command(b'eval', [], b'hg eval CMD') > def eval_(ui, repo, *cmds, **opts): - > cmd = " ".join(cmds) + > cmd = b" ".join(cmds) > res = str(eval(cmd, globals(), locals())) - > ui.warn("%s" % res) + > ui.warn(b"%s" % res) > EOF $ echo "[extensions]" >> $HGRCPATH diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-atomictempfile.py --- a/tests/test-atomictempfile.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-atomictempfile.py Sat Feb 24 17:49:10 2018 -0600 @@ -7,10 +7,14 @@ import unittest from mercurial import ( + pycompat, util, ) atomictempfile = util.atomictempfile +if pycompat.ispy3: + xrange = range + class testatomictempfile(unittest.TestCase): def setUp(self): self._testdir = tempfile.mkdtemp('atomictempfiletest') diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-basic.t --- a/tests/test-basic.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-basic.t Sat Feb 24 17:49:10 2018 -0600 @@ -87,6 +87,13 @@ checking files 1 files, 1 changesets, 1 total revisions +Repository root: + + $ hg root + $TESTTMP/t + $ hg log -l1 -T '{reporoot}\n' + $TESTTMP/t + At the end... $ cd .. diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-bundle2-exchange.t --- a/tests/test-bundle2-exchange.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-bundle2-exchange.t Sat Feb 24 17:49:10 2018 -0600 @@ -1,3 +1,13 @@ +#testcases sshv1 sshv2 + +#if sshv2 + $ cat >> $HGRCPATH << EOF + > [experimental] + > sshpeer.advertise-v2 = true + > sshserver.support-v2 = true + > EOF +#endif + Test exchange of common information using bundle2 diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-bundle2-pushback.t --- a/tests/test-bundle2-pushback.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-bundle2-pushback.t Sat Feb 24 17:49:10 2018 -0600 @@ -1,3 +1,13 @@ +#testcases sshv1 sshv2 + +#if sshv2 + $ cat >> $HGRCPATH << EOF + > [experimental] + > sshpeer.advertise-v2 = true + > sshserver.support-v2 = true + > EOF +#endif + $ cat > bundle2.py << EOF > """A small extension to test bundle2 pushback parts. > Current bundle2 implementation doesn't provide a way to generate those diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-bundle2-remote-changegroup.t --- a/tests/test-bundle2-remote-changegroup.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-bundle2-remote-changegroup.t Sat Feb 24 17:49:10 2018 -0600 @@ -1,5 +1,15 @@ #require killdaemons +#testcases sshv1 sshv2 + +#if sshv2 + $ cat >> $HGRCPATH << EOF + > [experimental] + > sshpeer.advertise-v2 = true + > sshserver.support-v2 = true + > EOF +#endif + Create an extension to test bundle2 remote-changegroup parts $ cat > bundle2.py << EOF diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-cappedreader.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-cappedreader.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,91 @@ +from __future__ import absolute_import, print_function + +import io +import unittest + +from mercurial import ( + util, +) + +class CappedReaderTests(unittest.TestCase): + def testreadfull(self): + source = io.BytesIO(b'x' * 100) + + reader = util.cappedreader(source, 10) + res = reader.read(10) + self.assertEqual(res, b'x' * 10) + self.assertEqual(source.tell(), 10) + source.seek(0) + + reader = util.cappedreader(source, 15) + res = reader.read(16) + self.assertEqual(res, b'x' * 15) + self.assertEqual(source.tell(), 15) + source.seek(0) + + reader = util.cappedreader(source, 100) + res = reader.read(100) + self.assertEqual(res, b'x' * 100) + self.assertEqual(source.tell(), 100) + source.seek(0) + + reader = util.cappedreader(source, 50) + res = reader.read() + self.assertEqual(res, b'x' * 50) + self.assertEqual(source.tell(), 50) + source.seek(0) + + def testreadnegative(self): + source = io.BytesIO(b'x' * 100) + + reader = util.cappedreader(source, 20) + res = reader.read(-1) + self.assertEqual(res, b'x' * 20) + self.assertEqual(source.tell(), 20) + source.seek(0) + + reader = util.cappedreader(source, 100) + res = reader.read(-1) + self.assertEqual(res, b'x' * 100) + self.assertEqual(source.tell(), 100) + source.seek(0) + + def testreadmultiple(self): + source = io.BytesIO(b'x' * 100) + + reader = util.cappedreader(source, 10) + for i in range(10): + res = reader.read(1) + self.assertEqual(res, b'x') + self.assertEqual(source.tell(), i + 1) + + self.assertEqual(source.tell(), 10) + res = reader.read(1) + self.assertEqual(res, b'') + self.assertEqual(source.tell(), 10) + source.seek(0) + + reader = util.cappedreader(source, 45) + for i in range(4): + res = reader.read(10) + self.assertEqual(res, b'x' * 10) + self.assertEqual(source.tell(), (i + 1) * 10) + + res = reader.read(10) + self.assertEqual(res, b'x' * 5) + self.assertEqual(source.tell(), 45) + + def readlimitpasteof(self): + source = io.BytesIO(b'x' * 100) + + reader = util.cappedreader(source, 1024) + res = reader.read(1000) + self.assertEqual(res, b'x' * 100) + self.assertEqual(source.tell(), 100) + res = reader.read(1000) + self.assertEqual(res, b'') + self.assertEqual(source.tell(), 100) + +if __name__ == '__main__': + import silenttestrunner + silenttestrunner.main(__name__) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-check-help.t --- a/tests/test-check-help.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-check-help.t Sat Feb 24 17:49:10 2018 -0600 @@ -10,9 +10,9 @@ > import os, msvcrt > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) > topics = set() - > topicre = re.compile(r':hg:`help ([a-z0-9\-.]+)`') + > topicre = re.compile(br':hg:`help ([a-z0-9\-.]+)`') > for fname in sys.argv: - > with open(fname) as f: + > with open(fname, 'rb') as f: > topics.update(m.group(1) for m in topicre.finditer(f.read())) > for s in sorted(topics): > print(s) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-check-interfaces.py --- a/tests/test-check-interfaces.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-check-interfaces.py Sat Feb 24 17:49:10 2018 -0600 @@ -51,10 +51,6 @@ pass # Facilitates testing sshpeer without requiring an SSH server. -class testingsshpeer(sshpeer.sshpeer): - def _validaterepo(self, *args, **kwargs): - pass - class badpeer(httppeer.httppeer): def __init__(self): super(badpeer, self).__init__(uimod.ui(), 'http://localhost') @@ -63,13 +59,20 @@ def badmethod(self): pass +class dummypipe(object): + def close(self): + pass + def main(): ui = uimod.ui() checkobject(badpeer()) checkobject(httppeer.httppeer(ui, 'http://localhost')) checkobject(localrepo.localpeer(dummyrepo())) - checkobject(testingsshpeer(ui, 'ssh://localhost/foo')) + checkobject(sshpeer.sshv1peer(ui, 'ssh://localhost/foo', None, dummypipe(), + dummypipe(), None, None)) + checkobject(sshpeer.sshv2peer(ui, 'ssh://localhost/foo', None, dummypipe(), + dummypipe(), None, None)) checkobject(bundlerepo.bundlepeer(dummyrepo())) checkobject(statichttprepo.statichttppeer(dummyrepo())) checkobject(unionrepo.unionpeer(dummyrepo())) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-clone.t --- a/tests/test-clone.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-clone.t Sat Feb 24 17:49:10 2018 -0600 @@ -1,3 +1,13 @@ +#testcases sshv1 sshv2 + +#if sshv2 + $ cat >> $HGRCPATH << EOF + > [experimental] + > sshpeer.advertise-v2 = true + > sshserver.support-v2 = true + > EOF +#endif + Prepare repo a: $ hg init a @@ -10,7 +20,7 @@ Create a non-inlined filelog: - $ $PYTHON -c 'file("data1", "wb").write("".join("%s\n" % x for x in range(10000)))' + $ $PYTHON -c 'open("data1", "wb").write(b"".join(b"%d\n" % x for x in range(10000)))' $ for j in 0 1 2 3 4 5 6 7 8 9; do > cat data1 >> b > hg commit -m test @@ -1142,12 +1152,14 @@ #if windows $ hg clone "ssh://%26touch%20owned%20/" --debug running sh -c "read l; read l; read l" "&touch owned " "hg -R . serve --stdio" + sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !) sending hello command sending between command abort: no suitable response from remote hg! [255] $ hg clone "ssh://example.com:%26touch%20owned%20/" --debug running sh -c "read l; read l; read l" -p "&touch owned " example.com "hg -R . serve --stdio" + sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !) sending hello command sending between command abort: no suitable response from remote hg! @@ -1155,12 +1167,14 @@ #else $ hg clone "ssh://%3btouch%20owned%20/" --debug running sh -c "read l; read l; read l" ';touch owned ' 'hg -R . serve --stdio' + sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !) sending hello command sending between command abort: no suitable response from remote hg! [255] $ hg clone "ssh://example.com:%3btouch%20owned%20/" --debug running sh -c "read l; read l; read l" -p ';touch owned ' example.com 'hg -R . serve --stdio' + sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !) sending hello command sending between command abort: no suitable response from remote hg! @@ -1169,6 +1183,7 @@ $ hg clone "ssh://v-alid.example.com/" --debug running sh -c "read l; read l; read l" v-alid\.example\.com ['"]hg -R \. serve --stdio['"] (re) + sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !) sending hello command sending between command abort: no suitable response from remote hg! diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-clonebundles.t --- a/tests/test-clonebundles.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-clonebundles.t Sat Feb 24 17:49:10 2018 -0600 @@ -53,7 +53,7 @@ $ echo 'http://does.not.exist/bundle.hg' > server/.hg/clonebundles.manifest $ hg clone http://localhost:$HGPORT 404-url applying clone bundle from http://does.not.exist/bundle.hg - error fetching bundle: (.* not known|No address associated with hostname) (re) (no-windows !) + error fetching bundle: (.* not known|(\[Errno -?\d+])? No address associated with hostname) (re) (no-windows !) error fetching bundle: [Errno 11004] getaddrinfo failed (windows !) abort: error applying bundle (if this error persists, consider contacting the server operator or disable clone bundles via "--config ui.clonebundles=false") diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-command-template.t --- a/tests/test-command-template.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-command-template.t Sat Feb 24 17:49:10 2018 -0600 @@ -2232,6 +2232,10 @@ $ hg debugtemplate '{"foo/bar"|basename}|{"foo/"|basename}|{"foo"|basename}|\n' bar||foo| + $ hg debugtemplate '{"foo/bar"|dirname}|{"foo/"|dirname}|{"foo"|dirname}|\n' + foo|foo|| + $ hg debugtemplate '{"foo/bar"|stripdir}|{"foo/"|stripdir}|{"foo"|stripdir}|\n' + foo|foo|foo| Add a dummy commit to make up for the instability of the above: diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-commandserver.t --- a/tests/test-commandserver.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-commandserver.t Sat Feb 24 17:49:10 2018 -0600 @@ -411,7 +411,7 @@ ... # load _phasecache._phaserevs and _phasesets ... runcommand(server, ['log', '-qr', 'draft()']) ... # create draft commits by another process - ... for i in xrange(5, 7): + ... for i in range(5, 7): ... f = open('a', 'ab') ... f.seek(0, os.SEEK_END) ... f.write('a\n') diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-completion.t --- a/tests/test-completion.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-completion.t Sat Feb 24 17:49:10 2018 -0600 @@ -102,6 +102,7 @@ debugnamecomplete debugobsolete debugpathcomplete + debugpeer debugpickmergetool debugpushkey debugpvec @@ -281,6 +282,7 @@ debugnamecomplete: debugobsolete: flags, record-parents, rev, exclusive, index, delete, date, user, template debugpathcomplete: full, normal, added, removed + debugpeer: debugpickmergetool: rev, changedelete, include, exclude, tool debugpushkey: debugpvec: diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-conflict.t --- a/tests/test-conflict.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-conflict.t Sat Feb 24 17:49:10 2018 -0600 @@ -138,9 +138,9 @@ $ hg up -q --clean . $ $PYTHON < fp = open('logfile', 'w') - > fp.write('12345678901234567890123456789012345678901234567890' + - > '1234567890') # there are 5 more columns for 80 columns + > fp = open('logfile', 'wb') + > fp.write(b'12345678901234567890123456789012345678901234567890' + + > b'1234567890') # there are 5 more columns for 80 columns > > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8')) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-context-metadata.t --- a/tests/test-context-metadata.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-context-metadata.t Sat Feb 24 17:49:10 2018 -0600 @@ -13,18 +13,19 @@ $ cat > metaedit.py < from __future__ import absolute_import - > from mercurial import context, registrar + > from mercurial import context, pycompat, registrar > cmdtable = {} > command = registrar.command(cmdtable) - > @command('metaedit') + > @command(b'metaedit') > def metaedit(ui, repo, arg): > # Modify commit message to "FOO" - > with repo.wlock(), repo.lock(), repo.transaction('metaedit'): - > old = repo['.'] - > kwargs = dict(s.split('=', 1) for s in arg.split(';')) + > with repo.wlock(), repo.lock(), repo.transaction(b'metaedit'): + > old = repo[b'.'] + > kwargs = dict(s.split(b'=', 1) for s in arg.split(b';')) > if 'parents' in kwargs: - > kwargs['parents'] = kwargs['parents'].split(',') - > new = context.metadataonlyctx(repo, old, **kwargs) + > kwargs[b'parents'] = kwargs[b'parents'].split(b',') + > new = context.metadataonlyctx(repo, old, + > **pycompat.strkwargs(kwargs)) > new.commit() > EOF $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'text=Changed' diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-contrib-perf.t --- a/tests/test-contrib-perf.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-contrib-perf.t Sat Feb 24 17:49:10 2018 -0600 @@ -114,6 +114,7 @@ perftags (no help text available) perftemplating (no help text available) + perfunidiff benchmark a unified diff between revisions perfvolatilesets benchmark the computation of various volatile set perfwalk (no help text available) @@ -126,6 +127,8 @@ $ hg perfannotate a $ hg perfbdiff -c 1 $ hg perfbdiff --alldata 1 + $ hg perfunidiff -c 1 + $ hg perfunidiff --alldata 1 $ hg perfbookmarks $ hg perfbranchmap $ hg perfcca diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-contrib.t --- a/tests/test-contrib.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-contrib.t Sat Feb 24 17:49:10 2018 -0600 @@ -201,7 +201,7 @@ binary file - $ $PYTHON -c "f = file('binary-local', 'w'); f.write('\x00'); f.close()" + $ $PYTHON -c "f = open('binary-local', 'w'); f.write('\x00'); f.close()" $ cat orig >> binary-local $ $PYTHON simplemerge -p binary-local base other warning: binary-local looks like a binary file. diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-convert-git.t --- a/tests/test-convert-git.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-convert-git.t Sat Feb 24 17:49:10 2018 -0600 @@ -420,7 +420,7 @@ $ mkdir git-repo3 $ cd git-repo3 $ git init-db >/dev/null 2>/dev/null - $ $PYTHON -c 'file("b", "wb").write("".join([chr(i) for i in range(256)])*16)' + $ $PYTHON -c 'import struct; open("b", "wb").write(b"".join([struct.Struct(">B").pack(i) for i in range(256)])*16)' $ git add b $ commit -a -m addbinary $ cd .. @@ -437,7 +437,7 @@ $ cd git-repo3-hg $ hg up -C 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ $PYTHON -c 'print len(file("b", "rb").read())' + $ $PYTHON -c 'print len(open("b", "rb").read())' 4096 $ cd .. diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-convert-hg-source.t --- a/tests/test-convert-hg-source.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-convert-hg-source.t Sat Feb 24 17:49:10 2018 -0600 @@ -126,9 +126,9 @@ $ cat > rewrite.py < import sys > # Interlace LF and CRLF - > lines = [(l.rstrip() + ((i % 2) and '\n' or '\r\n')) - > for i, l in enumerate(file(sys.argv[1]))] - > file(sys.argv[1], 'wb').write(''.join(lines)) + > lines = [(l.rstrip() + ((i % 2) and b'\n' or b'\r\n')) + > for i, l in enumerate(open(sys.argv[1], 'rb'))] + > open(sys.argv[1], 'wb').write(b''.join(lines)) > EOF $ $PYTHON rewrite.py new/.hg/shamap $ cd orig diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-convert-mtn.t --- a/tests/test-convert-mtn.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-convert-mtn.t Sat Feb 24 17:49:10 2018 -0600 @@ -43,7 +43,7 @@ $ mkdir dir $ echo b > dir/b $ echo d > dir/d - $ $PYTHON -c 'file("bin", "wb").write("a\\x00b")' + $ $PYTHON -c 'open("bin", "wb").write(b"a\\x00b")' $ echo c > c $ mtn add a dir/b dir/d c bin mtn: adding 'a' to workspace manifest @@ -65,7 +65,7 @@ $ echo b >> dir/b $ mtn drop c mtn: dropping 'c' from workspace manifest - $ $PYTHON -c 'file("bin", "wb").write("b\\x00c")' + $ $PYTHON -c 'open("bin", "wb").write(b"b\\x00c")' $ mtn ci -m update1 mtn: beginning commit on branch 'com.selenic.test' mtn: committed revision 51d0a982464573a2a2cf5ee2c9219c652aaebeff @@ -217,8 +217,8 @@ test large file support (> 32kB) - >>> fp = file('large-file', 'wb') - >>> for x in xrange(10000): fp.write('%d\n' % x) + >>> fp = open('large-file', 'wb') + >>> for x in range(10000): fp.write(b'%d\n' % x) >>> fp.close() $ md5sum.py large-file 5d6de8a95c3b6bf9e0ffb808ba5299c1 large-file diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-convert-p4-filetypes.t --- a/tests/test-convert-p4-filetypes.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-convert-p4-filetypes.t Sat Feb 24 17:49:10 2018 -0600 @@ -52,7 +52,7 @@ > p4 add -t $T file_$T2 > ;; > binary*) - > $PYTHON -c "file('file_$T2', 'wb').write('this is $T')" + > $PYTHON -c "open('file_$T2', 'wb').write(b'this is $T')" > p4 add -t $T file_$T2 > ;; > *) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-debugcommands.t --- a/tests/test-debugcommands.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-debugcommands.t Sat Feb 24 17:49:10 2018 -0600 @@ -381,3 +381,25 @@ https stream v2 + +Test debugpeer + + $ hg --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" debugpeer ssh://user@dummy/debugrevlog + url: ssh://user@dummy/debugrevlog + local: no + pushable: yes + + $ hg --config ui.ssh="\"$PYTHON\" \"$TESTDIR/dummyssh\"" --debug debugpeer ssh://user@dummy/debugrevlog + running "*" "*/tests/dummyssh" 'user@dummy' 'hg -R debugrevlog serve --stdio' (glob) (no-windows !) + running "*" "*\tests/dummyssh" "user@dummy" "hg -R debugrevlog serve --stdio" (glob) (windows !) + devel-peer-request: hello + sending hello command + devel-peer-request: between + devel-peer-request: pairs: 81 bytes + sending between command + remote: 384 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + remote: 1 + url: ssh://user@dummy/debugrevlog + local: no + pushable: yes diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-demandimport.py --- a/tests/test-demandimport.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-demandimport.py Sat Feb 24 17:49:10 2018 -0600 @@ -31,6 +31,27 @@ l = rsub("'<[a-z]*>'", "''", l) return l +demandimport.disable() +os.environ['HGDEMANDIMPORT'] = 'disable' +# this enable call should not actually enable demandimport! +demandimport.enable() +from mercurial import node +print("node =", f(node)) +# now enable it for real +del os.environ['HGDEMANDIMPORT'] +demandimport.enable() + +# Test access to special attributes through demandmod proxy +from mercurial import error as errorproxy +print("errorproxy =", f(errorproxy)) +print("errorproxy.__doc__ = %r" + % (' '.join(errorproxy.__doc__.split()[:3]) + ' ...')) +print("errorproxy.__name__ = %r" % errorproxy.__name__) +# __name__ must be accessible via __dict__ so the relative imports can be +# resolved +print("errorproxy.__dict__['__name__'] = %r" % errorproxy.__dict__['__name__']) +print("errorproxy =", f(errorproxy)) + import os print("os =", f(os)) @@ -69,17 +90,6 @@ print("re.stderr =", f(re.stderr)) print("re =", f(re)) -# Test access to special attributes through demandmod proxy -from mercurial import pvec as pvecproxy -print("pvecproxy =", f(pvecproxy)) -print("pvecproxy.__doc__ = %r" - % (' '.join(pvecproxy.__doc__.split()[:3]) + ' ...')) -print("pvecproxy.__name__ = %r" % pvecproxy.__name__) -# __name__ must be accessible via __dict__ so the relative imports can be -# resolved -print("pvecproxy.__dict__['__name__'] = %r" % pvecproxy.__dict__['__name__']) -print("pvecproxy =", f(pvecproxy)) - import contextlib print("contextlib =", f(contextlib)) try: @@ -97,10 +107,3 @@ print("__import__('contextlib', ..., ['unknownattr']) =", f(contextlibimp)) print("hasattr(contextlibimp, 'unknownattr') =", util.safehasattr(contextlibimp, 'unknownattr')) - -demandimport.disable() -os.environ['HGDEMANDIMPORT'] = 'disable' -# this enable call should not actually enable demandimport! -demandimport.enable() -from mercurial import node -print("node =", f(node)) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-demandimport.py.out --- a/tests/test-demandimport.py.out Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-demandimport.py.out Sat Feb 24 17:49:10 2018 -0600 @@ -1,3 +1,9 @@ +node = +errorproxy = +errorproxy.__doc__ = 'Mercurial exceptions. This ...' +errorproxy.__name__ = 'mercurial.error' +errorproxy.__dict__['__name__'] = 'mercurial.error' +errorproxy = os = os.system = os = @@ -18,13 +24,7 @@ re = re.stderr = ', mode 'w' at 0x?> re = -pvecproxy = -pvecproxy.__doc__ = 'A "pvec" is ...' -pvecproxy.__name__ = 'mercurial.pvec' -pvecproxy.__dict__['__name__'] = 'mercurial.pvec' -pvecproxy = contextlib = contextlib.unknownattr = ImportError: cannot import name unknownattr __import__('contextlib', ..., ['unknownattr']) = hasattr(contextlibimp, 'unknownattr') = False -node = diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-devel-warnings.t --- a/tests/test-devel-warnings.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-devel-warnings.t Sat Feb 24 17:49:10 2018 -0600 @@ -17,7 +17,7 @@ > > @command(b'buggytransaction', [], '') > def buggylocking(ui, repo): - > tr = repo.transaction('buggy') + > tr = repo.transaction(b'buggy') > # make sure we rollback the transaction as we don't want to rely on the__del__ > tr.release() > @@ -26,8 +26,8 @@ > """check that reentrance is fine""" > wl = repo.wlock() > lo = repo.lock() - > tr = repo.transaction('proper') - > tr2 = repo.transaction('proper') + > tr = repo.transaction(b'proper') + > tr2 = repo.transaction(b'proper') > lo2 = repo.lock() > wl2 = repo.wlock() > wl2.release() @@ -46,34 +46,34 @@ > > @command(b'no-wlock-write', [], '') > def nowlockwrite(ui, repo): - > with repo.vfs(b'branch', 'a'): + > with repo.vfs(b'branch', b'a'): > pass > > @command(b'no-lock-write', [], '') > def nolockwrite(ui, repo): - > with repo.svfs(b'fncache', 'a'): + > with repo.svfs(b'fncache', b'a'): > pass > > @command(b'stripintr', [], '') > def stripintr(ui, repo): > lo = repo.lock() - > tr = repo.transaction('foobar') + > tr = repo.transaction(b'foobar') > try: - > repair.strip(repo.ui, repo, [repo['.'].node()]) + > repair.strip(repo.ui, repo, [repo[b'.'].node()]) > finally: > lo.release() > @command(b'oldanddeprecated', [], '') > def oldanddeprecated(ui, repo): > """test deprecation warning API""" > def foobar(ui): - > ui.deprecwarn('foorbar is deprecated, go shopping', '42.1337') + > ui.deprecwarn(b'foorbar is deprecated, go shopping', b'42.1337') > foobar(ui) > @command(b'nouiwarning', [], '') > def nouiwarning(ui, repo): - > util.nouideprecwarn('this is a test', '13.37') + > util.nouideprecwarn(b'this is a test', b'13.37') > @command(b'programmingerror', [], '') > def programmingerror(ui, repo): - > raise error.ProgrammingError('something went wrong', hint='try again') + > raise error.ProgrammingError(b'something went wrong', hint=b'try again') > EOF $ cat << EOF >> $HGRCPATH @@ -331,7 +331,7 @@ $ hg nouiwarning $TESTTMP/buggylocking.py:*: DeprecationWarning: this is a test (glob) (compatibility will be dropped after Mercurial-13.37, update your code.) - util.nouideprecwarn('this is a test', '13.37') + util.nouideprecwarn(b'this is a test', b'13.37') (disabled outside of test run) @@ -350,25 +350,25 @@ > configtable = {} > configitem = registrar.configitem(configtable) > - > configitem('test', 'some', default='foo') - > configitem('test', 'dynamic', default=configitems.dynamicdefault) - > configitem('test', 'callable', default=list) + > configitem(b'test', b'some', default=b'foo') + > configitem(b'test', b'dynamic', default=configitems.dynamicdefault) + > configitem(b'test', b'callable', default=list) > # overwrite a core config - > configitem('ui', 'quiet', default=False) - > configitem('ui', 'interactive', default=None) + > configitem(b'ui', b'quiet', default=False) + > configitem(b'ui', b'interactive', default=None) > > @command(b'buggyconfig') > def cmdbuggyconfig(ui, repo): - > repo.ui.config('ui', 'quiet', True) - > repo.ui.config('ui', 'interactive', False) - > repo.ui.config('test', 'some', 'bar') - > repo.ui.config('test', 'some', 'foo') - > repo.ui.config('test', 'dynamic', 'some-required-default') - > repo.ui.config('test', 'dynamic') - > repo.ui.config('test', 'callable', []) - > repo.ui.config('test', 'callable', 'foo') - > repo.ui.config('test', 'unregistered') - > repo.ui.config('unregistered', 'unregistered') + > repo.ui.config(b'ui', b'quiet', True) + > repo.ui.config(b'ui', b'interactive', False) + > repo.ui.config(b'test', b'some', b'bar') + > repo.ui.config(b'test', b'some', b'foo') + > repo.ui.config(b'test', b'dynamic', b'some-required-default') + > repo.ui.config(b'test', b'dynamic') + > repo.ui.config(b'test', b'callable', []) + > repo.ui.config(b'test', b'callable', b'foo') + > repo.ui.config(b'test', b'unregistered') + > repo.ui.config(b'unregistered', b'unregistered') > EOF $ hg --config "extensions.buggyconfig=${TESTTMP}/buggyconfig.py" buggyconfig diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-diff-binary-file.t --- a/tests/test-diff-binary-file.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-diff-binary-file.t Sat Feb 24 17:49:10 2018 -0600 @@ -81,7 +81,7 @@ $ cat > writebin.py < import sys > path = sys.argv[1] - > open(path, 'wb').write('\x00\x01\x02\x03') + > open(path, 'wb').write(b'\x00\x01\x02\x03') > EOF $ $PYTHON writebin.py binfile.bin $ hg add binfile.bin diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-dispatch.py --- a/tests/test-dispatch.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-dispatch.py Sat Feb 24 17:49:10 2018 -0600 @@ -9,27 +9,27 @@ Prints command and result value, but does not handle quoting. """ - print("running: %s" % (cmd,)) + print(b"running: %s" % (cmd,)) req = dispatch.request(cmd.split()) result = dispatch.dispatch(req) - print("result: %r" % (result,)) + print(b"result: %r" % (result,)) -testdispatch("init test1") +testdispatch(b"init test1") os.chdir('test1') # create file 'foo', add and commit f = open('foo', 'wb') -f.write('foo\n') +f.write(b'foo\n') f.close() -testdispatch("add foo") -testdispatch("commit -m commit1 -d 2000-01-01 foo") +testdispatch(b"add foo") +testdispatch(b"commit -m commit1 -d 2000-01-01 foo") # append to file 'foo' and commit f = open('foo', 'ab') -f.write('bar\n') +f.write(b'bar\n') f.close() -testdispatch("commit -m commit2 -d 2000-01-02 foo") +testdispatch(b"commit -m commit2 -d 2000-01-02 foo") # check 88803a69b24 (fancyopts modified command table) -testdispatch("log -r 0") -testdispatch("log -r tip") +testdispatch(b"log -r 0") +testdispatch(b"log -r tip") diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-encoding-align.t --- a/tests/test-encoding-align.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-encoding-align.t Sat Feb 24 17:49:10 2018 -0600 @@ -6,16 +6,16 @@ $ cd t $ $PYTHON << EOF > # (byte, width) = (6, 4) - > s = "\xe7\x9f\xad\xe5\x90\x8d" + > s = b"\xe7\x9f\xad\xe5\x90\x8d" > # (byte, width) = (7, 7): odd width is good for alignment test - > m = "MIDDLE_" + > m = b"MIDDLE_" > # (byte, width) = (18, 12) - > l = "\xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d" - > f = file('s', 'w'); f.write(s); f.close() - > f = file('m', 'w'); f.write(m); f.close() - > f = file('l', 'w'); f.write(l); f.close() + > l = b"\xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d" + > f = open('s', 'wb'); f.write(s); f.close() + > f = open('m', 'wb'); f.write(m); f.close() + > f = open('l', 'wb'); f.write(l); f.close() > # instant extension to show list of options - > f = file('showoptlist.py', 'w'); f.write("""# encoding: utf-8 + > f = open('showoptlist.py', 'wb'); f.write(b"""# encoding: utf-8 > from mercurial import registrar > cmdtable = {} > command = registrar.command(cmdtable) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-encoding.t --- a/tests/test-encoding.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-encoding.t Sat Feb 24 17:49:10 2018 -0600 @@ -15,9 +15,9 @@ $ hg co 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ $PYTHON << EOF - > f = file('latin-1', 'w'); f.write("latin-1 e' encoded: \xe9"); f.close() - > f = file('utf-8', 'w'); f.write("utf-8 e' encoded: \xc3\xa9"); f.close() - > f = file('latin-1-tag', 'w'); f.write("\xe9"); f.close() + > f = open('latin-1', 'wb'); f.write(b"latin-1 e' encoded: \xe9"); f.close() + > f = open('utf-8', 'wb'); f.write(b"utf-8 e' encoded: \xc3\xa9"); f.close() + > f = open('latin-1-tag', 'wb'); f.write(b"\xe9"); f.close() > EOF should fail with encoding error diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-eol.t --- a/tests/test-eol.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-eol.t Sat Feb 24 17:49:10 2018 -0600 @@ -17,12 +17,12 @@ > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) > except ImportError: > pass - > (old, new) = sys.argv[1] == 'LF' and ('\n', '\r\n') or ('\r\n', '\n') + > (old, new) = sys.argv[1] == 'LF' and (b'\n', b'\r\n') or (b'\r\n', b'\n') > print("%% switching encoding from %r to %r" % (old, new)) > for path in sys.argv[2:]: - > data = file(path, 'rb').read() + > data = open(path, 'rb').read() > data = data.replace(old, new) - > file(path, 'wb').write(data) + > open(path, 'wb').write(data) > EOF $ seteol () { diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-export.t --- a/tests/test-export.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-export.t Sat Feb 24 17:49:10 2018 -0600 @@ -186,6 +186,12 @@ exporting patch: ____________0123456789_______ABCDEFGHIJKLMNOPQRSTUVWXYZ______abcdefghijklmnopqrstuvwxyz____.patch +Invalid pattern in file name: + + $ hg export -o '%x.patch' tip + abort: invalid format spec '%x' in output filename + [255] + Catch exporting unknown revisions (especially empty revsets, see issue3353) $ hg export diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-extdiff.t --- a/tests/test-extdiff.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-extdiff.t Sat Feb 24 17:49:10 2018 -0600 @@ -252,8 +252,8 @@ > #!$PYTHON > import time > time.sleep(1) # avoid unchanged-timestamp problems - > file('a/a', 'ab').write('edited\n') - > file('a/b', 'ab').write('edited\n') + > open('a/a', 'ab').write(b'edited\n') + > open('a/b', 'ab').write(b'edited\n') > EOT #if execbit @@ -424,7 +424,8 @@ Test handling of non-ASCII paths in generated docstrings (issue5301) - >>> open("u", "w").write("\xa5\xa5") + >>> with open("u", "wb") as f: + ... n = f.write(b"\xa5\xa5") $ U=`cat u` $ HGPLAIN=1 hg --config hgext.extdiff= --config extdiff.cmd.td=hi help -k xyzzy diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-extension.t --- a/tests/test-extension.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-extension.t Sat Feb 24 17:49:10 2018 -0600 @@ -1707,8 +1707,8 @@ > test_unicode_default_value = $TESTTMP/test_unicode_default_value.py > EOF $ hg -R $TESTTMP/opt-unicode-default dummy - *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: option 'dummy.opt' has a unicode default value - *** (change the dummy.opt default value to a non-unicode string) + *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: unicode u'value' found in cmdtable.dummy + *** (use b'' to make it byte string) hg: unknown command 'dummy' (did you mean summary?) [255] diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-filecache.py --- a/tests/test-filecache.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-filecache.py Sat Feb 24 17:49:10 2018 -0600 @@ -11,11 +11,15 @@ extensions, hg, localrepo, + pycompat, ui as uimod, util, vfs as vfsmod, ) +if pycompat.ispy3: + xrange = range + class fakerepo(object): def __init__(self): self._filecache = {} diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-fileset.t --- a/tests/test-fileset.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-fileset.t Sat Feb 24 17:49:10 2018 -0600 @@ -180,7 +180,7 @@ Test files properties - >>> file('bin', 'wb').write('\0a') + >>> open('bin', 'wb').write(b'\0a') $ fileset 'binary()' $ fileset 'binary() and unknown()' bin @@ -219,8 +219,8 @@ $ hg --config ui.portablefilenames=ignore add con.xml #endif - >>> file('1k', 'wb').write(' '*1024) - >>> file('2k', 'wb').write(' '*2048) + >>> open('1k', 'wb').write(b' '*1024) + >>> open('2k', 'wb').write(b' '*2048) $ hg add 1k 2k $ fileset 'size("bar")' hg: parse error: couldn't parse size: bar diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-glog.t --- a/tests/test-glog.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-glog.t Sat Feb 24 17:49:10 2018 -0600 @@ -87,21 +87,22 @@ > cmdutil, > commands, > extensions, + > logcmdutil, > revsetlang, > smartset, > ) > > def logrevset(repo, pats, opts): - > revs = cmdutil._logrevs(repo, opts) + > revs = logcmdutil._initialrevs(repo, opts) > if not revs: > return None - > match, pats, slowpath = cmdutil._makelogmatcher(repo, revs, pats, opts) - > return cmdutil._makelogrevset(repo, match, pats, slowpath, opts) + > match, pats, slowpath = logcmdutil._makematcher(repo, revs, pats, opts) + > return logcmdutil._makerevset(repo, match, pats, slowpath, opts) > > def uisetup(ui): > def printrevset(orig, repo, pats, opts): > revs, filematcher = orig(repo, pats, opts) - > if opts.get('print_revset'): + > if opts.get(b'print_revset'): > expr = logrevset(repo, pats, opts) > if expr: > tree = revsetlang.parse(expr) @@ -109,15 +110,15 @@ > else: > tree = [] > ui = repo.ui - > ui.write('%r\n' % (opts.get('rev', []),)) - > ui.write(revsetlang.prettyformat(tree) + '\n') - > ui.write(smartset.prettyformat(revs) + '\n') + > ui.write(b'%r\n' % (opts.get(b'rev', []),)) + > ui.write(revsetlang.prettyformat(tree) + b'\n') + > ui.write(smartset.prettyformat(revs) + b'\n') > revs = smartset.baseset() # display no revisions > return revs, filematcher - > extensions.wrapfunction(cmdutil, 'getlogrevs', printrevset) - > aliases, entry = cmdutil.findcmd('log', commands.table) - > entry[1].append(('', 'print-revset', False, - > 'print generated revset and exit (DEPRECATED)')) + > extensions.wrapfunction(logcmdutil, 'getrevs', printrevset) + > aliases, entry = cmdutil.findcmd(b'log', commands.table) + > entry[1].append((b'', b'print-revset', False, + > b'print generated revset and exit (DEPRECATED)')) > EOF $ echo "[extensions]" >> $HGRCPATH @@ -2420,7 +2421,7 @@ | ~ -node template with changeset_printer: +node template with changesetprinter: $ hg log -Gqr 5:7 --config ui.graphnodetemplate='"{rev}"' 7 7:02dbb8e276b8 @@ -2432,7 +2433,7 @@ | ~ -node template with changeset_templater (shared cache variable): +node template with changesettemplater (shared cache variable): $ hg log -Gr 5:7 -T '{latesttag % "{rev} {tag}+{distance}"}\n' \ > --config ui.graphnodetemplate='{ifeq(latesttagdistance, 0, "#", graphnode)}' diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-help.t --- a/tests/test-help.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-help.t Sat Feb 24 17:49:10 2018 -0600 @@ -274,6 +274,7 @@ purge command to delete untracked files from the working directory relink recreates hardlinks between repository clones + remotenames showing remotebookmarks and remotebranches in UI schemes extend schemes with shortcuts to repository swarms share share a common history between several working directories shelve save and restore changes to the working directory @@ -282,6 +283,11 @@ win32mbcs allow the use of MBCS paths with problematic encodings zeroconf discover and advertise repositories on the local network +Verify that deprecated extensions are included if --verbose: + + $ hg -v help extensions | grep children + children command to display child changesets (DEPRECATED) + Verify that extension keywords appear in help templates $ hg help --config extensions.transplant= templating|grep transplant > /dev/null @@ -948,6 +954,7 @@ debugoptEXP (no help text available) debugpathcomplete complete part or all of a tracked path + debugpeer establish a connection to a peer repository debugpickmergetool examine which merge tool is chosen for specified file debugpushkey access the pushkey key/value protocol @@ -1492,6 +1499,8 @@ Extensions: clonebundles advertise pre-generated bundles to seed clones + narrow create clones which fetch history data for subset of files + (EXPERIMENTAL) prefixedname matched against word "clone" relink recreates hardlinks between repository clones @@ -3387,6 +3396,70 @@ + $ get-with-headers.py 127.0.0.1:$HGPORT "help/unknowntopic" + 404 Not Found + + + + + + + + + + test: error + + + +
+ + +
+ + +

error

+ + + + +
+

+ An error occurred while processing your request: +

+

+ Not Found +

+
+
+
+ + + + + + + [1] + $ killdaemons.py #endif diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-hgweb-auth.py --- a/tests/test-hgweb-auth.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-hgweb-auth.py Sat Feb 24 17:49:10 2018 -0600 @@ -19,7 +19,7 @@ def writeauth(items): ui = origui.copy() - for name, value in items.iteritems(): + for name, value in items.items(): ui.setconfig('auth', name, value) return ui @@ -36,7 +36,7 @@ for name in ('.username', '.password'): if (p + name) not in auth: auth[p + name] = p - auth = dict((k, v) for k, v in auth.iteritems() if v is not None) + auth = dict((k, v) for k, v in auth.items() if v is not None) ui = writeauth(auth) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-hgweb.t --- a/tests/test-hgweb.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-hgweb.t Sat Feb 24 17:49:10 2018 -0600 @@ -333,14 +333,14 @@ Test the access/error files are opened in append mode - $ $PYTHON -c "print len(file('access.log').readlines()), 'log lines written'" + $ $PYTHON -c "print len(open('access.log', 'rb').readlines()), 'log lines written'" 14 log lines written static file $ get-with-headers.py --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server 200 Script output follows - content-length: 9118 + content-length: 9126 content-type: text/css body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; background: white; color: black; } @@ -374,7 +374,7 @@ div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; } div.log_body { padding:8px 8px 8px 150px; } .age { white-space:nowrap; } - span.age { position:relative; float:left; width:142px; font-style:italic; } + a.title span.age { position:relative; float:left; width:142px; font-style:italic; } div.log_link { padding:0px 8px; font-size:10px; font-family:sans-serif; font-style:normal; diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-histedit-arguments.t --- a/tests/test-histedit-arguments.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-histedit-arguments.t Sat Feb 24 17:49:10 2018 -0600 @@ -280,9 +280,9 @@ -------------------------------------------------------------------- $ $PYTHON < fp = open('logfile', 'w') - > fp.write('12345678901234567890123456789012345678901234567890' + - > '12345') # there are 5 more columns for 80 columns + > fp = open('logfile', 'wb') + > fp.write(b'12345678901234567890123456789012345678901234567890' + + > b'12345') # there are 5 more columns for 80 columns > > # 2 x 4 = 8 columns, but 3 x 4 = 12 bytes > fp.write(u'\u3042\u3044\u3046\u3048'.encode('utf-8')) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-histedit-fold.t --- a/tests/test-histedit-fold.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-histedit-fold.t Sat Feb 24 17:49:10 2018 -0600 @@ -154,9 +154,9 @@ > from mercurial import util > def abortfolding(ui, repo, hooktype, **kwargs): > ctx = repo[kwargs.get('node')] - > if set(ctx.files()) == {'c', 'd', 'f'}: + > if set(ctx.files()) == {b'c', b'd', b'f'}: > return True # abort folding commit only - > ui.warn('allow non-folding commit\\n') + > ui.warn(b'allow non-folding commit\\n') > EOF $ cat > .hg/hgrc < [hooks] diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-hook.t --- a/tests/test-hook.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-hook.t Sat Feb 24 17:49:10 2018 -0600 @@ -417,9 +417,9 @@ > def printargs(ui, args): > a = list(args.items()) > a.sort() - > ui.write('hook args:\n') + > ui.write(b'hook args:\n') > for k, v in a: - > ui.write(' %s %s\n' % (k, v)) + > ui.write(b' %s %s\n' % (k, v)) > > def passhook(ui, repo, **args): > printargs(ui, args) @@ -432,19 +432,19 @@ > pass > > def raisehook(**args): - > raise LocalException('exception from hook') + > raise LocalException(b'exception from hook') > > def aborthook(**args): - > raise error.Abort('raise abort from hook') + > raise error.Abort(b'raise abort from hook') > > def brokenhook(**args): > return 1 + {} > > def verbosehook(ui, **args): - > ui.note('verbose output from hook\n') + > ui.note(b'verbose output from hook\n') > > def printtags(ui, repo, **args): - > ui.write('%s\n' % sorted(repo.tags())) + > ui.write(b'%s\n' % sorted(repo.tags())) > > class container: > unreachable = 1 @@ -667,7 +667,7 @@ $ cd hooks $ cat > testhooks.py < def testhook(ui, **args): - > ui.write('hook works\n') + > ui.write(b'hook works\n') > EOF $ echo '[hooks]' > ../repo/.hg/hgrc $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc @@ -886,7 +886,7 @@ > def uisetup(ui): > class untrustedui(ui.__class__): > def _trusted(self, fp, f): - > if util.normpath(fp.name).endswith('untrusted/.hg/hgrc'): + > if util.normpath(fp.name).endswith(b'untrusted/.hg/hgrc'): > return False > return super(untrustedui, self)._trusted(fp, f) > ui.__class__ = untrustedui diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-http-branchmap.t --- a/tests/test-http-branchmap.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-http-branchmap.t Sat Feb 24 17:49:10 2018 -0600 @@ -68,22 +68,22 @@ > self._file = stdout > > def write(self, data): - > if data == '47\n': + > if data == b'47\n': > # latin1 encoding is one %xx (3 bytes) shorter - > data = '44\n' - > elif data.startswith('%C3%A6 '): + > data = b'44\n' + > elif data.startswith(b'%C3%A6 '): > # translate to latin1 encoding - > data = '%%E6 %s' % data[7:] + > data = b'%%E6 %s' % data[7:] > self._file.write(data) > > def __getattr__(self, name): > return getattr(self._file, name) > - > sys.stdout = StdoutWrapper(sys.stdout) - > sys.stderr = StdoutWrapper(sys.stderr) + > sys.stdout = StdoutWrapper(getattr(sys.stdout, 'buffer', sys.stdout)) + > sys.stderr = StdoutWrapper(getattr(sys.stderr, 'buffer', sys.stderr)) > > myui = ui.ui.load() - > repo = hg.repository(myui, 'a') + > repo = hg.repository(myui, b'a') > commands.serve(myui, repo, stdio=True, cmdserver=False) > EOF $ echo baz >> b/foo diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-http-bundle1.t --- a/tests/test-http-bundle1.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-http-bundle1.t Sat Feb 24 17:49:10 2018 -0600 @@ -68,7 +68,7 @@ $ cat > $TESTTMP/removesupportedformat.py << EOF > from mercurial import localrepo > def extsetup(ui): - > localrepo.localrepository.supportedformats.remove('generaldelta') + > localrepo.localrepository.supportedformats.remove(b'generaldelta') > EOF $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3 @@ -181,7 +181,8 @@ > if not auth: > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who', > [('WWW-Authenticate', 'Basic Realm="mercurial"')]) - > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']: + > if base64.b64decode(auth.split()[1]).split(b':', 1) != [b'user', + > b'pass']: > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no') > def extsetup(): > common.permhooks.insert(0, perform_authentication) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-impexp-branch.t --- a/tests/test-impexp-branch.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-impexp-branch.t Sat Feb 24 17:49:10 2018 -0600 @@ -74,9 +74,9 @@ $ hg strip --no-backup . 1 files updated, 0 files merged, 0 files removed, 0 files unresolved >>> import re - >>> p = file('../r1.patch', 'rb').read() + >>> p = open('../r1.patch', 'rb').read() >>> p = re.sub(r'Parent\s+', 'Parent ', p) - >>> file('../r1-ws.patch', 'wb').write(p) + >>> open('../r1-ws.patch', 'wb').write(p) $ hg import --exact ../r1-ws.patch applying ../r1-ws.patch diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-import-bypass.t --- a/tests/test-import-bypass.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-import-bypass.t Sat Feb 24 17:49:10 2018 -0600 @@ -227,7 +227,7 @@ (this also tests that editor is not invoked for '--bypass', if the commit message is explicitly specified, regardless of '--edit') - $ $PYTHON -c 'file("a", "wb").write("a\r\n")' + $ $PYTHON -c 'open("a", "wb").write(b"a\r\n")' $ hg ci -m makeacrlf $ HGEDITOR=cat hg import -m 'should fail because of eol' --edit --bypass ../test.diff applying ../test.diff diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-import-context.t --- a/tests/test-import-context.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-import-context.t Sat Feb 24 17:49:10 2018 -0600 @@ -7,7 +7,7 @@ > lasteol = sys.argv[2] == '1' > patterns = sys.argv[3:] > - > fp = file(path, 'wb') + > fp = open(path, 'wb') > for i, pattern in enumerate(patterns): > count = int(pattern[0:-1]) > char = pattern[-1] + '\n' @@ -19,7 +19,7 @@ > EOF $ cat > cat.py < import sys - > sys.stdout.write(repr(file(sys.argv[1], 'rb').read()) + '\n') + > sys.stdout.write(repr(open(sys.argv[1], 'rb').read()) + '\n') > EOF Initialize the test repository diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-import-eol.t --- a/tests/test-import-eol.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-import-eol.t Sat Feb 24 17:49:10 2018 -0600 @@ -1,21 +1,21 @@ $ cat > makepatch.py < f = file('eol.diff', 'wb') + > f = open('eol.diff', 'wb') > w = f.write - > w('test message\n') - > w('diff --git a/a b/a\n') - > w('--- a/a\n') - > w('+++ b/a\n') - > w('@@ -1,5 +1,5 @@\n') - > w(' a\n') - > w('-bbb\r\n') - > w('+yyyy\r\n') - > w(' cc\r\n') - > w(' \n') - > w(' d\n') - > w('-e\n') - > w('\ No newline at end of file\n') - > w('+z\r\n') - > w('\ No newline at end of file\r\n') + > w(b'test message\n') + > w(b'diff --git a/a b/a\n') + > w(b'--- a/a\n') + > w(b'+++ b/a\n') + > w(b'@@ -1,5 +1,5 @@\n') + > w(b' a\n') + > w(b'-bbb\r\n') + > w(b'+yyyy\r\n') + > w(b' cc\r\n') + > w(b' \n') + > w(b' d\n') + > w(b'-e\n') + > w(b'\ No newline at end of file\n') + > w(b'+z\r\n') + > w(b'\ No newline at end of file\r\n') > EOF $ hg init repo @@ -25,7 +25,7 @@ Test different --eol values - $ $PYTHON -c 'file("a", "wb").write("a\nbbb\ncc\n\nd\ne")' + $ $PYTHON -c 'open("a", "wb").write(b"a\nbbb\ncc\n\nd\ne")' $ hg ci -Am adda adding .hgignore adding a @@ -89,7 +89,7 @@ auto EOL on CRLF file - $ $PYTHON -c 'file("a", "wb").write("a\r\nbbb\r\ncc\r\n\r\nd\r\ne")' + $ $PYTHON -c 'open("a", "wb").write(b"a\r\nbbb\r\ncc\r\n\r\nd\r\ne")' $ hg commit -m 'switch EOLs in a' $ hg --traceback --config patch.eol='auto' import eol.diff applying eol.diff @@ -105,11 +105,11 @@ auto EOL on new file or source without any EOL - $ $PYTHON -c 'file("noeol", "wb").write("noeol")' + $ $PYTHON -c 'open("noeol", "wb").write(b"noeol")' $ hg add noeol $ hg commit -m 'add noeol' - $ $PYTHON -c 'file("noeol", "wb").write("noeol\r\nnoeol\n")' - $ $PYTHON -c 'file("neweol", "wb").write("neweol\nneweol\r\n")' + $ $PYTHON -c 'open("noeol", "wb").write(b"noeol\r\nnoeol\n")' + $ $PYTHON -c 'open("neweol", "wb").write(b"neweol\nneweol\r\n")' $ hg add neweol $ hg diff --git > noeol.diff $ hg revert --no-backup noeol neweol @@ -127,10 +127,10 @@ Test --eol and binary patches - $ $PYTHON -c 'file("b", "wb").write("a\x00\nb\r\nd")' + $ $PYTHON -c 'open("b", "wb").write(b"a\x00\nb\r\nd")' $ hg ci -Am addb adding b - $ $PYTHON -c 'file("b", "wb").write("a\x00\nc\r\nd")' + $ $PYTHON -c 'open("b", "wb").write(b"a\x00\nc\r\nd")' $ hg diff --git > bin.diff $ hg revert --no-backup b diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-import-git.t --- a/tests/test-import-git.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-import-git.t Sat Feb 24 17:49:10 2018 -0600 @@ -563,10 +563,10 @@ > Mc$`b*O5$Pw00T?_*Z=?k > > EOF - >>> fp = file('binary.diff', 'rb') + >>> fp = open('binary.diff', 'rb') >>> data = fp.read() >>> fp.close() - >>> file('binary.diff', 'wb').write(data.replace('\n', '\r\n')) + >>> open('binary.diff', 'wb').write(data.replace(b'\n', b'\r\n')) $ rm binary2 $ hg import --no-commit binary.diff applying binary.diff diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-import.t --- a/tests/test-import.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-import.t Sat Feb 24 17:49:10 2018 -0600 @@ -56,7 +56,7 @@ $ cat > dummypatch.py < from __future__ import print_function > print('patching file a') - > file('a', 'wb').write('line2\n') + > open('a', 'wb').write(b'line2\n') > EOF $ hg clone -r0 a b adding changesets @@ -291,7 +291,7 @@ > msg.set_payload('email commit message\n' + patch) > msg['Subject'] = 'email patch' > msg['From'] = 'email patcher' - > file(sys.argv[2], 'wb').write(msg.as_string()) + > open(sys.argv[2], 'wb').write(msg.as_string()) > EOF @@ -389,7 +389,7 @@ > msg.set_payload('email patch\n\nnext line\n---\n' + patch) > msg['Subject'] = '[PATCH] email patch' > msg['From'] = 'email patcher' - > file(sys.argv[2], 'wb').write(msg.as_string()) + > open(sys.argv[2], 'wb').write(msg.as_string()) > EOF @@ -829,7 +829,7 @@ $ hg init binaryremoval $ cd binaryremoval $ echo a > a - $ $PYTHON -c "file('b', 'wb').write('a\x00b')" + $ $PYTHON -c "open('b', 'wb').write(b'a\x00b')" $ hg ci -Am addall adding a adding b diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-install.t --- a/tests/test-install.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-install.t Sat Feb 24 17:49:10 2018 -0600 @@ -17,7 +17,7 @@ checking "re2" regexp engine \((available|missing)\) (re) checking templates (*mercurial?templates)... (glob) checking default template (*mercurial?templates?map-cmdline.default) (glob) - checking commit editor... (* -c "import sys; sys.exit(0)") (glob) + checking commit editor... (*) (glob) checking username (test) no problems detected @@ -31,7 +31,7 @@ "defaulttemplate": "*mercurial?templates?map-cmdline.default", (glob) "defaulttemplateerror": null, "defaulttemplatenotfound": "default", - "editor": "* -c \"import sys; sys.exit(0)\"", (glob) + "editor": "*", (glob) "editornotfound": false, "encoding": "ascii", "encodingerror": null, @@ -72,7 +72,7 @@ checking "re2" regexp engine \((available|missing)\) (re) checking templates (*mercurial?templates)... (glob) checking default template (*mercurial?templates?map-cmdline.default) (glob) - checking commit editor... (* -c "import sys; sys.exit(0)") (glob) + checking commit editor... (*) (glob) checking username... no username supplied (specify a username in your configuration file) @@ -120,6 +120,35 @@ checking username (test) no problems detected +print out the binary post-shlexsplit in the error message when commit editor is +not found (this is intentionally using backslashes to mimic a windows usecase). + $ HGEDITOR="c:\foo\bar\baz.exe -y -z" hg debuginstall + checking encoding (ascii)... + checking Python executable (*) (glob) + checking Python version (*) (glob) + checking Python lib (*lib*)... (glob) + checking Python security support (*) (glob) + TLS 1.2 not supported by Python install; network connections lack modern security (?) + SNI not supported by Python install; may have connectivity issues with some servers (?) + checking Mercurial version (*) (glob) + checking Mercurial custom build (*) (glob) + checking module policy (*) (glob) + checking installed modules (*mercurial)... (glob) + checking registered compression engines (*zlib*) (glob) + checking available compression engines (*zlib*) (glob) + checking available compression engines for wire protocol (*zlib*) (glob) + checking "re2" regexp engine \((available|missing)\) (re) + checking templates (*mercurial?templates)... (glob) + checking default template (*mercurial?templates?map-cmdline.default) (glob) + checking commit editor... (c:\foo\bar\baz.exe) (windows !) + Can't find editor 'c:\foo\bar\baz.exe' in PATH (windows !) + checking commit editor... (c:foobarbaz.exe) (no-windows !) + Can't find editor 'c:foobarbaz.exe' in PATH (no-windows !) + (specify a commit editor in your configuration file) + checking username (test) + 1 problems detected, please check your install! + [1] + #if test-repo $ . "$TESTDIR/helpers-testrepo.sh" diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-issue4074.t --- a/tests/test-issue4074.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-issue4074.t Sat Feb 24 17:49:10 2018 -0600 @@ -4,7 +4,7 @@ $ cat > s.py < import random - > for x in xrange(100000): + > for x in range(100000): > print > if random.randint(0, 100) >= 50: > x += 1 diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-largefiles-small-disk.t --- a/tests/test-largefiles-small-disk.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-largefiles-small-disk.t Sat Feb 24 17:49:10 2018 -0600 @@ -11,7 +11,7 @@ > _origcopyfileobj = shutil.copyfileobj > def copyfileobj(fsrc, fdst, length=16*1024): > # allow journal files (used by transaction) to be written - > if 'journal.' in fdst.name: + > if b'journal.' in fdst.name: > return _origcopyfileobj(fsrc, fdst, length) > fdst.write(fsrc.read(4)) > raise IOError(errno.ENOSPC, os.strerror(errno.ENOSPC)) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-largefiles-wireproto.t --- a/tests/test-largefiles-wireproto.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-largefiles-wireproto.t Sat Feb 24 17:49:10 2018 -0600 @@ -1,3 +1,13 @@ +#testcases sshv1 sshv2 + +#if sshv2 + $ cat >> $HGRCPATH << EOF + > [experimental] + > sshpeer.advertise-v2 = true + > sshserver.support-v2 = true + > EOF +#endif + This file contains testcases that tend to be related to the wire protocol part of largefiles. diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-lfs-largefiles.t --- a/tests/test-lfs-largefiles.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-lfs-largefiles.t Sat Feb 24 17:49:10 2018 -0600 @@ -298,7 +298,7 @@ $TESTTMP/nolargefiles/.hg/hgrc:*: extensions.lfs= (glob) $ hg log -r 'all()' -G -T '{rev} {join(lfs_files, ", ")} ({desc})\n' - o 8 (remove large_by_size.bin) + o 8 large_by_size.bin (remove large_by_size.bin) | o 7 large_by_size.bin (large by size) | diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-lfs-test-server.t --- a/tests/test-lfs-test-server.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-lfs-test-server.t Sat Feb 24 17:49:10 2018 -0600 @@ -48,6 +48,7 @@ searching for changes lfs: uploading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes) lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b + lfs: uploaded 1 files (12 bytes) 1 changesets found uncompressed size of bundle content: * (changelog) (glob) @@ -65,10 +66,10 @@ $ cd ../repo2 $ hg update tip -v resolving manifests - getting a lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes) lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b + getting a lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store 1 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -86,6 +87,7 @@ lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 lfs: uploading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes) lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 + lfs: uploaded 2 files (39 bytes) 1 changesets found uncompressed size of bundle content: adding changesets @@ -97,17 +99,18 @@ $ rm -rf `hg config lfs.usercache` $ hg --repo ../repo1 update tip -v resolving manifests - getting b - lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store - getting c + lfs: need to transfer 2 objects (39 bytes) + lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes) + lfs: adding 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 to the usercache + lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes) lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 + getting b + lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store + getting c lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store getting d - lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes) - lfs: adding 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 to the usercache - lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 lfs: found 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 in the local lfs store 3 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -121,11 +124,6 @@ $ hg --repo ../repo1 update -C tip -v resolving manifests - getting a - lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store - getting b - lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store - getting c lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes) abort: corrupt remote lfs object: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 [255] @@ -151,6 +149,75 @@ (run hg verify) [255] +Archive will prefetch blobs in a group + + $ rm -rf .hg/store/lfs `hg config lfs.usercache` + $ hg archive -vr 1 ../archive + lfs: need to transfer 3 objects (51 bytes) + lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes) + lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache + lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b + lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes) + lfs: adding 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 to the usercache + lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 + lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes) + lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache + lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 + lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store + lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store + lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store + lfs: found 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 in the local lfs store + $ find ../archive | sort + ../archive + ../archive/.hg_archival.txt + ../archive/a + ../archive/b + ../archive/c + ../archive/d + +Cat will prefetch blobs in a group + + $ rm -rf .hg/store/lfs `hg config lfs.usercache` + $ hg cat -vr 1 a b c + lfs: need to transfer 2 objects (31 bytes) + lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes) + lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache + lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b + lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes) + lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache + lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 + lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store + THIS-IS-LFS + lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store + THIS-IS-LFS + lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store + ANOTHER-LARGE-FILE + +Revert will prefetch blobs in a group + + $ rm -rf .hg/store/lfs + $ rm -rf `hg config lfs.usercache` + $ rm * + $ hg revert --all -r 1 -v + adding a + reverting b + reverting c + reverting d + lfs: need to transfer 3 objects (51 bytes) + lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes) + lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache + lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b + lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes) + lfs: adding 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 to the usercache + lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 + lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes) + lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache + lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 + lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store + lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store + lfs: found 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 in the local lfs store + lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store + Check error message when the remote missed a blob: $ echo FFFFF > b diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-lfs.t --- a/tests/test-lfs.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-lfs.t Sat Feb 24 17:49:10 2018 -0600 @@ -154,10 +154,32 @@ $ hg add . -q $ hg commit -m 'commit with lfs content' + $ hg files -r . 'set:added()' + large + small + $ hg files -r . 'set:added() & lfs()' + large + $ hg mv large l $ hg mv small s + $ hg status 'set:removed()' + R large + R small + $ hg status 'set:removed() & lfs()' + R large $ hg commit -m 'renames' + $ hg files -r . 'set:copied()' + l + s + $ hg files -r . 'set:copied() & lfs()' + l + $ hg status --change . 'set:removed()' + R large + R small + $ hg status --change . 'set:removed() & lfs()' + R large + $ echo SHORT > l $ echo BECOME-LARGER-FROM-SHORTER > s $ hg commit -m 'large to small, small to large' @@ -174,7 +196,7 @@ $ hg log -r 'all()' -T '{rev} {join(lfs_files, ", ")}\n' 0 large - 1 l + 1 l, large 2 s 3 s 4 l @@ -760,7 +782,6 @@ $ hg --config lfs.usercache=emptycache clone -v repo5 fromcorrupt2 updating to branch default resolving manifests - getting l abort: corrupt remote lfs object: 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b [255] @@ -1007,7 +1028,7 @@ The LFS policy stops when the .hglfs is gone - $ hg rm .hglfs + $ mv .hglfs .hglfs_ $ echo 'largefile3' > lfs.test $ echo '012345678901234567890abc' > nolfs.exclude $ echo '01234567890123456abc' > lfs.catchall @@ -1015,6 +1036,28 @@ $ hg log -r . -T '{rev}: {lfs_files % "{file}: {lfsoid}\n"}\n' 4: + $ mv .hglfs_ .hglfs + $ echo '012345678901234567890abc' > lfs.test + $ hg ci -m 'back to lfs' + $ hg rm lfs.test + $ hg ci -qm 'remove lfs' + +{lfs_files} will list deleted files too + + $ hg log -T "{lfs_files % '{rev} {file}: {lfspointer.oid}\n'}" + 6 lfs.test: + 5 lfs.test: sha256:43f8f41171b6f62a6b61ba4ce98a8a6c1649240a47ebafd43120aa215ac9e7f6 + 3 lfs.catchall: sha256:31f43b9c62b540126b0ad5884dc013d21a61c9329b77de1fceeae2fc58511573 + 3 lfs.test: sha256:8acd23467967bc7b8cc5a280056589b0ba0b17ff21dbd88a7b6474d6290378a6 + 2 lfs.catchall: sha256:d4ec46c2869ba22eceb42a729377432052d9dd75d82fc40390ebaadecee87ee9 + 2 lfs.test: sha256:5489e6ced8c36a7b267292bde9fd5242a5f80a7482e8f23fa0477393dfaa4d6c + + $ hg log -r 'file("set:lfs()")' -T '{rev} {join(lfs_files, ", ")}\n' + 2 lfs.catchall, lfs.test + 3 lfs.catchall, lfs.test + 5 lfs.test + 6 lfs.test + $ cd .. Unbundling adds a requirement to a non-lfs repo, if necessary. diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-log-exthook.t --- a/tests/test-log-exthook.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-log-exthook.t Sat Feb 24 17:49:10 2018 -0600 @@ -4,8 +4,8 @@ $ cat > $TESTTMP/logexthook.py < from __future__ import absolute_import > from mercurial import ( - > cmdutil, > commands, + > logcmdutil, > repair, > ) > def rot13description(self, ctx): @@ -13,7 +13,7 @@ > description = ctx.description().strip().splitlines()[0].encode('rot13') > self.ui.write("%s: %s\n" % (summary, description)) > def reposetup(ui, repo): - > cmdutil.changeset_printer._exthook = rot13description + > logcmdutil.changesetprinter._exthook = rot13description > EOF Prepare the repository diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-log-linerange.t --- a/tests/test-log-linerange.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-log-linerange.t Sat Feb 24 17:49:10 2018 -0600 @@ -172,6 +172,77 @@ +3 +4 + $ hg log -f --graph -L foo,5:7 -p + @ changeset: 5:cfdf972b3971 + | tag: tip + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+ + | + | diff --git a/foo b/foo + | --- a/foo + | +++ b/foo + | @@ -4,7 +4,7 @@ + | 0 + | 1 + | 2+ + | -3 + | +3+ + | 4 + | 5 + | 6 + | + o changeset: 4:eaec41c1a0c9 + : user: test + : date: Thu Jan 01 00:00:00 1970 +0000 + : summary: 11 -> 11+; leading space before "1" + : + : diff --git a/foo b/foo + : --- a/foo + : +++ b/foo + : @@ -2,7 +2,7 @@ + : 0 + : 0 + : 0 + : -1 + : + 1 + : 2+ + : 3 + : 4 + : + o changeset: 2:63a884426fd0 + : user: test + : date: Thu Jan 01 00:00:00 1970 +0000 + : summary: 2 -> 2+; added bar + : + : diff --git a/foo b/foo + : --- a/foo + : +++ b/foo + : @@ -3,6 +3,6 @@ + : 0 + : 0 + : 1 + : -2 + : +2+ + : 3 + : 4 + : + o changeset: 0:5ae1f82b9a00 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: init + + diff --git a/foo b/foo + new file mode 100644 + --- /dev/null + +++ b/foo + @@ -0,0 +1,5 @@ + +0 + +1 + +2 + +3 + +4 + With --template. @@ -849,9 +920,3 @@ $ hg log -f -L dir/baz,5:7 -p abort: cannot follow file not in parent revision: "dir/baz" [255] - -Graph log does work yet. - - $ hg log -f -L dir/baz,5:7 --graph - abort: graph not supported with line range patterns - [255] diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-log.t --- a/tests/test-log.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-log.t Sat Feb 24 17:49:10 2018 -0600 @@ -2258,7 +2258,7 @@ > foo = {'foo': repo[0].node()} > names = lambda r: foo.keys() > namemap = lambda r, name: foo.get(name) - > nodemap = lambda r, node: [name for name, n in foo.iteritems() + > nodemap = lambda r, node: [name for name, n in foo.items() > if n == node] > ns = namespaces.namespace( > "bars", templatename="bar", logname="barlog", diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-logexchange.t --- a/tests/test-logexchange.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-logexchange.t Sat Feb 24 17:49:10 2018 -0600 @@ -6,6 +6,9 @@ > glog = log -G -T '{rev}:{node|short} {desc}' > [experimental] > remotenames = True + > [extensions] + > remotenames = + > show = > EOF Making a server repo @@ -57,14 +60,27 @@ $ cat .hg/logexchange/bookmarks 0 - 87d6d66763085b629e6d7ed56778c79827273022\x00file:$TESTTMP/server\x00bar (esc) - 62615734edd52f06b6fb9c2beb429e4fe30d57b8\x00file:$TESTTMP/server\x00foo (esc) + 87d6d66763085b629e6d7ed56778c79827273022\x00default\x00bar (esc) + 62615734edd52f06b6fb9c2beb429e4fe30d57b8\x00default\x00foo (esc) $ cat .hg/logexchange/branches 0 - ec2426147f0e39dbc9cef599b066be6035ce691d\x00file:$TESTTMP/server\x00default (esc) - 3e1487808078543b0af6d10dadf5d46943578db0\x00file:$TESTTMP/server\x00wat (esc) + ec2426147f0e39dbc9cef599b066be6035ce691d\x00default\x00default (esc) + 3e1487808078543b0af6d10dadf5d46943578db0\x00default\x00wat (esc) + + $ hg show work + o 3e14 (wat) (default/wat) added bar + | + ~ + @ ec24 (default/default) Added h + | + ~ + + $ hg update "default/wat" + 1 files updated, 0 files merged, 3 files removed, 0 files unresolved + $ hg identify + 3e1487808078 (wat) tip Making a new server ------------------- @@ -94,15 +110,152 @@ $ cat .hg/logexchange/bookmarks 0 - 62615734edd52f06b6fb9c2beb429e4fe30d57b8\x00file:$TESTTMP/server\x00foo (esc) - 87d6d66763085b629e6d7ed56778c79827273022\x00file:$TESTTMP/server\x00bar (esc) - 87d6d66763085b629e6d7ed56778c79827273022\x00file:$TESTTMP/server2\x00bar (esc) - 62615734edd52f06b6fb9c2beb429e4fe30d57b8\x00file:$TESTTMP/server2\x00foo (esc) + 62615734edd52f06b6fb9c2beb429e4fe30d57b8\x00default\x00foo (esc) + 87d6d66763085b629e6d7ed56778c79827273022\x00default\x00bar (esc) + 87d6d66763085b629e6d7ed56778c79827273022\x00$TESTTMP/server2\x00bar (esc) + 62615734edd52f06b6fb9c2beb429e4fe30d57b8\x00$TESTTMP/server2\x00foo (esc) $ cat .hg/logexchange/branches 0 - 3e1487808078543b0af6d10dadf5d46943578db0\x00file:$TESTTMP/server\x00wat (esc) - ec2426147f0e39dbc9cef599b066be6035ce691d\x00file:$TESTTMP/server\x00default (esc) - ec2426147f0e39dbc9cef599b066be6035ce691d\x00file:$TESTTMP/server2\x00default (esc) - 3e1487808078543b0af6d10dadf5d46943578db0\x00file:$TESTTMP/server2\x00wat (esc) + 3e1487808078543b0af6d10dadf5d46943578db0\x00default\x00wat (esc) + ec2426147f0e39dbc9cef599b066be6035ce691d\x00default\x00default (esc) + ec2426147f0e39dbc9cef599b066be6035ce691d\x00$TESTTMP/server2\x00default (esc) + 3e1487808078543b0af6d10dadf5d46943578db0\x00$TESTTMP/server2\x00wat (esc) + + $ hg log -G + @ changeset: 8:3e1487808078 + | branch: wat + | tag: tip + | remote branch: $TESTTMP/server2/wat + | remote branch: default/wat + | parent: 4:aa98ab95a928 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: added bar + | + | o changeset: 7:ec2426147f0e + | | remote branch: $TESTTMP/server2/default + | | remote branch: default/default + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: Added h + | | + | o changeset: 6:87d6d6676308 + | | bookmark: bar + | | remote bookmark: $TESTTMP/server2/bar + | | remote bookmark: default/bar + | | user: test + | | date: Thu Jan 01 00:00:00 1970 +0000 + | | summary: Added g + | | + | o changeset: 5:825660c69f0c + |/ user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: Added f + | + o changeset: 4:aa98ab95a928 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: Added e + | + o changeset: 3:62615734edd5 + | bookmark: foo + | remote bookmark: $TESTTMP/server2/foo + | remote bookmark: default/foo + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: Added d + | + o changeset: 2:28ad74487de9 + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: Added c + | + o changeset: 1:29becc82797a + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: Added b + | + o changeset: 0:18d04c59bb5d + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: Added a + +Testing the templates provided by remotenames extension + +`remotenames` keyword + + $ hg log -G -T "{rev}:{node|short} {remotenames}\n" + @ 8:3e1487808078 $TESTTMP/server2/wat default/wat + | + | o 7:ec2426147f0e $TESTTMP/server2/default default/default + | | + | o 6:87d6d6676308 $TESTTMP/server2/bar default/bar + | | + | o 5:825660c69f0c + |/ + o 4:aa98ab95a928 + | + o 3:62615734edd5 $TESTTMP/server2/foo default/foo + | + o 2:28ad74487de9 + | + o 1:29becc82797a + | + o 0:18d04c59bb5d + +`remotebookmarks` and `remotebranches` keywords + + $ hg log -G -T "{rev}:{node|short} [{remotebookmarks}] ({remotebranches})" + @ 8:3e1487808078 [] ($TESTTMP/server2/wat default/wat) + | + | o 7:ec2426147f0e [] ($TESTTMP/server2/default default/default) + | | + | o 6:87d6d6676308 [$TESTTMP/server2/bar default/bar] () + | | + | o 5:825660c69f0c [] () + |/ + o 4:aa98ab95a928 [] () + | + o 3:62615734edd5 [$TESTTMP/server2/foo default/foo] () + | + o 2:28ad74487de9 [] () + | + o 1:29becc82797a [] () + | + o 0:18d04c59bb5d [] () + +Testing the revsets provided by remotenames extension + +`remotenames` revset + + $ hg log -r "remotenames()" -GT "{rev}:{node|short} {remotenames}\n" + @ 8:3e1487808078 $TESTTMP/server2/wat default/wat + : + : o 7:ec2426147f0e $TESTTMP/server2/default default/default + : | + : o 6:87d6d6676308 $TESTTMP/server2/bar default/bar + :/ + o 3:62615734edd5 $TESTTMP/server2/foo default/foo + | + ~ + +`remotebranches` revset + + $ hg log -r "remotebranches()" -GT "{rev}:{node|short} {remotenames}\n" + @ 8:3e1487808078 $TESTTMP/server2/wat default/wat + | + ~ + o 7:ec2426147f0e $TESTTMP/server2/default default/default + | + ~ + +`remotebookmarks` revset + + $ hg log -r "remotebookmarks()" -GT "{rev}:{node|short} {remotenames}\n" + o 6:87d6d6676308 $TESTTMP/server2/bar default/bar + : + o 3:62615734edd5 $TESTTMP/server2/foo default/foo + | + ~ diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-mactext.t --- a/tests/test-mactext.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-mactext.t Sat Feb 24 17:49:10 2018 -0600 @@ -3,9 +3,9 @@ > import sys > > for path in sys.argv[1:]: - > data = file(path, 'rb').read() - > data = data.replace('\n', '\r') - > file(path, 'wb').write(data) + > data = open(path, 'rb').read() + > data = data.replace(b'\n', b'\r') + > open(path, 'wb').write(data) > EOF $ cat > print.py < import sys diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-manifest.py --- a/tests/test-manifest.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-manifest.py Sat Feb 24 17:49:10 2018 -0600 @@ -11,7 +11,6 @@ ) EMTPY_MANIFEST = b'' -EMTPY_MANIFEST_V2 = b'\0\n' HASH_1 = b'1' * 40 BIN_HASH_1 = binascii.unhexlify(HASH_1) @@ -28,42 +27,6 @@ b'flag2': b'l', } -# Same data as A_SHORT_MANIFEST -A_SHORT_MANIFEST_V2 = ( - b'\0\n' - b'\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n' - b'\x00foo\0%(flag1)s\n%(hash1)s\n' - ) % {b'hash1': BIN_HASH_1, - b'flag1': b'', - b'hash2': BIN_HASH_2, - b'flag2': b'l', - } - -# Same data as A_SHORT_MANIFEST -A_METADATA_MANIFEST = ( - b'\0foo\0bar\n' - b'\x00bar/baz/qux.py\0%(flag2)s\0foo\0bar\n%(hash2)s\n' # flag and metadata - b'\x00foo\0%(flag1)s\0foo\n%(hash1)s\n' # no flag, but metadata - ) % {b'hash1': BIN_HASH_1, - b'flag1': b'', - b'hash2': BIN_HASH_2, - b'flag2': b'l', - } - -A_STEM_COMPRESSED_MANIFEST = ( - b'\0\n' - b'\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n' - b'\x04qux/foo.py\0%(flag1)s\n%(hash1)s\n' # simple case of 4 stem chars - b'\x0az.py\0%(flag1)s\n%(hash1)s\n' # tricky newline = 10 stem characters - b'\x00%(verylongdir)sx/x\0\n%(hash1)s\n' - b'\xffx/y\0\n%(hash2)s\n' # more than 255 stem chars - ) % {b'hash1': BIN_HASH_1, - b'flag1': b'', - b'hash2': BIN_HASH_2, - b'flag2': b'l', - b'verylongdir': 255 * b'x', - } - A_DEEPER_MANIFEST = ( b'a/b/c/bar.py\0%(hash3)s%(flag1)s\n' b'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n' @@ -111,11 +74,6 @@ self.assertEqual(0, len(m)) self.assertEqual([], list(m)) - def testEmptyManifestv2(self): - m = self.parsemanifest(EMTPY_MANIFEST_V2) - self.assertEqual(0, len(m)) - self.assertEqual([], list(m)) - def testManifest(self): m = self.parsemanifest(A_SHORT_MANIFEST) self.assertEqual([b'bar/baz/qux.py', b'foo'], list(m)) @@ -126,31 +84,6 @@ with self.assertRaises(KeyError): m[b'wat'] - def testParseManifestV2(self): - m1 = self.parsemanifest(A_SHORT_MANIFEST) - m2 = self.parsemanifest(A_SHORT_MANIFEST_V2) - # Should have same content as A_SHORT_MANIFEST - self.assertEqual(m1.text(), m2.text()) - - def testParseManifestMetadata(self): - # Metadata is for future-proofing and should be accepted but ignored - m = self.parsemanifest(A_METADATA_MANIFEST) - self.assertEqual(A_SHORT_MANIFEST, m.text()) - - def testParseManifestStemCompression(self): - m = self.parsemanifest(A_STEM_COMPRESSED_MANIFEST) - self.assertIn(b'bar/baz/qux.py', m) - self.assertIn(b'bar/qux/foo.py', m) - self.assertIn(b'bar/qux/foz.py', m) - self.assertIn(256 * b'x' + b'/x', m) - self.assertIn(256 * b'x' + b'/y', m) - self.assertEqual(A_STEM_COMPRESSED_MANIFEST, m.text(usemanifestv2=True)) - - def testTextV2(self): - m1 = self.parsemanifest(A_SHORT_MANIFEST) - v2text = m1.text(usemanifestv2=True) - self.assertEqual(A_SHORT_MANIFEST_V2, v2text) - def testSetItem(self): want = BIN_HASH_1 @@ -223,7 +156,7 @@ self.assertEqual(want, m[b'foo']) self.assertEqual([(b'bar/baz/qux.py', BIN_HASH_2), (b'foo', BIN_HASH_1 + b'a')], - list(m.iteritems())) + list(m.items())) # Sometimes it even tries a 22-byte fake hash, but we can # return 21 and it'll work out m[b'foo'] = want + b'+' @@ -238,7 +171,7 @@ # suffix with iteration self.assertEqual([(b'bar/baz/qux.py', BIN_HASH_2), (b'foo', want)], - list(m.iteritems())) + list(m.items())) # shows up in diff self.assertEqual({b'foo': ((want, f), (h, b''))}, m.diff(clean)) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-manifestv2.t --- a/tests/test-manifestv2.t Fri Feb 23 17:57:04 2018 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,102 +0,0 @@ -Create repo with old manifest - - $ cat << EOF >> $HGRCPATH - > [format] - > usegeneraldelta=yes - > EOF - - $ hg init existing - $ cd existing - $ echo footext > foo - $ hg add foo - $ hg commit -m initial - -We're using v1, so no manifestv2 entry is in requires yet. - - $ grep manifestv2 .hg/requires - [1] - -Let's clone this with manifestv2 enabled to switch to the new format for -future commits. - - $ cd .. - $ hg clone --pull existing new --config experimental.manifestv2=1 - requesting all changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files - new changesets 0fc9a4fafa44 - updating to branch default - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cd new - -Check that entry was added to .hg/requires. - - $ grep manifestv2 .hg/requires - manifestv2 - -Make a new commit. - - $ echo newfootext > foo - $ hg commit -m new - -Check that the manifest actually switched to v2. - - $ hg debugdata -m 0 - foo\x0021e958b1dca695a60ee2e9cf151753204ee0f9e9 (esc) - - $ hg debugdata -m 1 - \x00 (esc) - \x00foo\x00 (esc) - I\xab\x7f\xb8(\x83\xcas\x15\x9d\xc2\xd3\xd3:5\x08\xbad5_ (esc) - -Check that manifestv2 is used if the requirement is present, even if it's -disabled in the config. - - $ echo newerfootext > foo - $ hg --config experimental.manifestv2=False commit -m newer - - $ hg debugdata -m 2 - \x00 (esc) - \x00foo\x00 (esc) - \xa6\xb1\xfb\xef]\x91\xa1\x19`\xf3.#\x90S\xf8\x06 \xe2\x19\x00 (esc) - -Check that we can still read v1 manifests. - - $ hg files -r 0 - foo - - $ cd .. - -Check that entry is added to .hg/requires on repo creation - - $ hg --config experimental.manifestv2=True init repo - $ cd repo - $ grep manifestv2 .hg/requires - manifestv2 - -Set up simple repo - - $ echo a > file1 - $ echo b > file2 - $ echo c > file3 - $ hg ci -Aqm 'initial' - $ echo d > file2 - $ hg ci -m 'modify file2' - -Check that 'hg verify', which uses manifest.readdelta(), works - - $ hg verify - checking changesets - checking manifests - crosschecking files in changesets and manifests - checking files - 3 files, 2 changesets, 4 total revisions - -Check that manifest revlog is smaller than for v1 - - $ hg debugindex -m - rev offset length delta linkrev nodeid p1 p2 - 0 0 81 -1 0 57361477c778 000000000000 000000000000 - 1 81 33 0 1 aeaab5a2ef74 57361477c778 000000000000 diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-mdiff.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-mdiff.py Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,24 @@ +from __future__ import absolute_import +from __future__ import print_function + +import unittest + +from mercurial import ( + mdiff, +) + +class splitnewlinesTests(unittest.TestCase): + + def test_splitnewlines(self): + cases = {b'a\nb\nc\n': [b'a\n', b'b\n', b'c\n'], + b'a\nb\nc': [b'a\n', b'b\n', b'c'], + b'a\nb\nc\n\n': [b'a\n', b'b\n', b'c\n', b'\n'], + b'': [], + b'abcabc': [b'abcabc'], + } + for inp, want in cases.items(): + self.assertEqual(mdiff.splitnewlines(inp), want) + +if __name__ == '__main__': + import silenttestrunner + silenttestrunner.main(__name__) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-merge-tools.t --- a/tests/test-merge-tools.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-merge-tools.t Sat Feb 24 17:49:10 2018 -0600 @@ -1059,6 +1059,150 @@ # hg resolve --list R f +premerge=keep respects ui.mergemarkers=basic: + + $ beforemerge + [merge-tools] + false.whatever= + true.priority=1 + true.executable=cat + # hg update -C 1 + $ hg merge -r 4 --config merge-tools.true.premerge=keep --config ui.mergemarkers=basic + merging f + <<<<<<< working copy + revision 1 + space + ======= + revision 4 + >>>>>>> merge rev + revision 0 + space + revision 4 + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ aftermerge + # cat f + <<<<<<< working copy + revision 1 + space + ======= + revision 4 + >>>>>>> merge rev + # hg stat + M f + # hg resolve --list + R f + +premerge=keep ignores ui.mergemarkers=basic if true.mergemarkers=detailed: + + $ beforemerge + [merge-tools] + false.whatever= + true.priority=1 + true.executable=cat + # hg update -C 1 + $ hg merge -r 4 --config merge-tools.true.premerge=keep \ + > --config ui.mergemarkers=basic \ + > --config merge-tools.true.mergemarkers=detailed + merging f + <<<<<<< working copy: ef83787e2614 - test: revision 1 + revision 1 + space + ======= + revision 4 + >>>>>>> merge rev: 81448d39c9a0 - test: revision 4 + revision 0 + space + revision 4 + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ aftermerge + # cat f + <<<<<<< working copy: ef83787e2614 - test: revision 1 + revision 1 + space + ======= + revision 4 + >>>>>>> merge rev: 81448d39c9a0 - test: revision 4 + # hg stat + M f + # hg resolve --list + R f + +premerge=keep respects ui.mergemarkertemplate instead of +true.mergemarkertemplate if true.mergemarkers=basic: + + $ beforemerge + [merge-tools] + false.whatever= + true.priority=1 + true.executable=cat + # hg update -C 1 + $ hg merge -r 4 --config merge-tools.true.premerge=keep \ + > --config ui.mergemarkertemplate='uitmpl {rev}' \ + > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' + merging f + <<<<<<< working copy: uitmpl 1 + revision 1 + space + ======= + revision 4 + >>>>>>> merge rev: uitmpl 4 + revision 0 + space + revision 4 + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ aftermerge + # cat f + <<<<<<< working copy: uitmpl 1 + revision 1 + space + ======= + revision 4 + >>>>>>> merge rev: uitmpl 4 + # hg stat + M f + # hg resolve --list + R f + +premerge=keep respects true.mergemarkertemplate instead of +true.mergemarkertemplate if true.mergemarkers=detailed: + + $ beforemerge + [merge-tools] + false.whatever= + true.priority=1 + true.executable=cat + # hg update -C 1 + $ hg merge -r 4 --config merge-tools.true.premerge=keep \ + > --config ui.mergemarkertemplate='uitmpl {rev}' \ + > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \ + > --config merge-tools.true.mergemarkers=detailed + merging f + <<<<<<< working copy: tooltmpl ef83787e2614 + revision 1 + space + ======= + revision 4 + >>>>>>> merge rev: tooltmpl 81448d39c9a0 + revision 0 + space + revision 4 + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ aftermerge + # cat f + <<<<<<< working copy: tooltmpl ef83787e2614 + revision 1 + space + ======= + revision 4 + >>>>>>> merge rev: tooltmpl 81448d39c9a0 + # hg stat + M f + # hg resolve --list + R f Tool execution @@ -1190,6 +1334,142 @@ # hg resolve --list R f +Merge using a tool that supports labellocal, labelother, and labelbase, checking +that they're quoted properly as well. This is using the default 'basic' +mergemarkers even though ui.mergemarkers is 'detailed', so it's ignoring both +mergemarkertemplate settings: + + $ beforemerge + [merge-tools] + false.whatever= + true.priority=1 + true.executable=cat + # hg update -C 1 + $ cat < printargs_merge_tool + > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done + > EOF + $ hg --config merge-tools.true.executable='sh' \ + > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \ + > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \ + > --config ui.mergemarkertemplate='uitmpl {rev}' \ + > --config ui.mergemarkers=detailed \ + > merge -r 2 + merging f + arg: "ll:working copy" + arg: "lo:" + arg: "merge rev" + arg: "lb:base: */f~base.*" (glob) + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ rm -f 'printargs_merge_tool' + +Merge using a tool that supports labellocal, labelother, and labelbase, checking +that they're quoted properly as well. This is using 'detailed' mergemarkers, +even though ui.mergemarkers is 'basic', and using the tool's +mergemarkertemplate: + + $ beforemerge + [merge-tools] + false.whatever= + true.priority=1 + true.executable=cat + # hg update -C 1 + $ cat < printargs_merge_tool + > while test \$# -gt 0; do echo arg: \"\$1\"; shift; done + > EOF + $ hg --config merge-tools.true.executable='sh' \ + > --config merge-tools.true.args='./printargs_merge_tool ll:$labellocal lo: $labelother lb:$labelbase": "$base' \ + > --config merge-tools.true.mergemarkers=detailed \ + > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \ + > --config ui.mergemarkertemplate='uitmpl {rev}' \ + > --config ui.mergemarkers=basic \ + > merge -r 2 + merging f + arg: "ll:working copy: tooltmpl ef83787e2614" + arg: "lo:" + arg: "merge rev: tooltmpl 0185f4e0cf02" + arg: "lb:base: */f~base.*" (glob) + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ rm -f 'printargs_merge_tool' + +The merge tool still gets labellocal and labelother as 'basic' even when +premerge=keep is used and has 'detailed' markers: + + $ beforemerge + [merge-tools] + false.whatever= + true.priority=1 + true.executable=cat + # hg update -C 1 + $ cat < mytool + > echo labellocal: \"\$1\" + > echo labelother: \"\$2\" + > echo "output (arg)": \"\$3\" + > echo "output (contents)": + > cat "\$3" + > EOF + $ hg --config merge-tools.true.executable='sh' \ + > --config merge-tools.true.args='mytool $labellocal $labelother $output' \ + > --config merge-tools.true.premerge=keep \ + > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \ + > --config ui.mergemarkertemplate='uitmpl {rev}' \ + > --config ui.mergemarkers=detailed \ + > merge -r 2 + merging f + labellocal: "working copy" + labelother: "merge rev" + output (arg): "$TESTTMP/f" + output (contents): + <<<<<<< working copy: uitmpl 1 + revision 1 + ======= + revision 2 + >>>>>>> merge rev: uitmpl 2 + space + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ rm -f 'mytool' + +premerge=keep uses the *tool's* mergemarkertemplate if tool's +mergemarkers=detailed; labellocal and labelother also use the tool's template + + $ beforemerge + [merge-tools] + false.whatever= + true.priority=1 + true.executable=cat + # hg update -C 1 + $ cat < mytool + > echo labellocal: \"\$1\" + > echo labelother: \"\$2\" + > echo "output (arg)": \"\$3\" + > echo "output (contents)": + > cat "\$3" + > EOF + $ hg --config merge-tools.true.executable='sh' \ + > --config merge-tools.true.args='mytool $labellocal $labelother $output' \ + > --config merge-tools.true.premerge=keep \ + > --config merge-tools.true.mergemarkers=detailed \ + > --config merge-tools.true.mergemarkertemplate='tooltmpl {short(node)}' \ + > --config ui.mergemarkertemplate='uitmpl {rev}' \ + > --config ui.mergemarkers=detailed \ + > merge -r 2 + merging f + labellocal: "working copy: tooltmpl ef83787e2614" + labelother: "merge rev: tooltmpl 0185f4e0cf02" + output (arg): "$TESTTMP/f" + output (contents): + <<<<<<< working copy: tooltmpl ef83787e2614 + revision 1 + ======= + revision 2 + >>>>>>> merge rev: tooltmpl 0185f4e0cf02 + space + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ rm -f 'mytool' + Issue3581: Merging a filename that needs to be quoted (This test doesn't work on Windows filesystems even on Linux, so check for Unix-like permission) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-mq-eol.t --- a/tests/test-mq-eol.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-mq-eol.t Sat Feb 24 17:49:10 2018 -0600 @@ -10,29 +10,29 @@ > EOF $ cat > makepatch.py < f = file('eol.diff', 'wb') + > f = open('eol.diff', 'wb') > w = f.write - > w('test message\n') - > w('diff --git a/a b/a\n') - > w('--- a/a\n') - > w('+++ b/a\n') - > w('@@ -1,5 +1,5 @@\n') - > w(' a\n') - > w('-b\r\n') - > w('+y\r\n') - > w(' c\r\n') - > w(' d\n') - > w('-e\n') - > w('\ No newline at end of file\n') - > w('+z\r\n') - > w('\ No newline at end of file\r\n') + > w(b'test message\n') + > w(b'diff --git a/a b/a\n') + > w(b'--- a/a\n') + > w(b'+++ b/a\n') + > w(b'@@ -1,5 +1,5 @@\n') + > w(b' a\n') + > w(b'-b\r\n') + > w(b'+y\r\n') + > w(b' c\r\n') + > w(b' d\n') + > w(b'-e\n') + > w(b'\ No newline at end of file\n') + > w(b'+z\r\n') + > w(b'\ No newline at end of file\r\n') > EOF $ cat > cateol.py < import sys - > for line in file(sys.argv[1], 'rb'): - > line = line.replace('\r', '') - > line = line.replace('\n', '') + > for line in open(sys.argv[1], 'rb'): + > line = line.replace(b'\r', b'') + > line = line.replace(b'\n', b'') > print(line) > EOF @@ -44,7 +44,7 @@ Test different --eol values - $ $PYTHON -c 'file("a", "wb").write("a\nb\nc\nd\ne")' + $ $PYTHON -c 'open("a", "wb").write(b"a\nb\nc\nd\ne")' $ hg ci -Am adda adding .hgignore adding a @@ -152,15 +152,15 @@ $ hg init testeol $ cd testeol - $ $PYTHON -c "file('a', 'wb').write('1\r\n2\r\n3\r\n4')" + $ $PYTHON -c "open('a', 'wb').write(b'1\r\n2\r\n3\r\n4')" $ hg ci -Am adda adding a - $ $PYTHON -c "file('a', 'wb').write('1\r\n2\r\n33\r\n4')" + $ $PYTHON -c "open('a', 'wb').write(b'1\r\n2\r\n33\r\n4')" $ hg qnew patch1 $ hg qpop popping patch1 patch queue now empty - $ $PYTHON -c "file('a', 'wb').write('1\r\n22\r\n33\r\n4')" + $ $PYTHON -c "open('a', 'wb').write(b'1\r\n22\r\n33\r\n4')" $ hg ci -m changea $ hg --config 'patch.eol=LF' qpush diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-mq-missingfiles.t --- a/tests/test-mq-missingfiles.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-mq-missingfiles.t Sat Feb 24 17:49:10 2018 -0600 @@ -9,8 +9,8 @@ > args = sys.argv[2:] > assert (len(args) % 2) == 0 > - > f = file(path, 'wb') - > for i in xrange(len(args)/2): + > f = open(path, 'wb') + > for i in range(len(args) // 2): > count, s = args[2*i:2*i+2] > count = int(count) > s = s.decode('string_escape') diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-mq-qimport.t --- a/tests/test-mq-qimport.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-mq-qimport.t Sat Feb 24 17:49:10 2018 -0600 @@ -6,8 +6,8 @@ > args = sys.argv[2:] > assert (len(args) % 2) == 0 > - > f = file(path, 'wb') - > for i in xrange(len(args)/2): + > f = open(path, 'wb') + > for i in range(len(args)/2): > count, s = args[2*i:2*i+2] > count = int(count) > s = s.decode('string_escape') diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-mq-qrefresh-replace-log-message.t --- a/tests/test-mq-qrefresh-replace-log-message.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-mq-qrefresh-replace-log-message.t Sat Feb 24 17:49:10 2018 -0600 @@ -119,7 +119,7 @@ > def reposetup(ui, repo): > class commitfailure(repo.__class__): > def commit(self, *args, **kwargs): - > raise error.Abort('emulating unexpected abort') + > raise error.Abort(b'emulating unexpected abort') > repo.__class__ = commitfailure > EOF diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-acl.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-acl.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,42 @@ +Make a narrow clone then archive it + $ . "$TESTDIR/narrow-library.sh" + + $ hg init master + $ cd master + + $ for x in `$TESTDIR/seq.py 3`; do + > echo $x > "f$x" + > hg add "f$x" + > hg commit -m "Add $x" + > done + $ cat >> .hg/hgrc << EOF + > [narrowhgacl] + > default.includes=f1 f2 + > EOF + $ hg serve -a localhost -p $HGPORT1 -d --pid-file=hg.pid + $ cat hg.pid >> "$DAEMON_PIDS" + + $ cd .. + $ hg clone http://localhost:$HGPORT1 narrowclone1 + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 2 changes to 2 files + new changesets * (glob) + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + +The clone directory should only contain f1 and f2 + $ ls -1 narrowclone1 | sort + f1 + f2 + +Requirements should contain narrowhg + $ cat narrowclone1/.hg/requires | grep narrowhg + narrowhg-experimental + +NarrowHG should track f1 and f2 + $ hg -R narrowclone1 tracked + I path:f1 + I path:f2 diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-archive.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-archive.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,32 @@ +Make a narrow clone then archive it + $ . "$TESTDIR/narrow-library.sh" + + $ hg init master + $ cd master + + $ for x in `$TESTDIR/seq.py 3`; do + > echo $x > "f$x" + > hg add "f$x" + > hg commit -m "Add $x" + > done + + $ hg serve -a localhost -p $HGPORT1 -d --pid-file=hg.pid + $ cat hg.pid >> "$DAEMON_PIDS" + + $ cd .. + $ hg clone --narrow --include f1 --include f2 http://localhost:$HGPORT1/ narrowclone1 + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 2 changes to 2 files + new changesets * (glob) + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + +The tar should only contain f1 and f2 + $ cd narrowclone1 + $ hg archive -t tgz repo.tgz + $ tar tfz repo.tgz + repo/f1 + repo/f2 diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-clone-no-ellipsis.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-clone-no-ellipsis.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,130 @@ + $ . "$TESTDIR/narrow-library.sh" + + $ hg init master + $ cd master + $ mkdir dir + $ mkdir dir/src + $ cd dir/src + $ for x in `$TESTDIR/seq.py 20`; do echo $x > "f$x"; hg add "f$x"; hg commit -m "Commit src $x"; done + $ cd .. + $ mkdir tests + $ cd tests + $ for x in `$TESTDIR/seq.py 20`; do echo $x > "t$x"; hg add "t$x"; hg commit -m "Commit test $x"; done + $ cd ../../.. + +narrow clone a file, f10 + + $ hg clone --narrow ssh://user@dummy/master narrow --noupdate --include "dir/src/f10" + requesting all changes + adding changesets + adding manifests + adding file changes + added 40 changesets with 1 changes to 1 files + new changesets *:* (glob) + $ cd narrow + $ cat .hg/requires | grep -v generaldelta + dotencode + fncache + narrowhg-experimental + revlogv1 + store + + $ cat .hg/narrowspec + [includes] + path:dir/src/f10 + [excludes] + $ hg update + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ find * | sort + dir + dir/src + dir/src/f10 + $ cat dir/src/f10 + 10 + + $ cd .. + +narrow clone a directory, tests/, except tests/t19 + + $ hg clone --narrow ssh://user@dummy/master narrowdir --noupdate --include "dir/tests/" --exclude "dir/tests/t19" + requesting all changes + adding changesets + adding manifests + adding file changes + added 40 changesets with 19 changes to 19 files + new changesets *:* (glob) + $ cd narrowdir + $ cat .hg/narrowspec + [includes] + path:dir/tests + [excludes] + path:dir/tests/t19 + $ hg update + 19 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ find * | sort + dir + dir/tests + dir/tests/t1 + dir/tests/t10 + dir/tests/t11 + dir/tests/t12 + dir/tests/t13 + dir/tests/t14 + dir/tests/t15 + dir/tests/t16 + dir/tests/t17 + dir/tests/t18 + dir/tests/t2 + dir/tests/t20 + dir/tests/t3 + dir/tests/t4 + dir/tests/t5 + dir/tests/t6 + dir/tests/t7 + dir/tests/t8 + dir/tests/t9 + + $ cd .. + +narrow clone everything but a directory (tests/) + + $ hg clone --narrow ssh://user@dummy/master narrowroot --noupdate --exclude "dir/tests" + requesting all changes + adding changesets + adding manifests + adding file changes + added 40 changesets with 20 changes to 20 files + new changesets *:* (glob) + $ cd narrowroot + $ cat .hg/narrowspec + [includes] + path:. + [excludes] + path:dir/tests + $ hg update + 20 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ find * | sort + dir + dir/src + dir/src/f1 + dir/src/f10 + dir/src/f11 + dir/src/f12 + dir/src/f13 + dir/src/f14 + dir/src/f15 + dir/src/f16 + dir/src/f17 + dir/src/f18 + dir/src/f19 + dir/src/f2 + dir/src/f20 + dir/src/f3 + dir/src/f4 + dir/src/f5 + dir/src/f6 + dir/src/f7 + dir/src/f8 + dir/src/f9 + + $ cd .. diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-clone-non-narrow-server.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-clone-non-narrow-server.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,53 @@ +Test attempting a narrow clone against a server that doesn't support narrowhg. + + $ . "$TESTDIR/narrow-library.sh" + + $ hg init master + $ cd master + + $ for x in `$TESTDIR/seq.py 10`; do + > echo $x > "f$x" + > hg add "f$x" + > hg commit -m "Add $x" + > done + + $ hg serve -a localhost -p $HGPORT1 --config extensions.narrow=! -d \ + > --pid-file=hg.pid + $ cat hg.pid >> "$DAEMON_PIDS" + $ hg serve -a localhost -p $HGPORT2 -d --pid-file=hg.pid + $ cat hg.pid >> "$DAEMON_PIDS" + +Verify that narrow is advertised in the bundle2 capabilities: + $ echo hello | hg -R . serve --stdio | \ + > python -c "import sys, urllib; print urllib.unquote_plus(list(sys.stdin)[1])" | grep narrow + narrow=v0 + + $ cd .. + + $ hg clone --narrow --include f1 http://localhost:$HGPORT1/ narrowclone + requesting all changes + abort: server doesn't support narrow clones + [255] + +Make a narrow clone (via HGPORT2), then try to narrow and widen +into it (from HGPORT1) to prove that narrowing is fine and widening fails +gracefully: + $ hg clone -r 0 --narrow --include f1 http://localhost:$HGPORT2/ narrowclone + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + new changesets * (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrowclone + $ hg tracked --addexclude f2 http://localhost:$HGPORT1/ + comparing with http://localhost:$HGPORT1/ + searching for changes + looking for local changes to affected paths + $ hg tracked --addinclude f1 http://localhost:$HGPORT1/ + comparing with http://localhost:$HGPORT1/ + searching for changes + no changes found + abort: server doesn't support narrow clones + [255] diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-clone-nonlinear.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-clone-nonlinear.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,148 @@ +Testing narrow clones when changesets modifying a matching file exist on +multiple branches + + $ . "$TESTDIR/narrow-library.sh" + + $ hg init master + $ cd master + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + + $ hg branch default + marked working directory as branch default + (branches are permanent and global, did you want a bookmark?) + $ for x in `$TESTDIR/seq.py 10`; do + > echo $x > "f$x" + > hg add "f$x" + > hg commit -m "Add $x" + > done + + $ hg branch release-v1 + marked working directory as branch release-v1 + (branches are permanent and global, did you want a bookmark?) + $ hg commit -m "Start release for v1" + + $ hg update default + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ for x in `$TESTDIR/seq.py 10`; do + > echo "$x v2" > "f$x" + > hg commit -m "Update $x to v2" + > done + + $ hg update release-v1 + 10 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg branch release-v1 + marked working directory as branch release-v1 + $ for x in `$TESTDIR/seq.py 1 5`; do + > echo "$x v1 hotfix" > "f$x" + > hg commit -m "Hotfix $x in v1" + > done + + $ hg update default + 10 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg branch release-v2 + marked working directory as branch release-v2 + $ hg commit -m "Start release for v2" + + $ hg update default + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg branch default + marked working directory as branch default + $ for x in `$TESTDIR/seq.py 10`; do + > echo "$x v3" > "f$x" + > hg commit -m "Update $x to v3" + > done + + $ hg update release-v2 + 10 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg branch release-v2 + marked working directory as branch release-v2 + $ for x in `$TESTDIR/seq.py 4 9`; do + > echo "$x v2 hotfix" > "f$x" + > hg commit -m "Hotfix $x in v2" + > done + + $ hg heads -T '{rev} <- {p1rev} ({branch}): {desc}\n' + 42 <- 41 (release-v2): Hotfix 9 in v2 + 36 <- 35 (default): Update 10 to v3 + 25 <- 24 (release-v1): Hotfix 5 in v1 + + $ cd .. + +We now have 3 branches: default, which has v3 of all files, release-v1 which +has v1 of all files, and release-v2 with v2 of all files. + +Narrow clone which should get all branches + + $ hg clone --narrow ssh://user@dummy/master narrow --include "f5" + requesting all changes + adding changesets + adding manifests + adding file changes + added 12 changesets with 5 changes to 1 files (+2 heads) + new changesets *:* (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + $ hg log -G -T "{if(ellipsis, '...')}{node|short} ({branch}): {desc}\n" + o ...031f516143fe (release-v2): Hotfix 9 in v2 + | + o 9cd7f7bb9ca1 (release-v2): Hotfix 5 in v2 + | + o ...37bbc88f3ef0 (release-v2): Hotfix 4 in v2 + | + | @ ...dae2f368ca07 (default): Update 10 to v3 + | | + | o 9c224e89cb31 (default): Update 5 to v3 + | | + | o ...04fb59c7c9dc (default): Update 4 to v3 + |/ + | o b2253e82401f (release-v1): Hotfix 5 in v1 + | | + | o ...960ac37d74fd (release-v1): Hotfix 4 in v1 + | | + o | 986298e3f347 (default): Update 5 to v2 + | | + o | ...75d539c667ec (default): Update 4 to v2 + |/ + o 04c71bd5707f (default): Add 5 + | + o ...881b3891d041 (default): Add 4 + + +Narrow clone the first file, hitting edge condition where unaligned +changeset and manifest revnums cross branches. + + $ hg clone --narrow ssh://user@dummy/master narrow --include "f1" + requesting all changes + adding changesets + adding manifests + adding file changes + added 10 changesets with 4 changes to 1 files (+2 heads) + new changesets *:* (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + $ hg log -G -T "{if(ellipsis, '...')}{node|short} ({branch}): {desc}\n" + o ...031f516143fe (release-v2): Hotfix 9 in v2 + | + | @ ...dae2f368ca07 (default): Update 10 to v3 + | | + | o 1f5d184b8e96 (default): Update 1 to v3 + |/ + | o ...b2253e82401f (release-v1): Hotfix 5 in v1 + | | + | o 133502f6b7e5 (release-v1): Hotfix 1 in v1 + | | + o | ...79165c83d644 (default): Update 10 to v2 + | | + o | c7b7a5f2f088 (default): Update 1 to v2 + | | + | o ...f0531a3db7a9 (release-v1): Start release for v1 + |/ + o ...6a3f0f0abef3 (default): Add 10 + | + o e012ac15eaaa (default): Add 1 + diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-clone.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-clone.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,225 @@ + $ . "$TESTDIR/narrow-library.sh" + + $ hg init master + $ cd master + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + $ mkdir dir + $ mkdir dir/src + $ cd dir/src + $ for x in `$TESTDIR/seq.py 20`; do echo $x > "f$x"; hg add "f$x"; hg commit -m "Commit src $x"; done + $ cd .. + $ mkdir tests + $ cd tests + $ for x in `$TESTDIR/seq.py 20`; do echo $x > "t$x"; hg add "t$x"; hg commit -m "Commit test $x"; done + $ cd ../../.. + +narrow clone a file, f10 + + $ hg clone --narrow ssh://user@dummy/master narrow --noupdate --include "dir/src/f10" + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 1 changes to 1 files + new changesets *:* (glob) + $ cd narrow + $ cat .hg/requires | grep -v generaldelta + dotencode + fncache + narrowhg-experimental + revlogv1 + store + + $ cat .hg/narrowspec + [includes] + path:dir/src/f10 + [excludes] + $ hg tracked + I path:dir/src/f10 + $ hg update + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ find * | sort + dir + dir/src + dir/src/f10 + $ cat dir/src/f10 + 10 + + $ cd .. + +narrow clone with a newline should fail + + $ hg clone --narrow ssh://user@dummy/master narrow_fail --noupdate --include 'dir/src/f10 + > ' + requesting all changes + abort: newlines are not allowed in narrowspec paths + [255] + +narrow clone a directory, tests/, except tests/t19 + + $ hg clone --narrow ssh://user@dummy/master narrowdir --noupdate --include "dir/tests/" --exclude "dir/tests/t19" + requesting all changes + adding changesets + adding manifests + adding file changes + added 21 changesets with 19 changes to 19 files + new changesets *:* (glob) + $ cd narrowdir + $ cat .hg/narrowspec + [includes] + path:dir/tests + [excludes] + path:dir/tests/t19 + $ hg tracked + I path:dir/tests + X path:dir/tests/t19 + $ hg update + 19 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ find * | sort + dir + dir/tests + dir/tests/t1 + dir/tests/t10 + dir/tests/t11 + dir/tests/t12 + dir/tests/t13 + dir/tests/t14 + dir/tests/t15 + dir/tests/t16 + dir/tests/t17 + dir/tests/t18 + dir/tests/t2 + dir/tests/t20 + dir/tests/t3 + dir/tests/t4 + dir/tests/t5 + dir/tests/t6 + dir/tests/t7 + dir/tests/t8 + dir/tests/t9 + + $ cd .. + +narrow clone everything but a directory (tests/) + + $ hg clone --narrow ssh://user@dummy/master narrowroot --noupdate --exclude "dir/tests" + requesting all changes + adding changesets + adding manifests + adding file changes + added 21 changesets with 20 changes to 20 files + new changesets *:* (glob) + $ cd narrowroot + $ cat .hg/narrowspec + [includes] + path:. + [excludes] + path:dir/tests + $ hg tracked + I path:. + X path:dir/tests + $ hg update + 20 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ find * | sort + dir + dir/src + dir/src/f1 + dir/src/f10 + dir/src/f11 + dir/src/f12 + dir/src/f13 + dir/src/f14 + dir/src/f15 + dir/src/f16 + dir/src/f17 + dir/src/f18 + dir/src/f19 + dir/src/f2 + dir/src/f20 + dir/src/f3 + dir/src/f4 + dir/src/f5 + dir/src/f6 + dir/src/f7 + dir/src/f8 + dir/src/f9 + + $ cd .. + +narrow clone no paths at all + + $ hg clone --narrow ssh://user@dummy/master narrowempty --noupdate + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 0 changes to 0 files + new changesets * (glob) + $ cd narrowempty + $ hg tracked + $ hg update + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ ls + + $ cd .. + +simple clone + $ hg clone ssh://user@dummy/master simpleclone + requesting all changes + adding changesets + adding manifests + adding file changes + added 40 changesets with 40 changes to 40 files + new changesets * (glob) + updating to branch default + 40 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd simpleclone + $ find * | sort + dir + dir/src + dir/src/f1 + dir/src/f10 + dir/src/f11 + dir/src/f12 + dir/src/f13 + dir/src/f14 + dir/src/f15 + dir/src/f16 + dir/src/f17 + dir/src/f18 + dir/src/f19 + dir/src/f2 + dir/src/f20 + dir/src/f3 + dir/src/f4 + dir/src/f5 + dir/src/f6 + dir/src/f7 + dir/src/f8 + dir/src/f9 + dir/tests + dir/tests/t1 + dir/tests/t10 + dir/tests/t11 + dir/tests/t12 + dir/tests/t13 + dir/tests/t14 + dir/tests/t15 + dir/tests/t16 + dir/tests/t17 + dir/tests/t18 + dir/tests/t19 + dir/tests/t2 + dir/tests/t20 + dir/tests/t3 + dir/tests/t4 + dir/tests/t5 + dir/tests/t6 + dir/tests/t7 + dir/tests/t8 + dir/tests/t9 + + $ cd .. diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-commit.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-commit.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,102 @@ +#testcases flat tree + + $ . "$TESTDIR/narrow-library.sh" + +#if tree + $ cat << EOF >> $HGRCPATH + > [experimental] + > treemanifest = 1 + > EOF +#endif + +create full repo + + $ hg init master + $ cd master + + $ mkdir inside + $ echo inside > inside/f1 + $ mkdir outside + $ echo outside > outside/f1 + $ hg ci -Aqm 'initial' + + $ echo modified > inside/f1 + $ hg ci -qm 'modify inside' + + $ echo modified > outside/f1 + $ hg ci -qm 'modify outside' + + $ cd .. + + $ hg clone --narrow ssh://user@dummy/master narrow --include inside + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 2 changes to 1 files + new changesets *:* (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + + $ hg update -q 0 + +Can not modify dirstate outside + + $ mkdir outside + $ touch outside/f1 + $ hg debugwalk -I 'relglob:f1' + matcher: + f inside/f1 inside/f1 + $ hg add outside/f1 + abort: cannot track 'outside/f1' - it is outside the narrow clone + [255] + $ touch outside/f3 + $ hg add outside/f3 + abort: cannot track 'outside/f3' - it is outside the narrow clone + [255] + +But adding a truly excluded file shouldn't count + + $ hg add outside/f3 -X outside/f3 + + $ rm -r outside + +Can modify dirstate inside + + $ echo modified > inside/f1 + $ touch inside/f3 + $ hg add inside/f3 + $ hg status + M inside/f1 + A inside/f3 + $ hg revert -qC . + $ rm inside/f3 + +Can commit changes inside. Leaves outside unchanged. + + $ hg update -q 'desc("initial")' + $ echo modified2 > inside/f1 + $ hg manifest --debug + 4d6a634d5ba06331a60c29ee0db8412490a54fcd 644 inside/f1 + 7fb3bb6356d28d4dc352c5ba52d7350a81b6bd46 644 outside/f1 (flat !) + d0f2f706468ab0e8bec7af87446835fb1b13511b 755 d outside/ (tree !) + $ hg commit -m 'modify inside/f1' + created new head + $ hg files -r . + inside/f1 + outside/f1 (flat !) + outside/ (tree !) + $ hg manifest --debug + 3f4197b4a11b9016e77ebc47fe566944885fd11b 644 inside/f1 + 7fb3bb6356d28d4dc352c5ba52d7350a81b6bd46 644 outside/f1 (flat !) + d0f2f706468ab0e8bec7af87446835fb1b13511b 755 d outside/ (tree !) +Some filesystems (notably FAT/exFAT only store timestamps with 2 +seconds of precision, so by sleeping for 3 seconds, we can ensure that +the timestamps of files stored by dirstate will appear older than the +dirstate file, and therefore we'll be able to get stable output from +debugdirstate. If we don't do this, the test can be slightly flaky. + $ sleep 3 + $ hg status + $ hg debugdirstate --nodates + n 644 10 set inside/f1 diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-copies.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-copies.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,57 @@ + + $ . "$TESTDIR/narrow-library.sh" + +create full repo + + $ hg init master + $ cd master + + $ mkdir inside + $ echo inside > inside/f1 + $ mkdir outside + $ echo outside > outside/f2 + $ hg ci -Aqm 'initial' + + $ hg mv outside/f2 inside/f2 + $ hg ci -qm 'move f2 from outside' + + $ echo modified > inside/f2 + $ hg ci -qm 'modify inside/f2' + + $ cd .. + + $ hg clone --narrow ssh://user@dummy/master narrow --include inside + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 2 files + new changesets *:* (glob) + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + + $ hg co 'desc("move f2")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg status + $ hg diff + $ hg diff --change . --git + diff --git a/inside/f2 b/inside/f2 + new file mode 100644 + --- /dev/null + +++ b/inside/f2 + @@ -0,0 +1,1 @@ + +outside + + $ hg log --follow inside/f2 -r tip + changeset: 2:bcfb756e0ca9 + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: modify inside/f2 + + changeset: 1:5a016133b2bb + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: move f2 from outside + diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-debugcommands.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-debugcommands.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,43 @@ + $ . "$TESTDIR/narrow-library.sh" + $ hg init repo + $ cd repo + $ cat << EOF > .hg/narrowspec + > [includes] + > path:foo + > [excludes] + > EOF + $ echo treemanifest >> .hg/requires + $ echo narrowhg-experimental >> .hg/requires + $ mkdir -p foo/bar + $ echo b > foo/f + $ echo c > foo/bar/f + $ hg commit -Am hi + adding foo/bar/f + adding foo/f + $ hg debugindex -m + rev offset length delta linkrev nodeid p1 p2 + 0 0 47 -1 0 14a5d056d75a 000000000000 000000000000 + $ hg debugindex --dir foo + rev offset length delta linkrev nodeid p1 p2 + 0 0 77 -1 0 e635c7857aef 000000000000 000000000000 + $ hg debugindex --dir foo/ + rev offset length delta linkrev nodeid p1 p2 + 0 0 77 -1 0 e635c7857aef 000000000000 000000000000 + $ hg debugindex --dir foo/bar + rev offset length delta linkrev nodeid p1 p2 + 0 0 44 -1 0 e091d4224761 000000000000 000000000000 + $ hg debugindex --dir foo/bar/ + rev offset length delta linkrev nodeid p1 p2 + 0 0 44 -1 0 e091d4224761 000000000000 000000000000 + $ hg debugdata -m 0 + foo\x00e635c7857aef92ac761ce5741a99da159abbbb24t (esc) + $ hg debugdata --dir foo 0 + bar\x00e091d42247613adff5d41b67f15fe7189ee97b39t (esc) + f\x001e88685f5ddec574a34c70af492f95b6debc8741 (esc) + $ hg debugdata --dir foo/ 0 + bar\x00e091d42247613adff5d41b67f15fe7189ee97b39t (esc) + f\x001e88685f5ddec574a34c70af492f95b6debc8741 (esc) + $ hg debugdata --dir foo/bar 0 + f\x00149da44f2a4e14f488b7bd4157945a9837408c00 (esc) + $ hg debugdata --dir foo/bar/ 0 + f\x00149da44f2a4e14f488b7bd4157945a9837408c00 (esc) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-debugrebuilddirstate.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-debugrebuilddirstate.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,31 @@ + $ . "$TESTDIR/narrow-library.sh" + $ hg init master + $ cd master + $ echo treemanifest >> .hg/requires + $ echo 'contents of file' > file + $ mkdir foo + $ echo 'contents of foo/bar' > foo/bar + $ hg ci -Am 'some change' + adding file + adding foo/bar + + $ cd .. + $ hg clone --narrow ssh://user@dummy/master copy --include=foo + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + new changesets * (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd copy + + $ hg debugdirstate + n * 20 unset foo/bar (glob) + $ mv .hg/dirstate .hg/old_dirstate + $ dd bs=40 count=1 if=.hg/old_dirstate of=.hg/dirstate 2>/dev/null + $ hg debugdirstate + $ hg debugrebuilddirstate + $ hg debugdirstate + n * * unset foo/bar (glob) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-exchange-merges.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-exchange-merges.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,207 @@ + + $ . "$TESTDIR/narrow-library.sh" + +create full repo + + $ hg init master + $ cd master + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + + $ mkdir inside + $ echo 1 > inside/f + $ hg commit -Aqm 'initial inside' + + $ mkdir outside + $ echo 1 > outside/f + $ hg commit -Aqm 'initial outside' + + $ echo 2a > outside/f + $ hg commit -Aqm 'outside 2a' + $ echo 3 > inside/f + $ hg commit -Aqm 'inside 3' + $ echo 4a > outside/f + $ hg commit -Aqm 'outside 4a' + $ hg update '.~3' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ echo 2b > outside/f + $ hg commit -Aqm 'outside 2b' + $ echo 3 > inside/f + $ hg commit -Aqm 'inside 3' + $ echo 4b > outside/f + $ hg commit -Aqm 'outside 4b' + $ hg update '.~3' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ echo 2c > outside/f + $ hg commit -Aqm 'outside 2c' + $ echo 3 > inside/f + $ hg commit -Aqm 'inside 3' + $ echo 4c > outside/f + $ hg commit -Aqm 'outside 4c' + $ hg update '.~3' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ echo 2d > outside/f + $ hg commit -Aqm 'outside 2d' + $ echo 3 > inside/f + $ hg commit -Aqm 'inside 3' + $ echo 4d > outside/f + $ hg commit -Aqm 'outside 4d' + + $ hg update -r 'desc("outside 4a")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge -r 'desc("outside 4b")' 2>&1 | egrep -v '(warning:|incomplete!)' + merging outside/f + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon + $ echo 5 > outside/f + $ rm outside/f.orig + $ hg resolve --mark outside/f + (no more unresolved files) + $ hg commit -m 'merge a/b 5' + $ echo 6 > outside/f + $ hg commit -Aqm 'outside 6' + + $ hg merge -r 'desc("outside 4c")' 2>&1 | egrep -v '(warning:|incomplete!)' + merging outside/f + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon + $ echo 7 > outside/f + $ rm outside/f.orig + $ hg resolve --mark outside/f + (no more unresolved files) + $ hg commit -Aqm 'merge a/b/c 7' + $ echo 8 > outside/f + $ hg commit -Aqm 'outside 8' + + $ hg merge -r 'desc("outside 4d")' 2>&1 | egrep -v '(warning:|incomplete!)' + merging outside/f + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon + $ echo 9 > outside/f + $ rm outside/f.orig + $ hg resolve --mark outside/f + (no more unresolved files) + $ hg commit -Aqm 'merge a/b/c/d 9' + $ echo 10 > outside/f + $ hg commit -Aqm 'outside 10' + + $ echo 11 > inside/f + $ hg commit -Aqm 'inside 11' + $ echo 12 > outside/f + $ hg commit -Aqm 'outside 12' + + $ hg log -G -T '{rev} {node|short} {desc}\n' + @ 21 8d874d57adea outside 12 + | + o 20 7ef88b4dd4fa inside 11 + | + o 19 2a20009de83e outside 10 + | + o 18 3ac1f5779de3 merge a/b/c/d 9 + |\ + | o 17 38a9c2f7e546 outside 8 + | | + | o 16 094aa62fc898 merge a/b/c 7 + | |\ + | | o 15 f29d083d32e4 outside 6 + | | | + | | o 14 2dc11382541d merge a/b 5 + | | |\ + o | | | 13 27d07ef97221 outside 4d + | | | | + o | | | 12 465567bdfb2d inside 3 + | | | | + o | | | 11 d1c61993ec83 outside 2d + | | | | + | o | | 10 56859a8e33b9 outside 4c + | | | | + | o | | 9 bb96a08b062a inside 3 + | | | | + | o | | 8 b844052e7b3b outside 2c + |/ / / + | | o 7 9db2d8fcc2a6 outside 4b + | | | + | | o 6 6418167787a6 inside 3 + | | | + +---o 5 77344f344d83 outside 2b + | | + | o 4 9cadde08dc9f outside 4a + | | + | o 3 019ef06f125b inside 3 + | | + | o 2 75e40c075a19 outside 2a + |/ + o 1 906d6c682641 initial outside + | + o 0 9f8e82b51004 initial inside + + +Now narrow clone this and get a hopefully correct graph + + $ cd .. + $ hg clone --narrow ssh://user@dummy/master narrow --include inside + requesting all changes + adding changesets + adding manifests + adding file changes + added 14 changesets with 3 changes to 1 files + new changesets *:* (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + +To make updating the tests easier, we print the emitted nodes +sorted. This makes it easier to identify when the same node structure +has been emitted, just in a different order. + + $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort + ...094aa62fc898 6418167787a6 bb96a08b062a merge a/b/c 7 + ...2a20009de83e 019ef06f125b 3ac1f5779de3 outside 10 + ...3ac1f5779de3 465567bdfb2d 094aa62fc898 merge a/b/c/d 9 + ...75e40c075a19 9f8e82b51004 000000000000 outside 2a + ...77344f344d83 9f8e82b51004 000000000000 outside 2b + ...8d874d57adea 7ef88b4dd4fa 000000000000 outside 12 + ...b844052e7b3b 9f8e82b51004 000000000000 outside 2c + ...d1c61993ec83 9f8e82b51004 000000000000 outside 2d + 019ef06f125b 75e40c075a19 000000000000 inside 3 + 465567bdfb2d d1c61993ec83 000000000000 inside 3 + 6418167787a6 77344f344d83 000000000000 inside 3 + 7ef88b4dd4fa 2a20009de83e 000000000000 inside 11 + 9f8e82b51004 000000000000 000000000000 initial inside + bb96a08b062a b844052e7b3b 000000000000 inside 3 + +But seeing the graph is also nice: + $ hg log -G -T '{if(ellipsis,"...")}{node|short} {desc}\n' + @ ...8d874d57adea outside 12 + | + o 7ef88b4dd4fa inside 11 + | + o ...2a20009de83e outside 10 + |\ + | o ...3ac1f5779de3 merge a/b/c/d 9 + | |\ + | | o ...094aa62fc898 merge a/b/c 7 + | | |\ + | o | | 465567bdfb2d inside 3 + | | | | + | o | | ...d1c61993ec83 outside 2d + | | | | + | | | o bb96a08b062a inside 3 + | | | | + | +---o ...b844052e7b3b outside 2c + | | | + | | o 6418167787a6 inside 3 + | | | + | | o ...77344f344d83 outside 2b + | |/ + o | 019ef06f125b inside 3 + | | + o | ...75e40c075a19 outside 2a + |/ + o 9f8e82b51004 initial inside + diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-exchange.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-exchange.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,210 @@ + + $ . "$TESTDIR/narrow-library.sh" + +create full repo + + $ hg init master + $ cd master + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + + $ mkdir inside + $ echo 1 > inside/f + $ mkdir inside2 + $ echo 1 > inside2/f + $ mkdir outside + $ echo 1 > outside/f + $ hg ci -Aqm 'initial' + + $ echo 2 > inside/f + $ hg ci -qm 'inside 2' + + $ echo 2 > inside2/f + $ hg ci -qm 'inside2 2' + + $ echo 2 > outside/f + $ hg ci -qm 'outside 2' + + $ cd .. + + $ hg clone --narrow ssh://user@dummy/master narrow --include inside + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 2 changes to 1 files + new changesets *:* (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ hg clone --narrow ssh://user@dummy/master narrow2 --include inside --include inside2 + requesting all changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 4 changes to 2 files + new changesets *:* (glob) + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + +Can push to wider repo if change does not affect paths in wider repo that are +not also in narrower repo + + $ cd narrow + $ echo 3 > inside/f + $ hg ci -m 'inside 3' + $ hg push ssh://user@dummy/narrow2 + pushing to ssh://user@dummy/narrow2 + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + +Can push to narrower repo if change affects only paths within remote's +narrow spec + + $ cd ../narrow2 + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + $ hg co -r 'desc("inside 3")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo 4 > inside/f + $ hg ci -m 'inside 4' + $ hg push ssh://user@dummy/narrow + pushing to ssh://user@dummy/narrow + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + +Can push to narrow repo if change affects only paths outside remote's +narrow spec + + $ echo 3 > inside2/f + $ hg ci -m 'inside2 3' +TODO: this should be successful + $ hg push ssh://user@dummy/narrow + pushing to ssh://user@dummy/narrow + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: transaction abort! + remote: rollback completed + remote: abort: data/inside2/f.i@4a1aa07735e6: unknown parent! + abort: stream ended unexpectedly (got 0 bytes, expected 4) + [255] + +Can pull from wider repo if change affects only paths outside remote's +narrow spec + $ echo 4 > inside2/f + $ hg ci -m 'inside2 4' + $ hg log -G -T '{rev} {node|short} {files}\n' + @ 7 d78a96df731d inside2/f + | + o 6 8c26f5218962 inside2/f + | + o 5 ba3480e2f9de inside/f + | + o 4 4e5edd526618 inside/f + | + o 3 81e7e07b7ab0 outside/f + | + o 2 f3993b8c0c2b inside2/f + | + o 1 8cd66ca966b4 inside/f + | + o 0 c8057d6f53ab inside/f inside2/f outside/f + + $ cd ../narrow + $ hg log -G -T '{rev} {node|short} {files}\n' + o 4 ba3480e2f9de inside/f + | + @ 3 4e5edd526618 inside/f + | + o 2 81e7e07b7ab0 outside/f + | + o 1 8cd66ca966b4 inside/f + | + o 0 c8057d6f53ab inside/f inside2/f outside/f + + $ hg pull ssh://user@dummy/narrow2 + pulling from ssh://user@dummy/narrow2 + searching for changes + remote: abort: unable to resolve parent while packing 'data/inside2/f.i' 3 for changeset 5 (?) + adding changesets + remote: abort: unexpected error: unable to resolve parent while packing 'data/inside2/f.i' 3 for changeset 5 + transaction abort! + rollback completed + abort: pull failed on remote + [255] + +Check that the resulting history is valid in the full repo + + $ cd ../narrow2 + $ hg push ssh://user@dummy/master + pushing to ssh://user@dummy/master + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 4 changesets with 4 changes to 2 files + $ cd ../master + $ hg verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 3 files, 8 changesets, 10 total revisions + +Can not push to wider repo if change affects paths in wider repo that are +not also in narrower repo + $ cd ../master + $ hg co -r 'desc("inside2 4")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo 5 > inside2/f + $ hg ci -m 'inside2 5' + $ hg log -G -T '{rev} {node|short} {files}\n' + @ 8 5970befb64ba inside2/f + | + o 7 d78a96df731d inside2/f + | + o 6 8c26f5218962 inside2/f + | + o 5 ba3480e2f9de inside/f + | + o 4 4e5edd526618 inside/f + | + o 3 81e7e07b7ab0 outside/f + | + o 2 f3993b8c0c2b inside2/f + | + o 1 8cd66ca966b4 inside/f + | + o 0 c8057d6f53ab inside/f inside2/f outside/f + + $ cd ../narrow + $ hg pull + pulling from ssh://user@dummy/master + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 0 changes to 0 files + new changesets * (glob) + (run 'hg update' to get a working copy) +TODO: this should tell the user that their narrow clone does not have the +necessary content to be able to push to the target + $ hg push ssh://user@dummy/narrow2 + pushing to ssh://user@dummy/narrow2 + searching for changes + remote has heads on branch 'default' that are not known locally: d78a96df731d + abort: push creates new remote head 5970befb64ba! + (pull and merge or see 'hg help push' for details about pushing new heads) + [255] diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-expanddirstate.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-expanddirstate.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,162 @@ + $ . "$TESTDIR/narrow-library.sh" + + $ hg init master + $ cd master + + $ mkdir inside + $ echo inside > inside/f1 + $ mkdir outside + $ echo outside > outside/f2 + $ mkdir patchdir + $ echo patch_this > patchdir/f3 + $ hg ci -Aqm 'initial' + + $ cd .. + + $ hg clone --narrow ssh://user@dummy/master narrow --include inside + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + new changesets dff6a2a6d433 + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ cd narrow + + $ mkdir outside + $ echo other_contents > outside/f2 + $ grep outside .hg/narrowspec + [1] + $ grep outside .hg/dirstate + [1] + $ hg status + +`hg status` did not add outside. + $ grep outside .hg/narrowspec + [1] + $ grep outside .hg/dirstate + [1] + +Unfortunately this is not really a candidate for adding to narrowhg proper, +since it depends on some other source for providing the manifests (when using +treemanifests) and file contents. Something like a virtual filesystem and/or +remotefilelog. We want to be useful when not using those systems, so we do not +have this method available in narrowhg proper at the moment. + $ cat > "$TESTTMP/expand_extension.py" < import os + > import sys + > + > from mercurial import encoding + > from mercurial import extensions + > from mercurial import localrepo + > from mercurial import match as matchmod + > from mercurial import narrowspec + > from mercurial import patch + > from mercurial import util as hgutil + > + > def expandnarrowspec(ui, repo, newincludes=None): + > if not newincludes: + > return + > import sys + > newincludes = set([newincludes]) + > includes, excludes = repo.narrowpats + > currentmatcher = narrowspec.match(repo.root, includes, excludes) + > includes = includes | newincludes + > if not repo.currenttransaction(): + > ui.develwarn(b'expandnarrowspec called outside of transaction!') + > repo.setnarrowpats(includes, excludes) + > newmatcher = narrowspec.match(repo.root, includes, excludes) + > added = matchmod.differencematcher(newmatcher, currentmatcher) + > for f in repo[b'.'].manifest().walk(added): + > repo.dirstate.normallookup(f) + > + > def makeds(ui, repo): + > def wrapds(orig, self): + > ds = orig(self) + > class expandingdirstate(ds.__class__): + > @hgutil.propertycache + > def _map(self): + > ret = super(expandingdirstate, self)._map + > with repo.wlock(), repo.lock(), repo.transaction( + > b'expandnarrowspec'): + > expandnarrowspec(ui, repo, + > encoding.environ.get(b'DIRSTATEINCLUDES')) + > return ret + > ds.__class__ = expandingdirstate + > return ds + > return wrapds + > + > def reposetup(ui, repo): + > extensions.wrapfilecache(localrepo.localrepository, b'dirstate', + > makeds(ui, repo)) + > def overridepatch(orig, *args, **kwargs): + > with repo.wlock(): + > expandnarrowspec(ui, repo, encoding.environ.get(b'PATCHINCLUDES')) + > return orig(*args, **kwargs) + > + > extensions.wrapfunction(patch, b'patch', overridepatch) + > EOF + $ cat >> ".hg/hgrc" < [extensions] + > expand_extension = $TESTTMP/expand_extension.py + > EOF + +Since we do not have the ability to rely on a virtual filesystem or +remotefilelog in the test, we just fake it by copying the data from the 'master' +repo. + $ cp -a ../master/.hg/store/data/* .hg/store/data +Do that for patchdir as well. + $ cp -a ../master/patchdir . + +`hg status` will now add outside, but not patchdir. + $ DIRSTATEINCLUDES=path:outside hg status + M outside/f2 + $ grep outside .hg/narrowspec + path:outside + $ grep outside .hg/dirstate > /dev/null + $ grep patchdir .hg/narrowspec + [1] + $ grep patchdir .hg/dirstate + [1] + +Get rid of the modification to outside/f2. + $ hg update -C . + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + +This patch will not apply cleanly at the moment, so `hg import` will break + $ cat > "$TESTTMP/foo.patch" < --- patchdir/f3 + > +++ patchdir/f3 + > @@ -1,1 +1,1 @@ + > -this should be "patch_this", but its not, so patch fails + > +this text is irrelevant + > EOF + $ PATCHINCLUDES=path:patchdir hg import -p0 -e "$TESTTMP/foo.patch" -m ignored + applying $TESTTMP/foo.patch + patching file patchdir/f3 + Hunk #1 FAILED at 0 + 1 out of 1 hunks FAILED -- saving rejects to file patchdir/f3.rej + abort: patch failed to apply + [255] + $ grep patchdir .hg/narrowspec + [1] + $ grep patchdir .hg/dirstate > /dev/null + [1] + +Let's make it apply cleanly and see that it *did* expand properly + $ cat > "$TESTTMP/foo.patch" < --- patchdir/f3 + > +++ patchdir/f3 + > @@ -1,1 +1,1 @@ + > -patch_this + > +patched_this + > EOF + $ PATCHINCLUDES=path:patchdir hg import -p0 -e "$TESTTMP/foo.patch" -m message + applying $TESTTMP/foo.patch + $ cat patchdir/f3 + patched_this + $ grep patchdir .hg/narrowspec + path:patchdir + $ grep patchdir .hg/dirstate > /dev/null diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-merge.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-merge.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,104 @@ +#testcases flat tree + + $ . "$TESTDIR/narrow-library.sh" + +#if tree + $ cat << EOF >> $HGRCPATH + > [experimental] + > treemanifest = 1 + > EOF +#endif + +create full repo + + $ hg init master + $ cd master + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + + $ mkdir inside + $ echo inside1 > inside/f1 + $ echo inside2 > inside/f2 + $ mkdir outside + $ echo outside1 > outside/f1 + $ echo outside2 > outside/f2 + $ hg ci -Aqm 'initial' + + $ echo modified > inside/f1 + $ hg ci -qm 'modify inside/f1' + + $ hg update -q 0 + $ echo modified > inside/f2 + $ hg ci -qm 'modify inside/f2' + + $ hg update -q 0 + $ echo modified2 > inside/f1 + $ hg ci -qm 'conflicting inside/f1' + + $ hg update -q 0 + $ echo modified > outside/f1 + $ hg ci -qm 'modify outside/f1' + + $ hg update -q 0 + $ echo modified2 > outside/f1 + $ hg ci -qm 'conflicting outside/f1' + + $ cd .. + + $ hg clone --narrow ssh://user@dummy/master narrow --include inside + requesting all changes + adding changesets + adding manifests + adding file changes + added 6 changesets with 5 changes to 2 files (+4 heads) + new changesets *:* (glob) + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + + $ hg update -q 0 + +Can merge in when no files outside narrow spec are involved + + $ hg update -q 'desc("modify inside/f1")' + $ hg merge 'desc("modify inside/f2")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg commit -m 'merge inside changes' + +Can merge conflicting changes inside narrow spec + + $ hg update -q 'desc("modify inside/f1")' + $ hg merge 'desc("conflicting inside/f1")' 2>&1 | egrep -v '(warning:|incomplete!)' + merging inside/f1 + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon + $ echo modified3 > inside/f1 + $ hg resolve -m + (no more unresolved files) + $ hg commit -m 'merge inside/f1' + +TODO: Can merge non-conflicting changes outside narrow spec + + $ hg update -q 'desc("modify inside/f1")' + $ hg merge 'desc("modify outside/f1")' + abort: merge affects file 'outside/f1' outside narrow, which is not yet supported (flat !) + abort: merge affects file 'outside/' outside narrow, which is not yet supported (tree !) + (merging in the other direction may work) + [255] + + $ hg update -q 'desc("modify outside/f1")' + $ hg merge 'desc("modify inside/f1")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -m 'merge from inside to outside' + +Refuses merge of conflicting outside changes + + $ hg update -q 'desc("modify outside/f1")' + $ hg merge 'desc("conflicting outside/f1")' + abort: conflict in file 'outside/f1' is outside narrow clone (flat !) + abort: conflict in file 'outside/' is outside narrow clone (tree !) + [255] diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-patch.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-patch.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,84 @@ +#testcases flat tree + + $ . "$TESTDIR/narrow-library.sh" + +#if tree + $ cat << EOF >> $HGRCPATH + > [experimental] + > treemanifest = 1 + > EOF +#endif + +create full repo + + $ hg init master + $ cd master + + $ mkdir inside + $ echo inside > inside/f1 + $ mkdir outside + $ echo outside > outside/f1 + $ hg ci -Aqm 'initial' + + $ echo modified > inside/f1 + $ hg ci -qm 'modify inside' + + $ echo modified > outside/f1 + $ hg ci -qm 'modify outside' + + $ cd .. + + $ hg clone --narrow ssh://user@dummy/master narrow --include inside + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 2 changes to 1 files + new changesets *:* (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + +Can show patch touching paths outside + + $ hg log -p + changeset: 2:* (glob) + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: modify outside + + + changeset: 1:* (glob) + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: modify inside + + diff -r * -r * inside/f1 (glob) + --- a/inside/f1 Thu Jan 01 00:00:00 1970 +0000 + +++ b/inside/f1 Thu Jan 01 00:00:00 1970 +0000 + @@ -1,1 +1,1 @@ + -inside + +modified + + changeset: 0:* (glob) + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: initial + + diff -r 000000000000 -r * inside/f1 (glob) + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/inside/f1 Thu Jan 01 00:00:00 1970 +0000 + @@ -0,0 +1,1 @@ + +inside + + + $ hg status --rev 1 --rev 2 + +Can show copies inside the narrow clone + + $ hg cp inside/f1 inside/f2 + $ hg diff --git + diff --git a/inside/f1 b/inside/f2 + copy from inside/f1 + copy to inside/f2 diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-patterns.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-patterns.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,435 @@ + $ . "$TESTDIR/narrow-library.sh" + +initialize nested directories to validate complex include/exclude patterns + + $ hg init master + $ cd master + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + + $ echo root > root + $ hg add root + $ hg commit -m 'add root' + + $ for d in dir1 dir2 dir1/dirA dir1/dirB dir2/dirA dir2/dirB + > do + > mkdir -p $d + > echo $d/foo > $d/foo + > hg add $d/foo + > hg commit -m "add $d/foo" + > echo $d/bar > $d/bar + > hg add $d/bar + > hg commit -m "add $d/bar" + > done +#if execbit + $ chmod +x dir1/dirA/foo + $ hg commit -m "make dir1/dirA/foo executable" +#else + $ hg import --bypass - < # HG changeset patch + > make dir1/dirA/foo executable + > + > diff --git a/dir1/dirA/foo b/dir1/dirA/foo + > old mode 100644 + > new mode 100755 + > EOF + applying patch from stdin + $ hg update -qr tip +#endif + $ hg log -G -T '{rev} {node|short} {files}\n' + @ 13 c87ca422d521 dir1/dirA/foo + | + o 12 951b8a83924e dir2/dirB/bar + | + o 11 01ae5a51b563 dir2/dirB/foo + | + o 10 5eababdf0ac5 dir2/dirA/bar + | + o 9 99d690663739 dir2/dirA/foo + | + o 8 8e80155d5445 dir1/dirB/bar + | + o 7 406760310428 dir1/dirB/foo + | + o 6 623466a5f475 dir1/dirA/bar + | + o 5 06ff3a5be997 dir1/dirA/foo + | + o 4 33227af02764 dir2/bar + | + o 3 5e1f9d8d7c69 dir2/foo + | + o 2 594bc4b13d4a dir1/bar + | + o 1 47f480a08324 dir1/foo + | + o 0 2a4f0c3b67da root + + $ cd .. + +clone a narrow portion of the master, such that we can widen it later + + $ hg clone --narrow ssh://user@dummy/master narrow \ + > --include dir1 \ + > --include dir2 \ + > --exclude dir1/dirA \ + > --exclude dir1/dirB \ + > --exclude dir2/dirA \ + > --exclude dir2/dirB + requesting all changes + adding changesets + adding manifests + adding file changes + added 6 changesets with 4 changes to 4 files + new changesets *:* (glob) + updating to branch default + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ cd narrow + $ cat .hg/narrowspec + [includes] + path:dir1 + path:dir2 + [excludes] + path:dir1/dirA + path:dir1/dirB + path:dir2/dirA + path:dir2/dirB + $ hg manifest -r tip + dir1/bar + dir1/dirA/bar + dir1/dirA/foo + dir1/dirB/bar + dir1/dirB/foo + dir1/foo + dir2/bar + dir2/dirA/bar + dir2/dirA/foo + dir2/dirB/bar + dir2/dirB/foo + dir2/foo + root + $ find * | sort + dir1 + dir1/bar + dir1/foo + dir2 + dir2/bar + dir2/foo + $ hg log -G -T '{rev} {node|short}{if(ellipsis, "...")} {files}\n' + @ 5 c87ca422d521... dir1/dirA/foo + | + o 4 33227af02764 dir2/bar + | + o 3 5e1f9d8d7c69 dir2/foo + | + o 2 594bc4b13d4a dir1/bar + | + o 1 47f480a08324 dir1/foo + | + o 0 2a4f0c3b67da... root + + +widen the narrow checkout + + $ hg tracked --removeexclude dir1/dirA + comparing with ssh://user@dummy/master + searching for changes + no changes found + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob) + adding changesets + adding manifests + adding file changes + added 9 changesets with 6 changes to 6 files + new changesets *:* (glob) + $ cat .hg/narrowspec + [includes] + path:dir1 + path:dir2 + [excludes] + path:dir1/dirB + path:dir2/dirA + path:dir2/dirB + $ find * | sort + dir1 + dir1/bar + dir1/dirA + dir1/dirA/bar + dir1/dirA/foo + dir1/foo + dir2 + dir2/bar + dir2/foo + +#if execbit + $ test -x dir1/dirA/foo && echo executable + executable + $ test -x dir1/dirA/bar || echo not executable + not executable +#endif + + $ hg log -G -T '{rev} {node|short}{if(ellipsis, "...")} {files}\n' + @ 8 c87ca422d521 dir1/dirA/foo + | + o 7 951b8a83924e... dir2/dirB/bar + | + o 6 623466a5f475 dir1/dirA/bar + | + o 5 06ff3a5be997 dir1/dirA/foo + | + o 4 33227af02764 dir2/bar + | + o 3 5e1f9d8d7c69 dir2/foo + | + o 2 594bc4b13d4a dir1/bar + | + o 1 47f480a08324 dir1/foo + | + o 0 2a4f0c3b67da... root + + +widen narrow spec again, but exclude a file in previously included spec + + $ hg tracked --removeexclude dir2/dirB --addexclude dir1/dirA/bar + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + deleting data/dir1/dirA/bar.i + no changes found + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob) + adding changesets + adding manifests + adding file changes + added 11 changesets with 7 changes to 7 files + new changesets *:* (glob) + $ cat .hg/narrowspec + [includes] + path:dir1 + path:dir2 + [excludes] + path:dir1/dirA/bar + path:dir1/dirB + path:dir2/dirA + $ find * | sort + dir1 + dir1/bar + dir1/dirA + dir1/dirA/foo + dir1/foo + dir2 + dir2/bar + dir2/dirB + dir2/dirB/bar + dir2/dirB/foo + dir2/foo + $ hg log -G -T '{rev} {node|short}{if(ellipsis, "...")} {files}\n' + @ 10 c87ca422d521 dir1/dirA/foo + | + o 9 951b8a83924e dir2/dirB/bar + | + o 8 01ae5a51b563 dir2/dirB/foo + | + o 7 5eababdf0ac5... dir2/dirA/bar + | + o 6 623466a5f475... dir1/dirA/bar + | + o 5 06ff3a5be997 dir1/dirA/foo + | + o 4 33227af02764 dir2/bar + | + o 3 5e1f9d8d7c69 dir2/foo + | + o 2 594bc4b13d4a dir1/bar + | + o 1 47f480a08324 dir1/foo + | + o 0 2a4f0c3b67da... root + + +widen narrow spec yet again, excluding a directory in previous spec + + $ hg tracked --removeexclude dir2/dirA --addexclude dir1/dirA + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + deleting data/dir1/dirA/foo.i + no changes found + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob) + adding changesets + adding manifests + adding file changes + added 13 changesets with 8 changes to 8 files + new changesets *:* (glob) + $ cat .hg/narrowspec + [includes] + path:dir1 + path:dir2 + [excludes] + path:dir1/dirA + path:dir1/dirA/bar + path:dir1/dirB + $ find * | sort + dir1 + dir1/bar + dir1/foo + dir2 + dir2/bar + dir2/dirA + dir2/dirA/bar + dir2/dirA/foo + dir2/dirB + dir2/dirB/bar + dir2/dirB/foo + dir2/foo + $ hg log -G -T '{rev} {node|short}{if(ellipsis, "...")} {files}\n' + @ 12 c87ca422d521... dir1/dirA/foo + | + o 11 951b8a83924e dir2/dirB/bar + | + o 10 01ae5a51b563 dir2/dirB/foo + | + o 9 5eababdf0ac5 dir2/dirA/bar + | + o 8 99d690663739 dir2/dirA/foo + | + o 7 8e80155d5445... dir1/dirB/bar + | + o 6 623466a5f475... dir1/dirA/bar + | + o 5 06ff3a5be997... dir1/dirA/foo + | + o 4 33227af02764 dir2/bar + | + o 3 5e1f9d8d7c69 dir2/foo + | + o 2 594bc4b13d4a dir1/bar + | + o 1 47f480a08324 dir1/foo + | + o 0 2a4f0c3b67da... root + + +include a directory that was previously explicitly excluded + + $ hg tracked --removeexclude dir1/dirA + comparing with ssh://user@dummy/master + searching for changes + no changes found + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob) + adding changesets + adding manifests + adding file changes + added 13 changesets with 9 changes to 9 files + new changesets *:* (glob) + $ cat .hg/narrowspec + [includes] + path:dir1 + path:dir2 + [excludes] + path:dir1/dirA/bar + path:dir1/dirB + $ find * | sort + dir1 + dir1/bar + dir1/dirA + dir1/dirA/foo + dir1/foo + dir2 + dir2/bar + dir2/dirA + dir2/dirA/bar + dir2/dirA/foo + dir2/dirB + dir2/dirB/bar + dir2/dirB/foo + dir2/foo + $ hg log -G -T '{rev} {node|short}{if(ellipsis, "...")} {files}\n' + @ 12 c87ca422d521 dir1/dirA/foo + | + o 11 951b8a83924e dir2/dirB/bar + | + o 10 01ae5a51b563 dir2/dirB/foo + | + o 9 5eababdf0ac5 dir2/dirA/bar + | + o 8 99d690663739 dir2/dirA/foo + | + o 7 8e80155d5445... dir1/dirB/bar + | + o 6 623466a5f475... dir1/dirA/bar + | + o 5 06ff3a5be997 dir1/dirA/foo + | + o 4 33227af02764 dir2/bar + | + o 3 5e1f9d8d7c69 dir2/foo + | + o 2 594bc4b13d4a dir1/bar + | + o 1 47f480a08324 dir1/foo + | + o 0 2a4f0c3b67da... root + + + $ cd .. + +clone a narrow portion of the master, such that we can widen it later + + $ hg clone --narrow ssh://user@dummy/master narrow2 --include dir1/dirA + requesting all changes + adding changesets + adding manifests + adding file changes + added 5 changesets with 2 changes to 2 files + new changesets *:* (glob) + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow2 + $ find * | sort + dir1 + dir1/dirA + dir1/dirA/bar + dir1/dirA/foo + $ hg tracked --addinclude dir1 + comparing with ssh://user@dummy/master + searching for changes + no changes found + saved backup bundle to $TESTTMP/narrow2/.hg/strip-backup/*-widen.hg (glob) + adding changesets + adding manifests + adding file changes + added 10 changesets with 6 changes to 6 files + new changesets *:* (glob) + $ find * | sort + dir1 + dir1/bar + dir1/dirA + dir1/dirA/bar + dir1/dirA/foo + dir1/dirB + dir1/dirB/bar + dir1/dirB/foo + dir1/foo + $ hg log -G -T '{rev} {node|short}{if(ellipsis, "...")} {files}\n' + @ 9 c87ca422d521 dir1/dirA/foo + | + o 8 951b8a83924e... dir2/dirB/bar + | + o 7 8e80155d5445 dir1/dirB/bar + | + o 6 406760310428 dir1/dirB/foo + | + o 5 623466a5f475 dir1/dirA/bar + | + o 4 06ff3a5be997 dir1/dirA/foo + | + o 3 33227af02764... dir2/bar + | + o 2 594bc4b13d4a dir1/bar + | + o 1 47f480a08324 dir1/foo + | + o 0 2a4f0c3b67da... root + diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-pull.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-pull.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,175 @@ + $ . "$TESTDIR/narrow-library.sh" + + $ hg init master + $ cd master + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + $ for x in `$TESTDIR/seq.py 10` + > do + > echo $x > "f$x" + > hg add "f$x" + > hg commit -m "Commit f$x" + > done + $ cd .. + +narrow clone a couple files, f2 and f8 + + $ hg clone --narrow ssh://user@dummy/master narrow --include "f2" --include "f8" + requesting all changes + adding changesets + adding manifests + adding file changes + added 5 changesets with 2 changes to 2 files + new changesets *:* (glob) + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + $ ls + f2 + f8 + $ cat f2 f8 + 2 + 8 + + $ cd .. + +change every upstream file twice + + $ cd master + $ for x in `$TESTDIR/seq.py 10` + > do + > echo "update#1 $x" >> "f$x" + > hg commit -m "Update#1 to f$x" "f$x" + > done + $ for x in `$TESTDIR/seq.py 10` + > do + > echo "update#2 $x" >> "f$x" + > hg commit -m "Update#2 to f$x" "f$x" + > done + $ cd .. + +look for incoming changes + + $ cd narrow + $ hg incoming --limit 3 + comparing with ssh://user@dummy/master + searching for changes + changeset: 5:ddc055582556 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: Update#1 to f1 + + changeset: 6:f66eb5ad621d + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: Update#1 to f2 + + changeset: 7:c42ecff04e99 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: Update#1 to f3 + + +Interrupting the pull is safe + $ hg --config hooks.pretxnchangegroup.bad=false pull -q + transaction abort! + rollback completed + abort: pretxnchangegroup.bad hook exited with status 1 + [255] + $ hg id + 223311e70a6f tip + +pull new changes down to the narrow clone. Should get 8 new changesets: 4 +relevant to the narrow spec, and 4 ellipsis nodes gluing them all together. + + $ hg pull + pulling from ssh://user@dummy/master + searching for changes + adding changesets + adding manifests + adding file changes + added 9 changesets with 4 changes to 2 files + new changesets *:* (glob) + (run 'hg update' to get a working copy) + $ hg log -T '{rev}: {desc}\n' + 13: Update#2 to f10 + 12: Update#2 to f8 + 11: Update#2 to f7 + 10: Update#2 to f2 + 9: Update#2 to f1 + 8: Update#1 to f8 + 7: Update#1 to f7 + 6: Update#1 to f2 + 5: Update#1 to f1 + 4: Commit f10 + 3: Commit f8 + 2: Commit f7 + 1: Commit f2 + 0: Commit f1 + $ hg update tip + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + +add a change and push it + + $ echo "update#3 2" >> f2 + $ hg commit -m "Update#3 to f2" f2 + $ hg log f2 -T '{rev}: {desc}\n' + 14: Update#3 to f2 + 10: Update#2 to f2 + 6: Update#1 to f2 + 1: Commit f2 + $ hg push + pushing to ssh://user@dummy/master + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + $ cd .. + + $ cd master + $ hg log f2 -T '{rev}: {desc}\n' + 30: Update#3 to f2 + 21: Update#2 to f2 + 11: Update#1 to f2 + 1: Commit f2 + $ hg log -l 3 -T '{rev}: {desc}\n' + 30: Update#3 to f2 + 29: Update#2 to f10 + 28: Update#2 to f9 + +Can pull into repo with a single commit + + $ cd .. + $ hg clone -q --narrow ssh://user@dummy/master narrow2 --include "f1" -r 0 + $ cd narrow2 + $ hg pull -q -r 1 + transaction abort! + rollback completed + abort: pull failed on remote + [255] + +Can use 'hg share': + $ cat >> $HGRCPATH < [extensions] + > share= + > EOF + + $ cd .. + $ hg share narrow2 narrow2-share + updating working directory + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow2-share + $ hg status + +We should also be able to unshare without breaking everything: + $ hg unshare + devel-warn: write with no wlock: "narrowspec" at: */hgext/narrow/narrowrepo.py:* (unsharenarrowspec) (glob) + $ hg verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 1 files, 1 changesets, 1 total revisions diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-rebase.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-rebase.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,93 @@ + + $ . "$TESTDIR/narrow-library.sh" + +create full repo + + $ hg init master + $ cd master + + $ mkdir inside + $ echo inside1 > inside/f1 + $ echo inside2 > inside/f2 + $ mkdir outside + $ echo outside1 > outside/f1 + $ echo outside2 > outside/f2 + $ hg ci -Aqm 'initial' + + $ echo modified > inside/f1 + $ hg ci -qm 'modify inside/f1' + + $ hg update -q 0 + $ echo modified2 > inside/f2 + $ hg ci -qm 'modify inside/f2' + + $ hg update -q 0 + $ echo modified > outside/f1 + $ hg ci -qm 'modify outside/f1' + + $ hg update -q 0 + $ echo modified2 > outside/f1 + $ hg ci -qm 'conflicting outside/f1' + + $ cd .. + + $ hg clone --narrow ssh://user@dummy/master narrow --include inside + requesting all changes + adding changesets + adding manifests + adding file changes + added 5 changesets with 4 changes to 2 files (+3 heads) + new changesets *:* (glob) + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + $ cat >> $HGRCPATH < [extensions] + > rebase= + > EOF + + $ hg update -q 0 + +Can rebase onto commit where no files outside narrow spec are involved + + $ hg update -q 0 + $ echo modified > inside/f2 + $ hg ci -qm 'modify inside/f2' + $ hg rebase -d 'desc("modify inside/f1")' + rebasing 5:c2f36d04e05d "modify inside/f2" (tip) + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-rebase.hg (glob) + +Can rebase onto conflicting changes inside narrow spec + + $ hg update -q 0 + $ echo conflicting > inside/f1 + $ hg ci -qm 'conflicting inside/f1' + $ hg rebase -d 'desc("modify inside/f1")' 2>&1 | egrep -v '(warning:|incomplete!)' + rebasing 6:cdce97fbf653 "conflicting inside/f1" (tip) + merging inside/f1 + unresolved conflicts (see hg resolve, then hg rebase --continue) + $ echo modified3 > inside/f1 + $ hg resolve -m 2>&1 | grep -v continue: + (no more unresolved files) + $ hg rebase --continue + rebasing 6:cdce97fbf653 "conflicting inside/f1" (tip) + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-rebase.hg (glob) + +Can rebase onto non-conflicting changes outside narrow spec + + $ hg update -q 0 + $ echo modified > inside/f2 + $ hg ci -qm 'modify inside/f2' + $ hg rebase -d 'desc("modify outside/f1")' + rebasing 7:c2f36d04e05d "modify inside/f2" (tip) + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-rebase.hg (glob) + +Rebase interrupts on conflicting changes outside narrow spec + + $ hg update -q 'desc("conflicting outside/f1")' + $ hg phase -f -d . + no phases changed + $ hg rebase -d 'desc("modify outside/f1")' + rebasing 4:707c035aadb6 "conflicting outside/f1" + abort: conflict in file 'outside/f1' is outside narrow clone + [255] diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-shallow-merges.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-shallow-merges.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,345 @@ + $ . "$TESTDIR/narrow-library.sh" + +create full repo + + $ hg init master + $ cd master + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + + $ mkdir inside + $ echo 1 > inside/f + $ hg commit -Aqm 'initial inside' + + $ mkdir outside + $ echo 1 > outside/f + $ hg commit -Aqm 'initial outside' + + $ echo 2a > outside/f + $ hg commit -Aqm 'outside 2a' + $ echo 3 > inside/f + $ hg commit -Aqm 'inside 3' + $ echo 4a > outside/f + $ hg commit -Aqm 'outside 4a' + $ hg update '.~3' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ echo 2b > outside/f + $ hg commit -Aqm 'outside 2b' + $ echo 3 > inside/f + $ hg commit -Aqm 'inside 3' + $ echo 4b > outside/f + $ hg commit -Aqm 'outside 4b' + $ hg update '.~3' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ echo 2c > outside/f + $ hg commit -Aqm 'outside 2c' + $ echo 3 > inside/f + $ hg commit -Aqm 'inside 3' + $ echo 4c > outside/f + $ hg commit -Aqm 'outside 4c' + $ hg update '.~3' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ echo 2d > outside/f + $ hg commit -Aqm 'outside 2d' + $ echo 3 > inside/f + $ hg commit -Aqm 'inside 3' + $ echo 4d > outside/f + $ hg commit -Aqm 'outside 4d' + + $ hg update -r 'desc("outside 4a")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge -r 'desc("outside 4b")' 2>&1 | egrep -v '(warning:|incomplete!)' + merging outside/f + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon + $ echo 5 > outside/f + $ rm outside/f.orig + $ hg resolve --mark outside/f + (no more unresolved files) + $ hg commit -m 'merge a/b 5' + $ echo 6 > outside/f + $ hg commit -Aqm 'outside 6' + + $ hg merge -r 'desc("outside 4c")' 2>&1 | egrep -v '(warning:|incomplete!)' + merging outside/f + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon + $ echo 7 > outside/f + $ rm outside/f.orig + $ hg resolve --mark outside/f + (no more unresolved files) + $ hg commit -Aqm 'merge a/b/c 7' + $ echo 8 > outside/f + $ hg commit -Aqm 'outside 8' + + $ hg merge -r 'desc("outside 4d")' 2>&1 | egrep -v '(warning:|incomplete!)' + merging outside/f + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon + $ echo 9 > outside/f + $ rm outside/f.orig + $ hg resolve --mark outside/f + (no more unresolved files) + $ hg commit -Aqm 'merge a/b/c/d 9' + $ echo 10 > outside/f + $ hg commit -Aqm 'outside 10' + + $ echo 11 > inside/f + $ hg commit -Aqm 'inside 11' + $ echo 12 > outside/f + $ hg commit -Aqm 'outside 12' + + $ hg log -G -T '{rev} {node|short} {desc}\n' + @ 21 8d874d57adea outside 12 + | + o 20 7ef88b4dd4fa inside 11 + | + o 19 2a20009de83e outside 10 + | + o 18 3ac1f5779de3 merge a/b/c/d 9 + |\ + | o 17 38a9c2f7e546 outside 8 + | | + | o 16 094aa62fc898 merge a/b/c 7 + | |\ + | | o 15 f29d083d32e4 outside 6 + | | | + | | o 14 2dc11382541d merge a/b 5 + | | |\ + o | | | 13 27d07ef97221 outside 4d + | | | | + o | | | 12 465567bdfb2d inside 3 + | | | | + o | | | 11 d1c61993ec83 outside 2d + | | | | + | o | | 10 56859a8e33b9 outside 4c + | | | | + | o | | 9 bb96a08b062a inside 3 + | | | | + | o | | 8 b844052e7b3b outside 2c + |/ / / + | | o 7 9db2d8fcc2a6 outside 4b + | | | + | | o 6 6418167787a6 inside 3 + | | | + +---o 5 77344f344d83 outside 2b + | | + | o 4 9cadde08dc9f outside 4a + | | + | o 3 019ef06f125b inside 3 + | | + | o 2 75e40c075a19 outside 2a + |/ + o 1 906d6c682641 initial outside + | + o 0 9f8e82b51004 initial inside + + +Now narrow and shallow clone this and get a hopefully correct graph + + $ cd .. + $ hg clone --narrow ssh://user@dummy/master narrow --include inside --depth 7 + requesting all changes + adding changesets + adding manifests + adding file changes + added 8 changesets with 3 changes to 1 files + new changesets *:* (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + +To make updating the tests easier, we print the emitted nodes +sorted. This makes it easier to identify when the same node structure +has been emitted, just in a different order. + + $ hg log -G -T '{rev} {node|short}{if(ellipsis,"...")} {desc}\n' + @ 7 8d874d57adea... outside 12 + | + o 6 7ef88b4dd4fa inside 11 + | + o 5 2a20009de83e... outside 10 + | + o 4 3ac1f5779de3... merge a/b/c/d 9 + |\ + | o 3 465567bdfb2d inside 3 + | | + | o 2 d1c61993ec83... outside 2d + | + o 1 bb96a08b062a inside 3 + | + o 0 b844052e7b3b... outside 2c + + + $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort + ...2a20009de83e 000000000000 3ac1f5779de3 outside 10 + ...3ac1f5779de3 bb96a08b062a 465567bdfb2d merge a/b/c/d 9 + ...8d874d57adea 7ef88b4dd4fa 000000000000 outside 12 + ...b844052e7b3b 000000000000 000000000000 outside 2c + ...d1c61993ec83 000000000000 000000000000 outside 2d + 465567bdfb2d d1c61993ec83 000000000000 inside 3 + 7ef88b4dd4fa 2a20009de83e 000000000000 inside 11 + bb96a08b062a b844052e7b3b 000000000000 inside 3 + + $ cd .. + +Incremental test case: show a pull can pull in a conflicted merge even if elided + + $ hg init pullmaster + $ cd pullmaster + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + $ mkdir inside outside + $ echo v1 > inside/f + $ echo v1 > outside/f + $ hg add inside/f outside/f + $ hg commit -m init + + $ for line in a b c d + > do + > hg update -r 0 + > echo v2$line > outside/f + > hg commit -m "outside 2$line" + > echo v2$line > inside/f + > hg commit -m "inside 2$line" + > echo v3$line > outside/f + > hg commit -m "outside 3$line" + > echo v4$line > outside/f + > hg commit -m "outside 4$line" + > done + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + created new head + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + created new head + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + created new head + + $ cd .. + $ hg clone --narrow ssh://user@dummy/pullmaster pullshallow \ + > --include inside --depth 3 + requesting all changes + adding changesets + adding manifests + adding file changes + added 12 changesets with 5 changes to 1 files (+3 heads) + new changesets *:* (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd pullshallow + + $ hg log -G -T '{rev} {node|short}{if(ellipsis,"...")} {desc}\n' + @ 11 0ebbd712a0c8... outside 4d + | + o 10 0d4c867aeb23 inside 2d + | + o 9 e932969c3961... outside 2d + + o 8 33d530345455... outside 4c + | + o 7 0ce6481bfe07 inside 2c + | + o 6 caa65c940632... outside 2c + + o 5 3df233defecc... outside 4b + | + o 4 7162cc6d11a4 inside 2b + | + o 3 f2a632f0082d... outside 2b + + o 2 b8a3da16ba49... outside 4a + | + o 1 53f543eb8e45 inside 2a + | + o 0 1be3e5221c6a... outside 2a + + $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort + ...0ebbd712a0c8 0d4c867aeb23 000000000000 outside 4d + ...1be3e5221c6a 000000000000 000000000000 outside 2a + ...33d530345455 0ce6481bfe07 000000000000 outside 4c + ...3df233defecc 7162cc6d11a4 000000000000 outside 4b + ...b8a3da16ba49 53f543eb8e45 000000000000 outside 4a + ...caa65c940632 000000000000 000000000000 outside 2c + ...e932969c3961 000000000000 000000000000 outside 2d + ...f2a632f0082d 000000000000 000000000000 outside 2b + 0ce6481bfe07 caa65c940632 000000000000 inside 2c + 0d4c867aeb23 e932969c3961 000000000000 inside 2d + 53f543eb8e45 1be3e5221c6a 000000000000 inside 2a + 7162cc6d11a4 f2a632f0082d 000000000000 inside 2b + + $ cd ../pullmaster + $ hg update -r 'desc("outside 4a")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge -r 'desc("outside 4b")' 2>&1 | egrep -v '(warning:|incomplete!)' + merging inside/f + merging outside/f + 0 files updated, 0 files merged, 0 files removed, 2 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon + $ echo 3 > inside/f + $ echo 5 > outside/f + $ rm -f {in,out}side/f.orig + $ hg resolve --mark inside/f outside/f + (no more unresolved files) + $ hg commit -m 'merge a/b 5' + + $ hg update -r 'desc("outside 4c")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge -r 'desc("outside 4d")' 2>&1 | egrep -v '(warning:|incomplete!)' + merging inside/f + merging outside/f + 0 files updated, 0 files merged, 0 files removed, 2 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon + $ echo 3 > inside/f + $ echo 5 > outside/f + $ rm -f {in,out}side/f.orig + $ hg resolve --mark inside/f outside/f + (no more unresolved files) + $ hg commit -m 'merge c/d 5' + + $ hg update -r 'desc("merge a/b 5")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge -r 'desc("merge c/d 5")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ echo 6 > outside/f + $ hg commit -m 'outside 6' + $ echo 7 > outside/f + $ hg commit -m 'outside 7' + $ echo 8 > outside/f + $ hg commit -m 'outside 8' + + $ cd ../pullshallow + $ hg pull --depth 3 + pulling from ssh://user@dummy/pullmaster + searching for changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 3 changes to 1 files (-3 heads) + new changesets *:* (glob) + (run 'hg update' to get a working copy) + + $ hg log -T '{if(ellipsis,"...")}{node|short} {p1node|short} {p2node|short} {desc}\n' | sort + ...0ebbd712a0c8 0d4c867aeb23 000000000000 outside 4d + ...1be3e5221c6a 000000000000 000000000000 outside 2a + ...33d530345455 0ce6481bfe07 000000000000 outside 4c + ...3df233defecc 7162cc6d11a4 000000000000 outside 4b + ...b8a3da16ba49 53f543eb8e45 000000000000 outside 4a + ...bf545653453e 968003d40c60 000000000000 outside 8 + ...caa65c940632 000000000000 000000000000 outside 2c + ...e932969c3961 000000000000 000000000000 outside 2d + ...f2a632f0082d 000000000000 000000000000 outside 2b + 0ce6481bfe07 caa65c940632 000000000000 inside 2c + 0d4c867aeb23 e932969c3961 000000000000 inside 2d + 53f543eb8e45 1be3e5221c6a 000000000000 inside 2a + 67d49c0bdbda b8a3da16ba49 3df233defecc merge a/b 5 + 7162cc6d11a4 f2a632f0082d 000000000000 inside 2b + 968003d40c60 67d49c0bdbda e867021d52c2 outside 6 + e867021d52c2 33d530345455 0ebbd712a0c8 merge c/d 5 diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-shallow.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-shallow.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,122 @@ + $ . "$TESTDIR/narrow-library.sh" + + $ hg init master + $ cd master + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + $ for x in `$TESTDIR/seq.py 10` + > do + > echo $x > "f$x" + > hg add "f$x" + > done + $ hg commit -m "Add root files" + $ mkdir d1 d2 + $ for x in `$TESTDIR/seq.py 10` + > do + > echo d1/$x > "d1/f$x" + > hg add "d1/f$x" + > echo d2/$x > "d2/f$x" + > hg add "d2/f$x" + > done + $ hg commit -m "Add d1 and d2" + $ for x in `$TESTDIR/seq.py 10` + > do + > echo f$x rev2 > "f$x" + > echo d1/f$x rev2 > "d1/f$x" + > echo d2/f$x rev2 > "d2/f$x" + > hg commit -m "Commit rev2 of f$x, d1/f$x, d2/f$x" + > done + $ cd .. + +narrow and shallow clone the d2 directory + + $ hg clone --narrow ssh://user@dummy/master shallow --include "d2" --depth 2 + requesting all changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 13 changes to 10 files + new changesets *:* (glob) + updating to branch default + 10 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd shallow + $ hg log -T '{rev}{if(ellipsis,"...")}: {desc}\n' + 3: Commit rev2 of f10, d1/f10, d2/f10 + 2: Commit rev2 of f9, d1/f9, d2/f9 + 1: Commit rev2 of f8, d1/f8, d2/f8 + 0...: Commit rev2 of f7, d1/f7, d2/f7 + $ hg update 0 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cat d2/f7 d2/f8 + d2/f7 rev2 + d2/8 + + $ cd .. + +change every upstream file once + + $ cd master + $ for x in `$TESTDIR/seq.py 10` + > do + > echo f$x rev3 > "f$x" + > echo d1/f$x rev3 > "d1/f$x" + > echo d2/f$x rev3 > "d2/f$x" + > hg commit -m "Commit rev3 of f$x, d1/f$x, d2/f$x" + > done + $ cd .. + +pull new changes with --depth specified. There were 10 changes to the d2 +directory but the shallow pull should only fetch 3. + + $ cd shallow + $ hg pull --depth 2 + pulling from ssh://user@dummy/master + searching for changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 10 changes to 10 files + new changesets *:* (glob) + (run 'hg update' to get a working copy) + $ hg log -T '{rev}{if(ellipsis,"...")}: {desc}\n' + 7: Commit rev3 of f10, d1/f10, d2/f10 + 6: Commit rev3 of f9, d1/f9, d2/f9 + 5: Commit rev3 of f8, d1/f8, d2/f8 + 4...: Commit rev3 of f7, d1/f7, d2/f7 + 3: Commit rev2 of f10, d1/f10, d2/f10 + 2: Commit rev2 of f9, d1/f9, d2/f9 + 1: Commit rev2 of f8, d1/f8, d2/f8 + 0...: Commit rev2 of f7, d1/f7, d2/f7 + $ hg update 4 + merging d2/f1 + merging d2/f2 + merging d2/f3 + merging d2/f4 + merging d2/f5 + merging d2/f6 + merging d2/f7 + 3 files updated, 7 files merged, 0 files removed, 0 files unresolved + $ cat d2/f7 d2/f8 + d2/f7 rev3 + d2/f8 rev2 + $ hg update 7 + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cat d2/f10 + d2/f10 rev3 + + $ cd .. + +cannot clone with zero or negative depth + + $ hg clone --narrow ssh://user@dummy/master bad --include "d2" --depth 0 + requesting all changes + remote: abort: depth must be positive, got 0 + abort: pull failed on remote + [255] + $ hg clone --narrow ssh://user@dummy/master bad --include "d2" --depth -1 + requesting all changes + remote: abort: depth must be positive, got -1 + abort: pull failed on remote + [255] diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-strip.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-strip.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,163 @@ +#testcases flat tree + + $ . "$TESTDIR/narrow-library.sh" + +#if tree + $ cat << EOF >> $HGRCPATH + > [experimental] + > treemanifest = 1 + > EOF +#endif + +create full repo + + $ hg init master + $ cd master + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + + $ mkdir inside + $ echo inside > inside/f1 + $ mkdir outside + $ echo outside > outside/f1 + $ hg ci -Aqm 'initial' + + $ echo modified > inside/f1 + $ hg ci -qm 'modify inside' + + $ hg co -q 0 + $ echo modified > outside/f1 + $ hg ci -qm 'modify outside' + + $ echo modified again >> outside/f1 + $ hg ci -qm 'modify outside again' + + $ cd .. + + $ hg clone --narrow ssh://user@dummy/master narrow --include inside + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 2 changes to 1 files (+1 heads) + new changesets *:* (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + $ cat >> $HGRCPATH < [extensions] + > strip= + > EOF + +Can strip and recover changesets affecting only files within narrow spec + + $ hg co -r 'desc("modify inside")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ rm -f $TESTTMP/narrow/.hg/strip-backup/*-backup.hg + $ hg strip . + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-backup.hg (glob) + $ hg unbundle .hg/strip-backup/*-backup.hg + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + new changesets * (glob) + (run 'hg heads' to see heads, 'hg merge' to merge) + +Can strip and recover changesets affecting files outside of narrow spec + + $ hg co -r 'desc("modify outside")' + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -G -T '{rev} {desc}\n' + o 2 modify inside + | + | @ 1 modify outside again + |/ + o 0 initial + + $ hg debugdata -m 1 + inside/f1\x004d6a634d5ba06331a60c29ee0db8412490a54fcd (esc) (flat !) + outside/f1\x0084ba604d54dee1f13310ce3d4ac2e8a36636691a (esc) (flat !) + inside\x006a8bc41df94075d501f9740587a0c0e13c170dc5t (esc) (tree !) + outside\x00255c2627ebdd3c7dcaa6945246f9b9f02bd45a09t (esc) (tree !) + + $ rm -f $TESTTMP/narrow/.hg/strip-backup/*-backup.hg + $ hg strip . + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-backup.hg (glob) + $ hg unbundle .hg/strip-backup/*-backup.hg + adding changesets + adding manifests + adding file changes + added 1 changesets with 0 changes to 0 files (+1 heads) + new changesets * (glob) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg log -G -T '{rev} {desc}\n' + o 2 modify outside again + | + | o 1 modify inside + |/ + @ 0 initial + +Check that hash of file outside narrow spec got restored + $ hg debugdata -m 2 + inside/f1\x004d6a634d5ba06331a60c29ee0db8412490a54fcd (esc) (flat !) + outside/f1\x0084ba604d54dee1f13310ce3d4ac2e8a36636691a (esc) (flat !) + inside\x006a8bc41df94075d501f9740587a0c0e13c170dc5t (esc) (tree !) + outside\x00255c2627ebdd3c7dcaa6945246f9b9f02bd45a09t (esc) (tree !) + +Also verify we can apply the bundle with 'hg pull': + $ hg co -r 'desc("modify inside")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ rm .hg/strip-backup/*-backup.hg + $ hg strip . + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-backup.hg (glob) + $ hg pull .hg/strip-backup/*-backup.hg + pulling from .hg/strip-backup/*-backup.hg (glob) + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + new changesets * (glob) + (run 'hg heads' to see heads, 'hg merge' to merge) + + $ rm .hg/strip-backup/*-backup.hg + $ hg strip 0 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-backup.hg (glob) + $ hg incoming .hg/strip-backup/*-backup.hg + comparing with .hg/strip-backup/*-backup.hg (glob) + changeset: 0:* (glob) + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: initial + + changeset: 1:9e48d953700d (flat !) + changeset: 1:3888164bccf0 (tree !) + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: modify outside again + + changeset: 2:f505d5e96aa8 (flat !) + changeset: 2:40b66f95a209 (tree !) + tag: tip + parent: 0:a99f4d53924d (flat !) + parent: 0:c2a5fabcca3c (tree !) + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: modify inside + + $ hg pull .hg/strip-backup/*-backup.hg + pulling from .hg/strip-backup/*-backup.hg (glob) + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 2 changes to 1 files (+1 heads) + new changesets *:* (glob) + (run 'hg heads' to see heads, 'hg merge' to merge) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-update.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-update.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,76 @@ + + $ . "$TESTDIR/narrow-library.sh" + +create full repo + + $ hg init master + $ cd master + $ echo init > init + $ hg ci -Aqm 'initial' + + $ mkdir inside + $ echo inside > inside/f1 + $ mkdir outside + $ echo outside > outside/f1 + $ hg ci -Aqm 'add inside and outside' + + $ echo modified > inside/f1 + $ hg ci -qm 'modify inside' + + $ echo modified > outside/f1 + $ hg ci -qm 'modify outside' + + $ cd .. + + $ hg clone --narrow ssh://user@dummy/master narrow --include inside + requesting all changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 2 changes to 1 files + new changesets *:* (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + $ hg debugindex -c + rev offset length base linkrev nodeid p1 p2 + 0 0 64 0 0 9958b1af2add 000000000000 000000000000 + 1 64 81 1 1 2db4ce2a3bfe 9958b1af2add 000000000000 + 2 145 75 2 2 0980ee31a742 2db4ce2a3bfe 000000000000 + 3 220 (76|77) 3 3 4410145019b7 0980ee31a742 000000000000 (re) + + $ hg update -q 0 + +Can update to revision with changes inside + + $ hg update -q 'desc("add inside and outside")' + $ hg update -q 'desc("modify inside")' + $ find * + inside + inside/f1 + $ cat inside/f1 + modified + +Can update to revision with changes outside + + $ hg update -q 'desc("modify outside")' + $ find * + inside + inside/f1 + $ cat inside/f1 + modified + +Can update with a deleted file inside + + $ hg rm inside/f1 + $ hg update -q 'desc("modify inside")' + $ hg update -q 'desc("modify outside")' + $ hg update -q 'desc("initial")' + $ hg update -q 'desc("modify inside")' + +Can update with a moved file inside + + $ hg mv inside/f1 inside/f2 + $ hg update -q 'desc("modify outside")' + $ hg update -q 'desc("initial")' + $ hg update -q 'desc("modify inside")' diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow-widen.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow-widen.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,365 @@ +#testcases flat tree + $ . "$TESTDIR/narrow-library.sh" + +#if tree + $ cat << EOF >> $HGRCPATH + > [experimental] + > treemanifest = 1 + > EOF +#endif + + $ hg init master + $ cd master + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + + $ mkdir inside + $ echo 'inside' > inside/f + $ hg add inside/f + $ hg commit -m 'add inside' + + $ mkdir widest + $ echo 'widest' > widest/f + $ hg add widest/f + $ hg commit -m 'add widest' + + $ mkdir outside + $ echo 'outside' > outside/f + $ hg add outside/f + $ hg commit -m 'add outside' + + $ cd .. + +narrow clone the inside file + + $ hg clone --narrow ssh://user@dummy/master narrow --include inside + requesting all changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 1 changes to 1 files + new changesets *:* (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + $ hg tracked + I path:inside + $ ls + inside + $ cat inside/f + inside + $ cd .. + +add more upstream files which we will include in a wider narrow spec + + $ cd master + + $ mkdir wider + $ echo 'wider' > wider/f + $ hg add wider/f + $ echo 'widest v2' > widest/f + $ hg commit -m 'add wider, update widest' + + $ echo 'widest v3' > widest/f + $ hg commit -m 'update widest v3' + + $ echo 'inside v2' > inside/f + $ hg commit -m 'update inside' + + $ mkdir outside2 + $ echo 'outside2' > outside2/f + $ hg add outside2/f + $ hg commit -m 'add outside2' + + $ echo 'widest v4' > widest/f + $ hg commit -m 'update widest v4' + + $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n" + *: update widest v4 (glob) + *: add outside2 (glob) + *: update inside (glob) + *: update widest v3 (glob) + *: add wider, update widest (glob) + *: add outside (glob) + *: add widest (glob) + *: add inside (glob) + + $ cd .. + +Widen the narrow spec to see the wider file. This should not get the newly +added upstream revisions. + + $ cd narrow + $ hg tracked --addinclude wider/f + comparing with ssh://user@dummy/master + searching for changes + no changes found + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob) + adding changesets + adding manifests + adding file changes + added 2 changesets with 1 changes to 1 files + new changesets *:* (glob) + $ hg tracked + I path:inside + I path:wider/f + +Pull down the newly added upstream revision. + + $ hg pull + pulling from ssh://user@dummy/master + searching for changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 2 changes to 2 files + new changesets *:* (glob) + (run 'hg update' to get a working copy) + $ hg update -r 'desc("add wider")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cat wider/f + wider + + $ hg update -r 'desc("update inside")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cat wider/f + wider + $ cat inside/f + inside v2 + + $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n" + ...*: update widest v4 (glob) + *: update inside (glob) + ...*: update widest v3 (glob) + *: add wider, update widest (glob) + ...*: add outside (glob) + *: add inside (glob) + +Check that widening with a newline fails + + $ hg tracked --addinclude 'widest + > ' + abort: newlines are not allowed in narrowspec paths + [255] + +widen the narrow spec to include the widest file + + $ hg tracked --addinclude widest + comparing with ssh://user@dummy/master + searching for changes + no changes found + saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob) + adding changesets + adding manifests + adding file changes + added 8 changesets with 7 changes to 3 files + new changesets *:* (glob) + $ hg tracked + I path:inside + I path:wider/f + I path:widest + $ hg update 'desc("add widest")' + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ cat widest/f + widest + $ hg update 'desc("add wider, update widest")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cat wider/f + wider + $ cat widest/f + widest v2 + $ hg update 'desc("update widest v3")' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cat widest/f + widest v3 + $ hg update 'desc("update widest v4")' + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cat widest/f + widest v4 + + $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n" + *: update widest v4 (glob) + ...*: add outside2 (glob) + *: update inside (glob) + *: update widest v3 (glob) + *: add wider, update widest (glob) + ...*: add outside (glob) + *: add widest (glob) + *: add inside (glob) + +separate suite of tests: files from 0-10 modified in changes 0-10. This allows +more obvious precise tests tickling particular corner cases. + + $ cd .. + $ hg init upstream + $ cd upstream + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + $ for x in `$TESTDIR/seq.py 0 10` + > do + > mkdir d$x + > echo $x > d$x/f + > hg add d$x/f + > hg commit -m "add d$x/f" + > done + $ hg log -T "{node|short}: {desc}\n" + *: add d10/f (glob) + *: add d9/f (glob) + *: add d8/f (glob) + *: add d7/f (glob) + *: add d6/f (glob) + *: add d5/f (glob) + *: add d4/f (glob) + *: add d3/f (glob) + *: add d2/f (glob) + *: add d1/f (glob) + *: add d0/f (glob) + +make narrow clone with every third node. + + $ cd .. + $ hg clone --narrow ssh://user@dummy/upstream narrow2 --include d0 --include d3 --include d6 --include d9 + requesting all changes + adding changesets + adding manifests + adding file changes + added 8 changesets with 4 changes to 4 files + new changesets *:* (glob) + updating to branch default + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow2 + $ hg tracked + I path:d0 + I path:d3 + I path:d6 + I path:d9 + $ hg verify + checking changesets + checking manifests + checking directory manifests (tree !) + crosschecking files in changesets and manifests + checking files + 4 files, 8 changesets, 4 total revisions + $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n" + ...*: add d10/f (glob) + *: add d9/f (glob) + ...*: add d8/f (glob) + *: add d6/f (glob) + ...*: add d5/f (glob) + *: add d3/f (glob) + ...*: add d2/f (glob) + *: add d0/f (glob) + $ hg tracked --addinclude d1 + comparing with ssh://user@dummy/upstream + searching for changes + no changes found + saved backup bundle to $TESTTMP/narrow2/.hg/strip-backup/*-widen.hg (glob) + adding changesets + adding manifests + adding file changes + added 9 changesets with 5 changes to 5 files + new changesets *:* (glob) + $ hg tracked + I path:d0 + I path:d1 + I path:d3 + I path:d6 + I path:d9 + $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n" + ...*: add d10/f (glob) + *: add d9/f (glob) + ...*: add d8/f (glob) + *: add d6/f (glob) + ...*: add d5/f (glob) + *: add d3/f (glob) + ...*: add d2/f (glob) + *: add d1/f (glob) + *: add d0/f (glob) + +Verify shouldn't claim the repo is corrupt after a widen. + + $ hg verify + checking changesets + checking manifests + checking directory manifests (tree !) + crosschecking files in changesets and manifests + checking files + 5 files, 9 changesets, 5 total revisions + +Widening preserves parent of local commit + + $ cd .. + $ hg clone -q --narrow ssh://user@dummy/upstream narrow3 --include d2 -r 2 + $ cd narrow3 + $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n" + *: add d2/f (glob) + ...*: add d1/f (glob) + $ hg pull -q -r 3 + $ hg co -q tip + $ hg pull -q -r 4 + $ echo local > d2/f + $ hg ci -m local + created new head + $ hg tracked -q --addinclude d0 --addinclude d9 + +Widening preserves bookmarks + + $ cd .. + $ hg clone -q --narrow ssh://user@dummy/upstream narrow-bookmarks --include d4 + $ cd narrow-bookmarks + $ echo local > d4/f + $ hg ci -m local + $ hg bookmarks bookmark + $ hg bookmarks + * bookmark 3:* (glob) + $ hg -q tracked --addinclude d2 + $ hg bookmarks + * bookmark 5:* (glob) + $ hg log -r bookmark -T '{desc}\n' + local + +Widening that fails can be recovered from + + $ cd .. + $ hg clone -q --narrow ssh://user@dummy/upstream interrupted --include d0 + $ cd interrupted + $ echo local > d0/f + $ hg ci -m local + $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n" + 2: local + ...1: add d10/f + 0: add d0/f + $ hg bookmarks bookmark + $ hg --config hooks.pretxnchangegroup.bad=false tracked --addinclude d1 + comparing with ssh://user@dummy/upstream + searching for changes + no changes found + saved backup bundle to $TESTTMP/interrupted/.hg/strip-backup/*-widen.hg (glob) + adding changesets + adding manifests + adding file changes + added 3 changesets with 2 changes to 2 files + transaction abort! + rollback completed + abort: pretxnchangegroup.bad hook exited with status 1 + [255] + $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n" + $ hg bookmarks + no bookmarks set + $ hg unbundle .hg/strip-backup/*-widen.hg + adding changesets + adding manifests + adding file changes + added 3 changesets with 2 changes to 1 files + new changesets *:* (glob) + (run 'hg update' to get a working copy) + $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n" + 2: local + ...1: add d10/f + 0: add d0/f + $ hg bookmarks + * bookmark 2:* (glob) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-narrow.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-narrow.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,374 @@ +#testcases flat tree + + $ . "$TESTDIR/narrow-library.sh" + +#if tree + $ cat << EOF >> $HGRCPATH + > [experimental] + > treemanifest = 1 + > EOF +#endif + + $ hg init master + $ cd master + $ cat >> .hg/hgrc < [narrow] + > serveellipses=True + > EOF + $ for x in `$TESTDIR/seq.py 0 10` + > do + > mkdir d$x + > echo $x > d$x/f + > hg add d$x/f + > hg commit -m "add d$x/f" + > done + $ hg log -T "{node|short}: {desc}\n" + *: add d10/f (glob) + *: add d9/f (glob) + *: add d8/f (glob) + *: add d7/f (glob) + *: add d6/f (glob) + *: add d5/f (glob) + *: add d4/f (glob) + *: add d3/f (glob) + *: add d2/f (glob) + *: add d1/f (glob) + *: add d0/f (glob) + $ cd .. + +Error if '.' or '..' are in the directory to track. + $ hg clone --narrow ssh://user@dummy/master foo --include ./asdf + requesting all changes + abort: "." and ".." are not allowed in narrowspec paths + [255] + $ hg clone --narrow ssh://user@dummy/master foo --include asdf/.. + requesting all changes + abort: "." and ".." are not allowed in narrowspec paths + [255] + $ hg clone --narrow ssh://user@dummy/master foo --include a/./c + requesting all changes + abort: "." and ".." are not allowed in narrowspec paths + [255] + +Names with '.' in them are OK. + $ hg clone --narrow ssh://user@dummy/master should-work --include a/.b/c + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 0 changes to 0 files + new changesets * (glob) + updating to branch default + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + +Test repo with local changes + $ hg clone --narrow ssh://user@dummy/master narrow-local-changes --include d0 --include d3 --include d6 + requesting all changes + adding changesets + adding manifests + adding file changes + added 6 changesets with 3 changes to 3 files + new changesets *:* (glob) + updating to branch default + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow-local-changes + $ cat >> $HGRCPATH << EOF + > [experimental] + > evolution=createmarkers + > EOF + $ echo local change >> d0/f + $ hg ci -m 'local change to d0' + $ hg co '.^' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo local change >> d3/f + $ hg ci -m 'local hidden change to d3' + created new head + $ hg ci --amend -m 'local change to d3' + $ hg tracked --removeinclude d0 + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + The following changeset(s) or their ancestors have local changes not on the remote: + * (glob) + abort: local changes found + (use --force-delete-local-changes to ignore) + [255] +Check that nothing was removed by the failed attempts + $ hg tracked + I path:d0 + I path:d3 + I path:d6 + $ hg files + d0/f + d3/f + d6/f + $ find * + d0 + d0/f + d3 + d3/f + d6 + d6/f + $ hg verify -q +Force deletion of local changes + $ hg log -T "{node|short}: {desc} {outsidenarrow}\n" + *: local change to d3 (glob) + *: local change to d0 (glob) + *: add d10/f outsidenarrow (glob) + *: add d6/f (glob) + *: add d5/f outsidenarrow (glob) + *: add d3/f (glob) + *: add d2/f outsidenarrow (glob) + *: add d0/f (glob) + $ hg tracked --removeinclude d0 --force-delete-local-changes + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + The following changeset(s) or their ancestors have local changes not on the remote: + * (glob) + saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob) + deleting data/d0/f.i + deleting meta/d0/00manifest.i (tree !) + $ hg log -T "{node|short}: {desc} {outsidenarrow}\n" + *: local change to d3 (glob) + *: add d10/f outsidenarrow (glob) + *: add d6/f (glob) + *: add d5/f outsidenarrow (glob) + *: add d3/f (glob) + *: add d2/f outsidenarrow (glob) + *: add d0/f outsidenarrow (glob) +Can restore stripped local changes after widening + $ hg tracked --addinclude d0 -q + $ hg unbundle .hg/strip-backup/*-narrow.hg -q + $ hg --hidden co -r 'desc("local change to d0")' -q + $ cat d0/f + 0 + local change +Pruned commits affecting removed paths should not prevent narrowing + $ hg co '.^' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg debugobsolete `hg log -T '{node}' -r 'desc("local change to d0")'` + obsoleted 1 changesets + $ hg tracked --removeinclude d0 + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob) + deleting data/d0/f.i + deleting meta/d0/00manifest.i (tree !) +Updates off of stripped commit if necessary + $ hg co -r 'desc("local change to d3")' -q + $ echo local change >> d6/f + $ hg ci -m 'local change to d6' + $ hg tracked --removeinclude d3 --force-delete-local-changes + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + The following changeset(s) or their ancestors have local changes not on the remote: + * (glob) + * (glob) + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob) + deleting data/d3/f.i + deleting meta/d3/00manifest.i (tree !) + $ hg log -T '{desc}\n' -r . + add d10/f +Updates to nullid if necessary + $ hg tracked --addinclude d3 -q + $ hg co null -q + $ mkdir d3 + $ echo local change > d3/f + $ hg add d3/f + $ hg ci -m 'local change to d3' + created new head + $ hg tracked --removeinclude d3 --force-delete-local-changes + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + The following changeset(s) or their ancestors have local changes not on the remote: + * (glob) + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + saved backup bundle to $TESTTMP/narrow-local-changes/.hg/strip-backup/*-narrow.hg (glob) + deleting data/d3/f.i + deleting meta/d3/00manifest.i (tree !) + $ hg id + 000000000000 + $ cd .. + +Can remove last include, making repo empty + $ hg clone --narrow ssh://user@dummy/master narrow-empty --include d0 -r 5 + adding changesets + adding manifests + adding file changes + added 2 changesets with 1 changes to 1 files + new changesets *:* (glob) + updating to branch default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow-empty + $ hg tracked --removeinclude d0 + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + deleting data/d0/f.i + deleting meta/d0/00manifest.i (tree !) + $ hg tracked + $ hg files + [1] + $ test -d d0 + [1] +Do some work in the empty clone + $ hg diff --change . + $ hg branch foo + marked working directory as branch foo + (branches are permanent and global, did you want a bookmark?) + $ hg ci -m empty + $ hg pull -q +Can widen the empty clone + $ hg tracked --addinclude d0 + comparing with ssh://user@dummy/master + searching for changes + no changes found + saved backup bundle to $TESTTMP/narrow-empty/.hg/strip-backup/*-widen.hg (glob) + adding changesets + adding manifests + adding file changes + added 3 changesets with 1 changes to 1 files + new changesets *:* (glob) + $ hg tracked + I path:d0 + $ hg files + d0/f + $ find * + d0 + d0/f + $ cd .. + +TODO(martinvonz): test including e.g. d3/g and then removing it once +https://bitbucket.org/Google/narrowhg/issues/6 is fixed + + $ hg clone --narrow ssh://user@dummy/master narrow --include d0 --include d3 --include d6 --include d9 + requesting all changes + adding changesets + adding manifests + adding file changes + added 8 changesets with 4 changes to 4 files + new changesets *:* (glob) + updating to branch default + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd narrow + $ hg tracked + I path:d0 + I path:d3 + I path:d6 + I path:d9 + $ hg tracked --removeinclude d6 + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + deleting data/d6/f.i + deleting meta/d6/00manifest.i (tree !) + $ hg tracked + I path:d0 + I path:d3 + I path:d9 + $ hg debugrebuildfncache + fncache already up to date + $ find * + d0 + d0/f + d3 + d3/f + d9 + d9/f + $ hg verify -q + $ hg tracked --addexclude d3/f + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + deleting data/d3/f.i + $ hg tracked + I path:d0 + I path:d3 + I path:d9 + X path:d3/f + $ hg debugrebuildfncache + fncache already up to date + $ find * + d0 + d0/f + d9 + d9/f + $ hg verify -q + $ hg tracked --addexclude d0 + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + deleting data/d0/f.i + deleting meta/d0/00manifest.i (tree !) + $ hg tracked + I path:d3 + I path:d9 + X path:d0 + X path:d3/f + $ hg debugrebuildfncache + fncache already up to date + $ find * + d9 + d9/f + +Make a 15 of changes to d9 to test the path without --verbose +(Note: using regexes instead of "* (glob)" because if the test fails, it +produces more sensible diffs) + $ hg tracked + I path:d3 + I path:d9 + X path:d0 + X path:d3/f + $ for x in `$TESTDIR/seq.py 1 15` + > do + > echo local change >> d9/f + > hg commit -m "change $x to d9/f" + > done + $ hg tracked --removeinclude d9 + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + The following changeset(s) or their ancestors have local changes not on the remote: + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ...and 5 more, use --verbose to list all + abort: local changes found + (use --force-delete-local-changes to ignore) + [255] +Now test it *with* verbose. + $ hg tracked --removeinclude d9 --verbose + comparing with ssh://user@dummy/master + searching for changes + looking for local changes to affected paths + The following changeset(s) or their ancestors have local changes not on the remote: + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + ^[0-9a-f]{12}$ (re) + abort: local changes found + (use --force-delete-local-changes to ignore) + [255] diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-notify.t --- a/tests/test-notify.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-notify.t Sat Feb 24 17:49:10 2018 -0600 @@ -421,7 +421,7 @@ > test = False > mbox = mbox > EOF - $ $PYTHON -c 'file("a/a", "ab").write("no" * 500 + "\xd1\x84" + "\n")' + $ $PYTHON -c 'open("a/a", "ab").write("no" * 500 + "\xd1\x84" + "\n")' $ hg --cwd a commit -A -m "long line" $ hg --traceback --cwd b pull ../a pulling from ../a diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-obsolete-divergent.t --- a/tests/test-obsolete-divergent.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-obsolete-divergent.t Sat Feb 24 17:49:10 2018 -0600 @@ -621,6 +621,34 @@ a139f71be9da $ hg log -r 'contentdivergent()' +#if serve + + $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid --config web.view=all \ + > -A access.log -E errors.log + $ cat hg.pid >> $DAEMON_PIDS + +check an obsolete changeset that was rewritten and also split + + $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=paper' | egrep 'rewritten|split' + rewritten as bed64f5d2f5a by test Thu, 01 Jan 1970 00:00:00 +0000
+ split as 7ae126973a96 14608b260df8 by test Thu, 01 Jan 1970 00:00:00 +0000 + $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=coal' | egrep 'rewritten|split' + rewritten as bed64f5d2f5a by test Thu, 01 Jan 1970 00:00:00 +0000
+ split as 7ae126973a96 14608b260df8 by test Thu, 01 Jan 1970 00:00:00 +0000 + $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=gitweb' | egrep 'rewritten|split' + rewritten as bed64f5d2f5a by test Thu, 01 Jan 1970 00:00:00 +0000 + split as 7ae126973a96 14608b260df8 by test Thu, 01 Jan 1970 00:00:00 +0000 + $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=monoblue' | egrep 'rewritten|split' +
rewritten as bed64f5d2f5a by test Thu, 01 Jan 1970 00:00:00 +0000
+
split as 7ae126973a96 14608b260df8 by test Thu, 01 Jan 1970 00:00:00 +0000
+ $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=spartan' | egrep 'rewritten|split' + rewritten as bed64f5d2f5a by test Thu, 01 Jan 1970 00:00:00 +0000 + split as 7ae126973a96 14608b260df8 by test Thu, 01 Jan 1970 00:00:00 +0000 + + $ killdaemons.py + +#endif + $ cd .. diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-obsolete.t --- a/tests/test-obsolete.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-obsolete.t Sat Feb 24 17:49:10 2018 -0600 @@ -1049,20 +1049,8 @@ $ get-with-headers.py localhost:$HGPORT 'log?rev=first(obsolete())&style=monoblue' | grep '' draft obsolete $ get-with-headers.py localhost:$HGPORT 'log?rev=first(obsolete())&style=spartan' | grep 'class="obsolete"' - obsolete: - pruned - -check an obsolete changeset that has been rewritten - $ get-with-headers.py localhost:$HGPORT 'rev/cda648ca50f5?style=paper' | grep rewritten - rewritten as 3de5eca88c00 - $ get-with-headers.py localhost:$HGPORT 'rev/cda648ca50f5?style=coal' | grep rewritten - rewritten as 3de5eca88c00 - $ get-with-headers.py localhost:$HGPORT 'rev/cda648ca50f5?style=gitweb' | grep rewritten - obsoleterewritten as 3de5eca88c00 - $ get-with-headers.py localhost:$HGPORT 'rev/cda648ca50f5?style=monoblue' | grep rewritten -
obsolete
rewritten as 3de5eca88c00
- $ get-with-headers.py localhost:$HGPORT 'rev/cda648ca50f5?style=spartan' | grep rewritten - rewritten as 3de5eca88c00 + obsolete: + pruned by test Thu, 01 Jan 1970 00:00:00 +0000 check changeset with instabilities diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-parseindex.t --- a/tests/test-parseindex.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-parseindex.t Sat Feb 24 17:49:10 2018 -0600 @@ -41,10 +41,17 @@ > def __getattr__(self, key): > return getattr(self.real, key) > + > def __enter__(self): + > self.real.__enter__() + > return self + > + > def __exit__(self, *args, **kwargs): + > return self.real.__exit__(*args, **kwargs) + > > def opener(*args): > o = vfs.vfs(*args) - > def wrapper(*a): - > f = o(*a) + > def wrapper(*a, **kwargs): + > f = o(*a, **kwargs) > return singlebyteread(f) > return wrapper > diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-patch-offset.t --- a/tests/test-patch-offset.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-patch-offset.t Sat Feb 24 17:49:10 2018 -0600 @@ -5,7 +5,7 @@ > path = sys.argv[1] > patterns = sys.argv[2:] > - > fp = file(path, 'wb') + > fp = open(path, 'wb') > for pattern in patterns: > count = int(pattern[0:-1]) > char = pattern[-1] + '\n' diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-pathencode.py --- a/tests/test-pathencode.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-pathencode.py Sat Feb 24 17:49:10 2018 -0600 @@ -64,7 +64,7 @@ counts.pop(c, None) t = sum(counts.itervalues()) / 100.0 fp.write('probtable = (') - for i, (k, v) in enumerate(sorted(counts.iteritems(), key=lambda x: x[1], + for i, (k, v) in enumerate(sorted(counts.items(), key=lambda x: x[1], reverse=True)): if (i % 5) == 0: fp.write('\n ') diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-pending.t --- a/tests/test-pending.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-pending.t Sat Feb 24 17:49:10 2018 -0600 @@ -44,7 +44,7 @@ > import os, time > from mercurial import ui, localrepo > def rejecthook(ui, repo, hooktype, node, **opts): - > ui.write('hook %s\\n' % repo['tip'].hex()) + > ui.write(b'hook %s\\n' % repo[b'tip'].hex()) > # create the notify file so caller knows we're running > fpath = os.path.join('$d', 'notify') > f = open(fpath, 'w') diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-pull.t --- a/tests/test-pull.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-pull.t Sat Feb 24 17:49:10 2018 -0600 @@ -1,5 +1,15 @@ #require serve +#testcases sshv1 sshv2 + +#if sshv2 + $ cat >> $HGRCPATH << EOF + > [experimental] + > sshpeer.advertise-v2 = true + > sshserver.support-v2 = true + > EOF +#endif + $ hg init test $ cd test diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-pushvars.t --- a/tests/test-pushvars.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-pushvars.t Sat Feb 24 17:49:10 2018 -0600 @@ -11,8 +11,6 @@ $ cat >> $HGRCPATH << EOF > [hooks] > pretxnchangegroup = sh $TESTTMP/pretxnchangegroup.sh - > [experimental] - > bundle2-exp = true > EOF $ hg init repo diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-rebase-dest.t --- a/tests/test-rebase-dest.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-rebase-dest.t Sat Feb 24 17:49:10 2018 -0600 @@ -85,20 +85,20 @@ > from mercurial import registrar, revset, revsetlang, smartset > revsetpredicate = registrar.revsetpredicate() > cache = {} - > @revsetpredicate('map') + > @revsetpredicate(b'map') > def map(repo, subset, x): > """(set, mapping)""" - > setarg, maparg = revsetlang.getargs(x, 2, 2, '') + > setarg, maparg = revsetlang.getargs(x, 2, 2, b'') > rset = revset.getset(repo, smartset.fullreposet(repo), setarg) - > mapstr = revsetlang.getstring(maparg, '') - > map = dict(a.split(':') for a in mapstr.split(',')) + > mapstr = revsetlang.getstring(maparg, b'') + > map = dict(a.split(b':') for a in mapstr.split(b',')) > rev = rset.first() > desc = repo[rev].description() > newdesc = map.get(desc) - > if newdesc == 'null': + > if newdesc == b'null': > revs = [-1] > else: - > query = revsetlang.formatspec('desc(%s)', newdesc) + > query = revsetlang.formatspec(b'desc(%s)', newdesc) > revs = repo.revs(query) > return smartset.baseset(revs) > EOF diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-rebase-obsolete.t --- a/tests/test-rebase-obsolete.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-rebase-obsolete.t Sat Feb 24 17:49:10 2018 -0600 @@ -1218,6 +1218,46 @@ o 0:b173517d0057 a +issue5782 + $ hg strip -r 0: + $ hg debugdrawdag < d + > | + > c1 c # replace: c -> c1 + > \ / + > b + > | + > a + > EOF + 1 new orphan changesets + $ hg debugobsolete `hg log -T "{node}" --hidden -r 'desc("c1")'` + obsoleted 1 changesets + $ hg log -G -r 'a': --hidden + * 4:76be324c128b d + | + | x 3:ef8a456de8fa c1 (pruned) + | | + x | 2:a82ac2b38757 c (rewritten using replace as 3:ef8a456de8fa) + |/ + o 1:488e1b7e7341 b + | + o 0:b173517d0057 a + + $ hg rebase -d 0 -r 2 + rebasing 2:a82ac2b38757 "c" (c) + $ hg log -G -r 'a': --hidden + o 5:69ad416a4a26 c + | + | * 4:76be324c128b d + | | + | | x 3:ef8a456de8fa c1 (pruned) + | | | + | x | 2:a82ac2b38757 c (rewritten using replace as 3:ef8a456de8fa rewritten using rebase as 5:69ad416a4a26) + | |/ + | o 1:488e1b7e7341 b + |/ + o 0:b173517d0057 a + $ cd .. Rebase merge where successor of one parent is equal to destination (issue5198) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-relink.t --- a/tests/test-relink.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-relink.t Sat Feb 24 17:49:10 2018 -0600 @@ -49,7 +49,7 @@ Test files are read in binary mode - $ $PYTHON -c "file('.hg/store/data/dummy.i', 'wb').write('a\r\nb\n')" + $ $PYTHON -c "open('.hg/store/data/dummy.i', 'wb').write(b'a\r\nb\n')" $ cd .. @@ -68,7 +68,7 @@ $ echo b >> b $ hg ci -m changeb created new head - $ $PYTHON -c "file('.hg/store/data/dummy.i', 'wb').write('a\nb\r\n')" + $ $PYTHON -c "open('.hg/store/data/dummy.i', 'wb').write(b'a\nb\r\n')" relink diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-revert-interactive.t --- a/tests/test-revert-interactive.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-revert-interactive.t Sat Feb 24 17:49:10 2018 -0600 @@ -420,4 +420,13 @@ $ cat a 0 +When specified pattern does not exist, we should exit early (issue5789). + + $ hg files + a + $ hg rev b + b: no such file in rev b40d1912accf + $ hg rev -i b + b: no such file in rev b40d1912accf + $ cd .. diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-revset.t --- a/tests/test-revset.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-revset.t Sat Feb 24 17:49:10 2018 -0600 @@ -16,7 +16,7 @@ > return baseset() > return baseset([3,3,2,2]) > - > mercurial.revset.symbols['r3232'] = r3232 + > mercurial.revset.symbols[b'r3232'] = r3232 > EOF $ cat >> $HGRCPATH << EOF > [extensions] @@ -47,8 +47,8 @@ > cmdtable = {} > command = registrar.command(cmdtable) > @command(b'debugrevlistspec', - > [('', 'optimize', None, 'print parsed tree after optimizing'), - > ('', 'bin', None, 'unhexlify arguments')]) + > [(b'', b'optimize', None, b'print parsed tree after optimizing'), + > (b'', b'bin', None, b'unhexlify arguments')]) > def debugrevlistspec(ui, repo, fmt, *args, **opts): > if opts['bin']: > args = map(nodemod.bin, args) @@ -58,14 +58,14 @@ > ui.note(revsetlang.prettyformat(tree), "\n") > if opts["optimize"]: > opttree = revsetlang.optimize(revsetlang.analyze(tree)) - > ui.note("* optimized:\n", revsetlang.prettyformat(opttree), - > "\n") + > ui.note(b"* optimized:\n", revsetlang.prettyformat(opttree), + > b"\n") > func = revset.match(ui, expr, repo) > revs = func(repo) > if ui.verbose: - > ui.note("* set:\n", smartset.prettyformat(revs), "\n") + > ui.note(b"* set:\n", smartset.prettyformat(revs), b"\n") > for c in revs: - > ui.write("%s\n" % c) + > ui.write(b"%s\n" % c) > EOF $ cat <> $HGRCPATH > [extensions] diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-revset2.t --- a/tests/test-revset2.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-revset2.t Sat Feb 24 17:49:10 2018 -0600 @@ -420,7 +420,7 @@ test that repeated `-r` options never eat up stack (issue4565) (uses `-r 0::1` to avoid possible optimization at old-style parser) - $ hg log -T '{rev}\n' `$PYTHON -c "for i in xrange(500): print '-r 0::1 ',"` + $ hg log -T '{rev}\n' `$PYTHON -c "for i in range(500): print '-r 0::1 ',"` 0 1 diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-run-tests.t --- a/tests/test-run-tests.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-run-tests.t Sat Feb 24 17:49:10 2018 -0600 @@ -374,6 +374,7 @@ $ cat .testtimes + test-empty.t * (glob) test-failure-unicode.t * (glob) test-failure.t * (glob) test-success.t * (glob) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-sparse.t --- a/tests/test-sparse.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-sparse.t Sat Feb 24 17:49:10 2018 -0600 @@ -129,6 +129,10 @@ (include file with `hg debugsparse --include ` or use `hg add -s ` to include file directory while adding) [255] +But adding a truly excluded file shouldn't count + + $ hg add hide3 -X hide3 + Verify deleting sparseness while a file has changes fails $ hg debugsparse --delete 'show*' diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-ssh-bundle1.t --- a/tests/test-ssh-bundle1.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-ssh-bundle1.t Sat Feb 24 17:49:10 2018 -0600 @@ -1,6 +1,16 @@ This test is a duplicate of 'test-http.t' feel free to factor out parts that are not bundle1/bundle2 specific. +#testcases sshv1 sshv2 + +#if sshv2 + $ cat >> $HGRCPATH << EOF + > [experimental] + > sshpeer.advertise-v2 = true + > sshserver.support-v2 = true + > EOF +#endif + $ cat << EOF >> $HGRCPATH > [devel] > # This test is dedicated to interaction through old bundle @@ -465,11 +475,13 @@ $ hg pull --debug ssh://user@dummy/remote pulling from ssh://user@dummy/remote running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re) + sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !) sending hello command sending between command - remote: 384 + protocol upgraded to exp-ssh-v2-0001 (sshv2 !) + remote: 384 (sshv1 !) remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN - remote: 1 + remote: 1 (sshv1 !) preparing listkeys for "bookmarks" sending listkeys command received listkey for "bookmarks": 45 bytes diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-ssh-clone-r.t --- a/tests/test-ssh-clone-r.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-ssh-clone-r.t Sat Feb 24 17:49:10 2018 -0600 @@ -1,5 +1,15 @@ This test tries to exercise the ssh functionality with a dummy script +#testcases sshv1 sshv2 + +#if sshv2 + $ cat >> $HGRCPATH << EOF + > [experimental] + > sshpeer.advertise-v2 = true + > sshserver.support-v2 = true + > EOF +#endif + creating 'remote' repo $ hg init remote diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-ssh-proto.t --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-ssh-proto.t Sat Feb 24 17:49:10 2018 -0600 @@ -0,0 +1,635 @@ + $ cat >> $HGRCPATH << EOF + > [ui] + > ssh = $PYTHON "$TESTDIR/dummyssh" + > [devel] + > debug.peer-request = true + > [extensions] + > sshprotoext = $TESTDIR/sshprotoext.py + > EOF + + $ hg init server + $ cd server + $ echo 0 > foo + $ hg -q add foo + $ hg commit -m initial + $ cd .. + +Test a normal behaving server, for sanity + + $ hg --debug debugpeer ssh://user@dummy/server + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) + devel-peer-request: hello + sending hello command + devel-peer-request: between + devel-peer-request: pairs: 81 bytes + sending between command + remote: 384 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + remote: 1 + url: ssh://user@dummy/server + local: no + pushable: yes + +Server should answer the "hello" command in isolation + + $ hg -R server serve --stdio << EOF + > hello + > EOF + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + +>=0.9.1 clients send a "hello" + "between" for the null range as part of handshake. +Server should reply with capabilities and should send "1\n\n" as a successful +reply with empty response to the "between". + + $ hg -R server serve --stdio << EOF + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + > EOF + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + +SSH banner is not printed by default, ignored by clients + + $ SSHSERVERMODE=banner hg debugpeer ssh://user@dummy/server + url: ssh://user@dummy/server + local: no + pushable: yes + +--debug will print the banner + + $ SSHSERVERMODE=banner hg --debug debugpeer ssh://user@dummy/server + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) + devel-peer-request: hello + sending hello command + devel-peer-request: between + devel-peer-request: pairs: 81 bytes + sending between command + remote: banner: line 0 + remote: banner: line 1 + remote: banner: line 2 + remote: banner: line 3 + remote: banner: line 4 + remote: banner: line 5 + remote: banner: line 6 + remote: banner: line 7 + remote: banner: line 8 + remote: banner: line 9 + remote: 384 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + remote: 1 + url: ssh://user@dummy/server + local: no + pushable: yes + +And test the banner with the raw protocol + + $ SSHSERVERMODE=banner hg -R server serve --stdio << EOF + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + > EOF + banner: line 0 + banner: line 1 + banner: line 2 + banner: line 3 + banner: line 4 + banner: line 5 + banner: line 6 + banner: line 7 + banner: line 8 + banner: line 9 + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + +Connecting to a <0.9.1 server that doesn't support the hello command. +The client should refuse, as we dropped support for connecting to such +servers. + + $ SSHSERVERMODE=no-hello hg --debug debugpeer ssh://user@dummy/server + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) + devel-peer-request: hello + sending hello command + devel-peer-request: between + devel-peer-request: pairs: 81 bytes + sending between command + remote: 0 + remote: 1 + abort: no suitable response from remote hg! + [255] + +Sending an unknown command to the server results in an empty response to that command + + $ hg -R server serve --stdio << EOF + > pre-hello + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + > EOF + 0 + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + + $ hg --config sshpeer.mode=extra-handshake-commands --config sshpeer.handshake-mode=pre-no-args --debug debugpeer ssh://user@dummy/server + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) + sending no-args command + devel-peer-request: hello + sending hello command + devel-peer-request: between + devel-peer-request: pairs: 81 bytes + sending between command + remote: 0 + remote: 384 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + remote: 1 + url: ssh://user@dummy/server + local: no + pushable: yes + +Send multiple unknown commands before hello + + $ hg -R server serve --stdio << EOF + > unknown1 + > unknown2 + > unknown3 + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + > EOF + 0 + 0 + 0 + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + + $ hg --config sshpeer.mode=extra-handshake-commands --config sshpeer.handshake-mode=pre-multiple-no-args --debug debugpeer ssh://user@dummy/server + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) + sending unknown1 command + sending unknown2 command + sending unknown3 command + devel-peer-request: hello + sending hello command + devel-peer-request: between + devel-peer-request: pairs: 81 bytes + sending between command + remote: 0 + remote: 0 + remote: 0 + remote: 384 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + remote: 1 + url: ssh://user@dummy/server + local: no + pushable: yes + +Send an unknown command before hello that has arguments + + $ hg -R server serve --stdio << EOF + > with-args + > foo 13 + > value for foo + > bar 13 + > value for bar + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + > EOF + 0 + 0 + 0 + 0 + 0 + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + +Send an unknown command having an argument that looks numeric + + $ hg -R server serve --stdio << EOF + > unknown + > foo 1 + > 0 + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + > EOF + 0 + 0 + 0 + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + + $ hg -R server serve --stdio << EOF + > unknown + > foo 1 + > 1 + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + > EOF + 0 + 0 + 0 + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + +When sending a dict argument value, it is serialized to +" " followed by " \n" for each item +in the dict. + +Dictionary value for unknown command + + $ hg -R server serve --stdio << EOF + > unknown + > dict 3 + > key1 3 + > foo + > key2 3 + > bar + > key3 3 + > baz + > hello + > EOF + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + +Incomplete dictionary send + + $ hg -R server serve --stdio << EOF + > unknown + > dict 3 + > key1 3 + > foo + > EOF + 0 + 0 + 0 + 0 + +Incomplete value send + + $ hg -R server serve --stdio << EOF + > unknown + > dict 3 + > key1 3 + > fo + > EOF + 0 + 0 + 0 + 0 + +Send a command line with spaces + + $ hg -R server serve --stdio << EOF + > unknown withspace + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + > EOF + 0 + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + + $ hg -R server serve --stdio << EOF + > unknown with multiple spaces + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + > EOF + 0 + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + + $ hg -R server serve --stdio << EOF + > unknown with spaces + > key 10 + > some value + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + > EOF + 0 + 0 + 0 + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + +Send an unknown command after the "between" + + $ hg -R server serve --stdio << EOF + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000unknown + > EOF + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + 0 + +And one with arguments + + $ hg -R server serve --stdio << EOF + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000unknown + > foo 5 + > value + > bar 3 + > baz + > EOF + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + 0 + 0 + 0 + 0 + 0 + +Send a valid command before the handshake + + $ hg -R server serve --stdio << EOF + > heads + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + > EOF + 41 + 68986213bd4485ea51533535e3fc9e78007a711f + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + +And a variation that doesn't send the between command + + $ hg -R server serve --stdio << EOF + > heads + > hello + > EOF + 41 + 68986213bd4485ea51533535e3fc9e78007a711f + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + +Send an upgrade request to a server that doesn't support that command + + $ hg -R server serve --stdio << EOF + > upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=irrelevant1%2Cirrelevant2 + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + > EOF + 0 + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + + $ hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) + sending upgrade request: * proto=exp-ssh-v2-0001 (glob) + devel-peer-request: hello + sending hello command + devel-peer-request: between + devel-peer-request: pairs: 81 bytes + sending between command + remote: 0 + remote: 384 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + remote: 1 + url: ssh://user@dummy/server + local: no + pushable: yes + +Enable version 2 support on server. We need to do this in hgrc because we can't +use --config with `hg serve --stdio`. + + $ cat >> server/.hg/hgrc << EOF + > [experimental] + > sshserver.support-v2 = true + > EOF + +Send an upgrade request to a server that supports upgrade + + >>> with open('payload', 'wb') as fh: + ... fh.write(b'upgrade this-is-some-token proto=exp-ssh-v2-0001\n') + ... fh.write(b'hello\n') + ... fh.write(b'between\n') + ... fh.write(b'pairs 81\n') + ... fh.write(b'0000000000000000000000000000000000000000-0000000000000000000000000000000000000000') + + $ hg -R server serve --stdio < payload + upgraded this-is-some-token exp-ssh-v2-0001 + 383 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + + $ hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) + sending upgrade request: * proto=exp-ssh-v2-0001 (glob) + devel-peer-request: hello + sending hello command + devel-peer-request: between + devel-peer-request: pairs: 81 bytes + sending between command + protocol upgraded to exp-ssh-v2-0001 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + url: ssh://user@dummy/server + local: no + pushable: yes + +Verify the peer has capabilities + + $ hg --config experimental.sshpeer.advertise-v2=true --debug debugcapabilities ssh://user@dummy/server + running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !) + running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !) + sending upgrade request: * proto=exp-ssh-v2-0001 (glob) + devel-peer-request: hello + sending hello command + devel-peer-request: between + devel-peer-request: pairs: 81 bytes + sending between command + protocol upgraded to exp-ssh-v2-0001 + remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + Main capabilities: + batch + branchmap + $USUAL_BUNDLE2_CAPS_SERVER$ + changegroupsubset + getbundle + known + lookup + pushkey + streamreqs=generaldelta,revlogv1 + unbundle=HG10GZ,HG10BZ,HG10UN + unbundlehash + Bundle2 capabilities: + HG20 + bookmarks + changegroup + 01 + 02 + digests + md5 + sha1 + sha512 + error + abort + unsupportedcontent + pushraced + pushkey + hgtagsfnodes + listkeys + phases + heads + pushkey + remote-changegroup + http + https + +Command after upgrade to version 2 is processed + + >>> with open('payload', 'wb') as fh: + ... fh.write(b'upgrade this-is-some-token proto=exp-ssh-v2-0001\n') + ... fh.write(b'hello\n') + ... fh.write(b'between\n') + ... fh.write(b'pairs 81\n') + ... fh.write(b'0000000000000000000000000000000000000000-0000000000000000000000000000000000000000') + ... fh.write(b'hello\n') + $ hg -R server serve --stdio < payload + upgraded this-is-some-token exp-ssh-v2-0001 + 383 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + +Multiple upgrades is not allowed + + >>> with open('payload', 'wb') as fh: + ... fh.write(b'upgrade this-is-some-token proto=exp-ssh-v2-0001\n') + ... fh.write(b'hello\n') + ... fh.write(b'between\n') + ... fh.write(b'pairs 81\n') + ... fh.write(b'0000000000000000000000000000000000000000-0000000000000000000000000000000000000000') + ... fh.write(b'upgrade another-token proto=irrelevant\n') + ... fh.write(b'hello\n') + $ hg -R server serve --stdio < payload + upgraded this-is-some-token exp-ssh-v2-0001 + 383 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + cannot upgrade protocols multiple times + - + + +Malformed upgrade request line (not exactly 3 space delimited tokens) + + $ hg -R server serve --stdio << EOF + > upgrade + > EOF + 0 + + $ hg -R server serve --stdio << EOF + > upgrade token + > EOF + 0 + + $ hg -R server serve --stdio << EOF + > upgrade token foo=bar extra-token + > EOF + 0 + +Upgrade request to unsupported protocol is ignored + + $ hg -R server serve --stdio << EOF + > upgrade this-is-some-token proto=unknown1,unknown2 + > hello + > between + > pairs 81 + > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000 + > EOF + 0 + 384 + capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN + 1 + + +Upgrade request must be followed by hello + between + + $ hg -R server serve --stdio << EOF + > upgrade token proto=exp-ssh-v2-0001 + > invalid + > EOF + malformed handshake protocol: missing hello + - + + + $ hg -R server serve --stdio << EOF + > upgrade token proto=exp-ssh-v2-0001 + > hello + > invalid + > EOF + malformed handshake protocol: missing between + - + + + $ hg -R server serve --stdio << EOF + > upgrade token proto=exp-ssh-v2-0001 + > hello + > between + > invalid + > EOF + malformed handshake protocol: missing pairs 81 + - + diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-ssh.t --- a/tests/test-ssh.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-ssh.t Sat Feb 24 17:49:10 2018 -0600 @@ -1,3 +1,12 @@ +#testcases sshv1 sshv2 + +#if sshv2 + $ cat >> $HGRCPATH << EOF + > [experimental] + > sshpeer.advertise-v2 = true + > sshserver.support-v2 = true + > EOF +#endif This test tries to exercise the ssh functionality with a dummy script @@ -481,14 +490,16 @@ $ hg pull --debug ssh://user@dummy/remote --config devel.debug.peer-request=yes pulling from ssh://user@dummy/remote running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re) + sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !) devel-peer-request: hello sending hello command devel-peer-request: between devel-peer-request: pairs: 81 bytes sending between command - remote: 384 + remote: 384 (sshv1 !) + protocol upgraded to exp-ssh-v2-0001 (sshv2 !) remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN - remote: 1 + remote: 1 (sshv1 !) query 1; heads devel-peer-request: batch devel-peer-request: cmds: 141 bytes diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-sshserver.py --- a/tests/test-sshserver.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-sshserver.py Sat Feb 24 17:49:10 2018 -0600 @@ -6,30 +6,33 @@ import silenttestrunner from mercurial import ( - sshserver, util, wireproto, + wireprotoserver, ) class SSHServerGetArgsTests(unittest.TestCase): def testparseknown(self): tests = [ - ('* 0\nnodes 0\n', ['', {}]), - ('* 0\nnodes 40\n1111111111111111111111111111111111111111\n', - ['1111111111111111111111111111111111111111', {}]), + (b'* 0\nnodes 0\n', [b'', {}]), + (b'* 0\nnodes 40\n1111111111111111111111111111111111111111\n', + [b'1111111111111111111111111111111111111111', {}]), ] for input, expected in tests: - self.assertparse('known', input, expected) + self.assertparse(b'known', input, expected) def assertparse(self, cmd, input, expected): server = mockserver(input) + proto = wireprotoserver.sshv1protocolhandler(server._ui, + server._fin, + server._fout) _func, spec = wireproto.commands[cmd] - self.assertEqual(server.getargs(spec), expected) + self.assertEqual(proto.getargs(spec), expected) def mockserver(inbytes): ui = mockui(inbytes) repo = mockrepo(ui) - return sshserver.sshserver(ui, repo) + return wireprotoserver.sshserver(ui, repo) class mockrepo(object): def __init__(self, ui): diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-status.t --- a/tests/test-status.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-status.t Sat Feb 24 17:49:10 2018 -0600 @@ -465,12 +465,12 @@ $ hg init repo5 $ cd repo5 - >>> open("010a", "wb").write("\1\nfoo") + >>> open("010a", r"wb").write(b"\1\nfoo") $ hg ci -q -A -m 'initial checkin' $ hg status -A C 010a - >>> open("010a", "wb").write("\1\nbar") + >>> open("010a", r"wb").write(b"\1\nbar") $ hg status -A M 010a $ hg ci -q -m 'modify 010a' diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-strip.t --- a/tests/test-strip.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-strip.t Sat Feb 24 17:49:10 2018 -0600 @@ -893,17 +893,17 @@ > def test(transaction): > # observe cache inconsistency > try: - > [repo.changelog.node(r) for r in repo.revs("not public()")] + > [repo.changelog.node(r) for r in repo.revs(b"not public()")] > except IndexError: - > repo.ui.status("Index error!\n") + > repo.ui.status(b"Index error!\n") > transaction = orig(repo, desc, *args, **kwargs) > # warm up the phase cache - > list(repo.revs("not public()")) - > if desc != 'strip': - > transaction.addpostclose("phase invalidation test", test) + > list(repo.revs(b"not public()")) + > if desc != b'strip': + > transaction.addpostclose(b"phase invalidation test", test) > return transaction > def extsetup(ui): - > extensions.wrapfunction(localrepo.localrepository, "transaction", + > extensions.wrapfunction(localrepo.localrepository, b"transaction", > transactioncallback) > EOF $ hg up -C 2 @@ -930,9 +930,9 @@ > class crashstriprepo(repo.__class__): > def transaction(self, desc, *args, **kwargs): > tr = super(crashstriprepo, self).transaction(desc, *args, **kwargs) - > if desc == 'strip': - > def crash(tra): raise error.Abort('boom') - > tr.addpostclose('crash', crash) + > if desc == b'strip': + > def crash(tra): raise error.Abort(b'boom') + > tr.addpostclose(b'crash', crash) > return tr > repo.__class__ = crashstriprepo > EOF @@ -1175,16 +1175,16 @@ > from mercurial import commands, registrar, repair > cmdtable = {} > command = registrar.command(cmdtable) - > @command('testdelayedstrip') + > @command(b'testdelayedstrip') > def testdelayedstrip(ui, repo): > def getnodes(expr): > return [repo.changelog.node(r) for r in repo.revs(expr)] > with repo.wlock(): > with repo.lock(): - > with repo.transaction('delayedstrip'): - > repair.delayedstrip(ui, repo, getnodes('B+I+Z+D+E'), 'J') - > repair.delayedstrip(ui, repo, getnodes('G+H+Z'), 'I') - > commands.commit(ui, repo, message='J', date='0 0') + > with repo.transaction(b'delayedstrip'): + > repair.delayedstrip(ui, repo, getnodes(b'B+I+Z+D+E'), b'J') + > repair.delayedstrip(ui, repo, getnodes(b'G+H+Z'), b'I') + > commands.commit(ui, repo, message=b'J', date=b'0 0') > EOF $ hg testdelayedstrip --config extensions.t=$TESTTMP/delayedstrip.py warning: orphaned descendants detected, not stripping 08ebfeb61bac, 112478962961, 7fb047a69f22 @@ -1225,7 +1225,7 @@ > from mercurial import registrar, scmutil > cmdtable = {} > command = registrar.command(cmdtable) - > @command('testnodescleanup') + > @command(b'testnodescleanup') > def testnodescleanup(ui, repo): > def nodes(expr): > return [repo.changelog.node(r) for r in repo.revs(expr)] @@ -1233,12 +1233,13 @@ > return nodes(expr)[0] > with repo.wlock(): > with repo.lock(): - > with repo.transaction('delayedstrip'): - > mapping = {node('F'): [node('F2')], - > node('D'): [node('D2')], - > node('G'): [node('G2')]} - > scmutil.cleanupnodes(repo, mapping, 'replace') - > scmutil.cleanupnodes(repo, nodes('((B::)+I+Z)-D2'), 'replace') + > with repo.transaction(b'delayedstrip'): + > mapping = {node(b'F'): [node(b'F2')], + > node(b'D'): [node(b'D2')], + > node(b'G'): [node(b'G2')]} + > scmutil.cleanupnodes(repo, mapping, b'replace') + > scmutil.cleanupnodes(repo, nodes(b'((B::)+I+Z)-D2'), + > b'replace') > EOF $ hg testnodescleanup --config extensions.t=$TESTTMP/scmutilcleanup.py warning: orphaned descendants detected, not stripping 112478962961, 1fc8102cda62, 26805aba1e60 diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-subrepo-missing.t --- a/tests/test-subrepo-missing.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-subrepo-missing.t Sat Feb 24 17:49:10 2018 -0600 @@ -14,7 +14,7 @@ ignore blanklines in .hgsubstate - >>> file('.hgsubstate', 'wb').write('\n\n \t \n \n') + >>> open('.hgsubstate', 'wb').write(b'\n\n \t \n \n') $ hg st --subrepos M .hgsubstate $ hg revert -qC .hgsubstate @@ -22,7 +22,7 @@ abort more gracefully on .hgsubstate parsing error $ cp .hgsubstate .hgsubstate.old - >>> file('.hgsubstate', 'wb').write('\ninvalid') + >>> open('.hgsubstate', 'wb').write(b'\ninvalid') $ hg st --subrepos --cwd $TESTTMP -R $TESTTMP/repo abort: invalid subrepository revision specifier in 'repo/.hgsubstate' line 2 [255] diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-tag.t --- a/tests/test-tag.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-tag.t Sat Feb 24 17:49:10 2018 -0600 @@ -231,8 +231,8 @@ doesn't end with EOL $ $PYTHON << EOF - > f = file('.hg/localtags'); last = f.readlines()[-1][:-1]; f.close() - > f = file('.hg/localtags', 'w'); f.write(last); f.close() + > f = open('.hg/localtags'); last = f.readlines()[-1][:-1]; f.close() + > f = open('.hg/localtags', 'w'); f.write(last); f.close() > EOF $ cat .hg/localtags; echo acb14030fe0a21b60322c440ad2d20cf7685a376 localblah @@ -243,8 +243,8 @@ $ $PYTHON << EOF - > f = file('.hgtags'); last = f.readlines()[-1][:-1]; f.close() - > f = file('.hgtags', 'w'); f.write(last); f.close() + > f = open('.hgtags'); last = f.readlines()[-1][:-1]; f.close() + > f = open('.hgtags', 'w'); f.write(last); f.close() > EOF $ hg ci -m'broken manual edit of .hgtags' $ cat .hgtags; echo diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-template-engine.t --- a/tests/test-template-engine.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-template-engine.t Sat Feb 24 17:49:10 2018 -0600 @@ -13,7 +13,7 @@ > tmpl = self.loader(t) > props = self._defaults.copy() > props.update(map) - > for k, v in props.iteritems(): + > for k, v in props.items(): > if k in ('templ', 'ctx', 'repo', 'revcache', 'cache', 'troubles'): > continue > if hasattr(v, '__call__'): diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-transplant.t --- a/tests/test-transplant.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-transplant.t Sat Feb 24 17:49:10 2018 -0600 @@ -760,7 +760,7 @@ $ cd twin2 $ echo '[patch]' >> .hg/hgrc $ echo 'eol = crlf' >> .hg/hgrc - $ $PYTHON -c "file('b', 'wb').write('b\r\nb\r\n')" + $ $PYTHON -c "open('b', 'wb').write(b'b\r\nb\r\n')" $ hg ci -Am addb adding b $ hg transplant -s ../twin1 tip @@ -838,9 +838,9 @@ $ cd binarysource $ echo a > a $ hg ci -Am adda a - >>> file('b', 'wb').write('\0b1') + >>> open('b', 'wb').write(b'\0b1') $ hg ci -Am addb b - >>> file('b', 'wb').write('\0b2') + >>> open('b', 'wb').write(b'\0b2') $ hg ci -m changeb b $ cd .. @@ -891,14 +891,14 @@ > # emulate that patch.patch() is aborted at patching on "abort" file > from mercurial import error, extensions, patch as patchmod > def patch(orig, ui, repo, patchname, - > strip=1, prefix='', files=None, - > eolmode='strict', similarity=0): + > strip=1, prefix=b'', files=None, + > eolmode=b'strict', similarity=0): > if files is None: > files = set() > r = orig(ui, repo, patchname, > strip=strip, prefix=prefix, files=files, > eolmode=eolmode, similarity=similarity) - > if 'abort' in files: + > if b'abort' in files: > raise error.PatchError('intentional error while patching') > return r > def extsetup(ui): diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-ui-color.py --- a/tests/test-ui-color.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-ui-color.py Sat Feb 24 17:49:10 2018 -0600 @@ -9,27 +9,27 @@ # ensure errors aren't buffered testui = uimod.ui() testui.pushbuffer() -testui.write(('buffered\n')) -testui.warn(('warning\n')) -testui.write_err('error\n') +testui.write((b'buffered\n')) +testui.warn((b'warning\n')) +testui.write_err(b'error\n') print(repr(testui.popbuffer())) # test dispatch.dispatch with the same ui object -hgrc = open(os.environ["HGRCPATH"], 'w') -hgrc.write('[extensions]\n') -hgrc.write('color=\n') +hgrc = open(os.environ["HGRCPATH"], 'wb') +hgrc.write(b'[extensions]\n') +hgrc.write(b'color=\n') hgrc.close() ui_ = uimod.ui.load() -ui_.setconfig('ui', 'formatted', 'True') +ui_.setconfig(b'ui', b'formatted', b'True') # we're not interested in the output, so write that to devnull -ui_.fout = open(os.devnull, 'w') +ui_.fout = open(os.devnull, 'wb') # call some arbitrary command just so we go through # color's wrapped _runcommand twice. def runcmd(): - dispatch.dispatch(dispatch.request(['version', '-q'], ui_)) + dispatch.dispatch(dispatch.request([b'version', b'-q'], ui_)) runcmd() print("colored? %s" % (ui_._colormode is not None)) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-ui-verbosity.py --- a/tests/test-ui-verbosity.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-ui-verbosity.py Sat Feb 24 17:49:10 2018 -0600 @@ -2,9 +2,13 @@ import os from mercurial import ( + pycompat, ui as uimod, ) +if pycompat.ispy3: + xrange = range + hgrc = os.environ['HGRCPATH'] f = open(hgrc) basehgrc = f.read() diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-upgrade-repo.t --- a/tests/test-upgrade-repo.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-upgrade-repo.t Sat Feb 24 17:49:10 2018 -0600 @@ -31,23 +31,18 @@ abort: cannot upgrade repository; unsupported source requirement: shared [255] -Do not yet support upgrading manifestv2 and treemanifest repos - - $ hg --config experimental.manifestv2=true init manifestv2 - $ hg -R manifestv2 debugupgraderepo - abort: cannot upgrade repository; unsupported source requirement: manifestv2 - [255] +Do not yet support upgrading treemanifest repos $ hg --config experimental.treemanifest=true init treemanifest $ hg -R treemanifest debugupgraderepo abort: cannot upgrade repository; unsupported source requirement: treemanifest [255] -Cannot add manifestv2 or treemanifest requirement during upgrade +Cannot add treemanifest requirement during upgrade $ hg init disallowaddedreq - $ hg -R disallowaddedreq --config experimental.manifestv2=true --config experimental.treemanifest=true debugupgraderepo - abort: cannot upgrade repository; do not support adding requirement: manifestv2, treemanifest + $ hg -R disallowaddedreq --config experimental.treemanifest=true debugupgraderepo + abort: cannot upgrade repository; do not support adding requirement: treemanifest [255] An upgrade of a repository created with recommended settings only suggests optimizations diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-walk.t --- a/tests/test-walk.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-walk.t Sat Feb 24 17:49:10 2018 -0600 @@ -304,12 +304,10 @@ f beans/turtle beans/turtle $ hg debugwalk -Xbeans/black beans/black matcher: , m2=> - f beans/black beans/black exact $ hg debugwalk -Xbeans/black -Ibeans/black matcher: , m2=> $ hg debugwalk -Xbeans beans/black matcher: , m2=> - f beans/black beans/black exact $ hg debugwalk -Xbeans -Ibeans/black matcher: , m2=> $ hg debugwalk 'glob:mammals/../beans/b*' @@ -345,17 +343,13 @@ [255] Test explicit paths and excludes: -(BROKEN: nothing should be included, but wctx.walk() does) $ hg debugwalk fennel -X fennel matcher: , m2=> - f fennel fennel exact $ hg debugwalk fennel -X 'f*' matcher: , m2=> - f fennel fennel exact $ hg debugwalk beans/black -X 'path:beans' matcher: , m2=> - f beans/black beans/black exact $ hg debugwalk -I 'path:beans/black' -X 'path:beans' matcher: , m2=> @@ -494,12 +488,12 @@ Test listfile and listfile0 - $ $PYTHON -c "file('listfile0', 'wb').write('fenugreek\0new\0')" + $ $PYTHON -c "open('listfile0', 'wb').write(b'fenugreek\0new\0')" $ hg debugwalk -I 'listfile0:listfile0' matcher: f fenugreek fenugreek f new new - $ $PYTHON -c "file('listfile', 'wb').write('fenugreek\nnew\r\nmammals/skunk\n')" + $ $PYTHON -c "open('listfile', 'wb').write(b'fenugreek\nnew\r\nmammals/skunk\n')" $ hg debugwalk -I 'listfile:listfile' matcher: f fenugreek fenugreek @@ -525,7 +519,7 @@ $ cd t $ echo fennel > overflow.list - $ $PYTHON -c "for i in xrange(20000 / 100): print 'x' * 100" >> overflow.list + $ $PYTHON -c "for i in range(20000 / 100): print 'x' * 100" >> overflow.list $ echo fenugreek >> overflow.list $ hg debugwalk 'listfile:overflow.list' 2>&1 | egrep -v '(^matcher: |^xxx)' f fennel fennel exact diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-win32text.t --- a/tests/test-win32text.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-win32text.t Sat Feb 24 17:49:10 2018 -0600 @@ -5,9 +5,9 @@ > import sys > > for path in sys.argv[1:]: - > data = file(path, 'rb').read() - > data = data.replace('\n', '\r\n') - > file(path, 'wb').write(data) + > data = open(path, 'rb').read() + > data = data.replace(b'\n', b'\r\n') + > open(path, 'wb').write(data) > EOF $ echo '[hooks]' >> .hg/hgrc $ echo 'pretxncommit.crlf = python:hgext.win32text.forbidcrlf' >> .hg/hgrc @@ -118,7 +118,7 @@ $ hg rem f $ hg ci -m 4 - $ $PYTHON -c 'file("bin", "wb").write("hello\x00\x0D\x0A")' + $ $PYTHON -c 'open("bin", "wb").write(b"hello\x00\x0D\x0A")' $ hg add bin $ hg ci -m 5 $ hg log -v @@ -342,7 +342,7 @@ $ rm .hg/hgrc $ (echo some; echo text) > f3 - $ $PYTHON -c 'file("f4.bat", "wb").write("rem empty\x0D\x0A")' + $ $PYTHON -c 'open("f4.bat", "wb").write(b"rem empty\x0D\x0A")' $ hg add f3 f4.bat $ hg ci -m 6 $ cat bin @@ -395,7 +395,7 @@ $ cat f4.bat rem empty\r (esc) - $ $PYTHON -c 'file("f5.sh", "wb").write("# empty\x0D\x0A")' + $ $PYTHON -c 'open("f5.sh", "wb").write(b"# empty\x0D\x0A")' $ hg add f5.sh $ hg ci -m 7 $ cat f5.sh diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-wireproto.py --- a/tests/test-wireproto.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-wireproto.py Sat Feb 24 17:49:10 2018 -0600 @@ -1,8 +1,10 @@ from __future__ import absolute_import, print_function from mercurial import ( + error, util, wireproto, + wireprototypes, ) stringio = util.stringio @@ -42,7 +44,13 @@ return ['batch'] def _call(self, cmd, **args): - return wireproto.dispatch(self.serverrepo, proto(args), cmd) + res = wireproto.dispatch(self.serverrepo, proto(args), cmd) + if isinstance(res, wireprototypes.bytesresponse): + return res.data + elif isinstance(res, bytes): + return res + else: + raise error.Abort('dummy client does not support response type') def _callstream(self, cmd, **args): return stringio(self._call(cmd, **args)) diff -r fb39f6a8a864 -r eb73f8a6177e tests/test-worker.t --- a/tests/test-worker.t Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/test-worker.t Sat Feb 24 17:49:10 2018 -0600 @@ -12,7 +12,7 @@ > def abort(ui, args): > if args[0] == 0: > # by first worker for test stability - > raise error.Abort('known exception') + > raise error.Abort(b'known exception') > return runme(ui, []) > def exc(ui, args): > if args[0] == 0: @@ -21,25 +21,25 @@ > return runme(ui, []) > def runme(ui, args): > for arg in args: - > ui.status('run\n') + > ui.status(b'run\n') > yield 1, arg > time.sleep(0.1) # easier to trigger killworkers code path > functable = { - > 'abort': abort, - > 'exc': exc, - > 'runme': runme, + > b'abort': abort, + > b'exc': exc, + > b'runme': runme, > } > cmdtable = {} > command = registrar.command(cmdtable) - > @command(b'test', [], 'hg test [COST] [FUNC]') - > def t(ui, repo, cost=1.0, func='runme'): + > @command(b'test', [], b'hg test [COST] [FUNC]') + > def t(ui, repo, cost=1.0, func=b'runme'): > cost = float(cost) > func = functable[func] - > ui.status('start\n') + > ui.status(b'start\n') > runs = worker.worker(ui, cost, func, (ui,), range(8)) > for n, i in runs: > pass - > ui.status('done\n') + > ui.status(b'done\n') > EOF $ abspath=`pwd`/t.py $ hg init diff -r fb39f6a8a864 -r eb73f8a6177e tests/testlib/ext-phase-report.py --- a/tests/testlib/ext-phase-report.py Fri Feb 23 17:57:04 2018 -0800 +++ b/tests/testlib/ext-phase-report.py Sat Feb 24 17:49:10 2018 -0600 @@ -5,18 +5,18 @@ def reposetup(ui, repo): def reportphasemove(tr): - for rev, move in sorted(tr.changes['phases'].iteritems()): + for rev, move in sorted(tr.changes[b'phases'].items()): if move[0] is None: - ui.write(('test-debug-phase: new rev %d: x -> %d\n' + ui.write((b'test-debug-phase: new rev %d: x -> %d\n' % (rev, move[1]))) else: - ui.write(('test-debug-phase: move rev %d: %s -> %d\n' + ui.write((b'test-debug-phase: move rev %d: %d -> %d\n' % (rev, move[0], move[1]))) class reportphaserepo(repo.__class__): def transaction(self, *args, **kwargs): tr = super(reportphaserepo, self).transaction(*args, **kwargs) - tr.addpostclose('report-phase', reportphasemove) + tr.addpostclose(b'report-phase', reportphasemove) return tr repo.__class__ = reportphaserepo