Mercurial > hg
changeset 14357:cb4ff8ef466b
merge with stable
author | Martin Geisler <mg@aragost.com> |
---|---|
date | Wed, 18 May 2011 09:56:27 +0200 |
parents | 077cdf172580 (diff) 02a5bebd0dc4 (current diff) |
children | bf93e78f2638 |
files | mercurial/revset.py |
diffstat | 448 files changed, 18675 insertions(+), 9735 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Wed May 18 09:31:19 2011 +0200 +++ b/.hgignore Wed May 18 09:56:27 2011 +0200 @@ -7,6 +7,7 @@ *.mergebackup *.o *.so +*.dll *.pyd *.pyc *.pyo
--- a/Makefile Wed May 18 09:31:19 2011 +0200 +++ b/Makefile Wed May 18 09:56:27 2011 +0200 @@ -45,6 +45,7 @@ clean: -$(PYTHON) setup.py clean --all # ignore errors from this command find . \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';' + rm -f $(addprefix mercurial/,$(notdir $(wildcard mercurial/pure/*.py))) rm -f MANIFEST tests/*.err rm -rf build mercurial/locale $(MAKE) -C doc clean @@ -78,6 +79,8 @@ dist-notests: doc MANIFEST TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist +check: tests + tests: cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
--- a/contrib/bash_completion Wed May 18 09:31:19 2011 +0200 +++ b/contrib/bash_completion Wed May 18 09:56:27 2011 +0200 @@ -68,7 +68,7 @@ _hg_paths() { - local paths="$(_hg_cmd paths | sed -e 's/ = .*$//')" + local paths="$(_hg_cmd paths -q)" COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$paths' -- "$cur")) }
--- a/contrib/check-code.py Wed May 18 09:31:19 2011 +0200 +++ b/contrib/check-code.py Wed May 18 09:56:27 2011 +0200 @@ -42,6 +42,7 @@ testpats = [ + [ (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"), (r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"), (r'^function', "don't use 'function', use old style"), @@ -66,6 +67,10 @@ (r'^source\b', "don't use 'source', use '.'"), (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"), (r'ls\s+[^|-]+\s+-', "options to 'ls' must come before filenames"), + (r'[^>]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"), + ], + # warnings + [] ] testfilters = [ @@ -76,6 +81,7 @@ uprefix = r"^ \$ " uprefixc = r"^ > " utestpats = [ + [ (r'^(\S| $ ).*(\S\s+|^\s+)\n', "trailing whitespace on non-output"), (uprefix + r'.*\|\s*sed', "use regex test output patterns instead of sed"), (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"), @@ -84,20 +90,25 @@ "explicit exit code checks unnecessary"), (uprefix + r'set -e', "don't use set -e"), (uprefixc + r'( *)\t', "don't use tabs to indent"), + ], + # warnings + [] ] -for p, m in testpats: - if p.startswith('^'): - p = uprefix + p[1:] - else: - p = uprefix + p - utestpats.append((p, m)) +for i in [0, 1]: + for p, m in testpats[i]: + if p.startswith('^'): + p = uprefix + p[1:] + else: + p = uprefix + p + utestpats[i].append((p, m)) utestfilters = [ (r"( *)(#([^\n]*\S)?)", repcomment), ] pypats = [ + [ (r'^\s*def\s*\w+\s*\(.*,\s*\(', "tuple parameter unpacking not available in Python 3+"), (r'lambda\s*\(.*,.*\)', @@ -111,7 +122,6 @@ (r'\w[+/*\-<>]\w', "missing whitespace in expression"), (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"), (r'.{85}', "line too long"), - (r'.{81}', "warning: line over 80 characters"), (r'[^\n]\Z', "no trailing newline"), (r'(\S\s+|^\s+)\n', "trailing whitespace"), # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"), @@ -129,6 +139,7 @@ (r'[\x80-\xff]', "non-ASCII character literal"), (r'("\')\.format\(', "str.format() not available in Python 2.4"), (r'^\s*with\s+', "with not available in Python 2.4"), + (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"), (r'^\s*except.* as .*:', "except as not available in Python 2.4"), (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"), (r'(?<!def)\s+(any|all|format)\(', @@ -146,14 +157,32 @@ "missing whitespace around operator"), (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S', "missing whitespace around operator"), - (r'[^+=*!<>&| -](\s=|=\s)[^= ]', + (r'[^+=*/!<>&| -](\s=|=\s)[^= ]', "wrong whitespace around ="), (r'raise Exception', "don't raise generic exceptions"), - (r'ui\.(status|progress|write|note|warn)\([\'\"]x', - "warning: unwrapped ui message"), (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"), (r' [=!]=\s+(True|False|None)', "comparison with singleton, use 'is' or 'is not' instead"), + (r'opener\([^)]*\).read\(', + "use opener.read() instead"), + (r'opener\([^)]*\).write\(', + "use opener.write() instead"), + (r'[\s\(](open|file)\([^)]*\)\.read\(', + "use util.readfile() instead"), + (r'[\s\(](open|file)\([^)]*\)\.write\(', + "use util.readfile() instead"), + (r'^[\s\(]*(open(er)?|file)\([^)]*\)', + "always assign an opened file to a variable, and close it afterwards"), + (r'[\s\(](open|file)\([^)]*\)\.', + "always assign an opened file to a variable, and close it afterwards"), + ], + # warnings + [ + (r'.{81}', "warning: line over 80 characters"), + (r'^\s*except:$', "warning: naked except clause"), + (r'ui\.(status|progress|write|note|warn)\([\'\"]x', + "warning: unwrapped ui message"), + ] ] pyfilters = [ @@ -164,6 +193,7 @@ ] cpats = [ + [ (r'//', "don't use //-style comments"), (r'^ ', "don't use spaces to indent"), (r'\S\t', "don't use tabs except for indent"), @@ -176,9 +206,13 @@ (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"), (r'\S+ (\+\+|--)', "use foo++, not foo ++"), (r'\w,\w', "missing whitespace after ,"), - (r'\w[+/*]\w', "missing whitespace in expression"), + (r'^[^#]\w[+/*]\w', "missing whitespace in expression"), (r'^#\s+\w', "use #foo, not # foo"), (r'[^\n]\Z', "no trailing newline"), + (r'^\s*#import\b', "use only #include in standard C code"), + ], + # warnings + [] ] cfilters = [ @@ -188,11 +222,31 @@ (r'(\()([^)]+\))', repcallspaces), ] +inutilpats = [ + [ + (r'\bui\.', "don't use ui in util"), + ], + # warnings + [] +] + +inrevlogpats = [ + [ + (r'\brepo\.', "don't use repo in revlog"), + ], + # warnings + [] +] + checks = [ ('python', r'.*\.(py|cgi)$', pyfilters, pypats), ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats), ('c', r'.*\.c$', cfilters, cpats), ('unified test', r'.*\.t$', utestfilters, utestpats), + ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters, + inrevlogpats), + ('layering violation ui in util', r'mercurial/util\.py', pyfilters, + inutilpats), ] class norepeatlogger(object): @@ -231,7 +285,7 @@ return lines def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False, - blame=False): + blame=False, debug=False): """checks style and portability of a given file :f: filepath @@ -245,24 +299,39 @@ blamecache = None result = True for name, match, filters, pats in checks: + if debug: + print name, f fc = 0 if not re.match(match, f): + if debug: + print "Skipping %s for %s it doesn't match %s" % ( + name, match, f) continue fp = open(f) pre = post = fp.read() fp.close() if "no-" + "check-code" in pre: + if debug: + print "Skipping %s for %s it has no- and check-code" % ( + name, f) break for p, r in filters: post = re.sub(p, r, post) + if warnings: + pats = pats[0] + pats[1] + else: + pats = pats[0] # print post # uncomment to show filtered version z = enumerate(zip(pre.splitlines(), post.splitlines(True))) + if debug: + print "Checking %s for %s" % (name, f) for n, l in z: if "check-code" + "-ignore" in l[0]: + if debug: + print "Skipping %s for %s:%s (check-code -ignore)" % ( + name, f, n) continue for p, msg in pats: - if not warnings and msg.startswith("warning"): - continue if re.search(p, l[1]): bd = "" if blame: @@ -279,7 +348,6 @@ if maxerr is not None and fc >= maxerr: print " (too many errors, giving up)" break - break return result if __name__ == "__main__": @@ -290,8 +358,10 @@ help="max warnings per file") parser.add_option("-b", "--blame", action="store_true", help="use annotate to generate blame info") + parser.add_option("", "--debug", action="store_true", + help="show debug information") - parser.set_defaults(per_file=15, warnings=False, blame=False) + parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False) (options, args) = parser.parse_args() if len(args) == 0: @@ -302,6 +372,6 @@ for f in check: ret = 0 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings, - blame=options.blame): + blame=options.blame, debug=options.debug): ret = 1 sys.exit(ret)
--- a/contrib/dumprevlog Wed May 18 09:31:19 2011 +0200 +++ b/contrib/dumprevlog Wed May 18 09:56:27 2011 +0200 @@ -6,7 +6,7 @@ from mercurial import revlog, node, util for fp in (sys.stdin, sys.stdout, sys.stderr): - util.set_binary(fp) + util.setbinary(fp) for f in sys.argv[1:]: binopen = lambda fn: open(fn, 'rb')
--- a/contrib/hg-ssh Wed May 18 09:31:19 2011 +0200 +++ b/contrib/hg-ssh Wed May 18 09:56:27 2011 +0200 @@ -16,7 +16,7 @@ (probably together with these other useful options: no-port-forwarding,no-X11-forwarding,no-agent-forwarding) -This allows pull/push over ssh to to the repositories given as arguments. +This allows pull/push over ssh from/to the repositories given as arguments. If all your repositories are subdirectories of a common directory, you can allow shorter paths with:
--- a/contrib/perf.py Wed May 18 09:31:19 2011 +0200 +++ b/contrib/perf.py Wed May 18 09:56:27 2011 +0200 @@ -1,7 +1,7 @@ # perf.py - performance test routines '''helper extension to measure performance''' -from mercurial import cmdutil, match, commands +from mercurial import cmdutil, scmutil, match, commands import time, os, sys def timer(func, title=None): @@ -31,11 +31,11 @@ def perfwalk(ui, repo, *pats): try: - m = cmdutil.match(repo, pats, {}) + m = scmutil.match(repo, pats, {}) timer(lambda: len(list(repo.dirstate.walk(m, [], True, False)))) except: try: - m = cmdutil.match(repo, pats, {}) + m = scmutil.match(repo, pats, {}) timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)])) except: timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
--- a/contrib/python-hook-examples.py Wed May 18 09:31:19 2011 +0200 +++ b/contrib/python-hook-examples.py Wed May 18 09:56:27 2011 +0200 @@ -13,7 +13,7 @@ if kwargs.get('parent2'): return node = kwargs['node'] - first = repo[node].parents()[0].node() + first = repo[node].p1().node() if 'url' in kwargs: last = repo['tip'].node() else:
--- a/contrib/shrink-revlog.py Wed May 18 09:31:19 2011 +0200 +++ b/contrib/shrink-revlog.py Wed May 18 09:56:27 2011 +0200 @@ -1,5 +1,4 @@ -"""\ -reorder a revlog (the manifest by default) to save space +"""reorder a revlog (the manifest by default) to save space Specifically, this topologically sorts the revisions in the revlog so that revisions on the same branch are adjacent as much as possible. This is a @@ -18,7 +17,7 @@ # (maybe: export before, shrink, export after, diff). import os, tempfile, errno -from mercurial import revlog, transaction, node, util +from mercurial import revlog, transaction, node, util, scmutil from mercurial import changegroup from mercurial.i18n import _ @@ -31,7 +30,10 @@ while visit: cur = visit[-1] for p in edges[cur]: - if p not in finished: + # defend against node.nullrev because it's occasionally + # possible for a node to have parents (null, something) + # rather than (something, null) + if p not in finished and p != node.nullrev: visit.append(p) break else: @@ -102,19 +104,21 @@ ui.status(_('writing revs\n')) - count = [0] - def progress(*args): - ui.progress(_('writing'), count[0], total=len(order)) - count[0] += 1 order = [r1.node(r) for r in order] # this is a bit ugly, but it works - lookup = lambda x: "%020d" % r1.linkrev(r1.rev(x)) + count = [0] + def lookup(revl, x): + count[0] += 1 + ui.progress(_('writing'), count[0], total=len(order)) + return "%020d" % revl.linkrev(revl.rev(x)) + unlookup = lambda x: int(x, 10) try: - group = util.chunkbuffer(r1.group(order, lookup, progress)) + bundler = changegroup.bundle10(lookup) + group = util.chunkbuffer(r1.group(order, bundler)) group = changegroup.unbundle10(group, "UN") r2.addgroup(group, unlookup, tr) finally: @@ -190,8 +194,8 @@ prefix = os.path.basename(indexfn)[:-1] tmpindexfn = util.mktempcopy(indexfn, emptyok=True) - r1 = revlog.revlog(util.opener(os.getcwd(), audit=False), indexfn) - r2 = revlog.revlog(util.opener(os.getcwd(), audit=False), tmpindexfn) + r1 = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), indexfn) + r2 = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), tmpindexfn) datafn, tmpdatafn = r1.datafile, r2.datafile @@ -247,8 +251,8 @@ if not opts.get('dry_run'): # racy, both files cannot be renamed atomically # copy files - util.os_link(indexfn, oldindexfn) - ignoremissing(util.os_link)(datafn, olddatafn) + util.oslink(indexfn, oldindexfn) + ignoremissing(util.oslink)(datafn, olddatafn) # rename util.rename(tmpindexfn, indexfn)
--- a/contrib/simplemerge Wed May 18 09:31:19 2011 +0200 +++ b/contrib/simplemerge Wed May 18 09:56:27 2011 +0200 @@ -43,7 +43,7 @@ try: for fp in (sys.stdin, sys.stdout, sys.stderr): - util.set_binary(fp) + util.setbinary(fp) opts = {} try:
--- a/contrib/undumprevlog Wed May 18 09:31:19 2011 +0200 +++ b/contrib/undumprevlog Wed May 18 09:56:27 2011 +0200 @@ -4,12 +4,12 @@ # $ undumprevlog < repo.dump import sys -from mercurial import revlog, node, util, transaction +from mercurial import revlog, node, scmutil, util, transaction for fp in (sys.stdin, sys.stdout, sys.stderr): - util.set_binary(fp) + util.setbinary(fp) -opener = util.opener('.', False) +opener = scmutil.opener('.', False) tr = transaction.transaction(sys.stderr.write, opener, "undump.journal") while 1: l = sys.stdin.readline()
--- a/contrib/win32/mercurial.ini Wed May 18 09:31:19 2011 +0200 +++ b/contrib/win32/mercurial.ini Wed May 18 09:56:27 2011 +0200 @@ -37,7 +37,6 @@ [extensions] ;acl = ;alias = -;bookmarks = ;bugzilla = ;children = ;churn = @@ -56,7 +55,6 @@ ;mq = ;notify = ;pager = -;parentrevspec = ;patchbomb = ;progress = ;purge =
--- a/contrib/wix/guids.wxi Wed May 18 09:31:19 2011 +0200 +++ b/contrib/wix/guids.wxi Wed May 18 09:56:27 2011 +0200 @@ -33,7 +33,7 @@ <?define templates.raw.guid = {04DE03A2-FBFD-4c5f-8DEA-5436DDF4689D} ?> <?define templates.rss.guid = {A7D608DE-0CF6-44f4-AF1E-EE30CC237FDA} ?> <?define templates.spartan.guid = {80222625-FA8F-44b1-86CE-1781EF375D09} ?> - <?define templates.static.guid = {68C9F843-DE7E-480f-9DA2-D220B19D02C3} ?> + <?define templates.static.guid = {B27D7311-050A-4A96-9971-B674A0EA21D0} ?> <!-- mercurial.wxs --> <?define ProductUpgradeCode = {A1CC6134-E945-4399-BE36-EB0017FDF7CF} ?>
--- a/contrib/wix/templates.wxs Wed May 18 09:31:19 2011 +0200 +++ b/contrib/wix/templates.wxs Wed May 18 09:56:27 2011 +0200 @@ -195,7 +195,7 @@ <File Id="static.coal.file.png" Name="coal-file.png" /> <File Id="static.coal.folder.png" Name="coal-folder.png" /> <File Id="static.excanvas.js" Name="excanvas.js" /> - <File Id="static.graph.js" Name="graph.js" /> + <File Id="static.mercurial.js" Name="mercurial.js" /> <File Id="static.hgicon.png" Name="hgicon.png" /> <File Id="static.hglogo.png" Name="hglogo.png" /> <File Id="static.style.coal.css" Name="style-coal.css" />
--- a/contrib/zsh_completion Wed May 18 09:31:19 2011 +0200 +++ b/contrib/zsh_completion Wed May 18 09:56:27 2011 +0200 @@ -360,8 +360,8 @@ '(--help -h)'{-h,--help}'[display help and exit]' '--debug[debug mode]' '--debugger[start debugger]' - '--encoding[set the charset encoding (default: UTF8)]' - '--encodingmode[set the charset encoding mode (default: strict)]' + '--encoding[set the charset encoding]' + '--encodingmode[set the charset encoding mode]' '--lsprof[print improved command execution profile]' '--traceback[print traceback on exception]' '--time[time how long the command takes]'
--- a/doc/gendoc.py Wed May 18 09:31:19 2011 +0200 +++ b/doc/gendoc.py Wed May 18 09:56:27 2011 +0200 @@ -157,16 +157,7 @@ def allextensionnames(): - extensionnames = [] - - extensionsdictionary = extensions.enabled()[0] - extensionnames.extend(extensionsdictionary.keys()) - - extensionsdictionary = extensions.disabled()[0] - extensionnames.extend(extensionsdictionary.keys()) - - return extensionnames - + return extensions.enabled().keys() + extensions.disabled().keys() if __name__ == "__main__": show_doc(sys.stdout)
--- a/doc/hgignore.5.txt Wed May 18 09:31:19 2011 +0200 +++ b/doc/hgignore.5.txt Wed May 18 09:56:27 2011 +0200 @@ -11,85 +11,7 @@ :Manual section: 5 :Manual group: Mercurial Manual -Synopsis --------- - -The Mercurial system uses a file called ``.hgignore`` in the root -directory of a repository to control its behavior when it searches -for files that it is not currently tracking. - -Description ------------ - -The working directory of a Mercurial repository will often contain -files that should not be tracked by Mercurial. These include backup -files created by editors and build products created by compilers. -These files can be ignored by listing them in a ``.hgignore`` file in -the root of the working directory. The ``.hgignore`` file must be -created manually. It is typically put under version control, so that -the settings will propagate to other repositories with push and pull. - -An untracked file is ignored if its path relative to the repository -root directory, or any prefix path of that path, is matched against -any pattern in ``.hgignore``. - -For example, say we have an untracked file, ``file.c``, at -``a/b/file.c`` inside our repository. Mercurial will ignore ``file.c`` -if any pattern in ``.hgignore`` matches ``a/b/file.c``, ``a/b`` or ``a``. - -In addition, a Mercurial configuration file can reference a set of -per-user or global ignore files. See the |hgrc(5)|_ man page for details -of how to configure these files. Look for the "ignore" entry in the -"ui" section. - -To control Mercurial's handling of files that it manages, see the -|hg(1)|_ man page. Look for the ``-I`` and ``-X`` options. - -Syntax ------- - -An ignore file is a plain text file consisting of a list of patterns, -with one pattern per line. Empty lines are skipped. The ``#`` -character is treated as a comment character, and the ``\`` character -is treated as an escape character. - -Mercurial supports several pattern syntaxes. The default syntax used -is Python/Perl-style regular expressions. - -To change the syntax used, use a line of the following form:: - - syntax: NAME - -where ``NAME`` is one of the following: - -``regexp`` - Regular expression, Python/Perl syntax. -``glob`` - Shell-style glob. - -The chosen syntax stays in effect when parsing all patterns that -follow, until another syntax is selected. - -Neither glob nor regexp patterns are rooted. A glob-syntax pattern of -the form ``*.c`` will match a file ending in ``.c`` in any directory, -and a regexp pattern of the form ``\.c$`` will do the same. To root a -regexp pattern, start it with ``^``. - -Example -------- - -Here is an example ignore file. :: - - # use glob syntax. - syntax: glob - - *.elc - *.pyc - *~ - - # switch to regexp syntax. - syntax: regexp - ^\.pc/ +.. include:: ../mercurial/help/hgignore.txt Author ------ @@ -109,3 +31,4 @@ Public License version 2 or any later version. .. include:: common.txt +
--- a/doc/hgrc.5.txt Wed May 18 09:31:19 2011 +0200 +++ b/doc/hgrc.5.txt Wed May 18 09:56:27 2011 +0200 @@ -181,7 +181,11 @@ """"""""" Defines command aliases. Aliases allow you to define your own commands in terms of other -commands (or aliases), optionally including arguments. +commands (or aliases), optionally including arguments. Positional +arguments in the form of ``$1``, ``$2``, etc in the alias definition +are expanded by Mercurial before execution. Positional arguments not +already used by ``$N`` in the definition are put at the end of the +command to be executed. Alias definitions consist of lines of the form:: @@ -654,6 +658,10 @@ Run before starting a local commit. Exit status 0 allows the commit to proceed. Non-zero status will cause the commit to fail. Parent changeset IDs are in ``$HG_PARENT1`` and ``$HG_PARENT2``. +``prelistkeys`` + Run before listing pushkeys (like bookmarks) in the + repository. Non-zero status will cause failure. The key namespace is + in ``$HG_NAMESPACE``. ``preoutgoing`` Run before collecting changes to send from the local repository to another. Non-zero status will cause failure. This lets you prevent @@ -663,6 +671,12 @@ ``$HG_SOURCE``. If "serve", operation is happening on behalf of remote SSH or HTTP repository. If "push", "pull" or "bundle", operation is happening on behalf of repository on same system. +``prepushkey`` + Run before a pushkey (like a bookmark) is added to the + repository. Non-zero status will cause the key to be rejected. The + key namespace is in ``$HG_NAMESPACE``, the key is in ``$HG_KEY``, + the old value (if any) is in ``$HG_OLD``, and the new value is in + ``$HG_NEW``. ``pretag`` Run before creating a tag. Exit status 0 allows the tag to be created. Non-zero status will cause the tag to fail. ID of @@ -689,6 +703,15 @@ the update to proceed. Non-zero status will prevent the update. Changeset ID of first new parent is in ``$HG_PARENT1``. If merge, ID of second new parent is in ``$HG_PARENT2``. +``listkeys`` + Run after listing pushkeys (like bookmarks) in the repository. The + key namespace is in ``$HG_NAMESPACE``. ``$HG_VALUES`` is a + dictionary containing the keys and values. +``pushkey`` + Run after a pushkey (like a bookmark) is added to the + repository. The key namespace is in ``$HG_NAMESPACE``, the key is in + ``$HG_KEY``, the old value (if any) is in ``$HG_OLD``, and the new + value is in ``$HG_NEW``. ``tag`` Run after a tag is created. ID of tagged changeset is in ``$HG_NODE``. Name of tag is in ``$HG_TAG``. Tag is local if ``$HG_LOCAL=1``, in @@ -930,9 +953,17 @@ The conflict resolution program to use during a manual merge. For more information on merge tools see :hg:`help merge-tools`. For configuring merge tools see the merge-tools_ section. -``patch`` - command to use to apply patches. Look for ``gpatch`` or ``patch`` in - PATH if unset. +``portablefilenames`` + Check for portable filenames. Can be ``warn``, ``ignore`` or ``abort``. + Default is ``warn``. + If set to ``warn`` (or ``true``), a warning message is printed on POSIX + platforms, if a file with a non-portable filename is added (e.g. a file + with a name that can't be created on Windows because it contains reserved + parts like ``AUX``, reserved characters like ``:``, or would cause a case + collision with an existing file). + If set to ``ignore`` (or ``false``), no warning is printed. + If set to ``abort``, the command is aborted. + On Windows, this configuration option is ignored and the command aborted. ``quiet`` Reduce the amount of output printed. True or False. Default is False. ``remotecmd`` @@ -1065,6 +1096,8 @@ To disable SSL verification temporarily, specify ``--insecure`` from command line. +``cache`` + Whether to support caching in hgweb. Defaults to True. ``contact`` Name or email address of the person in charge of the repository. Defaults to ui.username or ``$EMAIL`` or "unknown" if unset or empty. @@ -1105,6 +1138,9 @@ Default is False. ``ipv6`` Whether to use IPv6. Default is False. +``logourl`` + Base URL to use for logos. If unset, ``http://mercurial.selenic.com/`` + will be used. ``name`` Repository name to use in the web interface. Default is current working directory.
--- a/hg Wed May 18 09:31:19 2011 +0200 +++ b/hg Wed May 18 09:56:27 2011 +0200 @@ -33,6 +33,6 @@ import mercurial.dispatch for fp in (sys.stdin, sys.stdout, sys.stderr): - mercurial.util.set_binary(fp) + mercurial.util.setbinary(fp) mercurial.dispatch.run()
--- a/hgext/bugzilla.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/bugzilla.py Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,7 @@ # bugzilla.py - bugzilla integration for mercurial # # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> +# Copyright 2011 Jim Hague <jim.hague@acm.org> # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. @@ -8,97 +9,161 @@ '''hooks for integrating with the Bugzilla bug tracker This hook extension adds comments on bugs in Bugzilla when changesets -that refer to bugs by Bugzilla ID are seen. The hook does not change -bug status. +that refer to bugs by Bugzilla ID are seen. The comment is formatted using +the Mercurial template mechanism. + +The hook does not change bug status. + +Three basic modes of access to Bugzilla are provided: + +1. Access via the Bugzilla XMLRPC interface. Requires Bugzilla 3.4 or later. + +2. Check data via the Bugzilla XMLRPC interface and submit bug change + via email to Bugzilla email interface. Requires Bugzilla 3.4 or later. + +3. Writing directly to the Bugzilla database. Only Bugzilla installations + using MySQL are supported. Requires Python MySQLdb. -The hook updates the Bugzilla database directly. Only Bugzilla -installations using MySQL are supported. +Writing directly to the database is susceptible to schema changes, and +relies on a Bugzilla contrib script to send out bug change +notification emails. This script runs as the user running Mercurial, +must be run on the host with the Bugzilla install, and requires +permission to read Bugzilla configuration details and the necessary +MySQL user and password to have full access rights to the Bugzilla +database. For these reasons this access mode is now considered +deprecated, and will not be updated for new Bugzilla versions going +forward. + +Access via XMLRPC needs a Bugzilla username and password to be specified +in the configuration. Comments are added under that username. Since the +configuration must be readable by all Mercurial users, it is recommended +that the rights of that user are restricted in Bugzilla to the minimum +necessary to add comments. -The hook relies on a Bugzilla script to send bug change notification -emails. That script changes between Bugzilla versions; the -'processmail' script used prior to 2.18 is replaced in 2.18 and -subsequent versions by 'config/sendbugmail.pl'. Note that these will -be run by Mercurial as the user pushing the change; you will need to -ensure the Bugzilla install file permissions are set appropriately. +Access via XMLRPC/email uses XMLRPC to query Bugzilla, but sends +email to the Bugzilla email interface to submit comments to bugs. +The From: address in the email is set to the email address of the Mercurial +user, so the comment appears to come from the Mercurial user. In the event +that the Mercurial user email is not recognised by Bugzilla as a Bugzilla +user, the email associated with the Bugzilla username used to log into +Bugzilla is used instead as the source of the comment. + +Configuration items common to all access modes: + +bugzilla.version + This access type to use. Values recognised are: + + :``xmlrpc``: Bugzilla XMLRPC interface. + :``xmlrpc+email``: Bugzilla XMLRPC and email interfaces. + :``3.0``: MySQL access, Bugzilla 3.0 and later. + :``2.18``: MySQL access, Bugzilla 2.18 and up to but not + including 3.0. + :``2.16``: MySQL access, Bugzilla 2.16 and up to but not + including 2.18. + +bugzilla.regexp + Regular expression to match bug IDs in changeset commit message. + Must contain one "()" group. The default expression matches ``Bug + 1234``, ``Bug no. 1234``, ``Bug number 1234``, ``Bugs 1234,5678``, + ``Bug 1234 and 5678`` and variations thereof. Matching is case + insensitive. + +bugzilla.style + The style file to use when formatting comments. + +bugzilla.template + Template to use when formatting comments. Overrides style if + specified. In addition to the usual Mercurial keywords, the + extension specifies: -The extension is configured through three different configuration -sections. These keys are recognized in the [bugzilla] section: + :``{bug}``: The Bugzilla bug ID. + :``{root}``: The full pathname of the Mercurial repository. + :``{webroot}``: Stripped pathname of the Mercurial repository. + :``{hgweb}``: Base URL for browsing Mercurial repositories. + + Default ``changeset {node|short} in repo {root} refers to bug + {bug}.\\ndetails:\\n\\t{desc|tabindent}`` + +bugzilla.strip + The number of path separator characters to strip from the front of + the Mercurial repository path (``{root}`` in templates) to produce + ``{webroot}``. For example, a repository with ``{root}`` + ``/var/local/my-project`` with a strip of 2 gives a value for + ``{webroot}`` of ``my-project``. Default 0. -host - Hostname of the MySQL server holding the Bugzilla database. +web.baseurl + Base URL for browsing Mercurial repositories. Referenced from + templates as ``{hgweb}``. + +Configuration items common to XMLRPC+email and MySQL access modes: + +bugzilla.usermap + Path of file containing Mercurial committer email to Bugzilla user email + mappings. If specified, the file should contain one mapping per + line:: + + committer = Bugzilla user + + See also the ``[usermap]`` section. + +The ``[usermap]`` section is used to specify mappings of Mercurial +committer email to Bugzilla user email. See also ``bugzilla.usermap``. +Contains entries of the form ``committer = Bugzilla user``. -db - Name of the Bugzilla database in MySQL. Default 'bugs'. +XMLRPC access mode configuration: + +bugzilla.bzurl + The base URL for the Bugzilla installation. + Default ``http://localhost/bugzilla``. + +bugzilla.user + The username to use to log into Bugzilla via XMLRPC. Default + ``bugs``. + +bugzilla.password + The password for Bugzilla login. + +XMLRPC+email access mode uses the XMLRPC access mode configuration items, +and also: -user - Username to use to access MySQL server. Default 'bugs'. +bugzilla.bzemail + The Bugzilla email address. + +In addition, the Mercurial email settings must be configured. See the +documentation in hgrc(5), sections ``[email]`` and ``[smtp]``. + +MySQL access mode configuration: -password +bugzilla.host + Hostname of the MySQL server holding the Bugzilla database. + Default ``localhost``. + +bugzilla.db + Name of the Bugzilla database in MySQL. Default ``bugs``. + +bugzilla.user + Username to use to access MySQL server. Default ``bugs``. + +bugzilla.password Password to use to access MySQL server. -timeout +bugzilla.timeout Database connection timeout (seconds). Default 5. -version - Bugzilla version. Specify '3.0' for Bugzilla versions 3.0 and later, - '2.18' for Bugzilla versions from 2.18 and '2.16' for versions prior - to 2.18. - -bzuser +bugzilla.bzuser Fallback Bugzilla user name to record comments with, if changeset committer cannot be found as a Bugzilla user. -bzdir +bugzilla.bzdir Bugzilla install directory. Used by default notify. Default - '/var/www/html/bugzilla'. - -notify - The command to run to get Bugzilla to send bug change notification - emails. Substitutes from a map with 3 keys, 'bzdir', 'id' (bug id) - and 'user' (committer bugzilla email). Default depends on version; - from 2.18 it is "cd %(bzdir)s && perl -T contrib/sendbugmail.pl - %(id)s %(user)s". - -regexp - Regular expression to match bug IDs in changeset commit message. - Must contain one "()" group. The default expression matches 'Bug - 1234', 'Bug no. 1234', 'Bug number 1234', 'Bugs 1234,5678', 'Bug - 1234 and 5678' and variations thereof. Matching is case insensitive. - -style - The style file to use when formatting comments. - -template - Template to use when formatting comments. Overrides style if - specified. In addition to the usual Mercurial keywords, the - extension specifies:: + ``/var/www/html/bugzilla``. - {bug} The Bugzilla bug ID. - {root} The full pathname of the Mercurial repository. - {webroot} Stripped pathname of the Mercurial repository. - {hgweb} Base URL for browsing Mercurial repositories. - - Default 'changeset {node|short} in repo {root} refers ' - 'to bug {bug}.\\ndetails:\\n\\t{desc|tabindent}' - -strip - The number of slashes to strip from the front of {root} to produce - {webroot}. Default 0. - -usermap - Path of file containing Mercurial committer ID to Bugzilla user ID - mappings. If specified, the file should contain one mapping per - line, "committer"="Bugzilla user". See also the [usermap] section. - -The [usermap] section is used to specify mappings of Mercurial -committer ID to Bugzilla user ID. See also [bugzilla].usermap. -"committer"="Bugzilla user" - -Finally, the [web] section supports one entry: - -baseurl - Base URL for browsing Mercurial repositories. Reference from - templates as {hgweb}. +bugzilla.notify + The command to run to get Bugzilla to send bug change notification + emails. Substitutes from a map with 3 keys, ``bzdir``, ``id`` (bug + id) and ``user`` (committer bugzilla email). Default depends on + version; from 2.18 it is "cd %(bzdir)s && perl -T + contrib/sendbugmail.pl %(id)s %(user)s". Activating the extension:: @@ -109,11 +174,58 @@ # run bugzilla hook on every change pulled or pushed in here incoming.bugzilla = python:hgext.bugzilla.hook -Example configuration: +Example configurations: + +XMLRPC example configuration. This uses the Bugzilla at +``http://my-project.org/bugzilla``, logging in as user +``bugmail@my-project.org`` with password ``plugh``. It is used with a +collection of Mercurial repositories in ``/var/local/hg/repos/``, +with a web interface at ``http://my-project.org/hg``. :: + + [bugzilla] + bzurl=http://my-project.org/bugzilla + user=bugmail@my-project.org + password=plugh + version=xmlrpc + template=Changeset {node|short} in {root|basename}. + {hgweb}/{webroot}/rev/{node|short}\\n + {desc}\\n + strip=5 + + [web] + baseurl=http://my-project.org/hg -This example configuration is for a collection of Mercurial -repositories in /var/local/hg/repos/ used with a local Bugzilla 3.2 -installation in /opt/bugzilla-3.2. :: +XMLRPC+email example configuration. This uses the Bugzilla at +``http://my-project.org/bugzilla``, logging in as user +``bugmail@my-project.org`` wityh password ``plugh``. It is used with a +collection of Mercurial repositories in ``/var/local/hg/repos/``, +with a web interface at ``http://my-project.org/hg``. Bug comments +are sent to the Bugzilla email address +``bugzilla@my-project.org``. :: + + [bugzilla] + bzurl=http://my-project.org/bugzilla + user=bugmail@my-project.org + password=plugh + version=xmlrpc + bzemail=bugzilla@my-project.org + template=Changeset {node|short} in {root|basename}. + {hgweb}/{webroot}/rev/{node|short}\\n + {desc}\\n + strip=5 + + [web] + baseurl=http://my-project.org/hg + + [usermap] + user@emaildomain.com=user.name@bugzilladomain.com + +MySQL example configuration. This has a local Bugzilla 3.2 installation +in ``/opt/bugzilla-3.2``. The MySQL database is on ``localhost``, +the Bugzilla database name is ``bugs`` and MySQL is +accessed with MySQL username ``bugs`` password ``XYZZY``. It is used +with a collection of Mercurial repositories in ``/var/local/hg/repos/``, +with a web interface at ``http://my-project.org/hg``. :: [bugzilla] host=localhost @@ -127,46 +239,98 @@ strip=5 [web] - baseurl=http://dev.domain.com/hg + baseurl=http://my-project.org/hg [usermap] user@emaildomain.com=user.name@bugzilladomain.com -Commits add a comment to the Bugzilla bug record of the form:: +All the above add a comment to the Bugzilla bug record of the form:: Changeset 3b16791d6642 in repository-name. - http://dev.domain.com/hg/repository-name/rev/3b16791d6642 + http://my-project.org/hg/repository-name/rev/3b16791d6642 Changeset commit comment. Bug 1234. ''' from mercurial.i18n import _ from mercurial.node import short -from mercurial import cmdutil, templater, util -import re, time - -MySQLdb = None +from mercurial import cmdutil, mail, templater, util +import re, time, xmlrpclib -def buglist(ids): - return '(' + ','.join(map(str, ids)) + ')' - -class bugzilla_2_16(object): - '''support for bugzilla version 2.16.''' +class bzaccess(object): + '''Base class for access to Bugzilla.''' def __init__(self, ui): self.ui = ui + usermap = self.ui.config('bugzilla', 'usermap') + if usermap: + self.ui.readconfig(usermap, sections=['usermap']) + + def map_committer(self, user): + '''map name of committer to Bugzilla user name.''' + for committer, bzuser in self.ui.configitems('usermap'): + if committer.lower() == user.lower(): + return bzuser + return user + + # Methods to be implemented by access classes. + def filter_real_bug_ids(self, ids): + '''remove bug IDs that do not exist in Bugzilla from set.''' + pass + + def filter_cset_known_bug_ids(self, node, ids): + '''remove bug IDs where node occurs in comment text from set.''' + pass + + def add_comment(self, bugid, text, committer): + '''add comment to bug. + + If possible add the comment as being from the committer of + the changeset. Otherwise use the default Bugzilla user. + ''' + pass + + def notify(self, ids, committer): + '''Force sending of Bugzilla notification emails.''' + pass + +# Bugzilla via direct access to MySQL database. +class bzmysql(bzaccess): + '''Support for direct MySQL access to Bugzilla. + + The earliest Bugzilla version this is tested with is version 2.16. + + If your Bugzilla is version 3.2 or above, you are strongly + recommended to use the XMLRPC access method instead. + ''' + + @staticmethod + def sql_buglist(ids): + '''return SQL-friendly list of bug ids''' + return '(' + ','.join(map(str, ids)) + ')' + + _MySQLdb = None + + def __init__(self, ui): + try: + import MySQLdb as mysql + bzmysql._MySQLdb = mysql + except ImportError, err: + raise util.Abort(_('python mysql support not available: %s') % err) + + bzaccess.__init__(self, ui) + host = self.ui.config('bugzilla', 'host', 'localhost') user = self.ui.config('bugzilla', 'user', 'bugs') passwd = self.ui.config('bugzilla', 'password') db = self.ui.config('bugzilla', 'db', 'bugs') timeout = int(self.ui.config('bugzilla', 'timeout', 5)) - usermap = self.ui.config('bugzilla', 'usermap') - if usermap: - self.ui.readconfig(usermap, sections=['usermap']) self.ui.note(_('connecting to %s:%s as %s, password %s\n') % (host, db, user, '*' * len(passwd))) - self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd, - db=db, connect_timeout=timeout) + self.conn = bzmysql._MySQLdb.connect(host=host, + user=user, passwd=passwd, + db=db, + connect_timeout=timeout) self.cursor = self.conn.cursor() self.longdesc_id = self.get_longdesc_id() self.user_ids = {} @@ -177,7 +341,7 @@ self.ui.note(_('query: %s %s\n') % (args, kwargs)) try: self.cursor.execute(*args, **kwargs) - except MySQLdb.MySQLError: + except bzmysql._MySQLdb.MySQLError: self.ui.note(_('failed query: %s %s\n') % (args, kwargs)) raise @@ -190,22 +354,22 @@ return ids[0][0] def filter_real_bug_ids(self, ids): - '''filter not-existing bug ids from list.''' - self.run('select bug_id from bugs where bug_id in %s' % buglist(ids)) - return sorted([c[0] for c in self.cursor.fetchall()]) + '''filter not-existing bug ids from set.''' + self.run('select bug_id from bugs where bug_id in %s' % + bzmysql.sql_buglist(ids)) + return set([c[0] for c in self.cursor.fetchall()]) - def filter_unknown_bug_ids(self, node, ids): - '''filter bug ids from list that already refer to this changeset.''' + def filter_cset_known_bug_ids(self, node, ids): + '''filter bug ids that already refer to this changeset from set.''' self.run('''select bug_id from longdescs where bug_id in %s and thetext like "%%%s%%"''' % - (buglist(ids), short(node))) - unknown = set(ids) + (bzmysql.sql_buglist(ids), short(node))) for (id,) in self.cursor.fetchall(): self.ui.status(_('bug %d already knows about changeset %s\n') % (id, short(node))) - unknown.discard(id) - return sorted(unknown) + ids.discard(id) + return ids def notify(self, ids, committer): '''tell bugzilla to send mail.''' @@ -230,7 +394,7 @@ if ret: self.ui.warn(out) raise util.Abort(_('bugzilla notify command %s') % - util.explain_exit(ret)[0]) + util.explainexit(ret)[0]) self.ui.status(_('done\n')) def get_user_id(self, user): @@ -251,15 +415,8 @@ self.user_ids[user] = userid return userid - def map_committer(self, user): - '''map name of committer to bugzilla user name.''' - for committer, bzuser in self.ui.configitems('usermap'): - if committer.lower() == user.lower(): - return bzuser - return user - def get_bugzilla_user(self, committer): - '''see if committer is a registered bugzilla user. Return + '''See if committer is a registered bugzilla user. Return bugzilla username and userid if so. If not, return default bugzilla username and userid.''' user = self.map_committer(committer) @@ -292,19 +449,19 @@ (bugid, userid, now, self.longdesc_id)) self.conn.commit() -class bugzilla_2_18(bugzilla_2_16): +class bzmysql_2_18(bzmysql): '''support for bugzilla 2.18 series.''' def __init__(self, ui): - bugzilla_2_16.__init__(self, ui) + bzmysql.__init__(self, ui) self.default_notify = \ "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s" -class bugzilla_3_0(bugzilla_2_18): +class bzmysql_3_0(bzmysql_2_18): '''support for bugzilla 3.0 series.''' def __init__(self, ui): - bugzilla_2_18.__init__(self, ui) + bzmysql_2_18.__init__(self, ui) def get_longdesc_id(self): '''get identity of longdesc field''' @@ -314,13 +471,176 @@ raise util.Abort(_('unknown database schema')) return ids[0][0] +# Buzgilla via XMLRPC interface. + +class CookieSafeTransport(xmlrpclib.SafeTransport): + """A SafeTransport that retains cookies over its lifetime. + + The regular xmlrpclib transports ignore cookies. Which causes + a bit of a problem when you need a cookie-based login, as with + the Bugzilla XMLRPC interface. + + So this is a SafeTransport which looks for cookies being set + in responses and saves them to add to all future requests. + It appears a SafeTransport can do both HTTP and HTTPS sessions, + which saves us having to do a CookieTransport too. + """ + + # Inspiration drawn from + # http://blog.godson.in/2010/09/how-to-make-python-xmlrpclib-client.html + # http://www.itkovian.net/base/transport-class-for-pythons-xml-rpc-lib/ + + cookies = [] + def send_cookies(self, connection): + if self.cookies: + for cookie in self.cookies: + connection.putheader("Cookie", cookie) + + def request(self, host, handler, request_body, verbose=0): + self.verbose = verbose + + # issue XML-RPC request + h = self.make_connection(host) + if verbose: + h.set_debuglevel(1) + + self.send_request(h, handler, request_body) + self.send_host(h, host) + self.send_cookies(h) + self.send_user_agent(h) + self.send_content(h, request_body) + + # Deal with differences between Python 2.4-2.6 and 2.7. + # In the former h is a HTTP(S). In the latter it's a + # HTTP(S)Connection. Luckily, the 2.4-2.6 implementation of + # HTTP(S) has an underlying HTTP(S)Connection, so extract + # that and use it. + try: + response = h.getresponse() + except AttributeError: + response = h._conn.getresponse() + + # Add any cookie definitions to our list. + for header in response.msg.getallmatchingheaders("Set-Cookie"): + val = header.split(": ", 1)[1] + cookie = val.split(";", 1)[0] + self.cookies.append(cookie) + + if response.status != 200: + raise xmlrpclib.ProtocolError(host + handler, response.status, + response.reason, response.msg.headers) + + payload = response.read() + parser, unmarshaller = self.getparser() + parser.feed(payload) + parser.close() + + return unmarshaller.close() + +class bzxmlrpc(bzaccess): + """Support for access to Bugzilla via the Bugzilla XMLRPC API. + + Requires a minimum Bugzilla version 3.4. + """ + + def __init__(self, ui): + bzaccess.__init__(self, ui) + + bzweb = self.ui.config('bugzilla', 'bzurl', + 'http://localhost/bugzilla/') + bzweb = bzweb.rstrip("/") + "/xmlrpc.cgi" + + user = self.ui.config('bugzilla', 'user', 'bugs') + passwd = self.ui.config('bugzilla', 'password') + + self.bzproxy = xmlrpclib.ServerProxy(bzweb, CookieSafeTransport()) + self.bzproxy.User.login(dict(login=user, password=passwd)) + + def get_bug_comments(self, id): + """Return a string with all comment text for a bug.""" + c = self.bzproxy.Bug.comments(dict(ids=[id])) + return ''.join([t['text'] for t in c['bugs'][str(id)]['comments']]) + + def filter_real_bug_ids(self, ids): + res = set() + bugs = self.bzproxy.Bug.get(dict(ids=sorted(ids), permissive=True)) + for bug in bugs['bugs']: + res.add(bug['id']) + return res + + def filter_cset_known_bug_ids(self, node, ids): + for id in sorted(ids): + if self.get_bug_comments(id).find(short(node)) != -1: + self.ui.status(_('bug %d already knows about changeset %s\n') % + (id, short(node))) + ids.discard(id) + return ids + + def add_comment(self, bugid, text, committer): + self.bzproxy.Bug.add_comment(dict(id=bugid, comment=text)) + +class bzxmlrpcemail(bzxmlrpc): + """Read data from Bugzilla via XMLRPC, send updates via email. + + Advantages of sending updates via email: + 1. Comments can be added as any user, not just logged in user. + 2. Bug statuses and other fields not accessible via XMLRPC can + be updated. This is not currently used. + """ + + def __init__(self, ui): + bzxmlrpc.__init__(self, ui) + + self.bzemail = self.ui.config('bugzilla', 'bzemail') + if not self.bzemail: + raise util.Abort(_("configuration 'bzemail' missing")) + mail.validateconfig(self.ui) + + def send_bug_modify_email(self, bugid, commands, comment, committer): + '''send modification message to Bugzilla bug via email. + + The message format is documented in the Bugzilla email_in.pl + specification. commands is a list of command lines, comment is the + comment text. + + To stop users from crafting commit comments with + Bugzilla commands, specify the bug ID via the message body, rather + than the subject line, and leave a blank line after it. + ''' + user = self.map_committer(committer) + matches = self.bzproxy.User.get(dict(match=[user])) + if not matches['users']: + user = self.ui.config('bugzilla', 'user', 'bugs') + matches = self.bzproxy.User.get(dict(match=[user])) + if not matches['users']: + raise util.Abort(_("default bugzilla user %s email not found") % + user) + user = matches['users'][0]['email'] + + text = "\n".join(commands) + "\n@bug_id = %d\n\n" % bugid + comment + + _charsets = mail._charsets(self.ui) + user = mail.addressencode(self.ui, user, _charsets) + bzemail = mail.addressencode(self.ui, self.bzemail, _charsets) + msg = mail.mimeencode(self.ui, text, _charsets) + msg['From'] = user + msg['To'] = bzemail + msg['Subject'] = mail.headencode(self.ui, "Bug modification", _charsets) + sendmail = mail.connect(self.ui) + sendmail(user, bzemail, msg.as_string()) + + def add_comment(self, bugid, text, committer): + self.send_bug_modify_email(bugid, [], text, committer) + class bugzilla(object): # supported versions of bugzilla. different versions have # different schemas. _versions = { - '2.16': bugzilla_2_16, - '2.18': bugzilla_2_18, - '3.0': bugzilla_3_0 + '2.16': bzmysql, + '2.18': bzmysql_2_18, + '3.0': bzmysql_3_0, + 'xmlrpc': bzxmlrpc, + 'xmlrpc+email': bzxmlrpcemail } _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*' @@ -353,10 +673,12 @@ _split_re = None def find_bug_ids(self, ctx): - '''find valid bug ids that are referred to in changeset - comments and that do not already have references to this - changeset.''' + '''return set of integer bug IDs from commit comment. + Extract bug IDs from changeset comments. Filter out any that are + not known to Bugzilla, and any that already have a reference to + the given changeset in their comments. + ''' if bugzilla._bug_re is None: bugzilla._bug_re = re.compile( self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re), @@ -376,7 +698,7 @@ if ids: ids = self.filter_real_bug_ids(ids) if ids: - ids = self.filter_unknown_bug_ids(ctx.node(), ids) + ids = self.filter_cset_known_bug_ids(ctx.node(), ids) return ids def update(self, bugid, ctx): @@ -418,13 +740,6 @@ '''add comment to bugzilla for each changeset that refers to a bugzilla bug id. only add a comment once per bug, so same change seen multiple times does not fill bug with duplicate data.''' - try: - import MySQLdb as mysql - global MySQLdb - MySQLdb = mysql - except ImportError, err: - raise util.Abort(_('python mysql support not available: %s') % err) - if node is None: raise util.Abort(_('hook type %s does not pass a changeset id') % hooktype) @@ -436,6 +751,6 @@ for id in ids: bz.update(id, ctx) bz.notify(ids, util.email(ctx.user())) - except MySQLdb.MySQLError, err: - raise util.Abort(_('database error: %s') % err.args[1]) + except Exception, e: + raise util.Abort(_('Bugzilla error: %s') % e)
--- a/hgext/churn.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/churn.py Wed May 18 09:56:27 2011 +0200 @@ -9,7 +9,7 @@ '''command to display statistics about repository history''' from mercurial.i18n import _ -from mercurial import patch, cmdutil, util, templater, commands +from mercurial import patch, cmdutil, scmutil, util, templater, commands import os import time, datetime @@ -24,7 +24,7 @@ def changedlines(ui, repo, ctx1, ctx2, fns): added, removed = 0, 0 - fmatch = cmdutil.matchfiles(repo, fns) + fmatch = scmutil.matchfiles(repo, fns) diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch)) for l in diff.split('\n'): if l.startswith("+") and not l.startswith("+++ "): @@ -54,15 +54,14 @@ if opts.get('date'): df = util.matchdate(opts['date']) - m = cmdutil.match(repo, pats, opts) + m = scmutil.match(repo, pats, opts) def prep(ctx, fns): rev = ctx.rev() if df and not df(ctx.date()[0]): # doesn't match date format return - key = getkey(ctx) + key = getkey(ctx).strip() key = amap.get(key, key) # alias remap - key = key.strip() # ignore leading and trailing spaces if opts.get('changesets'): rate[key] = (rate.get(key, (0,))[0] + 1, 0) else:
--- a/hgext/color.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/color.py Wed May 18 09:56:27 2011 +0200 @@ -18,14 +18,16 @@ '''colorize output from some commands -This extension modifies the status and resolve commands to add color to their -output to reflect file status, the qseries command to add color to reflect -patch status (applied, unapplied, missing), and to diff-related -commands to highlight additions, removals, diff headers, and trailing -whitespace. +This extension modifies the status and resolve commands to add color +to their output to reflect file status, the qseries command to add +color to reflect patch status (applied, unapplied, missing), and to +diff-related commands to highlight additions, removals, diff headers, +and trailing whitespace. Other effects in addition to color, like bold and underlined text, are -also available. Effects are rendered with the ECMA-48 SGR control +also available. By default, the terminfo database is used to find the +terminal codes used to change color and effect. If terminfo is not +available, then effects are rendered with the ECMA-48 SGR control function (aka ANSI escape codes). Default effects may be overridden from your configuration file:: @@ -66,13 +68,35 @@ branches.current = green branches.inactive = none -The color extension will try to detect whether to use ANSI codes or -Win32 console APIs, unless it is made explicit:: +The available effects in terminfo mode are 'blink', 'bold', 'dim', +'inverse', 'invisible', 'italic', 'standout', and 'underline'; in +ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and +'underline'. How each is rendered depends on the terminal emulator. +Some may not be available for a given terminal type, and will be +silently ignored. + +Because there are only eight standard colors, this module allows you +to define color names for other color slots which might be available +for your terminal type, assuming terminfo mode. For instance:: + + color.brightblue = 12 + color.pink = 207 + color.orange = 202 + +to set 'brightblue' to color slot 12 (useful for 16 color terminals +that have brighter colors defined in the upper eight) and, 'pink' and +'orange' to colors in 256-color xterm's default color cube. These +defined colors may then be used as any of the pre-defined eight, +including appending '_background' to set the background to that color. + +The color extension will try to detect whether to use terminfo, ANSI +codes or Win32 console APIs, unless it is made explicit; e.g.:: [color] mode = ansi -Any value other than 'ansi', 'win32', or 'auto' will disable color. +Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will +disable color. ''' @@ -90,6 +114,66 @@ 'blue_background': 44, 'purple_background': 45, 'cyan_background': 46, 'white_background': 47} +def _terminfosetup(ui): + '''Initialize terminfo data and the terminal if we're in terminfo mode.''' + + global _terminfo_params + # If we failed to load curses, we go ahead and return. + if not _terminfo_params: + return + # Otherwise, see what the config file says. + mode = ui.config('color', 'mode', 'auto') + if mode not in ('auto', 'terminfo'): + return + + _terminfo_params.update((key[6:], (False, int(val))) + for key, val in ui.configitems('color') + if key.startswith('color.')) + + try: + curses.setupterm() + except curses.error, e: + _terminfo_params = {} + return + + for key, (b, e) in _terminfo_params.items(): + if not b: + continue + if not curses.tigetstr(e): + # Most terminals don't support dim, invis, etc, so don't be + # noisy and use ui.debug(). + ui.debug("no terminfo entry for %s\n" % e) + del _terminfo_params[key] + if not curses.tigetstr('setaf') or not curses.tigetstr('setab'): + ui.warn(_("no terminfo entry for setab/setaf: reverting to " + "ECMA-48 color\n")) + _terminfo_params = {} + +try: + import curses + # Mapping from effect name to terminfo attribute name or color number. + # This will also force-load the curses module. + _terminfo_params = {'none': (True, 'sgr0'), + 'standout': (True, 'smso'), + 'underline': (True, 'smul'), + 'reverse': (True, 'rev'), + 'inverse': (True, 'rev'), + 'blink': (True, 'blink'), + 'dim': (True, 'dim'), + 'bold': (True, 'bold'), + 'invisible': (True, 'invis'), + 'italic': (True, 'sitm'), + 'black': (False, curses.COLOR_BLACK), + 'red': (False, curses.COLOR_RED), + 'green': (False, curses.COLOR_GREEN), + 'yellow': (False, curses.COLOR_YELLOW), + 'blue': (False, curses.COLOR_BLUE), + 'magenta': (False, curses.COLOR_MAGENTA), + 'cyan': (False, curses.COLOR_CYAN), + 'white': (False, curses.COLOR_WHITE)} +except ImportError: + _terminfo_params = False + _styles = {'grep.match': 'red bold', 'bookmarks.current': 'green', 'branches.active': 'none', @@ -107,6 +191,7 @@ 'diff.trailingwhitespace': 'bold red_background', 'diffstat.deleted': 'red', 'diffstat.inserted': 'green', + 'ui.prompt': 'yellow', 'log.changeset': 'yellow', 'resolve.resolved': 'green bold', 'resolve.unresolved': 'red bold', @@ -120,13 +205,33 @@ 'status.unknown': 'magenta bold underline'} +def _effect_str(effect): + '''Helper function for render_effects().''' + + bg = False + if effect.endswith('_background'): + bg = True + effect = effect[:-11] + attr, val = _terminfo_params[effect] + if attr: + return curses.tigetstr(val) + elif bg: + return curses.tparm(curses.tigetstr('setab'), val) + else: + return curses.tparm(curses.tigetstr('setaf'), val) + def render_effects(text, effects): 'Wrap text in commands to turn on each effect.' if not text: return text - start = [str(_effects[e]) for e in ['none'] + effects.split()] - start = '\033[' + ';'.join(start) + 'm' - stop = '\033[' + str(_effects['none']) + 'm' + if not _terminfo_params: + start = [str(_effects[e]) for e in ['none'] + effects.split()] + start = '\033[' + ';'.join(start) + 'm' + stop = '\033[' + str(_effects['none']) + 'm' + else: + start = ''.join(_effect_str(effect) + for effect in ['none'] + effects.split()) + stop = _effect_str('none') return ''.join([start, text, stop]) def extstyles(): @@ -135,13 +240,15 @@ def configstyles(ui): for status, cfgeffects in ui.configitems('color'): - if '.' not in status: + if '.' not in status or status.startswith('color.'): continue cfgeffects = ui.configlist('color', status) if cfgeffects: good = [] for e in cfgeffects: - if e in _effects: + if not _terminfo_params and e in _effects: + good.append(e) + elif e in _terminfo_params or e[:-11] in _terminfo_params: good.append(e) else: ui.warn(_("ignoring unknown color/effect %r " @@ -183,7 +290,7 @@ s = _styles.get(l, '') if s: effects.append(s) - effects = ''.join(effects) + effects = ' '.join(effects) if effects: return '\n'.join([render_effects(s, effects) for s in msg.split('\n')]) @@ -191,30 +298,43 @@ def uisetup(ui): + global _terminfo_params if ui.plain(): return + + formatted = (os.environ.get('TERM') != 'dumb' and ui.formatted()) mode = ui.config('color', 'mode', 'auto') if mode == 'auto': if os.name == 'nt' and 'TERM' not in os.environ: # looks line a cmd.exe console, use win32 API or nothing mode = w32effects and 'win32' or 'none' else: - mode = 'ansi' + if not formatted: + _terminfo_params = False + else: + _terminfosetup(ui) + if not _terminfo_params: + mode = 'ansi' + else: + mode = 'terminfo' if mode == 'win32': if w32effects is None: # only warn if color.mode is explicitly set to win32 ui.warn(_('warning: failed to set color mode to %s\n') % mode) return _effects.update(w32effects) - elif mode != 'ansi': + elif mode == 'ansi': + _terminfo_params = {} + elif mode == 'terminfo': + _terminfosetup(ui) + else: return def colorcmd(orig, ui_, opts, cmd, cmdfunc): coloropt = opts['color'] auto = coloropt == 'auto' always = util.parsebool(coloropt) if (always or - (always is None and - (auto and (os.environ.get('TERM') != 'dumb' and ui_.formatted())))): + (always is None and auto and formatted)): colorui._colormode = mode colorui.__bases__ = (ui_.__class__,) ui_.__class__ = colorui @@ -348,13 +468,15 @@ # Look for ANSI-like codes embedded in text m = re.match(ansire, text) - while m: - for sattr in m.group(1).split(';'): - if sattr: - attr = mapcolor(int(sattr), attr) - _kernel32.SetConsoleTextAttribute(stdout, attr) - orig(m.group(2), **opts) - m = re.match(ansire, m.group(3)) - # Explicity reset original attributes - _kernel32.SetConsoleTextAttribute(stdout, origattr) + try: + while m: + for sattr in m.group(1).split(';'): + if sattr: + attr = mapcolor(int(sattr), attr) + _kernel32.SetConsoleTextAttribute(stdout, attr) + orig(m.group(2), **opts) + m = re.match(ansire, m.group(3)) + finally: + # Explicity reset original attributes + _kernel32.SetConsoleTextAttribute(stdout, origattr)
--- a/hgext/convert/__init__.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/convert/__init__.py Wed May 18 09:56:27 2011 +0200 @@ -10,7 +10,7 @@ import convcmd import cvsps import subversion -from mercurial import commands +from mercurial import commands, templatekw from mercurial.i18n import _ # Commands definition was moved elsewhere to ease demandload job. @@ -334,3 +334,34 @@ ], _('hg debugcvsps [OPTION]... [PATH]...')), } + +def kwconverted(ctx, name): + rev = ctx.extra().get('convert_revision', '') + if rev.startswith('svn:'): + if name == 'svnrev': + return str(subversion.revsplit(rev)[2]) + elif name == 'svnpath': + return subversion.revsplit(rev)[1] + elif name == 'svnuuid': + return subversion.revsplit(rev)[0] + return rev + +def kwsvnrev(repo, ctx, **args): + """:svnrev: String. Converted subversion revision number.""" + return kwconverted(ctx, 'svnrev') + +def kwsvnpath(repo, ctx, **args): + """:svnpath: String. Converted subversion revision project path.""" + return kwconverted(ctx, 'svnpath') + +def kwsvnuuid(repo, ctx, **args): + """:svnuuid: String. Converted subversion revision repository identifier.""" + return kwconverted(ctx, 'svnuuid') + +def extsetup(ui): + templatekw.keywords['svnrev'] = kwsvnrev + templatekw.keywords['svnpath'] = kwsvnpath + templatekw.keywords['svnuuid'] = kwsvnuuid + +# tell hggettext to extract docstrings from these functions: +i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
--- a/hgext/convert/common.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/convert/common.py Wed May 18 09:56:27 2011 +0200 @@ -29,7 +29,7 @@ def checktool(exe, name=None, abort=True): name = name or exe - if not util.find_exe(exe): + if not util.findexe(exe): exc = abort and util.Abort or MissingTool raise exc(_('cannot find required "%s" tool') % name) @@ -151,6 +151,13 @@ """ return None + def getbookmarks(self): + """Return the bookmarks as a dictionary of name: revision + + Bookmark names are to be UTF-8 strings. + """ + return {} + class converter_sink(object): """Conversion sink (target) interface""" @@ -228,6 +235,13 @@ def after(self): pass + def putbookmarks(self, bookmarks): + """Put bookmarks into sink. + + bookmarks: {bookmarkname: sink_rev_id, ...} + where bookmarkname is an UTF-8 string. + """ + pass class commandline(object): def __init__(self, ui, command): @@ -240,7 +254,7 @@ def postrun(self): pass - def _cmdline(self, cmd, *args, **kwargs): + def _cmdline(self, cmd, closestdin, *args, **kwargs): cmdline = [self.command, cmd] + list(args) for k, v in kwargs.iteritems(): if len(k) == 1: @@ -257,16 +271,23 @@ cmdline = [util.shellquote(arg) for arg in cmdline] if not self.ui.debugflag: cmdline += ['2>', util.nulldev] - cmdline += ['<', util.nulldev] + if closestdin: + cmdline += ['<', util.nulldev] cmdline = ' '.join(cmdline) return cmdline def _run(self, cmd, *args, **kwargs): - cmdline = self._cmdline(cmd, *args, **kwargs) + return self._dorun(util.popen, cmd, True, *args, **kwargs) + + def _run2(self, cmd, *args, **kwargs): + return self._dorun(util.popen2, cmd, False, *args, **kwargs) + + def _dorun(self, openfunc, cmd, closestdin, *args, **kwargs): + cmdline = self._cmdline(cmd, closestdin, *args, **kwargs) self.ui.debug('running: %s\n' % (cmdline,)) self.prerun() try: - return util.popen(cmdline) + return openfunc(cmdline) finally: self.postrun() @@ -287,7 +308,7 @@ if output: self.ui.warn(_('%s error:\n') % self.command) self.ui.warn(output) - msg = util.explain_exit(status)[0] + msg = util.explainexit(status)[0] raise util.Abort('%s %s' % (self.command, msg)) def run0(self, cmd, *args, **kwargs): @@ -322,8 +343,9 @@ self._argmax = self._argmax / 2 - 1 return self._argmax - def limit_arglist(self, arglist, cmd, *args, **kwargs): - limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs)) + def limit_arglist(self, arglist, cmd, closestdin, *args, **kwargs): + cmdlen = len(self._cmdline(cmd, closestdin, *args, **kwargs)) + limit = self.getargmax() - cmdlen bytes = 0 fl = [] for fn in arglist: @@ -339,7 +361,7 @@ yield fl def xargs(self, arglist, cmd, *args, **kwargs): - for l in self.limit_arglist(arglist, cmd, *args, **kwargs): + for l in self.limit_arglist(arglist, cmd, True, *args, **kwargs): self.run0(cmd, *(list(args) + l), **kwargs) class mapfile(dict):
--- a/hgext/convert/convcmd.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/convert/convcmd.py Wed May 18 09:56:27 2011 +0200 @@ -378,6 +378,16 @@ if tagsparents: self.map[tagsparents[0][0]] = nrev + bookmarks = self.source.getbookmarks() + cbookmarks = {} + for k in bookmarks: + v = bookmarks[k] + if self.map.get(v, SKIPREV) != SKIPREV: + cbookmarks[k] = self.map[v] + + if c and cbookmarks: + self.dest.putbookmarks(cbookmarks) + self.writeauthormap() finally: self.cleanup()
--- a/hgext/convert/darcs.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/convert/darcs.py Wed May 18 09:56:27 2011 +0200 @@ -7,7 +7,7 @@ from common import NoRepo, checktool, commandline, commit, converter_source from mercurial.i18n import _ -from mercurial import encoding, util +from mercurial import util import os, shutil, tempfile, re # The naming drift of ElementTree is fun! @@ -191,7 +191,7 @@ if rev != self.lastrev: raise util.Abort(_('internal calling inconsistency')) path = os.path.join(self.tmppath, name) - data = open(path, 'rb').read() + data = util.readfile(path) mode = os.lstat(path).st_mode mode = (mode & 0111) and 'x' or '' return data, mode
--- a/hgext/convert/git.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/convert/git.py Wed May 18 09:56:27 2011 +0200 @@ -17,19 +17,27 @@ # cannot remove environment variable. Just assume none have # both issues. if hasattr(os, 'unsetenv'): - def gitopen(self, s): + def gitopen(self, s, noerr=False): prevgitdir = os.environ.get('GIT_DIR') os.environ['GIT_DIR'] = self.path try: - return util.popen(s, 'rb') + if noerr: + (stdin, stdout, stderr) = util.popen3(s) + return stdout + else: + return util.popen(s, 'rb') finally: if prevgitdir is None: del os.environ['GIT_DIR'] else: os.environ['GIT_DIR'] = prevgitdir else: - def gitopen(self, s): - return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb') + def gitopen(self, s, noerr=False): + if noerr: + (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s)) + return so + else: + util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb') def gitread(self, s): fh = self.gitopen(s) @@ -168,3 +176,30 @@ raise util.Abort(_('cannot read changes in %s') % version) return changes + + def getbookmarks(self): + bookmarks = {} + + # Interesting references in git are prefixed + prefix = 'refs/heads/' + prefixlen = len(prefix) + + # factor two commands + gitcmd = { 'remote/': 'git ls-remote --heads origin', + '': 'git show-ref'} + + # Origin heads + for reftype in gitcmd: + try: + fh = self.gitopen(gitcmd[reftype], noerr=True) + for line in fh: + line = line.strip() + rev, name = line.split(None, 1) + if not name.startswith(prefix): + continue + name = '%s%s' % (reftype, name[prefixlen:]) + bookmarks[name] = rev + except: + pass + + return bookmarks
--- a/hgext/convert/gnuarch.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/convert/gnuarch.py Wed May 18 09:56:27 2011 +0200 @@ -36,10 +36,10 @@ # Could use checktool, but we want to check for baz or tla. self.execmd = None - if util.find_exe('baz'): + if util.findexe('baz'): self.execmd = 'baz' else: - if util.find_exe('tla'): + if util.findexe('tla'): self.execmd = 'tla' else: raise util.Abort(_('cannot find a GNU Arch tool'))
--- a/hgext/convert/hg.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/convert/hg.py Wed May 18 09:56:27 2011 +0200 @@ -21,7 +21,7 @@ import os, time, cStringIO from mercurial.i18n import _ from mercurial.node import bin, hex, nullid -from mercurial import hg, util, context, error +from mercurial import hg, util, context, bookmarks, error from common import NoRepo, commit, converter_source, converter_sink @@ -214,6 +214,16 @@ def setfilemapmode(self, active): self.filemapmode = active + def putbookmarks(self, updatedbookmark): + if not len(updatedbookmark): + return + + self.ui.status(_("updating bookmarks\n")) + for bookmark in updatedbookmark: + self.repo._bookmarks[bookmark] = bin(updatedbookmark[bookmark]) + bookmarks.write(self.repo) + + class mercurial_source(converter_source): def __init__(self, ui, path, rev=None): converter_source.__init__(self, ui, path, rev) @@ -277,10 +287,9 @@ parents = self.parents(ctx) if not parents: files = sorted(ctx.manifest()) - if self.ignoreerrors: - # calling getcopies() is a simple way to detect missing - # revlogs and populate self.ignored - self.getcopies(ctx, parents, files) + # getcopies() is not needed for roots, but it is a simple way to + # detect missing revlogs and abort on errors or populate self.ignored + self.getcopies(ctx, parents, files) return [(f, rev) for f in files if f not in self.ignored], {} if self._changescache and self._changescache[0] == rev: m, a, r = self._changescache[1] @@ -374,3 +383,6 @@ return hex(self.repo.lookup(rev)) except error.RepoError: return None + + def getbookmarks(self): + return bookmarks.listbookmarks(self.repo)
--- a/hgext/convert/monotone.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/convert/monotone.py Wed May 18 09:56:27 2011 +0200 @@ -19,13 +19,17 @@ self.ui = ui self.path = path + self.automatestdio = False + self.rev = rev norepo = NoRepo(_("%s does not look like a monotone repository") % path) if not os.path.exists(os.path.join(path, '_MTN')): # Could be a monotone repository (SQLite db file) try: - header = file(path, 'rb').read(16) + f = file(path, 'rb') + header = f.read(16) + f.close() except: header = '' if header != 'SQLite format 3\x00': @@ -64,18 +68,103 @@ checktool('mtn', abort=False) - # test if there are any revisions - self.rev = None - try: - self.getheads() - except: - raise norepo - self.rev = rev + def mtnrun(self, *args, **kwargs): + if self.automatestdio: + return self.mtnrunstdio(*args, **kwargs) + else: + return self.mtnrunsingle(*args, **kwargs) - def mtnrun(self, *args, **kwargs): + def mtnrunsingle(self, *args, **kwargs): kwargs['d'] = self.path return self.run0('automate', *args, **kwargs) + def mtnrunstdio(self, *args, **kwargs): + # Prepare the command in automate stdio format + command = [] + for k, v in kwargs.iteritems(): + command.append("%s:%s" % (len(k), k)) + if v: + command.append("%s:%s" % (len(v), v)) + if command: + command.insert(0, 'o') + command.append('e') + + command.append('l') + for arg in args: + command += "%s:%s" % (len(arg), arg) + command.append('e') + command = ''.join(command) + + self.ui.debug("mtn: sending '%s'\n" % command) + self.mtnwritefp.write(command) + self.mtnwritefp.flush() + + return self.mtnstdioreadcommandoutput(command) + + def mtnstdioreadpacket(self): + read = None + commandnbr = '' + while read != ':': + read = self.mtnreadfp.read(1) + if not read: + raise util.Abort(_('bad mtn packet - no end of commandnbr')) + commandnbr += read + commandnbr = commandnbr[:-1] + + stream = self.mtnreadfp.read(1) + if stream not in 'mewptl': + raise util.Abort(_('bad mtn packet - bad stream type %s' % stream)) + + read = self.mtnreadfp.read(1) + if read != ':': + raise util.Abort(_('bad mtn packet - no divider before size')) + + read = None + lengthstr = '' + while read != ':': + read = self.mtnreadfp.read(1) + if not read: + raise util.Abort(_('bad mtn packet - no end of packet size')) + lengthstr += read + try: + length = long(lengthstr[:-1]) + except TypeError: + raise util.Abort(_('bad mtn packet - bad packet size %s') + % lengthstr) + + read = self.mtnreadfp.read(length) + if len(read) != length: + raise util.Abort(_("bad mtn packet - unable to read full packet " + "read %s of %s") % (len(read), length)) + + return (commandnbr, stream, length, read) + + def mtnstdioreadcommandoutput(self, command): + retval = [] + while True: + commandnbr, stream, length, output = self.mtnstdioreadpacket() + self.ui.debug('mtn: read packet %s:%s:%s\n' % + (commandnbr, stream, length)) + + if stream == 'l': + # End of command + if output != '0': + raise util.Abort(_("mtn command '%s' returned %s") % + (command, output)) + break + elif stream in 'ew': + # Error, warning output + self.ui.warn(_('%s error:\n') % self.command) + self.ui.warn(output) + elif stream == 'p': + # Progress messages + self.ui.debug('mtn: ' + output) + elif stream == 'm': + # Main stream - command output + retval.append(output) + + return ''.join(retval) + def mtnloadmanifest(self, rev): if self.manifest_rev == rev: return @@ -204,14 +293,18 @@ return data, attr def getcommit(self, rev): - certs = self.mtngetcerts(rev) + extra = {} + certs = self.mtngetcerts(rev) + if certs.get('suspend') == certs["branch"]: + extra['close'] = '1' return commit( author=certs["author"], date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")), desc=certs["changelog"], rev=rev, parents=self.mtnrun("parents", rev).splitlines(), - branch=certs["branch"]) + branch=certs["branch"], + extra=extra) def gettags(self): tags = {} @@ -225,3 +318,43 @@ # This function is only needed to support --filemap # ... and we don't support that raise NotImplementedError() + + def before(self): + # Check if we have a new enough version to use automate stdio + version = 0.0 + try: + versionstr = self.mtnrunsingle("interface_version") + version = float(versionstr) + except Exception: + raise util.Abort(_("unable to determine mtn automate interface " + "version")) + + if version >= 12.0: + self.automatestdio = True + self.ui.debug("mtn automate version %s - using automate stdio\n" % + version) + + # launch the long-running automate stdio process + self.mtnwritefp, self.mtnreadfp = self._run2('automate', 'stdio', + '-d', self.path) + # read the headers + read = self.mtnreadfp.readline() + if read != 'format-version: 2\n': + raise util.Abort(_('mtn automate stdio header unexpected: %s') + % read) + while read != '\n': + read = self.mtnreadfp.readline() + if not read: + raise util.Abort(_("failed to reach end of mtn automate " + "stdio headers")) + else: + self.ui.debug("mtn automate version %s - not using automate stdio " + "(automate >= 12.0 - mtn >= 0.46 is needed)\n" % version) + + def after(self): + if self.automatestdio: + self.mtnwritefp.close() + self.mtnwritefp = None + self.mtnreadfp.close() + self.mtnreadfp = None +
--- a/hgext/convert/subversion.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/convert/subversion.py Wed May 18 09:56:27 2011 +0200 @@ -10,7 +10,7 @@ import urllib import urllib2 -from mercurial import strutil, util, encoding +from mercurial import strutil, scmutil, util, encoding from mercurial.i18n import _ # Subversion stuff. Works best with very recent Python SVN bindings @@ -41,6 +41,15 @@ class SvnPathNotFound(Exception): pass +def revsplit(rev): + """Parse a revision string and return (uuid, path, revnum).""" + url, revnum = rev.rsplit('@', 1) + parts = url.split('/', 1) + mod = '' + if len(parts) > 1: + mod = '/' + parts[1] + return parts[0][4:], mod, int(revnum) + def geturl(path): try: return svn.client.url_from_path(svn.core.svn_path_canonicalize(path)) @@ -103,8 +112,8 @@ """Fetch SVN log in a subprocess and channel them back to parent to avoid memory collection issues. """ - util.set_binary(sys.stdin) - util.set_binary(sys.stdout) + util.setbinary(sys.stdin) + util.setbinary(sys.stdout) args = decodeargs(sys.stdin.read()) get_log_child(sys.stdout, *args) @@ -259,6 +268,7 @@ except ValueError: raise util.Abort(_('svn: revision %s is not an integer') % rev) + self.trunkname = self.ui.config('convert', 'svn.trunk', 'trunk').strip('/') self.startrev = self.ui.config('convert', 'svn.startrev', default=0) try: self.startrev = int(self.startrev) @@ -268,7 +278,10 @@ raise util.Abort(_('svn: start revision %s is not an integer') % self.startrev) - self.head = self.latest(self.module, latest) + try: + self.head = self.latest(self.module, latest) + except SvnPathNotFound: + self.head = None if not self.head: raise util.Abort(_('no revision found in module %s') % self.module) @@ -285,7 +298,7 @@ def setrevmap(self, revmap): lastrevs = {} for revid in revmap.iterkeys(): - uuid, module, revnum = self.revsplit(revid) + uuid, module, revnum = revsplit(revid) lastrevnum = lastrevs.setdefault(module, revnum) if revnum > lastrevnum: lastrevs[module] = revnum @@ -380,7 +393,7 @@ files, self.removed, copies = self.expandpaths(rev, paths, parents) else: # Perform a full checkout on roots - uuid, module, revnum = self.revsplit(rev) + uuid, module, revnum = revsplit(rev) entries = svn.client.ls(self.baseurl + urllib.quote(module), optrev(revnum), True, self.ctx) files = [n for n, e in entries.iteritems() @@ -402,7 +415,7 @@ def getcommit(self, rev): if rev not in self.commits: - uuid, module, revnum = self.revsplit(rev) + uuid, module, revnum = revsplit(rev) self.module = module self.reparent(module) # We assume that: @@ -529,16 +542,6 @@ def revnum(self, rev): return int(rev.split('@')[-1]) - def revsplit(self, rev): - url, revnum = rev.rsplit('@', 1) - revnum = int(revnum) - parts = url.split('/', 1) - uuid = parts.pop(0)[4:] - mod = '' - if parts: - mod = '/' + parts[0] - return uuid, mod, revnum - def latest(self, path, stop=0): """Find the latest revid affecting path, up to stop. It may return a revision in a different module, since a branch may be moved without @@ -605,7 +608,7 @@ changed, removed = set(), set() copies = {} - new_module, revnum = self.revsplit(rev)[1:] + new_module, revnum = revsplit(rev)[1:] if new_module != self.module: self.module = new_module self.reparent(self.module) @@ -622,7 +625,7 @@ continue # Copy sources not in parent revisions cannot be # represented, ignore their origin for now - pmodule, prevnum = self.revsplit(parents[0])[1:] + pmodule, prevnum = revsplit(parents[0])[1:] if ent.copyfrom_rev < prevnum: continue copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule) @@ -633,7 +636,7 @@ copies[self.recode(entrypath)] = self.recode(copyfrom_path) elif kind == 0: # gone, but had better be a deleted *file* self.ui.debug("gone from %s\n" % ent.copyfrom_rev) - pmodule, prevnum = self.revsplit(parents[0])[1:] + pmodule, prevnum = revsplit(parents[0])[1:] parentpath = pmodule + "/" + entrypath fromkind = self._checkpath(entrypath, prevnum, pmodule) @@ -659,7 +662,7 @@ if ent.action == 'R' and parents: # If a directory is replacing a file, mark the previous # file as deleted - pmodule, prevnum = self.revsplit(parents[0])[1:] + pmodule, prevnum = revsplit(parents[0])[1:] pkind = self._checkpath(entrypath, prevnum, pmodule) if pkind == svn.core.svn_node_file: removed.add(self.recode(entrypath)) @@ -681,7 +684,7 @@ continue # Copy sources not in parent revisions cannot be # represented, ignore their origin for now - pmodule, prevnum = self.revsplit(parents[0])[1:] + pmodule, prevnum = revsplit(parents[0])[1:] if ent.copyfrom_rev < prevnum: continue copyfrompath = self.getrelpath(ent.copyfrom_path, pmodule) @@ -736,7 +739,7 @@ # ent.copyfrom_rev may not be the actual last revision previd = self.latest(newpath, ent.copyfrom_rev) if previd is not None: - prevmodule, prevnum = self.revsplit(previd)[1:] + prevmodule, prevnum = revsplit(previd)[1:] if prevnum >= self.startrev: parents = [previd] self.ui.note( @@ -761,9 +764,8 @@ author = author and self.recode(author) or '' try: branch = self.module.split("/")[-1] - trunkname = self.ui.config('convert', 'svn.trunk', 'trunk') - if branch == trunkname.strip('/'): - branch = '' + if branch == self.trunkname: + branch = None except IndexError: branch = None @@ -834,7 +836,7 @@ raise IOError() mode = '' try: - new_module, revnum = self.revsplit(rev)[1:] + new_module, revnum = revsplit(rev)[1:] if self.module != new_module: self.module = new_module self.reparent(self.module) @@ -944,6 +946,7 @@ class svn_sink(converter_sink, commandline): commit_re = re.compile(r'Committed revision (\d+).', re.M) + uuid_re = re.compile(r'Repository UUID:\s*(\S+)', re.M) def prerun(self): if self.wc: @@ -964,8 +967,6 @@ def __init__(self, ui, path): - if svn is None: - raise MissingTool(_('Could not load Subversion python bindings')) converter_sink.__init__(self, ui, path) commandline.__init__(self, ui, 'svn') self.delete = [] @@ -1000,20 +1001,20 @@ self.run0('checkout', path, wcpath) self.wc = wcpath - self.opener = util.opener(self.wc) - self.wopener = util.opener(self.wc) + self.opener = scmutil.opener(self.wc) + self.wopener = scmutil.opener(self.wc) self.childmap = mapfile(ui, self.join('hg-childmap')) - self.is_exec = util.checkexec(self.wc) and util.is_exec or None + self.is_exec = util.checkexec(self.wc) and util.isexec or None if created: hook = os.path.join(created, 'hooks', 'pre-revprop-change') fp = open(hook, 'w') fp.write(pre_revprop_change) fp.close() - util.set_flags(hook, False, True) + util.setflags(hook, False, True) - xport = transport.SvnRaTransport(url=geturl(path)) - self.uuid = svn.ra.get_uuid(xport.ra) + output = self.run0('info') + self.uuid = self.uuid_re.search(output).group(1).strip() def wjoin(self, *names): return os.path.join(self.wc, *names) @@ -1027,7 +1028,7 @@ os.unlink(filename) except OSError: pass - self.wopener(filename, 'w').write(data) + self.wopener.write(filename, data) if self.is_exec: was_exec = self.is_exec(self.wjoin(filename)) @@ -1037,7 +1038,7 @@ # systematically is just as expensive and much simpler. was_exec = 'x' not in flags - util.set_flags(self.wjoin(filename), False, 'x' in flags) + util.setflags(self.wjoin(filename), False, 'x' in flags) if was_exec: if 'x' not in flags: self.delexec.append(filename)
--- a/hgext/eol.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/eol.py Wed May 18 09:56:27 2011 +0200 @@ -73,11 +73,13 @@ only need to these filters until you have prepared a ``.hgeol`` file. The ``win32text.forbid*`` hooks provided by the win32text extension -have been unified into a single hook named ``eol.hook``. The hook will -lookup the expected line endings from the ``.hgeol`` file, which means -you must migrate to a ``.hgeol`` file first before using the hook. -Remember to enable the eol extension in the repository where you -install the hook. +have been unified into a single hook named ``eol.checkheadshook``. The +hook will lookup the expected line endings from the ``.hgeol`` file, +which means you must migrate to a ``.hgeol`` file first before using +the hook. ``eol.checkheadshook`` only checks heads, intermediate +invalid revisions will be pushed. To forbid them completely, use the +``eol.checkallhook`` hook. These hooks are best used as +``pretxnchangegroup`` hooks. See :hg:`help patterns` for more information about the glob patterns used. @@ -127,36 +129,119 @@ 'cleverdecode:': tocrlf } +class eolfile(object): + def __init__(self, ui, root, data): + self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'} + self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'} -def hook(ui, repo, node, hooktype, **kwargs): - """verify that files have expected EOLs""" + self.cfg = config.config() + # Our files should not be touched. The pattern must be + # inserted first override a '** = native' pattern. + self.cfg.set('patterns', '.hg*', 'BIN') + # We can then parse the user's patterns. + self.cfg.parse('.hgeol', data) + + isrepolf = self.cfg.get('repository', 'native') != 'CRLF' + self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf' + iswdlf = ui.config('eol', 'native', os.linesep) in ('LF', '\n') + self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf' + + include = [] + exclude = [] + for pattern, style in self.cfg.items('patterns'): + key = style.upper() + if key == 'BIN': + exclude.append(pattern) + else: + include.append(pattern) + # This will match the files for which we need to care + # about inconsistent newlines. + self.match = match.match(root, '', [], include, exclude) + + def setfilters(self, ui): + for pattern, style in self.cfg.items('patterns'): + key = style.upper() + try: + ui.setconfig('decode', pattern, self._decode[key]) + ui.setconfig('encode', pattern, self._encode[key]) + except KeyError: + ui.warn(_("ignoring unknown EOL style '%s' from %s\n") + % (style, self.cfg.source('patterns', pattern))) + + def checkrev(self, repo, ctx, files): + failed = [] + for f in (files or ctx.files()): + if f not in ctx: + continue + for pattern, style in self.cfg.items('patterns'): + if not match.match(repo.root, '', [pattern])(f): + continue + target = self._encode[style.upper()] + data = ctx[f].data() + if (target == "to-lf" and "\r\n" in data + or target == "to-crlf" and singlelf.search(data)): + failed.append((str(ctx), target, f)) + break + return failed + +def parseeol(ui, repo, nodes): + try: + for node in nodes: + try: + if node is None: + # Cannot use workingctx.data() since it would load + # and cache the filters before we configure them. + data = repo.wfile('.hgeol').read() + else: + data = repo[node]['.hgeol'].data() + return eolfile(ui, repo.root, data) + except (IOError, LookupError): + pass + except error.ParseError, inst: + ui.warn(_("warning: ignoring .hgeol file due to parse error " + "at %s: %s\n") % (inst.args[1], inst.args[0])) + return None + +def _checkhook(ui, repo, node, headsonly): + # Get revisions to check and touched files at the same time files = set() + revs = set() for rev in xrange(repo[node].rev(), len(repo)): - files.update(repo[rev].files()) - tip = repo['tip'] - for f in files: - if f not in tip: - continue - for pattern, target in ui.configitems('encode'): - if match.match(repo.root, '', [pattern])(f): - data = tip[f].data() - if target == "to-lf" and "\r\n" in data: - raise util.Abort(_("%s should not have CRLF line endings") - % f) - elif target == "to-crlf" and singlelf.search(data): - raise util.Abort(_("%s should not have LF line endings") - % f) - # Ignore other rules for this file - break + revs.add(rev) + if headsonly: + ctx = repo[rev] + files.update(ctx.files()) + for pctx in ctx.parents(): + revs.discard(pctx.rev()) + failed = [] + for rev in revs: + ctx = repo[rev] + eol = parseeol(ui, repo, [ctx.node()]) + if eol: + failed.extend(eol.checkrev(repo, ctx, files)) + if failed: + eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'} + msgs = [] + for node, target, f in failed: + msgs.append(_(" %s in %s should not have %s line endings") % + (f, node, eols[target])) + raise util.Abort(_("end-of-line check failed:\n") + "\n".join(msgs)) + +def checkallhook(ui, repo, node, hooktype, **kwargs): + """verify that files have expected EOLs""" + _checkhook(ui, repo, node, False) + +def checkheadshook(ui, repo, node, hooktype, **kwargs): + """verify that files have expected EOLs""" + _checkhook(ui, repo, node, True) + +# "checkheadshook" used to be called "hook" +hook = checkheadshook def preupdate(ui, repo, hooktype, parent1, parent2): #print "preupdate for %s: %s -> %s" % (repo.root, parent1, parent2) - try: - repo.readhgeol(parent1) - except error.ParseError, inst: - ui.warn(_("warning: ignoring .hgeol file due to parse error " - "at %s: %s\n") % (inst.args[1], inst.args[0])) + repo.loadeol([parent1]) return False def uisetup(ui): @@ -184,66 +269,15 @@ class eolrepo(repo.__class__): - _decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'} - _encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'} - - def readhgeol(self, node=None, data=None): - if data is None: - try: - if node is None: - data = self.wfile('.hgeol').read() - else: - data = self[node]['.hgeol'].data() - except (IOError, LookupError): - return None - - if self.ui.config('eol', 'native', os.linesep) in ('LF', '\n'): - self._decode['NATIVE'] = 'to-lf' - else: - self._decode['NATIVE'] = 'to-crlf' - - eol = config.config() - # Our files should not be touched. The pattern must be - # inserted first override a '** = native' pattern. - eol.set('patterns', '.hg*', 'BIN') - # We can then parse the user's patterns. - eol.parse('.hgeol', data) - - if eol.get('repository', 'native') == 'CRLF': - self._encode['NATIVE'] = 'to-crlf' - else: - self._encode['NATIVE'] = 'to-lf' - - for pattern, style in eol.items('patterns'): - key = style.upper() - try: - self.ui.setconfig('decode', pattern, self._decode[key]) - self.ui.setconfig('encode', pattern, self._encode[key]) - except KeyError: - self.ui.warn(_("ignoring unknown EOL style '%s' from %s\n") - % (style, eol.source('patterns', pattern))) - - include = [] - exclude = [] - for pattern, style in eol.items('patterns'): - key = style.upper() - if key == 'BIN': - exclude.append(pattern) - else: - include.append(pattern) - - # This will match the files for which we need to care - # about inconsistent newlines. - return match.match(self.root, '', [], include, exclude) + def loadeol(self, nodes): + eol = parseeol(self.ui, self, nodes) + if eol is None: + return None + eol.setfilters(self.ui) + return eol.match def _hgcleardirstate(self): - try: - self._eolfile = self.readhgeol() or self.readhgeol('tip') - except error.ParseError, inst: - ui.warn(_("warning: ignoring .hgeol file due to parse error " - "at %s: %s\n") % (inst.args[1], inst.args[0])) - self._eolfile = None - + self._eolfile = self.loadeol([None, 'tip']) if not self._eolfile: self._eolfile = util.never return
--- a/hgext/extdiff.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/extdiff.py Wed May 18 09:56:27 2011 +0200 @@ -13,7 +13,7 @@ non-option arguments: paths to directories containing snapshots of files to compare. -The extdiff extension also allows to configure new diff commands, so +The extdiff extension also allows you to configure new diff commands, so you do not need to type :hg:`extdiff -p kdiff3` always. :: [extdiff] @@ -40,6 +40,7 @@ $parent1, $plabel1 - filename, descriptive label of first parent $child, $clabel - filename, descriptive label of child revision $parent2, $plabel2 - filename, descriptive label of second parent + $root - repository root $parent is an alias for $parent1. The extdiff extension will look in your [diff-tools] and [merge-tools] @@ -61,7 +62,7 @@ from mercurial.i18n import _ from mercurial.node import short, nullid -from mercurial import cmdutil, util, commands, encoding +from mercurial import scmutil, scmutil, util, commands, encoding import os, shlex, shutil, tempfile, re def snapshot(ui, repo, files, node, tmproot): @@ -81,7 +82,7 @@ else: ui.note(_('making snapshot of %d files from working directory\n') % (len(files))) - wopener = util.opener(base) + wopener = scmutil.opener(base) fns_and_mtime = [] ctx = repo[node] for fn in files: @@ -96,9 +97,9 @@ if 'l' in fctx.flags(): wopener.symlink(data, wfn) else: - wopener(wfn, 'w').write(data) + wopener.write(wfn, data) if 'x' in fctx.flags(): - util.set_flags(dest, False, True) + util.setflags(dest, False, True) if node is None: fns_and_mtime.append((dest, repo.wjoin(fn), os.lstat(dest).st_mtime)) @@ -122,12 +123,12 @@ msg = _('cannot specify --rev and --change at the same time') raise util.Abort(msg) elif change: - node2 = repo.lookup(change) + node2 = scmutil.revsingle(repo, change, None).node() node1a, node1b = repo.changelog.parents(node2) else: - node1a, node2 = cmdutil.revpair(repo, revs) + node1a, node2 = scmutil.revpair(repo, revs) if not revs: - node1b = repo.dirstate.parents()[1] + node1b = repo.dirstate.p2() else: node1b = nullid @@ -136,7 +137,7 @@ if node1b == nullid: do3way = False - matcher = cmdutil.match(repo, pats, opts) + matcher = scmutil.match(repo, pats, opts) mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3]) if do3way: mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3]) @@ -188,14 +189,14 @@ # Handle bogus modifies correctly by checking if the files exist if len(common) == 1: common_file = util.localpath(common.pop()) - dir1a = os.path.join(dir1a, common_file) + dir1a = os.path.join(tmproot, dir1a, common_file) label1a = common_file + rev1a - if not os.path.isfile(os.path.join(tmproot, dir1a)): + if not os.path.isfile(dir1a): dir1a = os.devnull if do3way: - dir1b = os.path.join(dir1b, common_file) + dir1b = os.path.join(tmproot, dir1b, common_file) label1b = common_file + rev1b - if not os.path.isfile(os.path.join(tmproot, dir1b)): + if not os.path.isfile(dir1b): dir1b = os.devnull dir2 = os.path.join(dir2root, dir2, common_file) label2 = common_file + rev2 @@ -205,7 +206,8 @@ # returned for parent2 replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b, plabel1=label1a, plabel2=label1b, - clabel=label2, child=dir2) + clabel=label2, child=dir2, + root=repo.root) def quote(match): key = match.group()[1:] if not do3way and key == 'parent2': @@ -213,7 +215,7 @@ return util.shellquote(replace[key]) # Match parent2 first, so 'parent1?' will match both parent1 and parent - regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel)' + regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)' if not do3way and not re.search(regex, args): args += ' $parent1 $child' args = re.sub(regex, quote, args)
--- a/hgext/fetch.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/fetch.py Wed May 18 09:56:27 2011 +0200 @@ -9,7 +9,7 @@ from mercurial.i18n import _ from mercurial.node import nullid, short -from mercurial import commands, cmdutil, hg, util, url, error +from mercurial import commands, cmdutil, hg, util, error from mercurial.lock import release def fetch(ui, repo, source='default', **opts): @@ -66,7 +66,7 @@ other = hg.repository(hg.remoteui(repo, opts), ui.expandpath(source)) ui.status(_('pulling from %s\n') % - url.hidepassword(ui.expandpath(source))) + util.hidepassword(ui.expandpath(source))) revs = None if opts['rev']: try: @@ -125,7 +125,7 @@ # we don't translate commit messages message = (cmdutil.logmessage(opts) or ('Automated merge with %s' % - url.removeauth(other.url()))) + util.removeauth(other.url()))) editor = cmdutil.commiteditor if opts.get('force_editor') or opts.get('edit'): editor = cmdutil.commitforceeditor
--- a/hgext/gpg.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/gpg.py Wed May 18 09:56:27 2011 +0200 @@ -6,10 +6,13 @@ '''commands to sign and verify changesets''' import os, tempfile, binascii -from mercurial import util, commands, match +from mercurial import util, commands, match, cmdutil from mercurial import node as hgnode from mercurial.i18n import _ +cmdtable = {} +command = cmdutil.command(cmdtable) + class gpg(object): def __init__(self, path, key=None): self.path = path @@ -135,6 +138,7 @@ validkeys.append((key[1], key[2], key[3])) return validkeys +@command("sigs", [], _('hg sigs')) def sigs(ui, repo): """list signed changesets""" mygpg = newgpg(ui) @@ -159,6 +163,7 @@ r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev))) ui.write("%-30s %s\n" % (keystr(ui, k), r)) +@command("sigcheck", [], _('hg sigcheck REVISION')) def check(ui, repo, rev): """verify all the signatures there may be for a particular revision""" mygpg = newgpg(ui) @@ -191,6 +196,16 @@ else: return user +@command("sign", + [('l', 'local', None, _('make the signature local')), + ('f', 'force', None, _('sign even if the sigfile is modified')), + ('', 'no-commit', None, _('do not commit the sigfile after signing')), + ('k', 'key', '', + _('the key id to sign with'), _('ID')), + ('m', 'message', '', + _('commit message'), _('TEXT')), + ] + commands.commitopts2, + _('hg sign [OPTION]... [REVISION]...')) def sign(ui, repo, *revs, **opts): """add a signature for the current or given revision @@ -234,7 +249,7 @@ # write it if opts['local']: - repo.opener("localsigs", "ab").write(sigmessage) + repo.opener.append("localsigs", sigmessage) return msigs = match.exact(repo.root, '', ['.hgsigs']) @@ -272,19 +287,3 @@ else: raise util.Abort(_("unknown signature version")) -cmdtable = { - "sign": - (sign, - [('l', 'local', None, _('make the signature local')), - ('f', 'force', None, _('sign even if the sigfile is modified')), - ('', 'no-commit', None, _('do not commit the sigfile after signing')), - ('k', 'key', '', - _('the key id to sign with'), _('ID')), - ('m', 'message', '', - _('commit message'), _('TEXT')), - ] + commands.commitopts2, - _('hg sign [OPTION]... [REVISION]...')), - "sigcheck": (check, [], _('hg sigcheck REVISION')), - "sigs": (sigs, [], _('hg sigs')), -} -
--- a/hgext/graphlog.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/graphlog.py Wed May 18 09:56:27 2011 +0200 @@ -12,17 +12,19 @@ revision graph is also shown. ''' -import os -from mercurial.cmdutil import revrange, show_changeset +from mercurial.cmdutil import show_changeset from mercurial.commands import templateopts from mercurial.i18n import _ from mercurial.node import nullrev -from mercurial import cmdutil, commands, extensions +from mercurial import cmdutil, commands, extensions, scmutil from mercurial import hg, util, graphmod +cmdtable = {} +command = cmdutil.command(cmdtable) + ASCIIDATA = 'ASC' -def asciiedges(seen, rev, parents): +def asciiedges(type, char, lines, seen, rev, parents): """adds edge info to changelog DAG walk suitable for ascii()""" if rev not in seen: seen.append(rev) @@ -37,16 +39,33 @@ newparents.append(parent) ncols = len(seen) - seen[nodeidx:nodeidx + 1] = newparents - edges = [(nodeidx, seen.index(p)) for p in knownparents] + nextseen = seen[:] + nextseen[nodeidx:nodeidx + 1] = newparents + edges = [(nodeidx, nextseen.index(p)) for p in knownparents] + + while len(newparents) > 2: + # ascii() only knows how to add or remove a single column between two + # calls. Nodes with more than two parents break this constraint so we + # introduce intermediate expansion lines to grow the active node list + # slowly. + edges.append((nodeidx, nodeidx)) + edges.append((nodeidx, nodeidx + 1)) + nmorecols = 1 + yield (type, char, lines, (nodeidx, edges, ncols, nmorecols)) + char = '\\' + lines = [] + nodeidx += 1 + ncols += 1 + edges = [] + del newparents[0] if len(newparents) > 0: edges.append((nodeidx, nodeidx)) if len(newparents) > 1: edges.append((nodeidx, nodeidx + 1)) - - nmorecols = len(seen) - ncols - return nodeidx, edges, ncols, nmorecols + nmorecols = len(nextseen) - ncols + seen[:] = nextseen + yield (type, char, lines, (nodeidx, edges, ncols, nmorecols)) def fix_long_right_edges(edges): for (i, (start, end)) in enumerate(edges): @@ -208,20 +227,71 @@ def get_revs(repo, rev_opt): if rev_opt: - revs = revrange(repo, rev_opt) + revs = scmutil.revrange(repo, rev_opt) if len(revs) == 0: return (nullrev, nullrev) return (max(revs), min(revs)) else: return (len(repo) - 1, 0) -def check_unsupported_flags(opts): - for op in ["follow", "follow_first", "date", "copies", "keyword", "remove", - "only_merges", "user", "branch", "only_branch", "prune", - "newest_first", "no_merges", "include", "exclude"]: +def check_unsupported_flags(pats, opts): + for op in ["follow_first", "copies", "newest_first"]: if op in opts and opts[op]: - raise util.Abort(_("--graph option is incompatible with --%s") + raise util.Abort(_("-G/--graph option is incompatible with --%s") % op.replace("_", "-")) + if pats and opts.get('follow'): + raise util.Abort(_("-G/--graph option is incompatible with --follow " + "with file argument")) + +def revset(pats, opts): + """Return revset str built of revisions, log options and file patterns. + """ + opt2revset = { + 'follow': (0, 'follow()'), + 'no_merges': (0, 'not merge()'), + 'only_merges': (0, 'merge()'), + 'removed': (0, 'removes("*")'), + 'date': (1, 'date($)'), + 'branch': (2, 'branch($)'), + 'exclude': (2, 'not file($)'), + 'include': (2, 'file($)'), + 'keyword': (2, 'keyword($)'), + 'only_branch': (2, 'branch($)'), + 'prune': (2, 'not ($ or ancestors($))'), + 'user': (2, 'user($)'), + } + optrevset = [] + revset = [] + for op, val in opts.iteritems(): + if not val: + continue + if op == 'rev': + # Already a revset + revset.extend(val) + if op not in opt2revset: + continue + arity, revop = opt2revset[op] + revop = revop.replace('$', '%(val)r') + if arity == 0: + optrevset.append(revop) + elif arity == 1: + optrevset.append(revop % {'val': val}) + else: + for f in val: + optrevset.append(revop % {'val': f}) + + for path in pats: + optrevset.append('file(%r)' % path) + + if revset or optrevset: + if revset: + revset = ['(' + ' or '.join(revset) + ')'] + if optrevset: + revset.append('(' + ' and '.join(optrevset) + ')') + revset = ' and '.join(revset) + else: + revset = 'all()' + return revset def generate(ui, dag, displayer, showparents, edgefn): seen, state = [], asciistate() @@ -230,10 +300,19 @@ displayer.show(ctx) lines = displayer.hunk.pop(rev).split('\n')[:-1] displayer.flush(rev) - ascii(ui, state, type, char, lines, edgefn(seen, rev, parents)) + edges = edgefn(type, char, lines, seen, rev, parents) + for type, char, lines, coldata in edges: + ascii(ui, state, type, char, lines, coldata) displayer.close() -def graphlog(ui, repo, path=None, **opts): +@command('glog', + [('l', 'limit', '', + _('limit number of changes displayed'), _('NUM')), + ('p', 'patch', False, _('show patch')), + ('r', 'rev', [], _('show the specified revision or range'), _('REV')), + ] + templateopts, + _('hg glog [OPTION]... [FILE]')) +def graphlog(ui, repo, *pats, **opts): """show revision history alongside an ASCII revision graph Print a revision history alongside a revision graph drawn with @@ -243,20 +322,13 @@ directory. """ - check_unsupported_flags(opts) - limit = cmdutil.loglimit(opts) - start, stop = get_revs(repo, opts["rev"]) - if start == nullrev: - return + check_unsupported_flags(pats, opts) - if path: - path = util.canonpath(repo.root, os.getcwd(), path) - if path: # could be reset in canonpath - revdag = graphmod.filerevs(repo, path, start, stop, limit) - else: - if limit is not None: - stop = max(stop, start - limit + 1) - revdag = graphmod.revisions(repo, start, stop) + revs = sorted(scmutil.revrange(repo, [revset(pats, opts)]), reverse=1) + limit = cmdutil.loglimit(opts) + if limit is not None: + revs = revs[:limit] + revdag = graphmod.dagwalker(repo, revs) displayer = show_changeset(ui, repo, opts, buffered=True) showparents = [ctx.node() for ctx in repo[None].parents()] @@ -279,7 +351,7 @@ directory. """ - check_unsupported_flags(opts) + check_unsupported_flags([], opts) o = hg._outgoing(ui, repo, dest, opts) if o is None: return @@ -301,7 +373,7 @@ def subreporecurse(): return 1 - check_unsupported_flags(opts) + check_unsupported_flags([], opts) def display(other, chlist, displayer): revdag = graphrevs(other, chlist, opts) showparents = [ctx.node() for ctx in repo[None].parents()] @@ -319,23 +391,7 @@ '''wrap the command''' def graph(orig, *args, **kwargs): if kwargs['graph']: - try: - return wrapfn(*args, **kwargs) - except TypeError, e: - if len(args) > wrapfn.func_code.co_argcount: - raise util.Abort(_('--graph option allows at most one file')) + return wrapfn(*args, **kwargs) return orig(*args, **kwargs) entry = extensions.wrapcommand(table, cmd, graph) entry[1].append(('G', 'graph', None, _("show the revision DAG"))) - -cmdtable = { - "glog": - (graphlog, - [('l', 'limit', '', - _('limit number of changes displayed'), _('NUM')), - ('p', 'patch', False, _('show patch')), - ('r', 'rev', [], - _('show the specified revision or range'), _('REV')), - ] + templateopts, - _('hg glog [OPTION]... [FILE]')), -}
--- a/hgext/hgcia.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/hgcia.py Wed May 18 09:56:27 2011 +0200 @@ -18,7 +18,7 @@ # Append a diffstat to the log message (optional) #diffstat = False # Template to use for log messages (optional) - #template = {desc}\\n{baseurl}/rev/{node}-- {diffstat} + #template = {desc}\\n{baseurl}{webroot}/rev/{node}-- {diffstat} # Style to use (optional) #style = foo # The URL of the CIA notification service (optional) @@ -28,6 +28,8 @@ #url = http://cia.vc/ # print message instead of sending it (optional) #test = False + # number of slashes to strip for url paths + #strip = 0 [hooks] # one of these: @@ -66,6 +68,8 @@ self.cia = cia self.ctx = ctx self.url = self.cia.url + if self.url: + self.url += self.cia.root def fileelem(self, path, uri, action): if uri: @@ -75,7 +79,7 @@ def fileelems(self): n = self.ctx.node() - f = self.cia.repo.status(self.ctx.parents()[0].node(), n) + f = self.cia.repo.status(self.ctx.p1().node(), n) url = self.url or '' elems = [] for path in f[0]: @@ -120,7 +124,9 @@ diffstat = self.cia.diffstat and self.diffstat() or '' self.cia.ui.pushbuffer() self.cia.templater.show(self.ctx, changes=self.ctx.changeset(), - url=self.cia.url, diffstat=diffstat) + baseurl=self.cia.ui.config('web', 'baseurl'), + url=self.url, diffstat=diffstat, + webroot=self.cia.root) return self.cia.ui.popbuffer() def xml(self): @@ -184,6 +190,8 @@ self.emailfrom = self.ui.config('email', 'from') self.dryrun = self.ui.configbool('cia', 'test') self.url = self.ui.config('web', 'baseurl') + self.stripcount = int(self.ui.config('cia', 'strip', 0)) + self.root = self.strip(self.repo.root) style = self.ui.config('cia', 'style') template = self.ui.config('cia', 'template') @@ -195,6 +203,19 @@ t.use_template(template) self.templater = t + def strip(self, path): + '''strip leading slashes from local path, turn into web-safe path.''' + + path = util.pconvert(path) + count = self.stripcount + while count > 0: + c = path.find('/') + if c == -1: + break + path = path[c + 1:] + count -= 1 + return path + def sendrpc(self, msg): srv = xmlrpclib.Server(self.ciaurl) res = srv.hub.deliver(msg)
--- a/hgext/hgk.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/hgk.py Wed May 18 09:56:27 2011 +0200 @@ -35,7 +35,7 @@ ''' import os -from mercurial import commands, util, patch, revlog, cmdutil +from mercurial import commands, util, patch, revlog, scmutil from mercurial.node import nullid, nullrev, short from mercurial.i18n import _ @@ -45,7 +45,7 @@ assert node2 is not None mmap = repo[node1].manifest() mmap2 = repo[node2].manifest() - m = cmdutil.match(repo, files) + m = scmutil.match(repo, files) modified, added, removed = repo.status(node1, node2, m)[:3] empty = short(nullid) @@ -81,7 +81,7 @@ if opts['patch']: if opts['pretty']: catcommit(ui, repo, node2, "") - m = cmdutil.match(repo, files) + m = scmutil.match(repo, files) chunks = patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, {'git': True})) for chunk in chunks:
--- a/hgext/inotify/linuxserver.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/inotify/linuxserver.py Wed May 18 09:56:27 2011 +0200 @@ -44,7 +44,7 @@ def _explain_watch_limit(ui, dirstate, rootabs): path = '/proc/sys/fs/inotify/max_user_watches' try: - limit = int(file(path).read()) + limit = int(util.readfile(path)) except IOError, err: if err.errno != errno.ENOENT: raise
--- a/hgext/keyword.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/keyword.py Wed May 18 09:56:27 2011 +0200 @@ -60,11 +60,11 @@ control run :hg:`kwdemo`. See :hg:`help templates` for a list of available templates and filters. -Three additional date template filters are provided:: +Three additional date template filters are provided: - utcdate "2006/09/18 15:13:13" - svnutcdate "2006-09-18 15:13:13Z" - svnisodate "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)" +:``utcdate``: "2006/09/18 15:13:13" +:``svnutcdate``: "2006-09-18 15:13:13Z" +:``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)" The default template mappings (view with :hg:`kwdemo -d`) can be replaced with customized keywords and templates. Again, run @@ -83,12 +83,16 @@ from mercurial import commands, context, cmdutil, dispatch, filelog, extensions from mercurial import localrepo, match, patch, templatefilters, templater, util +from mercurial import scmutil from mercurial.hgweb import webcommands from mercurial.i18n import _ import os, re, shutil, tempfile commands.optionalrepo += ' kwdemo' +cmdtable = {} +command = cmdutil.command(cmdtable) + # hg commands that do not act on keywords nokwcommands = ('add addremove annotate bundle export grep incoming init log' ' outgoing push tip verify convert email glog') @@ -109,11 +113,26 @@ } # date like in cvs' $Date -utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S') +def utcdate(text): + ''':utcdate: Date. Returns a UTC-date in this format: "2009/08/18 11:00:13". + ''' + return util.datestr((text[0], 0), '%Y/%m/%d %H:%M:%S') # date like in svn's $Date -svnisodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)') +def svnisodate(text): + ''':svnisodate: Date. Returns a date in this format: "2009-08-18 13:00:13 + +0200 (Tue, 18 Aug 2009)". + ''' + return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)') # date like in svn's $Id -svnutcdate = lambda x: util.datestr((x[0], 0), '%Y-%m-%d %H:%M:%SZ') +def svnutcdate(text): + ''':svnutcdate: Date. Returns a UTC-date in this format: "2009-08-18 + 11:00:13Z". + ''' + return util.datestr((text[0], 0), '%Y-%m-%d %H:%M:%SZ') + +templatefilters.filters.update({'utcdate': utcdate, + 'svnisodate': svnisodate, + 'svnutcdate': svnutcdate}) # make keyword tools accessible kwtools = {'templater': None, 'hgcmd': ''} @@ -176,9 +195,6 @@ for k, v in kwmaps) else: self.templates = _defaultkwmaps(self.ui) - templatefilters.filters.update({'utcdate': utcdate, - 'svnisodate': svnisodate, - 'svnutcdate': svnutcdate}) @util.propertycache def escape(self): @@ -310,7 +326,7 @@ '''Bails out if [keyword] configuration is not active. Returns status of working directory.''' if kwt: - return repo.status(match=cmdutil.match(repo, pats, opts), clean=True, + return repo.status(match=scmutil.match(repo, pats, opts), clean=True, unknown=opts.get('unknown') or opts.get('all')) if ui.configitems('keyword'): raise util.Abort(_('[keyword] patterns cannot match')) @@ -332,6 +348,11 @@ finally: wlock.release() +@command('kwdemo', + [('d', 'default', None, _('show default keyword template maps')), + ('f', 'rcfile', '', + _('read maps from rcfile'), _('FILE'))], + _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')) def demo(ui, repo, *args, **opts): '''print [keywordmaps] configuration and an expansion example @@ -400,7 +421,7 @@ demoitems('keywordset', ui.configitems('keywordset')) demoitems('keywordmaps', kwmaps.iteritems()) keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n' - repo.wopener(fn, 'w').write(keywords) + repo.wopener.write(fn, keywords) repo[None].add([fn]) ui.note(_('\nkeywords written to %s:\n') % fn) ui.note(keywords) @@ -415,6 +436,7 @@ ui.write(repo.wread(fn)) shutil.rmtree(tmpdir, ignore_errors=True) +@command('kwexpand', commands.walkopts, _('hg kwexpand [OPTION]... [FILE]...')) def expand(ui, repo, *pats, **opts): '''expand keywords in the working directory @@ -425,6 +447,12 @@ # 3rd argument sets expansion to True _kwfwrite(ui, repo, True, *pats, **opts) +@command('kwfiles', + [('A', 'all', None, _('show keyword status flags of all files')), + ('i', 'ignore', None, _('show files excluded from expansion')), + ('u', 'unknown', None, _('only show unknown (not tracked) files')), + ] + commands.walkopts, + _('hg kwfiles [OPTION]... [FILE]...')) def files(ui, repo, *pats, **opts): '''show files configured for keyword expansion @@ -471,6 +499,7 @@ for f in filenames: ui.write(fmt % repo.pathto(f, cwd), label='kwfiles.' + kwstate) +@command('kwshrink', commands.walkopts, _('hg kwshrink [OPTION]... [FILE]...')) def shrink(ui, repo, *pats, **opts): '''revert expanded keywords in the working directory @@ -566,11 +595,11 @@ wlock.release() # monkeypatches - def kwpatchfile_init(orig, self, ui, fname, opener, + def kwpatchfile_init(orig, self, ui, fname, backend, missing=False, eolmode=None): '''Monkeypatch/wrap patch.patchfile.__init__ to avoid rejects or conflicts due to expanded keywords in working dir.''' - orig(self, ui, fname, opener, missing, eolmode) + orig(self, ui, fname, backend, missing, eolmode) # shrink keywords read from working dir self.lines = kwt.shrinklines(self.fname, self.lines) @@ -607,8 +636,8 @@ expansion. ''' source = repo.dirstate.copied(dest) if 'l' in wctx.flags(source): - source = util.canonpath(repo.root, cwd, - os.path.realpath(source)) + source = scmutil.canonpath(repo.root, cwd, + os.path.realpath(source)) return kwt.match(source) candidates = [f for f in repo.dirstate.copies() if @@ -660,23 +689,3 @@ pass repo.__class__ = kwrepo - -cmdtable = { - 'kwdemo': - (demo, - [('d', 'default', None, _('show default keyword template maps')), - ('f', 'rcfile', '', - _('read maps from rcfile'), _('FILE'))], - _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')), - 'kwexpand': (expand, commands.walkopts, - _('hg kwexpand [OPTION]... [FILE]...')), - 'kwfiles': - (files, - [('A', 'all', None, _('show keyword status flags of all files')), - ('i', 'ignore', None, _('show files excluded from expansion')), - ('u', 'unknown', None, _('only show unknown (not tracked) files')), - ] + commands.walkopts, - _('hg kwfiles [OPTION]... [FILE]...')), - 'kwshrink': (shrink, commands.walkopts, - _('hg kwshrink [OPTION]... [FILE]...')), -}
--- a/hgext/mq.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/mq.py Wed May 18 09:56:27 2011 +0200 @@ -45,12 +45,18 @@ from mercurial.i18n import _ from mercurial.node import bin, hex, short, nullid, nullrev from mercurial.lock import release -from mercurial import commands, cmdutil, hg, patch, util +from mercurial import commands, cmdutil, hg, scmutil, util, revset from mercurial import repair, extensions, url, error +from mercurial import patch as patchmod import os, sys, re, errno, shutil commands.norepo += " qclone" +seriesopts = [('s', 'summary', None, _('print first line of patch header'))] + +cmdtable = {} +command = cmdutil.command(cmdtable) + # Patch names looks like unix-file names. # They must be joinable with queue directory and result in the patch path. normname = util.normpath @@ -259,7 +265,7 @@ except IOError: curpath = os.path.join(path, 'patches') self.path = patchdir or curpath - self.opener = util.opener(self.path) + self.opener = scmutil.opener(self.path) self.ui = ui self.applied_dirty = 0 self.series_dirty = 0 @@ -291,14 +297,14 @@ elif l.strip(): self.ui.warn(_('malformated mq status line: %s\n') % entry) # else we ignore empty lines - lines = self.opener(self.status_path).read().splitlines() + lines = self.opener.read(self.status_path).splitlines() return list(parselines(lines)) return [] @util.propertycache def full_series(self): if os.path.exists(self.join(self.series_path)): - return self.opener(self.series_path).read().splitlines() + return self.opener.read(self.series_path).splitlines() return [] @util.propertycache @@ -321,7 +327,7 @@ self.active_guards = None def diffopts(self, opts={}, patchfn=None): - diffopts = patch.diffopts(self.ui, opts) + diffopts = patchmod.diffopts(self.ui, opts) if self.gitmode == 'auto': diffopts.upgrade = True elif self.gitmode == 'keep': @@ -412,7 +418,7 @@ if self.active_guards is None: self.active_guards = [] try: - guards = self.opener(self.guards_path).read().split() + guards = self.opener.read(self.guards_path).split() except IOError, err: if err.errno != errno.ENOENT: raise @@ -513,7 +519,7 @@ def printdiff(self, repo, diffopts, node1, node2=None, files=None, fp=None, changes=None, opts={}): stat = opts.get('stat') - m = cmdutil.match(repo, files, opts) + m = scmutil.match(repo, files, opts) cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m, changes, stat, fp) @@ -613,15 +619,14 @@ patchfile: name of patch file''' files = {} try: - fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root, - files=files, eolmode=None) + fuzz = patchmod.patch(self.ui, repo, patchfile, strip=1, + cwd=repo.root, files=files, eolmode=None) + return (True, list(files), fuzz) except Exception, inst: self.ui.note(str(inst) + '\n') if not self.ui.verbose: self.ui.warn(_("patch failed, unable to continue (try -v)\n")) - return (False, files, False) - - return (True, files, fuzz) + return (False, list(files), False) def apply(self, repo, series, list=False, update_status=True, strict=False, patchdir=None, merge=None, all_files=None): @@ -666,7 +671,7 @@ try: ph = patchheader(self.join(patchname), self.plainmode) - except: + except IOError: self.ui.warn(_("unable to read %s\n") % patchname) err = 1 break @@ -706,8 +711,7 @@ p1, p2 = repo.dirstate.parents() repo.dirstate.setparents(p1, merge) - files = cmdutil.updatedir(self.ui, repo, files) - match = cmdutil.matchfiles(repo, files or []) + match = scmutil.matchfiles(repo, files or []) n = repo.commit(message, ph.user, ph.date, match=match, force=True) if n is None: @@ -737,11 +741,29 @@ os.unlink(self.join(p)) if numrevs: + qfinished = self.applied[:numrevs] del self.applied[:numrevs] self.applied_dirty = 1 - for i in sorted([self.find_series(p) for p in patches], reverse=True): - del self.full_series[i] + unknown = [] + + for (i, p) in sorted([(self.find_series(p), p) for p in patches], + reverse=True): + if i is not None: + del self.full_series[i] + else: + unknown.append(p) + + if unknown: + if numrevs: + rev = dict((entry.name, entry.node) for entry in qfinished) + for p in unknown: + msg = _('revision %s refers to unknown patches: %s\n') + self.ui.warn(msg % (short(rev[p]), p)) + else: + msg = _('unknown patches: %s\n') + raise util.Abort(''.join(msg % p for p in unknown)) + self.parse_series() self.series_dirty = 1 @@ -793,7 +815,7 @@ if opts.get('rev'): if not self.applied: raise util.Abort(_('no patches applied')) - revs = cmdutil.revrange(repo, opts.get('rev')) + revs = scmutil.revrange(repo, opts.get('rev')) if len(revs) > 1 and revs[0] > revs[1]: revs.reverse() revpatches = self._revpatches(repo, revs) @@ -825,21 +847,32 @@ inclsubs.append(s) return inclsubs + def localchangesfound(self, refresh=True): + if refresh: + raise util.Abort(_("local changes found, refresh first")) + else: + raise util.Abort(_("local changes found")) + def check_localchanges(self, repo, force=False, refresh=True): m, a, r, d = repo.status()[:4] if (m or a or r or d) and not force: - if refresh: - raise util.Abort(_("local changes found, refresh first")) - else: - raise util.Abort(_("local changes found")) + self.localchangesfound(refresh) return m, a, r, d _reserved = ('series', 'status', 'guards', '.', '..') def check_reserved_name(self, name): - if (name in self._reserved or name.startswith('.hg') - or name.startswith('.mq') or '#' in name or ':' in name): + if name in self._reserved: raise util.Abort(_('"%s" cannot be used as the name of a patch') % name) + for prefix in ('.hg', '.mq'): + if name.startswith(prefix): + raise util.Abort(_('patch name cannot begin with "%s"') + % prefix) + for c in ('#', ':'): + if c in name: + raise util.Abort(_('"%s" cannot be used in the name of a patch') + % c) + def new(self, repo, patchfn, *pats, **opts): """options: @@ -865,7 +898,7 @@ if opts.get('include') or opts.get('exclude') or pats: if inclsubs: pats = list(pats or []) + inclsubs - match = cmdutil.match(repo, pats, opts) + match = scmutil.match(repo, pats, opts) # detect missing files in pats def badfn(f, msg): if f != '.hgsubstate': # .hgsubstate is auto-created @@ -874,7 +907,7 @@ m, a, r, d = repo.status(match=match)[:4] else: m, a, r, d = self.check_localchanges(repo, force=True) - match = cmdutil.matchfiles(repo, m + a + r + inclsubs) + match = scmutil.matchfiles(repo, m + a + r + inclsubs) if len(repo[None].parents()) > 1: raise util.Abort(_('cannot manage merge changesets')) commitfiles = m + a + r @@ -899,7 +932,7 @@ else: p.write("# HG changeset patch\n") p.write("# Parent " - + hex(repo[None].parents()[0].node()) + "\n") + + hex(repo[None].p1().node()) + "\n") if user: p.write("# User " + user + "\n") if date: @@ -921,7 +954,7 @@ p.write(msg) if commitfiles: parent = self.qparents(repo, n) - chunks = patch.diff(repo, node1=parent, node2=n, + chunks = patchmod.diff(repo, node1=parent, node2=n, match=match, opts=diffopts) for chunk in chunks: p.write(chunk) @@ -1054,7 +1087,7 @@ heads += ls if not heads: heads = [nullid] - if repo.dirstate.parents()[0] not in heads and not exact: + if repo.dirstate.p1() not in heads and not exact: self.ui.status(_("(working directory not at a head)\n")) if not self.series: @@ -1100,8 +1133,6 @@ if start == len(self.series): self.ui.warn(_('patch series already fully applied\n')) return 1 - if not force: - self.check_localchanges(repo) if exact: if move: @@ -1140,6 +1171,19 @@ end = self.series.index(patch, start) + 1 s = self.series[start:end] + + if not force: + mm, aa, rr, dd = repo.status()[:4] + wcfiles = set(mm + aa + rr + dd) + if wcfiles: + for patchname in s: + pf = os.path.join(self.path, patchname) + patchfiles = patchmod.changedfiles(self.ui, repo, pf) + if wcfiles.intersection(patchfiles): + self.localchangesfound(self.applied) + elif mergeq: + self.check_localchanges(refresh=self.applied) + all_files = set() try: if mergeq: @@ -1148,7 +1192,7 @@ ret = self.apply(repo, s, list, all_files=all_files) except: self.ui.warn(_('cleaning up working directory...')) - node = repo.dirstate.parents()[0] + node = repo.dirstate.p1() hg.revert(repo, node, None) # only remove unknown files that we know we touched or # created while patching @@ -1220,9 +1264,6 @@ break update = needupdate - if not force and update: - self.check_localchanges(repo) - self.applied_dirty = 1 end = len(self.applied) rev = self.applied[start].node @@ -1245,6 +1286,12 @@ qp = self.qparents(repo, rev) ctx = repo[qp] m, a, r, d = repo.status(qp, top)[:4] + parentfiles = set(m + a + r + d) + if not force and parentfiles: + mm, aa, rr, dd = repo.status()[:4] + wcfiles = set(mm + aa + rr + dd) + if wcfiles.intersection(parentfiles): + self.localchangesfound() if d: raise util.Abort(_("deletions found between repo revs")) for f in a: @@ -1332,17 +1379,17 @@ changes = repo.changelog.read(top) man = repo.manifest.read(changes[0]) aaa = aa[:] - matchfn = cmdutil.match(repo, pats, opts) + matchfn = scmutil.match(repo, pats, opts) # in short mode, we only diff the files included in the # patch already plus specified files if opts.get('short'): # if amending a patch, we start with existing # files plus specified files - unfiltered - match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files()) + match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files()) # filter with inc/exl options - matchfn = cmdutil.match(repo, opts=opts) + matchfn = scmutil.match(repo, opts=opts) else: - match = cmdutil.matchall(repo) + match = scmutil.matchall(repo) m, a, r, d = repo.status(match=match)[:4] mm = set(mm) aa = set(aa) @@ -1380,8 +1427,8 @@ r = list(dd) a = list(aa) c = [filter(matchfn, l) for l in (m, a, r)] - match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs)) - chunks = patch.diff(repo, patchparent, match=match, + match = scmutil.matchfiles(repo, set(c[0] + c[1] + c[2] + inclsubs)) + chunks = patchmod.diff(repo, patchparent, match=match, changes=c, opts=diffopts) for chunk in chunks: patchf.write(chunk) @@ -1701,7 +1748,7 @@ if files: raise util.Abort(_('option "-r" not valid when importing ' 'files')) - rev = cmdutil.revrange(repo, rev) + rev = scmutil.revrange(repo, rev) rev.sort(reverse=True) if (len(files) > 1 or len(rev) > 1) and patchname: raise util.Abort(_('option "-n" not valid when importing multiple ' @@ -1814,6 +1861,11 @@ self.removeundo(repo) +@command("qdelete|qremove|qrm", + [('k', 'keep', None, _('keep patch file')), + ('r', 'rev', [], + _('stop managing a revision (DEPRECATED)'), _('REV'))], + _('hg qdelete [-k] [PATCH]...')) def delete(ui, repo, *patches, **opts): """remove patches from queue @@ -1827,6 +1879,10 @@ q.save_dirty() return 0 +@command("qapplied", + [('1', 'last', None, _('show only the last patch')) + ] + seriesopts, + _('hg qapplied [-1] [-s] [PATCH]')) def applied(ui, repo, patch=None, **opts): """print the patches already applied @@ -1857,6 +1913,9 @@ summary=opts.get('summary')) +@command("qunapplied", + [('1', 'first', None, _('show only the first patch'))] + seriesopts, + _('hg qunapplied [-1] [-s] [PATCH]')) def unapplied(ui, repo, patch=None, **opts): """print the patches not yet applied @@ -1878,6 +1937,16 @@ q.qseries(repo, start=start, length=length, status='U', summary=opts.get('summary')) +@command("qimport", + [('e', 'existing', None, _('import file in patch directory')), + ('n', 'name', '', + _('name of patch file'), _('NAME')), + ('f', 'force', None, _('overwrite existing files')), + ('r', 'rev', [], + _('place existing revisions under mq control'), _('REV')), + ('g', 'git', None, _('use git extended diff format')), + ('P', 'push', None, _('qpush after importing'))], + _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')) def qimport(ui, repo, *filename, **opts): """import a patch @@ -1899,7 +1968,7 @@ With -g/--git, patches imported with --rev will use the git diff format. See the diffs help topic for information on why this is important for preserving rename/copy information and permission - changes. + changes. Use :hg:`qfinish` to remove changesets from mq control. To import a patch from standard input, pass - as the patch file. When importing from standard input, a patch name must be specified @@ -1949,6 +2018,9 @@ commands.add(ui, r) return 0 +@command("^qinit", + [('c', 'create-repo', None, _('create queue repository'))], + _('hg qinit [-c]')) def init(ui, repo, **opts): """init a new queue repository (DEPRECATED) @@ -1962,6 +2034,15 @@ commands. With -c, use :hg:`init --mq` instead.""" return qinit(ui, repo, create=opts.get('create_repo')) +@command("qclone", + [('', 'pull', None, _('use pull protocol to copy metadata')), + ('U', 'noupdate', None, _('do not update the new working directories')), + ('', 'uncompressed', None, + _('use uncompressed transfer (fast over LAN)')), + ('p', 'patches', '', + _('location of source patch repository'), _('REPO')), + ] + commands.remoteopts, + _('hg qclone [OPTION]... SOURCE [DEST]')) def clone(ui, source, dest=None, **opts): '''clone main and patch repository at same time @@ -2028,6 +2109,9 @@ ui.note(_('updating destination repository\n')) hg.update(dr, dr.changelog.tip()) +@command("qcommit|qci", + commands.table["^commit|ci"][1], + _('hg qcommit [OPTION]... [FILE]...')) def commit(ui, repo, *pats, **opts): """commit changes in the queue repository (DEPRECATED) @@ -2038,6 +2122,10 @@ raise util.Abort('no queue repository') commands.commit(r.ui, r, *pats, **opts) +@command("qseries", + [('m', 'missing', None, _('print patches not in series')), + ] + seriesopts, + _('hg qseries [-ms]')) def series(ui, repo, **opts): """print the entire series file @@ -2045,6 +2133,7 @@ repo.mq.qseries(repo, missing=opts.get('missing'), summary=opts.get('summary')) return 0 +@command("qtop", [] + seriesopts, _('hg qtop [-s]')) def top(ui, repo, **opts): """print the name of the current patch @@ -2058,6 +2147,7 @@ ui.write(_("no patches applied\n")) return 1 +@command("qnext", [] + seriesopts, _('hg qnext [-s]')) def next(ui, repo, **opts): """print the name of the next patch @@ -2069,6 +2159,7 @@ return 1 q.qseries(repo, start=end, length=1, summary=opts.get('summary')) +@command("qprev", [] + seriesopts, _('hg qprev [-s]')) def prev(ui, repo, **opts): """print the name of the previous patch @@ -2090,6 +2181,18 @@ if not opts.get('date') and opts.get('currentdate'): opts['date'] = "%d %d" % util.makedate() +@command("^qnew", + [('e', 'edit', None, _('edit commit message')), + ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')), + ('g', 'git', None, _('use git extended diff format')), + ('U', 'currentuser', None, _('add "From: <current user>" to patch')), + ('u', 'user', '', + _('add "From: <USER>" to patch'), _('USER')), + ('D', 'currentdate', None, _('add "Date: <current date>" to patch')), + ('d', 'date', '', + _('add "Date: <DATE>" to patch'), _('DATE')) + ] + commands.walkopts + commands.commitopts, + _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')) def new(ui, repo, patch, *args, **opts): """create a new patch @@ -2129,6 +2232,21 @@ q.save_dirty() return 0 +@command("^qrefresh", + [('e', 'edit', None, _('edit commit message')), + ('g', 'git', None, _('use git extended diff format')), + ('s', 'short', None, + _('refresh only files already in the patch and specified files')), + ('U', 'currentuser', None, + _('add/update author field in patch with current user')), + ('u', 'user', '', + _('add/update author field in patch with given user'), _('USER')), + ('D', 'currentdate', None, + _('add/update date field in patch with current date')), + ('d', 'date', '', + _('add/update date field in patch with given date'), _('DATE')) + ] + commands.walkopts + commands.commitopts, + _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')) def refresh(ui, repo, *pats, **opts): """update the current patch @@ -2170,6 +2288,9 @@ q.save_dirty() return ret +@command("^qdiff", + commands.diffopts + commands.diffopts2 + commands.walkopts, + _('hg qdiff [OPTION]... [FILE]...')) def diff(ui, repo, *pats, **opts): """diff of the current patch and subsequent modifications @@ -2188,6 +2309,11 @@ repo.mq.diff(repo, pats, opts) return 0 +@command('qfold', + [('e', 'edit', None, _('edit patch header')), + ('k', 'keep', None, _('keep folded patch files')), + ] + commands.commitopts, + _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')) def fold(ui, repo, *files, **opts): """fold the named patches into the current patch @@ -2236,7 +2362,6 @@ (patchsuccess, files, fuzz) = q.patch(repo, pf) if not patchsuccess: raise util.Abort(_('error folding patch %s') % p) - cmdutil.updatedir(ui, repo, files) if not message: ph = patchheader(q.join(parent), q.plainmode) @@ -2254,6 +2379,9 @@ q.delete(repo, patches, opts) q.save_dirty() +@command("qgoto", + [('f', 'force', None, _('overwrite any local changes'))], + _('hg qgoto [OPTION]... PATCH')) def goto(ui, repo, patch, **opts): '''push or pop patches until named patch is at top of stack @@ -2267,6 +2395,10 @@ q.save_dirty() return ret +@command("qguard", + [('l', 'list', None, _('list all patches and guards')), + ('n', 'none', None, _('drop all guards'))], + _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')) def guard(ui, repo, *args, **opts): '''set or print guards for a patch @@ -2336,6 +2468,7 @@ else: status(q.series.index(q.lookup(patch))) +@command("qheader", [], _('hg qheader [PATCH]')) def header(ui, repo, patch=None): """print the header of the topmost or specified patch @@ -2377,6 +2510,16 @@ newpath = path + ".%d" % (index + 1) return newpath +@command("^qpush", + [('f', 'force', None, _('apply on top of local changes')), + ('e', 'exact', None, _('apply the target patch to its recorded parent')), + ('l', 'list', None, _('list patch name in commit text')), + ('a', 'all', None, _('apply all patches')), + ('m', 'merge', None, _('merge from another queue (DEPRECATED)')), + ('n', 'name', '', + _('merge queue name (DEPRECATED)'), _('NAME')), + ('', 'move', None, _('reorder patch series and apply only the patch'))], + _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]')) def push(ui, repo, patch=None, **opts): """push the next patch onto the stack @@ -2403,6 +2546,12 @@ exact=opts.get('exact')) return ret +@command("^qpop", + [('a', 'all', None, _('pop all patches')), + ('n', 'name', '', + _('queue name to pop (DEPRECATED)'), _('NAME')), + ('f', 'force', None, _('forget any local changes to patched files'))], + _('hg qpop [-a] [-f] [PATCH | INDEX]')) def pop(ui, repo, patch=None, **opts): """pop the current patch off the stack @@ -2424,6 +2573,7 @@ q.save_dirty() return ret +@command("qrename|qmv", [], _('hg qrename PATCH1 [PATCH2]')) def rename(ui, repo, patch, name=None, **opts): """rename a patch @@ -2490,6 +2640,10 @@ q.save_dirty() +@command("qrestore", + [('d', 'delete', None, _('delete save entry')), + ('u', 'update', None, _('update queue working directory'))], + _('hg qrestore [-d] [-u] REV')) def restore(ui, repo, rev, **opts): """restore the queue state saved by a revision (DEPRECATED) @@ -2501,6 +2655,13 @@ q.save_dirty() return 0 +@command("qsave", + [('c', 'copy', None, _('copy patch directory')), + ('n', 'name', '', + _('copy directory name'), _('NAME')), + ('e', 'empty', None, _('clear queue status file')), + ('f', 'force', None, _('force copy'))] + commands.commitopts, + _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')) def save(ui, repo, **opts): """save current queue state (DEPRECATED) @@ -2533,12 +2694,23 @@ pass return 0 +@command("strip", + [('f', 'force', None, _('force removal of changesets, discard ' + 'uncommitted changes (no backup)')), + ('b', 'backup', None, _('bundle only changesets with local revision' + ' number greater than REV which are not' + ' descendants of REV (DEPRECATED)')), + ('n', 'no-backup', None, _('no backups')), + ('', 'nobackup', None, _('no backups (DEPRECATED)')), + ('k', 'keep', None, _("do not modify working copy during strip"))], + _('hg strip [-k] [-f] [-n] REV...')) def strip(ui, repo, *revs, **opts): """strip changesets and all their descendants from the repository The strip command removes the specified changesets and all their - descendants. If the working directory has uncommitted changes, - the operation is aborted unless the --force flag is supplied. + descendants. If the working directory has uncommitted changes, the + operation is aborted unless the --force flag is supplied, in which + case changes will be discarded. If a parent of the working directory is stripped, then the working directory will automatically be updated to the most recent @@ -2564,7 +2736,7 @@ backup = 'none' cl = repo.changelog - revs = set(cmdutil.revrange(repo, revs)) + revs = set(scmutil.revrange(repo, revs)) if not revs: raise util.Abort(_('empty revision set')) @@ -2614,6 +2786,12 @@ force=opts.get('force')) return 0 +@command("qselect", + [('n', 'none', None, _('disable all guards')), + ('s', 'series', None, _('list all guards in series file')), + ('', 'pop', None, _('pop to before first guarded applied patch')), + ('', 'reapply', None, _('pop, then reapply patches'))], + _('hg qselect [OPTION]... [GUARD]...')) def select(ui, repo, *args, **opts): '''set or print guarded patches to push @@ -2719,6 +2897,9 @@ finally: q.save_dirty() +@command("qfinish", + [('a', 'applied', None, _('finish all applied changesets'))], + _('hg qfinish [-a] [REV]...')) def finish(ui, repo, *revrange, **opts): """move applied patches into repository history @@ -2747,11 +2928,19 @@ ui.status(_('no patches applied\n')) return 0 - revs = cmdutil.revrange(repo, revrange) + revs = scmutil.revrange(repo, revrange) q.finish(repo, revs) q.save_dirty() return 0 +@command("qqueue", + [('l', 'list', False, _('list all available queues')), + ('c', 'create', False, _('create new queue')), + ('', 'rename', False, _('rename active queue')), + ('', 'delete', False, _('delete reference to queue')), + ('', 'purge', False, _('delete queue, and remove patch dir')), + ], + _('[OPTION] [QUEUE]')) def qqueue(ui, repo, name=None, **opts): '''manage multiple patch queues @@ -2960,7 +3149,7 @@ mqtags = [(patch.node, patch.name) for patch in q.applied] try: - r = self.changelog.rev(mqtags[-1][0]) + self.changelog.rev(mqtags[-1][0]) except error.RepoLookupError: self.ui.warn(_('mq status file refers to unknown node %s\n') % short(mqtags[-1][0])) @@ -3069,6 +3258,20 @@ ui.note(_("mq: (empty queue)\n")) return r +def revsetmq(repo, subset, x): + """``mq()`` + Changesets managed by MQ. + """ + revset.getargs(x, 0, 0, _("mq takes no arguments")) + applied = set([repo[r.node].rev() for r in repo.mq.applied]) + return [r for r in subset if r in applied] + +def extsetup(ui): + revset.symbols['mq'] = revsetmq + +# tell hggettext to extract docstrings from these functions: +i18nfunctions = [revsetmq] + def uisetup(ui): mqopt = [('', 'mq', None, _("operate on patch repository"))] @@ -3094,175 +3297,6 @@ if extmodule.__file__ != __file__: dotable(getattr(extmodule, 'cmdtable', {})) -seriesopts = [('s', 'summary', None, _('print first line of patch header'))] - -cmdtable = { - "qapplied": - (applied, - [('1', 'last', None, _('show only the last patch'))] + seriesopts, - _('hg qapplied [-1] [-s] [PATCH]')), - "qclone": - (clone, - [('', 'pull', None, _('use pull protocol to copy metadata')), - ('U', 'noupdate', None, _('do not update the new working directories')), - ('', 'uncompressed', None, - _('use uncompressed transfer (fast over LAN)')), - ('p', 'patches', '', - _('location of source patch repository'), _('REPO')), - ] + commands.remoteopts, - _('hg qclone [OPTION]... SOURCE [DEST]')), - "qcommit|qci": - (commit, - commands.table["^commit|ci"][1], - _('hg qcommit [OPTION]... [FILE]...')), - "^qdiff": - (diff, - commands.diffopts + commands.diffopts2 + commands.walkopts, - _('hg qdiff [OPTION]... [FILE]...')), - "qdelete|qremove|qrm": - (delete, - [('k', 'keep', None, _('keep patch file')), - ('r', 'rev', [], - _('stop managing a revision (DEPRECATED)'), _('REV'))], - _('hg qdelete [-k] [PATCH]...')), - 'qfold': - (fold, - [('e', 'edit', None, _('edit patch header')), - ('k', 'keep', None, _('keep folded patch files')), - ] + commands.commitopts, - _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')), - 'qgoto': - (goto, - [('f', 'force', None, _('overwrite any local changes'))], - _('hg qgoto [OPTION]... PATCH')), - 'qguard': - (guard, - [('l', 'list', None, _('list all patches and guards')), - ('n', 'none', None, _('drop all guards'))], - _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')), - 'qheader': (header, [], _('hg qheader [PATCH]')), - "qimport": - (qimport, - [('e', 'existing', None, _('import file in patch directory')), - ('n', 'name', '', - _('name of patch file'), _('NAME')), - ('f', 'force', None, _('overwrite existing files')), - ('r', 'rev', [], - _('place existing revisions under mq control'), _('REV')), - ('g', 'git', None, _('use git extended diff format')), - ('P', 'push', None, _('qpush after importing'))], - _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')), - "^qinit": - (init, - [('c', 'create-repo', None, _('create queue repository'))], - _('hg qinit [-c]')), - "^qnew": - (new, - [('e', 'edit', None, _('edit commit message')), - ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')), - ('g', 'git', None, _('use git extended diff format')), - ('U', 'currentuser', None, _('add "From: <current user>" to patch')), - ('u', 'user', '', - _('add "From: <USER>" to patch'), _('USER')), - ('D', 'currentdate', None, _('add "Date: <current date>" to patch')), - ('d', 'date', '', - _('add "Date: <DATE>" to patch'), _('DATE')) - ] + commands.walkopts + commands.commitopts, - _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')), - "qnext": (next, [] + seriesopts, _('hg qnext [-s]')), - "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')), - "^qpop": - (pop, - [('a', 'all', None, _('pop all patches')), - ('n', 'name', '', - _('queue name to pop (DEPRECATED)'), _('NAME')), - ('f', 'force', None, _('forget any local changes to patched files'))], - _('hg qpop [-a] [-f] [PATCH | INDEX]')), - "^qpush": - (push, - [('f', 'force', None, _('apply on top of local changes')), - ('e', 'exact', None, _('apply the target patch to its recorded parent')), - ('l', 'list', None, _('list patch name in commit text')), - ('a', 'all', None, _('apply all patches')), - ('m', 'merge', None, _('merge from another queue (DEPRECATED)')), - ('n', 'name', '', - _('merge queue name (DEPRECATED)'), _('NAME')), - ('', 'move', None, _('reorder patch series and apply only the patch'))], - _('hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]')), - "^qrefresh": - (refresh, - [('e', 'edit', None, _('edit commit message')), - ('g', 'git', None, _('use git extended diff format')), - ('s', 'short', None, - _('refresh only files already in the patch and specified files')), - ('U', 'currentuser', None, - _('add/update author field in patch with current user')), - ('u', 'user', '', - _('add/update author field in patch with given user'), _('USER')), - ('D', 'currentdate', None, - _('add/update date field in patch with current date')), - ('d', 'date', '', - _('add/update date field in patch with given date'), _('DATE')) - ] + commands.walkopts + commands.commitopts, - _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')), - 'qrename|qmv': - (rename, [], _('hg qrename PATCH1 [PATCH2]')), - "qrestore": - (restore, - [('d', 'delete', None, _('delete save entry')), - ('u', 'update', None, _('update queue working directory'))], - _('hg qrestore [-d] [-u] REV')), - "qsave": - (save, - [('c', 'copy', None, _('copy patch directory')), - ('n', 'name', '', - _('copy directory name'), _('NAME')), - ('e', 'empty', None, _('clear queue status file')), - ('f', 'force', None, _('force copy'))] + commands.commitopts, - _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')), - "qselect": - (select, - [('n', 'none', None, _('disable all guards')), - ('s', 'series', None, _('list all guards in series file')), - ('', 'pop', None, _('pop to before first guarded applied patch')), - ('', 'reapply', None, _('pop, then reapply patches'))], - _('hg qselect [OPTION]... [GUARD]...')), - "qseries": - (series, - [('m', 'missing', None, _('print patches not in series')), - ] + seriesopts, - _('hg qseries [-ms]')), - "strip": - (strip, - [('f', 'force', None, _('force removal of changesets even if the ' - 'working directory has uncommitted changes')), - ('b', 'backup', None, _('bundle only changesets with local revision' - ' number greater than REV which are not' - ' descendants of REV (DEPRECATED)')), - ('n', 'no-backup', None, _('no backups')), - ('', 'nobackup', None, _('no backups (DEPRECATED)')), - ('k', 'keep', None, _("do not modify working copy during strip"))], - _('hg strip [-k] [-f] [-n] REV...')), - "qtop": (top, [] + seriesopts, _('hg qtop [-s]')), - "qunapplied": - (unapplied, - [('1', 'first', None, _('show only the first patch'))] + seriesopts, - _('hg qunapplied [-1] [-s] [PATCH]')), - "qfinish": - (finish, - [('a', 'applied', None, _('finish all applied changesets'))], - _('hg qfinish [-a] [REV]...')), - 'qqueue': - (qqueue, - [ - ('l', 'list', False, _('list all available queues')), - ('c', 'create', False, _('create new queue')), - ('', 'rename', False, _('rename active queue')), - ('', 'delete', False, _('delete reference to queue')), - ('', 'purge', False, _('delete queue, and remove patch dir')), - ], - _('[OPTION] [QUEUE]')), -} colortable = {'qguard.negative': 'red', 'qguard.positive': 'yellow',
--- a/hgext/notify.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/notify.py Wed May 18 09:56:27 2011 +0200 @@ -249,7 +249,7 @@ def diff(self, ctx, ref=None): maxdiff = int(self.ui.config('notify', 'maxdiff', 300)) - prev = ctx.parents()[0].node() + prev = ctx.p1().node() ref = ref and ref.node() or ctx.node() chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui)) difflines = ''.join(chunks).splitlines()
--- a/hgext/parentrevspec.py Wed May 18 09:31:19 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,96 +0,0 @@ -# Mercurial extension to make it easy to refer to the parent of a revision -# -# Copyright (C) 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br> -# -# This software may be used and distributed according to the terms of the -# GNU General Public License version 2 or any later version. - -'''interpret suffixes to refer to ancestor revisions - -This extension allows you to use git-style suffixes to refer to the -ancestors of a specific revision. - -For example, if you can refer to a revision as "foo", then:: - - foo^N = Nth parent of foo - foo^0 = foo - foo^1 = first parent of foo - foo^2 = second parent of foo - foo^ = foo^1 - - foo~N = Nth first grandparent of foo - foo~0 = foo - foo~1 = foo^1 = foo^ = first parent of foo - foo~2 = foo^1^1 = foo^^ = first parent of first parent of foo -''' -from mercurial import error - -def reposetup(ui, repo): - if not repo.local(): - return - - class parentrevspecrepo(repo.__class__): - def lookup(self, key): - try: - _super = super(parentrevspecrepo, self) - return _super.lookup(key) - except error.RepoError: - pass - - circ = key.find('^') - tilde = key.find('~') - if circ < 0 and tilde < 0: - raise - elif circ >= 0 and tilde >= 0: - end = min(circ, tilde) - else: - end = max(circ, tilde) - - cl = self.changelog - base = key[:end] - try: - node = _super.lookup(base) - except error.RepoError: - # eek - reraise the first error - return _super.lookup(key) - - rev = cl.rev(node) - suffix = key[end:] - i = 0 - while i < len(suffix): - # foo^N => Nth parent of foo - # foo^0 == foo - # foo^1 == foo^ == 1st parent of foo - # foo^2 == 2nd parent of foo - if suffix[i] == '^': - j = i + 1 - p = cl.parentrevs(rev) - if j < len(suffix) and suffix[j].isdigit(): - j += 1 - n = int(suffix[i + 1:j]) - if n > 2 or n == 2 and p[1] == -1: - raise - else: - n = 1 - if n: - rev = p[n - 1] - i = j - # foo~N => Nth first grandparent of foo - # foo~0 = foo - # foo~1 = foo^1 == foo^ == 1st parent of foo - # foo~2 = foo^1^1 == foo^^ == 1st parent of 1st parent of foo - elif suffix[i] == '~': - j = i + 1 - while j < len(suffix) and suffix[j].isdigit(): - j += 1 - if j == i + 1: - raise - n = int(suffix[i + 1:j]) - for k in xrange(n): - rev = cl.parentrevs(rev)[0] - i = j - else: - raise - return cl.node(rev) - - repo.__class__ = parentrevspecrepo
--- a/hgext/patchbomb.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/patchbomb.py Wed May 18 09:56:27 2011 +0200 @@ -48,10 +48,14 @@ import os, errno, socket, tempfile, cStringIO, time import email.MIMEMultipart, email.MIMEBase import email.Utils, email.Encoders, email.Generator -from mercurial import cmdutil, commands, hg, mail, patch, util, discovery, url +from mercurial import cmdutil, commands, hg, mail, patch, util, discovery +from mercurial import scmutil from mercurial.i18n import _ from mercurial.node import bin +cmdtable = {} +command = cmdutil.command(cmdtable) + def prompt(ui, prompt, default=None, rest=':'): if not ui.interactive() and default is None: raise util.Abort(_("%s Please enter a valid value" % (prompt + rest))) @@ -119,10 +123,10 @@ if patchtags: patchname = patchtags[0] elif total > 1: - patchname = cmdutil.make_filename(repo, '%b-%n.patch', + patchname = cmdutil.makefilename(repo, '%b-%n.patch', binnode, seqno=idx, total=total) else: - patchname = cmdutil.make_filename(repo, '%b.patch', binnode) + patchname = cmdutil.makefilename(repo, '%b.patch', binnode) disposition = 'inline' if opts.get('attach'): disposition = 'attachment' @@ -146,6 +150,40 @@ msg['X-Mercurial-Node'] = node return msg, subj, ds +emailopts = [ + ('a', 'attach', None, _('send patches as attachments')), + ('i', 'inline', None, _('send patches as inline attachments')), + ('', 'bcc', [], _('email addresses of blind carbon copy recipients')), + ('c', 'cc', [], _('email addresses of copy recipients')), + ('', 'confirm', None, _('ask for confirmation before sending')), + ('d', 'diffstat', None, _('add diffstat output to messages')), + ('', 'date', '', _('use the given date as the sending date')), + ('', 'desc', '', _('use the given file as the series description')), + ('f', 'from', '', _('email address of sender')), + ('n', 'test', None, _('print messages that would be sent')), + ('m', 'mbox', '', _('write messages to mbox file instead of sending them')), + ('', 'reply-to', [], _('email addresses replies should be sent to')), + ('s', 'subject', '', _('subject of first message (intro or single patch)')), + ('', 'in-reply-to', '', _('message identifier to reply to')), + ('', 'flag', [], _('flags to add in subject prefixes')), + ('t', 'to', [], _('email addresses of recipients'))] + +@command('email', + [('g', 'git', None, _('use git extended diff format')), + ('', 'plain', None, _('omit hg patch header')), + ('o', 'outgoing', None, + _('send changes not found in the target repository')), + ('b', 'bundle', None, _('send changes not in target as a binary bundle')), + ('', 'bundlename', 'bundle', + _('name of the bundle attachment file'), _('NAME')), + ('r', 'rev', [], _('a revision to send'), _('REV')), + ('', 'force', None, _('run even when remote repository is unrelated ' + '(with -b/--bundle)')), + ('', 'base', [], _('a base changeset to specify instead of a destination ' + '(with -b/--bundle)'), _('REV')), + ('', 'intro', None, _('send an introduction email for a single patch')), + ] + emailopts + commands.remoteopts, + _('hg email [OPTION]... [DEST]...')) def patchbomb(ui, repo, *revs, **opts): '''send changesets by email @@ -238,19 +276,18 @@ dest = ui.expandpath(dest or 'default-push', dest or 'default') dest, branches = hg.parseurl(dest) revs, checkout = hg.addbranchrevs(repo, repo, branches, revs) - if revs: - revs = [repo.lookup(rev) for rev in revs] other = hg.repository(hg.remoteui(repo, opts), dest) - ui.status(_('comparing with %s\n') % url.hidepassword(dest)) - o = discovery.findoutgoing(repo, other) + ui.status(_('comparing with %s\n') % util.hidepassword(dest)) + common, _anyinc, _heads = discovery.findcommonincoming(repo, other) + nodes = revs and map(repo.lookup, revs) or revs + o = repo.changelog.findmissing(common, heads=nodes) if not o: ui.status(_("no changes found\n")) return [] - o = repo.changelog.nodesbetween(o, revs)[0] return [str(repo.changelog.rev(r)) for r in o] def getpatches(revs): - for r in cmdutil.revrange(repo, revs): + for r in scmutil.revrange(repo, revs): output = cStringIO.StringIO() cmdutil.export(repo, [r], fp=output, opts=patch.diffopts(ui, opts)) @@ -512,52 +549,3 @@ ui.progress(_('writing'), None) ui.progress(_('sending'), None) - -emailopts = [ - ('a', 'attach', None, _('send patches as attachments')), - ('i', 'inline', None, _('send patches as inline attachments')), - ('', 'bcc', [], _('email addresses of blind carbon copy recipients')), - ('c', 'cc', [], _('email addresses of copy recipients')), - ('', 'confirm', None, _('ask for confirmation before sending')), - ('d', 'diffstat', None, _('add diffstat output to messages')), - ('', 'date', '', _('use the given date as the sending date')), - ('', 'desc', '', _('use the given file as the series description')), - ('f', 'from', '', _('email address of sender')), - ('n', 'test', None, _('print messages that would be sent')), - ('m', 'mbox', '', - _('write messages to mbox file instead of sending them')), - ('', 'reply-to', [], _('email addresses replies should be sent to')), - ('s', 'subject', '', - _('subject of first message (intro or single patch)')), - ('', 'in-reply-to', '', - _('message identifier to reply to')), - ('', 'flag', [], _('flags to add in subject prefixes')), - ('t', 'to', [], _('email addresses of recipients')), - ] - - -cmdtable = { - "email": - (patchbomb, - [('g', 'git', None, _('use git extended diff format')), - ('', 'plain', None, _('omit hg patch header')), - ('o', 'outgoing', None, - _('send changes not found in the target repository')), - ('b', 'bundle', None, - _('send changes not in target as a binary bundle')), - ('', 'bundlename', 'bundle', - _('name of the bundle attachment file'), _('NAME')), - ('r', 'rev', [], - _('a revision to send'), _('REV')), - ('', 'force', None, - _('run even when remote repository is unrelated ' - '(with -b/--bundle)')), - ('', 'base', [], - _('a base changeset to specify instead of a destination ' - '(with -b/--bundle)'), - _('REV')), - ('', 'intro', None, - _('send an introduction email for a single patch')), - ] + emailopts + commands.remoteopts, - _('hg email [OPTION]... [DEST]...')) -}
--- a/hgext/progress.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/progress.py Wed May 18 09:56:27 2011 +0200 @@ -37,9 +37,9 @@ # disable is given Valid entries for the format field are topic, bar, number, unit, -estimate, and item. item defaults to the last 20 characters of the -item, but this can be changed by adding either ``-<num>`` which would -take the last num characters, or ``+<num>`` for the first num +estimate, speed, and item. item defaults to the last 20 characters of +the item, but this can be changed by adding either ``-<num>`` which +would take the last num characters, or ``+<num>`` for the first num characters. """ @@ -47,7 +47,6 @@ import time from mercurial.i18n import _ -from mercurial import util def spacejoin(*args): return ' '.join(s for s in args if s) @@ -152,6 +151,8 @@ add = unit elif indicator == 'estimate': add = self.estimate(topic, pos, total, now) + elif indicator == 'speed': + add = self.speed(topic, pos, unit, now) if not needprogress: head = spacejoin(head, add) else: @@ -217,6 +218,15 @@ return fmtremaining(seconds) return '' + def speed(self, topic, pos, unit, now): + initialpos = self.startvals[topic] + delta = pos - initialpos + elapsed = now - self.starttimes[topic] + if elapsed > float( + self.ui.config('progress', 'estimate', default=2)): + return _('%d %s/sec') % (delta / elapsed, unit) + return '' + def progress(self, topic, pos, item='', unit='', total=None): now = time.time() if pos is None: @@ -239,7 +249,6 @@ self.topicstates[topic] = pos, item, unit, total if now - self.lastprint >= self.refresh and self.topics: self.lastprint = now - current = self.topics[-1] self.show(now, topic, *self.topicstates[topic]) def uisetup(ui):
--- a/hgext/purge.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/purge.py Wed May 18 09:56:27 2011 +0200 @@ -25,10 +25,21 @@ '''command to delete untracked files from the working directory''' -from mercurial import util, commands, cmdutil +from mercurial import util, commands, cmdutil, scmutil from mercurial.i18n import _ import os, stat +cmdtable = {} +command = cmdutil.command(cmdtable) + +@command('purge|clean', + [('a', 'abort-on-err', None, _('abort if an error occurs')), + ('', 'all', None, _('purge ignored files too')), + ('p', 'print', None, _('print filenames instead of deleting them')), + ('0', 'print0', None, _('end filenames with NUL, for use with xargs' + ' (implies -p/--print)')), + ] + commands.walkopts, + _('hg purge [OPTION]... [DIR]...')) def purge(ui, repo, *dirs, **opts): '''removes files not tracked by Mercurial @@ -85,7 +96,7 @@ os.remove(path) directories = [] - match = cmdutil.match(repo, dirs, opts) + match = scmutil.match(repo, dirs, opts) match.dir = directories.append status = repo.status(match=match, ignored=opts['all'], unknown=True) @@ -97,15 +108,3 @@ if match(f) and not os.listdir(repo.wjoin(f)): ui.note(_('Removing directory %s\n') % f) remove(os.rmdir, f) - -cmdtable = { - 'purge|clean': - (purge, - [('a', 'abort-on-err', None, _('abort if an error occurs')), - ('', 'all', None, _('purge ignored files too')), - ('p', 'print', None, _('print filenames instead of deleting them')), - ('0', 'print0', None, _('end filenames with NUL, for use with xargs' - ' (implies -p/--print)')), - ] + commands.walkopts, - _('hg purge [OPTION]... [DIR]...')) -}
--- a/hgext/rebase.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/rebase.py Wed May 18 09:56:27 2011 +0200 @@ -15,7 +15,7 @@ ''' from mercurial import hg, util, repair, merge, cmdutil, commands -from mercurial import extensions, ancestor, copies, patch +from mercurial import extensions, copies, patch from mercurial.commands import templateopts from mercurial.node import nullrev from mercurial.lock import release @@ -24,6 +24,33 @@ nullmerge = -2 +cmdtable = {} +command = cmdutil.command(cmdtable) + +@command('rebase', + [('s', 'source', '', + _('rebase from the specified changeset'), _('REV')), + ('b', 'base', '', + _('rebase from the base of the specified changeset ' + '(up to greatest common ancestor of base and dest)'), + _('REV')), + ('d', 'dest', '', + _('rebase onto the specified changeset'), _('REV')), + ('', 'collapse', False, _('collapse the rebased changesets')), + ('m', 'message', '', + _('use text as collapse commit message'), _('TEXT')), + ('l', 'logfile', '', + _('read collapse commit message from file'), _('FILE')), + ('', 'keep', False, _('keep original changesets')), + ('', 'keepbranches', False, _('keep original branch names')), + ('', 'detach', False, _('force detaching of source from its original ' + 'branch')), + ('t', 'tool', '', _('specify merge tool')), + ('c', 'continue', False, _('continue an interrupted rebase')), + ('a', 'abort', False, _('abort an interrupted rebase'))] + + templateopts, + _('hg rebase [-s REV | -b REV] [-d REV] [options]\n' + 'hg rebase {-a|-c}')) def rebase(ui, repo, **opts): """move changeset (and descendants) to a different branch @@ -90,7 +117,8 @@ contf = opts.get('continue') abortf = opts.get('abort') collapsef = opts.get('collapse', False) - extrafn = opts.get('extrafn') + collapsemsg = cmdutil.logmessage(opts) + extrafn = opts.get('extrafn') # internal, used by e.g. hgsubversion keepf = opts.get('keep', False) keepbranchesf = opts.get('keepbranches', False) detachf = opts.get('detach', False) @@ -98,6 +126,10 @@ # other extensions keepopen = opts.get('keepopen', False) + if collapsemsg and not collapsef: + raise util.Abort( + _('message can only be specified with collapse')) + if contf or abortf: if contf and abortf: raise util.Abort(_('cannot use both abort and continue')) @@ -109,6 +141,8 @@ if srcf or basef or destf: raise util.Abort( _('abort and continue do not allow specifying revisions')) + if opts.get('tool', False): + ui.warn(_('tool option will be ignored\n')) (originalwd, target, state, skipped, collapsef, keepf, keepbranchesf, external) = restorestatus(repo) @@ -125,7 +159,7 @@ if basef: raise util.Abort(_('cannot specify a base with detach')) - cmdutil.bail_if_changed(repo) + cmdutil.bailifchanged(repo) result = buildstate(repo, destf, srcf, basef, detachf) if not result: # Empty state built, nothing to rebase @@ -138,8 +172,7 @@ external = checkexternal(repo, state, targetancestors) if keepbranchesf: - if extrafn: - raise util.Abort(_('cannot use both keepbranches and extrafn')) + assert not extrafn, 'cannot use both keepbranches and extrafn' def extrafn(ctx, extra): extra['branch'] = ctx.branch() @@ -163,10 +196,14 @@ if len(repo.parents()) == 2: repo.ui.debug('resuming interrupted rebase\n') else: - stats = rebasenode(repo, rev, p1, p2, state) - if stats and stats[3] > 0: - raise util.Abort(_('unresolved conflicts (see hg ' - 'resolve, then hg rebase --continue)')) + try: + ui.setconfig('ui', 'forcemerge', opts.get('tool', '')) + stats = rebasenode(repo, rev, p1, state) + if stats and stats[3] > 0: + raise util.Abort(_('unresolved conflicts (see hg ' + 'resolve, then hg rebase --continue)')) + finally: + ui.setconfig('ui', 'forcemerge', '') updatedirstate(repo, rev, target, p2) if not collapsef: newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn) @@ -190,11 +227,14 @@ if collapsef and not keepopen: p1, p2 = defineparents(repo, min(state), target, state, targetancestors) - commitmsg = 'Collapsed revision' - for rebased in state: - if rebased not in skipped and state[rebased] != nullmerge: - commitmsg += '\n* %s' % repo[rebased].description() - commitmsg = ui.edit(commitmsg, repo.ui.username()) + if collapsemsg: + commitmsg = collapsemsg + else: + commitmsg = 'Collapsed revision' + for rebased in state: + if rebased not in skipped and state[rebased] != nullmerge: + commitmsg += '\n* %s' % repo[rebased].description() + commitmsg = ui.edit(commitmsg, repo.ui.username()) newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg, extrafn=extrafn) @@ -221,25 +261,6 @@ finally: release(lock, wlock) -def rebasemerge(repo, rev, first=False): - 'return the correct ancestor' - oldancestor = ancestor.ancestor - - def newancestor(a, b, pfunc): - if b == rev: - return repo[rev].parents()[0].rev() - return oldancestor(a, b, pfunc) - - if not first: - ancestor.ancestor = newancestor - else: - repo.ui.debug("first revision, do not change ancestor\n") - try: - stats = merge.update(repo, rev, True, True, False) - return stats - finally: - ancestor.ancestor = oldancestor - def checkexternal(repo, state, targetancestors): """Check whether one or more external revisions need to be taken in consideration. In the latter case, abort. @@ -293,7 +314,7 @@ repo.dirstate.invalidate() raise -def rebasenode(repo, rev, p1, p2, state): +def rebasenode(repo, rev, p1, state): 'Rebase a single revision' # Merge phase # Update to target and merge it with local @@ -304,9 +325,10 @@ repo.ui.debug(" already in target\n") repo.dirstate.write() repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev])) - first = repo[rev].rev() == repo[min(state)].rev() - stats = rebasemerge(repo, rev, first) - return stats + base = None + if repo[rev].rev() != repo[min(state)].rev(): + base = repo[rev].p1().node() + return merge.update(repo, rev, True, True, False, base) def defineparents(repo, rev, target, state, targetancestors): 'Return the new parent relationship of the revision that will be rebased' @@ -354,6 +376,8 @@ 'Update rebased mq patches - finalize and then import them' mqrebase = {} mq = repo.mq + original_series = mq.full_series[:] + for p in mq.applied: rev = repo[p.node].rev() if rev in state: @@ -371,6 +395,15 @@ repo.ui.debug('import mq patch %d (%s)\n' % (state[rev], name)) mq.qimport(repo, (), patchname=name, git=isgit, rev=[str(state[rev])]) + + # Restore missing guards + for s in original_series: + pname = mq.guard_re.split(s, 1)[0] + if pname in mq.full_series: + repo.ui.debug('restoring guard for patch %s' % (pname)) + mq.full_series.remove(pname) + mq.full_series.append(s) + mq.series_dirty = True mq.save_dirty() def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches, @@ -475,9 +508,10 @@ if src: commonbase = repo[src].ancestor(repo[dest]) + samebranch = repo[src].branch() == repo[dest].branch() if commonbase == repo[src]: raise util.Abort(_('source is ancestor of destination')) - if commonbase == repo[dest]: + if samebranch and commonbase == repo[dest]: raise util.Abort(_('source is descendant of destination')) source = repo[src].rev() if detach: @@ -524,7 +558,7 @@ ui.debug('--update and --rebase are not compatible, ignoring ' 'the update flag\n') - cmdutil.bail_if_changed(repo) + cmdutil.bailifchanged(repo) revsprepull = len(repo) origpostincoming = commands.postincoming def _dummy(*args, **kwargs): @@ -551,27 +585,3 @@ entry[1].append(('', 'rebase', None, _("rebase working directory to branch head")) ) - -cmdtable = { -"rebase": - (rebase, - [ - ('s', 'source', '', - _('rebase from the specified changeset'), _('REV')), - ('b', 'base', '', - _('rebase from the base of the specified changeset ' - '(up to greatest common ancestor of base and dest)'), - _('REV')), - ('d', 'dest', '', - _('rebase onto the specified changeset'), _('REV')), - ('', 'collapse', False, _('collapse the rebased changesets')), - ('', 'keep', False, _('keep original changesets')), - ('', 'keepbranches', False, _('keep original branch names')), - ('', 'detach', False, _('force detaching of source from its original ' - 'branch')), - ('c', 'continue', False, _('continue an interrupted rebase')), - ('a', 'abort', False, _('abort an interrupted rebase'))] + - templateopts, - _('hg rebase [-s REV | -b REV] [-d REV] [options]\n' - 'hg rebase {-a|-c}')) -}
--- a/hgext/record.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/record.py Wed May 18 09:56:27 2011 +0200 @@ -324,10 +324,12 @@ for i, chunk in enumerate(h.hunks): if skipfile is None and skipall is None: chunk.pretty(ui) - msg = (total == 1 - and (_('record this change to %r?') % chunk.filename()) - or (_('record change %d/%d to %r?') % - (pos - len(h.hunks) + i, total, chunk.filename()))) + if total == 1: + msg = _('record this change to %r?') % chunk.filename() + else: + idx = pos - len(h.hunks) + i + msg = _('record change %d/%d to %r?') % (idx, total, + chunk.filename()) r, skipfile, skipall = prompt(skipfile, skipall, msg) if r: if fixoffset: @@ -467,7 +469,7 @@ # 3a. apply filtered patch to clean repo (clean) if backups: - hg.revert(repo, repo.dirstate.parents()[0], + hg.revert(repo, repo.dirstate.p1(), lambda key: key in backups) # 3b. (apply) @@ -475,10 +477,8 @@ try: ui.debug('applying patch\n') ui.debug(fp.getvalue()) - pfiles = {} - patch.internalpatch(fp, ui, 1, repo.root, files=pfiles, + patch.internalpatch(ui, repo, fp, 1, repo.root, eolmode=None) - cmdutil.updatedir(ui, repo, pfiles) except patch.PatchError, err: raise util.Abort(str(err)) del fp @@ -533,6 +533,9 @@ "record": (record, commands.table['^commit|ci'][1], # same options as commit _('hg record [OPTION]... [FILE]...')), + "qrecord": + (qrecord, {}, # placeholder until mq is available + _('hg qrecord [OPTION]... PATCH [FILE]...')), }
--- a/hgext/relink.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/relink.py Wed May 18 09:56:27 2011 +0200 @@ -38,9 +38,9 @@ """ if not hasattr(util, 'samefile') or not hasattr(util, 'samedevice'): raise util.Abort(_('hardlinks are not supported on this system')) - src = hg.repository( - hg.remoteui(repo, opts), - ui.expandpath(origin or 'default-relink', origin or 'default')) + src = hg.repository(hg.remoteui(repo, opts), + ui.expandpath(origin or 'default-relink', + origin or 'default')) if not src.local(): raise util.Abort(_('must specify local origin repository')) ui.status(_('relinking %s to %s\n') % (src.store.path, repo.store.path)) @@ -132,7 +132,7 @@ bak = dst + '.bak' os.rename(dst, bak) try: - util.os_link(src, dst) + util.oslink(src, dst) except OSError: os.rename(bak, dst) raise @@ -172,8 +172,8 @@ ui.progress(_('relinking'), None) - ui.status(_('relinked %d files (%d bytes reclaimed)\n') % - (relinked, savedbytes)) + ui.status(_('relinked %d files (%s reclaimed)\n') % + (relinked, util.bytecount(savedbytes))) cmdtable = { 'relink': (
--- a/hgext/schemes.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/schemes.py Wed May 18 09:56:27 2011 +0200 @@ -40,8 +40,9 @@ same name. """ -import re -from mercurial import hg, templater +import os, re +from mercurial import extensions, hg, templater, util +from mercurial.i18n import _ class ShortRepository(object): @@ -58,6 +59,7 @@ return '<ShortRepository: %s>' % self.scheme def instance(self, ui, url, create): + # Should this use urlmod.url(), or is manual parsing better? url = url.split('://', 1)[1] parts = url.split('/', self.parts) if len(parts) > self.parts: @@ -69,6 +71,12 @@ url = ''.join(self.templater.process(self.url, context)) + tail return hg._lookup(url).instance(ui, url, create) +def hasdriveletter(orig, path): + for scheme in schemes: + if path.startswith(scheme + ':'): + return False + return orig(path) + schemes = { 'py': 'http://hg.python.org/', 'bb': 'https://bitbucket.org/', @@ -81,4 +89,10 @@ schemes.update(dict(ui.configitems('schemes'))) t = templater.engine(lambda x: x) for scheme, url in schemes.items(): + if (os.name == 'nt' and len(scheme) == 1 and scheme.isalpha() + and os.path.exists('%s:\\' % scheme)): + raise util.Abort(_('custom scheme %s:// conflicts with drive ' + 'letter %s:\\\n') % (scheme, scheme.upper())) hg.schemes[scheme] = ShortRepository(url, scheme, t) + + extensions.wrapfunction(util, 'hasdriveletter', hasdriveletter)
--- a/hgext/transplant.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/transplant.py Wed May 18 09:56:27 2011 +0200 @@ -15,9 +15,12 @@ from mercurial.i18n import _ import os, tempfile -from mercurial import bundlerepo, cmdutil, hg, merge, match -from mercurial import patch, revlog, util, error -from mercurial import revset +from mercurial import bundlerepo, hg, merge, match +from mercurial import patch, revlog, scmutil, util, error, cmdutil +from mercurial import revset, templatekw + +cmdtable = {} +command = cmdutil.command(cmdtable) class transplantentry(object): def __init__(self, lnode, rnode): @@ -31,7 +34,7 @@ self.opener = opener if not opener: - self.opener = util.opener(self.path) + self.opener = scmutil.opener(self.path) self.transplants = {} self.dirty = False self.read() @@ -39,7 +42,7 @@ def read(self): abspath = os.path.join(self.path, self.transplantfile) if self.transplantfile and os.path.exists(abspath): - for line in self.opener(self.transplantfile).read().splitlines(): + for line in self.opener.read(self.transplantfile).splitlines(): lnode, rnode = map(revlog.bin, line.split(':')) list = self.transplants.setdefault(rnode, []) list.append(transplantentry(lnode, rnode)) @@ -74,7 +77,7 @@ def __init__(self, ui, repo): self.ui = ui self.path = repo.join('transplant') - self.opener = util.opener(self.path) + self.opener = scmutil.opener(self.path) self.transplants = transplants(self.path, 'transplants', opener=self.opener) @@ -177,12 +180,11 @@ lock.release() wlock.release() - def filter(self, filter, changelog, patchfile): + def filter(self, filter, node, changelog, patchfile): '''arbitrarily rewrite changeset before applying it''' 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, 'w') fp.write("# HG changeset patch\n") @@ -194,7 +196,9 @@ try: util.system('%s %s %s' % (filter, util.shellquote(headerfile), util.shellquote(patchfile)), - environ={'HGUSER': changelog[1]}, + environ={'HGUSER': changelog[1], + 'HGREVISION': revlog.hex(node), + }, onerr=util.Abort, errprefix=_('filter failed')) user, date, msg = self.parselog(file(headerfile))[1:4] finally: @@ -209,7 +213,7 @@ date = "%d %d" % (time, timezone) extra = {'transplant_source': node} if filter: - (user, date, message) = self.filter(filter, cl, patchfile) + (user, date, message) = self.filter(filter, node, cl, patchfile) if log: # we don't translate messages inserted into commits @@ -223,20 +227,17 @@ if patchfile: try: files = {} - try: - patch.patch(patchfile, self.ui, cwd=repo.root, - files=files, eolmode=None) - if not files: - self.ui.warn(_('%s: empty changeset') - % revlog.hex(node)) - return None - finally: - files = cmdutil.updatedir(self.ui, repo, files) + patch.patch(self.ui, repo, patchfile, cwd=repo.root, + files=files, eolmode=None) + files = list(files) + if not files: + self.ui.warn(_('%s: empty changeset') % revlog.hex(node)) + return None except Exception, inst: seriespath = os.path.join(self.path, 'series') if os.path.exists(seriespath): os.unlink(seriespath) - p1 = repo.dirstate.parents()[0] + p1 = repo.dirstate.p1() p2 = node self.log(user, date, message, p1, p2, merge=merge) self.ui.write(str(inst) + '\n') @@ -317,7 +318,7 @@ nodes = [] merges = [] cur = nodes - for line in self.opener('series').read().splitlines(): + for line in self.opener.read('series').splitlines(): if line.startswith('# Merges'): cur = merges continue @@ -345,6 +346,8 @@ message = [] node = revlog.nullid inmsg = False + user = None + date = None for line in fp.read().splitlines(): if inmsg: message.append(line) @@ -359,6 +362,8 @@ elif not line.startswith('# '): inmsg = True message.append(line) + if None in (user, date): + raise util.Abort(_("filter corrupted changeset (no user or date)")) return (node, user, date, '\n'.join(message), parents) def log(self, user, date, message, p1, p2, merge=False): @@ -448,6 +453,20 @@ displayer.close() return (transplants, merges) +@command('transplant', + [('s', 'source', '', _('pull patches from REPO'), _('REPO')), + ('b', 'branch', [], + _('pull patches from branch BRANCH'), _('BRANCH')), + ('a', 'all', None, _('pull all changesets up to BRANCH')), + ('p', 'prune', [], _('skip over REV'), _('REV')), + ('m', 'merge', [], _('merge at REV'), _('REV')), + ('', 'log', None, _('append transplant info to log message')), + ('c', 'continue', None, _('continue last transplant session ' + 'after repair')), + ('', 'filter', '', + _('filter changesets through command'), _('CMD'))], + _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] ' + '[-m REV] [REV]...')) def transplant(ui, repo, *revs, **opts): '''transplant changesets from another branch @@ -489,10 +508,8 @@ and then resume where you left off by calling :hg:`transplant --continue/-c`. ''' - def incwalk(repo, incoming, branches, match=util.always): - if not branches: - branches = None - for node in repo.changelog.nodesbetween(incoming, branches)[0]: + def incwalk(repo, csets, match=util.always): + for node in csets: if match(node): yield node @@ -542,15 +559,16 @@ if m or a or r or d: raise util.Abort(_('outstanding local changes')) - bundle = None - source = opts.get('source') - if source: - sourcerepo = ui.expandpath(source) - source = hg.repository(ui, sourcerepo) - source, incoming, bundle = bundlerepo.getremotechanges(ui, repo, source, - force=True) + sourcerepo = opts.get('source') + if sourcerepo: + source = hg.repository(ui, ui.expandpath(sourcerepo)) + branches = map(source.lookup, opts.get('branch', ())) + source, csets, cleanupfn = bundlerepo.getremotechanges(ui, repo, source, + onlyheads=branches, force=True) else: source = repo + branches = map(source.lookup, opts.get('branch', ())) + cleanupfn = None try: if opts.get('continue'): @@ -560,20 +578,18 @@ tf = tp.transplantfilter(repo, source, p1) if opts.get('prune'): prune = [source.lookup(r) - for r in cmdutil.revrange(source, opts.get('prune'))] + for r in scmutil.revrange(source, opts.get('prune'))] matchfn = lambda x: tf(x) and x not in prune else: matchfn = tf - branches = map(source.lookup, opts.get('branch', ())) merges = map(source.lookup, opts.get('merge', ())) revmap = {} if revs: - for r in cmdutil.revrange(source, revs): + for r in scmutil.revrange(source, revs): revmap[int(r)] = source.lookup(r) elif opts.get('all') or not merges: if source != repo: - alltransplants = incwalk(source, incoming, branches, - match=matchfn) + alltransplants = incwalk(source, csets, match=matchfn) else: alltransplants = transplantwalk(source, p1, branches, match=matchfn) @@ -589,9 +605,8 @@ tp.apply(repo, source, revmap, merges, opts) finally: - if bundle: - source.close() - os.unlink(bundle) + if cleanupfn: + cleanupfn() def revsettransplanted(repo, subset, x): """``transplanted([set])`` @@ -601,35 +616,17 @@ s = revset.getset(repo, subset, x) else: s = subset - cs = set() - for r in xrange(0, len(repo)): - if repo[r].extra().get('transplant_source'): - cs.add(r) - return [r for r in s if r in cs] + return [r for r in s if repo[r].extra().get('transplant_source')] + +def kwtransplanted(repo, ctx, **args): + """:transplanted: String. The node identifier of the transplanted + changeset if any.""" + n = ctx.extra().get('transplant_source') + return n and revlog.hex(n) or '' def extsetup(ui): revset.symbols['transplanted'] = revsettransplanted - -cmdtable = { - "transplant": - (transplant, - [('s', 'source', '', - _('pull patches from REPO'), _('REPO')), - ('b', 'branch', [], - _('pull patches from branch BRANCH'), _('BRANCH')), - ('a', 'all', None, _('pull all changesets up to BRANCH')), - ('p', 'prune', [], - _('skip over REV'), _('REV')), - ('m', 'merge', [], - _('merge at REV'), _('REV')), - ('', 'log', None, _('append transplant info to log message')), - ('c', 'continue', None, _('continue last transplant session ' - 'after repair')), - ('', 'filter', '', - _('filter changesets through command'), _('CMD'))], - _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] ' - '[-m REV] [REV]...')) -} + templatekw.keywords['transplanted'] = kwtransplanted # tell hggettext to extract docstrings from these functions: -i18nfunctions = [revsettransplanted] +i18nfunctions = [revsettransplanted, kwtransplanted]
--- a/hgext/zeroconf/Zeroconf.py Wed May 18 09:31:19 2011 +0200 +++ b/hgext/zeroconf/Zeroconf.py Wed May 18 09:56:27 2011 +0200 @@ -237,14 +237,14 @@ """Class accessor""" try: return _CLASSES[clazz] - except: + except KeyError: return "?(%s)" % (clazz) def getType(self, type): """Type accessor""" try: return _TYPES[type] - except: + except KeyError: return "?(%s)" % (type) def toString(self, hdr, other): @@ -360,7 +360,7 @@ """String representation""" try: return socket.inet_ntoa(self.address) - except: + except Exception: return self.address class DNSHinfo(DNSRecord): @@ -790,7 +790,7 @@ """Adds an entry""" try: list = self.cache[entry.key] - except: + except KeyError: list = self.cache[entry.key] = [] list.append(entry) @@ -799,7 +799,7 @@ try: list = self.cache[entry.key] list.remove(entry) - except: + except KeyError: pass def get(self, entry): @@ -808,7 +808,7 @@ try: list = self.cache[entry.key] return list[list.index(entry)] - except: + except (KeyError, ValueError): return None def getByDetails(self, name, type, clazz): @@ -821,7 +821,7 @@ """Returns a list of entries whose key matches the name.""" try: return self.cache[name] - except: + except KeyError: return [] def entries(self): @@ -829,7 +829,7 @@ def add(x, y): return x+y try: return reduce(add, self.cache.values()) - except: + except Exception: return [] @@ -869,10 +869,10 @@ for socket in rr: try: self.readers[socket].handle_read() - except: + except Exception: if not globals()['_GLOBAL_DONE']: traceback.print_exc() - except: + except Exception: pass def getReaders(self): @@ -988,7 +988,7 @@ callback = lambda x: self.listener.removeService(x, self.type, record.alias) self.list.append(callback) return - except: + except Exception: if not expired: self.services[record.alias.lower()] = record callback = lambda x: self.listener.addService(x, self.type, record.alias) @@ -1117,7 +1117,7 @@ result[key] = value self.properties = result - except: + except Exception: traceback.print_exc() self.properties = None @@ -1255,7 +1255,7 @@ try: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - except: + except Exception: # SO_REUSEADDR should be equivalent to SO_REUSEPORT for # multicast UDP sockets (p 731, "TCP/IP Illustrated, # Volume 2"), but some BSD-derived systems require @@ -1270,7 +1270,7 @@ self.socket.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) try: self.socket.bind(self.group) - except: + except Exception: # Some versions of linux raise an exception even though # the SO_REUSE* options have been set, so ignore it # @@ -1370,7 +1370,7 @@ self.servicetypes[info.type]-=1 else: del self.servicetypes[info.type] - except: + except KeyError: pass now = currentTimeMillis() nextTime = now @@ -1455,7 +1455,7 @@ try: self.listeners.remove(listener) self.notifyAll() - except: + except Exception: pass def updateRecord(self, now, rec): @@ -1528,7 +1528,7 @@ out.addAnswer(msg, DNSText(question.name, _TYPE_TXT, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.text)) if question.type == _TYPE_SRV: out.addAdditionalAnswer(DNSAddress(service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE, _DNS_TTL, service.address)) - except: + except Exception: traceback.print_exc() if out is not None and out.answers: @@ -1541,7 +1541,7 @@ #temp = DNSIncoming(out.packet()) try: self.socket.sendto(out.packet(), 0, (addr, port)) - except: + except Exception: # Ignore this, it may be a temporary loss of network connection pass
--- a/i18n/da.po Wed May 18 09:31:19 2011 +0200 +++ b/i18n/da.po Wed May 18 09:56:27 2011 +0200 @@ -17,8 +17,8 @@ msgstr "" "Project-Id-Version: Mercurial\n" "Report-Msgid-Bugs-To: <mercurial-devel@selenic.com>\n" -"POT-Creation-Date: 2011-03-16 17:41+0100\n" -"PO-Revision-Date: 2011-03-16 17:42+0100\n" +"POT-Creation-Date: 2011-04-19 09:32+0200\n" +"PO-Revision-Date: 2011-04-19 09:45+0200\n" "Last-Translator: <mg@lazybytes.net>\n" "Language-Team: Danish\n" "Language: Danish\n" @@ -294,140 +294,231 @@ msgid "" "This hook extension adds comments on bugs in Bugzilla when changesets\n" -"that refer to bugs by Bugzilla ID are seen. The hook does not change\n" -"bug status." -msgstr "" - -msgid "" -"The hook updates the Bugzilla database directly. Only Bugzilla\n" -"installations using MySQL are supported." -msgstr "" - -msgid "" -"The hook relies on a Bugzilla script to send bug change notification\n" -"emails. That script changes between Bugzilla versions; the\n" -"'processmail' script used prior to 2.18 is replaced in 2.18 and\n" -"subsequent versions by 'config/sendbugmail.pl'. Note that these will\n" -"be run by Mercurial as the user pushing the change; you will need to\n" -"ensure the Bugzilla install file permissions are set appropriately." -msgstr "" - -msgid "" -"The extension is configured through three different configuration\n" -"sections. These keys are recognized in the [bugzilla] section:" -msgstr "" - -msgid "" -"host\n" -" Hostname of the MySQL server holding the Bugzilla database." -msgstr "" - -msgid "" -"db\n" -" Name of the Bugzilla database in MySQL. Default 'bugs'." -msgstr "" - -msgid "" -"user\n" -" Username to use to access MySQL server. Default 'bugs'." -msgstr "" - -msgid "" -"password\n" +"that refer to bugs by Bugzilla ID are seen. The comment is formatted using\n" +"the Mercurial template mechanism." +msgstr "" + +msgid "The hook does not change bug status." +msgstr "" + +msgid "Three basic modes of access to Bugzilla are provided:" +msgstr "" + +msgid "" +"1. Access via the Bugzilla XMLRPC interface. Requires Bugzilla 3.4 or later." +msgstr "" + +msgid "" +"2. Check data via the Bugzilla XMLRPC interface and submit bug change\n" +" via email to Bugzilla email interface. Requires Bugzilla 3.4 or later." +msgstr "" + +msgid "" +"3. Writing directly to the Bugzilla database. Only Bugzilla installations\n" +" using MySQL are supported. Requires Python MySQLdb." +msgstr "" + +msgid "" +"Writing directly to the database is susceptible to schema changes, and\n" +"relies on a Bugzilla contrib script to send out bug change\n" +"notification emails. This script runs as the user running Mercurial,\n" +"must be run on the host with the Bugzilla install, and requires\n" +"permission to read Bugzilla configuration details and the necessary\n" +"MySQL user and password to have full access rights to the Bugzilla\n" +"database. For these reasons this access mode is now considered\n" +"deprecated, and will not be updated for new Bugzilla versions going\n" +"forward." +msgstr "" + +msgid "" +"Access via XMLRPC needs a Bugzilla username and password to be specified\n" +"in the configuration. Comments are added under that username. Since the\n" +"configuration must be readable by all Mercurial users, it is recommended\n" +"that the rights of that user are restricted in Bugzilla to the minimum\n" +"necessary to add comments." +msgstr "" + +msgid "" +"Access via XMLRPC/email uses XMLRPC to query Bugzilla, but sends\n" +"email to the Bugzilla email interface to submit comments to bugs.\n" +"The From: address in the email is set to the email address of the Mercurial\n" +"user, so the comment appears to come from the Mercurial user. In the event\n" +"that the Mercurial user email is not recognised by Bugzilla as a Bugzilla\n" +"user, the email associated with the Bugzilla username used to log into\n" +"Bugzilla is used instead as the source of the comment." +msgstr "" + +msgid "Configuration items common to all access modes:" +msgstr "" + +msgid "" +"bugzilla.version\n" +" This access type to use. Values recognised are:" +msgstr "" + +msgid "" +" :``xmlrpc``: Bugzilla XMLRPC interface.\n" +" :``xmlrpc+email``: Bugzilla XMLRPC and email interfaces.\n" +" :``3.0``: MySQL access, Bugzilla 3.0 and later.\n" +" :``2.18``: MySQL access, Bugzilla 2.18 and up to but not\n" +" including 3.0.\n" +" :``2.16``: MySQL access, Bugzilla 2.16 and up to but not\n" +" including 2.18." +msgstr "" + +msgid "" +"bugzilla.regexp\n" +" Regular expression to match bug IDs in changeset commit message.\n" +" Must contain one \"()\" group. The default expression matches ``Bug\n" +" 1234``, ``Bug no. 1234``, ``Bug number 1234``, ``Bugs 1234,5678``,\n" +" ``Bug 1234 and 5678`` and variations thereof. Matching is case\n" +" insensitive." +msgstr "" + +msgid "" +"bugzilla.style\n" +" The style file to use when formatting comments." +msgstr "" + +msgid "" +"bugzilla.template\n" +" Template to use when formatting comments. Overrides style if\n" +" specified. In addition to the usual Mercurial keywords, the\n" +" extension specifies:" +msgstr "" + +msgid "" +" :``{bug}``: The Bugzilla bug ID.\n" +" :``{root}``: The full pathname of the Mercurial repository.\n" +" :``{webroot}``: Stripped pathname of the Mercurial repository.\n" +" :``{hgweb}``: Base URL for browsing Mercurial repositories." +msgstr "" + +msgid "" +" Default ``changeset {node|short} in repo {root} refers to bug\n" +" {bug}.\\ndetails:\\n\\t{desc|tabindent}``" +msgstr "" + +msgid "" +"bugzilla.strip\n" +" The number of path separator characters to strip from the front of\n" +" the Mercurial repository path (``{root}`` in templates) to produce\n" +" ``{webroot}``. For example, a repository with ``{root}``\n" +" ``/var/local/my-project`` with a strip of 2 gives a value for\n" +" ``{webroot}`` of ``my-project``. Default 0." +msgstr "" + +msgid "" +"web.baseurl\n" +" Base URL for browsing Mercurial repositories. Referenced from\n" +" templates as ``{hgweb}``." +msgstr "" + +msgid "Configuration items common to XMLRPC+email and MySQL access modes:" +msgstr "" + +msgid "" +"bugzilla.usermap\n" +" Path of file containing Mercurial committer email to Bugzilla user email\n" +" mappings. If specified, the file should contain one mapping per\n" +" line::" +msgstr "" + +msgid " committer = Bugzilla user" +msgstr "" + +msgid " See also the ``[usermap]`` section." +msgstr "" + +msgid "" +"The ``[usermap]`` section is used to specify mappings of Mercurial\n" +"committer email to Bugzilla user email. See also ``bugzilla.usermap``.\n" +"Contains entries of the form ``committer = Bugzilla user``." +msgstr "" + +msgid "XMLRPC access mode configuration:" +msgstr "" + +msgid "" +"bugzilla.bzurl\n" +" The base URL for the Bugzilla installation.\n" +" Default ``http://localhost/bugzilla``." +msgstr "" + +msgid "" +"bugzilla.user\n" +" The username to use to log into Bugzilla via XMLRPC. Default\n" +" ``bugs``." +msgstr "" + +msgid "" +"bugzilla.password\n" +" The password for Bugzilla login." +msgstr "" + +msgid "" +"XMLRPC+email access mode uses the XMLRPC access mode configuration items,\n" +"and also:" +msgstr "" + +msgid "" +"bugzilla.bzemail\n" +" The Bugzilla email address." +msgstr "" + +msgid "" +"In addition, the Mercurial email settings must be configured. See the\n" +"documentation in hgrc(5), sections ``[email]`` and ``[smtp]``." +msgstr "" + +msgid "MySQL access mode configuration:" +msgstr "" + +msgid "" +"bugzilla.host\n" +" Hostname of the MySQL server holding the Bugzilla database.\n" +" Default ``localhost``." +msgstr "" + +msgid "" +"bugzilla.db\n" +" Name of the Bugzilla database in MySQL. Default ``bugs``." +msgstr "" + +msgid "" +"bugzilla.user\n" +" Username to use to access MySQL server. Default ``bugs``." +msgstr "" + +msgid "" +"bugzilla.password\n" " Password to use to access MySQL server." msgstr "" msgid "" -"timeout\n" +"bugzilla.timeout\n" " Database connection timeout (seconds). Default 5." msgstr "" msgid "" -"version\n" -" Bugzilla version. Specify '3.0' for Bugzilla versions 3.0 and later,\n" -" '2.18' for Bugzilla versions from 2.18 and '2.16' for versions prior\n" -" to 2.18." -msgstr "" - -msgid "" -"bzuser\n" +"bugzilla.bzuser\n" " Fallback Bugzilla user name to record comments with, if changeset\n" " committer cannot be found as a Bugzilla user." msgstr "" msgid "" -"bzdir\n" +"bugzilla.bzdir\n" " Bugzilla install directory. Used by default notify. Default\n" -" '/var/www/html/bugzilla'." -msgstr "" - -msgid "" -"notify\n" +" ``/var/www/html/bugzilla``." +msgstr "" + +msgid "" +"bugzilla.notify\n" " The command to run to get Bugzilla to send bug change notification\n" -" emails. Substitutes from a map with 3 keys, 'bzdir', 'id' (bug id)\n" -" and 'user' (committer bugzilla email). Default depends on version;\n" -" from 2.18 it is \"cd %(bzdir)s && perl -T contrib/sendbugmail.pl\n" -" %(id)s %(user)s\"." -msgstr "" - -msgid "" -"regexp\n" -" Regular expression to match bug IDs in changeset commit message.\n" -" Must contain one \"()\" group. The default expression matches 'Bug\n" -" 1234', 'Bug no. 1234', 'Bug number 1234', 'Bugs 1234,5678', 'Bug\n" -" 1234 and 5678' and variations thereof. Matching is case insensitive." -msgstr "" - -msgid "" -"style\n" -" The style file to use when formatting comments." -msgstr "" - -msgid "" -"template\n" -" Template to use when formatting comments. Overrides style if\n" -" specified. In addition to the usual Mercurial keywords, the\n" -" extension specifies::" -msgstr "" - -msgid "" -" {bug} The Bugzilla bug ID.\n" -" {root} The full pathname of the Mercurial repository.\n" -" {webroot} Stripped pathname of the Mercurial repository.\n" -" {hgweb} Base URL for browsing Mercurial repositories." -msgstr "" - -msgid "" -" Default 'changeset {node|short} in repo {root} refers '\n" -" 'to bug {bug}.\\ndetails:\\n\\t{desc|tabindent}'" -msgstr "" - -msgid "" -"strip\n" -" The number of slashes to strip from the front of {root} to produce\n" -" {webroot}. Default 0." -msgstr "" - -msgid "" -"usermap\n" -" Path of file containing Mercurial committer ID to Bugzilla user ID\n" -" mappings. If specified, the file should contain one mapping per\n" -" line, \"committer\"=\"Bugzilla user\". See also the [usermap] section." -msgstr "" - -msgid "" -"The [usermap] section is used to specify mappings of Mercurial\n" -"committer ID to Bugzilla user ID. See also [bugzilla].usermap.\n" -"\"committer\"=\"Bugzilla user\"" -msgstr "" - -msgid "Finally, the [web] section supports one entry:" -msgstr "" - -msgid "" -"baseurl\n" -" Base URL for browsing Mercurial repositories. Reference from\n" -" templates as {hgweb}." +" emails. Substitutes from a map with 3 keys, ``bzdir``, ``id`` (bug\n" +" id) and ``user`` (committer bugzilla email). Default depends on\n" +" version; from 2.18 it is \"cd %(bzdir)s && perl -T\n" +" contrib/sendbugmail.pl %(id)s %(user)s\"." msgstr "" msgid "Activating the extension::" @@ -446,13 +537,92 @@ " incoming.bugzilla = python:hgext.bugzilla.hook" msgstr "" -msgid "Example configuration:" -msgstr "" - -msgid "" -"This example configuration is for a collection of Mercurial\n" -"repositories in /var/local/hg/repos/ used with a local Bugzilla 3.2\n" -"installation in /opt/bugzilla-3.2. ::" +msgid "Example configurations:" +msgstr "Eksempelkonfigurationer:" + +msgid "" +"XMLRPC example configuration. This uses the Bugzilla at\n" +"``http://my-project.org/bugzilla``, logging in as user\n" +"``bugmail@my-project.org`` with password ``plugh``. It is used with a\n" +"collection of Mercurial repositories in ``/var/local/hg/repos/``,\n" +"with a web interface at ``http://my-project.org/hg``. ::" +msgstr "" + +msgid "" +" [bugzilla]\n" +" bzurl=http://my-project.org/bugzilla\n" +" user=bugmail@my-project.org\n" +" password=plugh\n" +" version=xmlrpc\n" +" template=Changeset {node|short} in {root|basename}.\n" +" {hgweb}/{webroot}/rev/{node|short}\\n\n" +" {desc}\\n\n" +" strip=5" +msgstr "" +" [bugzilla]\n" +" bzurl=http://my-project.org/bugzilla\n" +" user=bugmail@my-project.org\n" +" password=plugh\n" +" version=xmlrpc\n" +" template=Changeset {node|short} in {root|basename}.\n" +" {hgweb}/{webroot}/rev/{node|short}\\n\n" +" {desc}\\n\n" +" strip=5" + +msgid "" +" [web]\n" +" baseurl=http://my-project.org/hg" +msgstr "" +" [web]\n" +" baseurl=http://my-project.org/hg" + +msgid "" +"XMLRPC+email example configuration. This uses the Bugzilla at\n" +"``http://my-project.org/bugzilla``, logging in as user\n" +"``bugmail@my-project.org`` wityh password ``plugh``. It is used with a\n" +"collection of Mercurial repositories in ``/var/local/hg/repos/``,\n" +"with a web interface at ``http://my-project.org/hg``. Bug comments\n" +"are sent to the Bugzilla email address\n" +"``bugzilla@my-project.org``. ::" +msgstr "" + +msgid "" +" [bugzilla]\n" +" bzurl=http://my-project.org/bugzilla\n" +" user=bugmail@my-project.org\n" +" password=plugh\n" +" version=xmlrpc\n" +" bzemail=bugzilla@my-project.org\n" +" template=Changeset {node|short} in {root|basename}.\n" +" {hgweb}/{webroot}/rev/{node|short}\\n\n" +" {desc}\\n\n" +" strip=5" +msgstr "" +" [bugzilla]\n" +" bzurl=http://my-project.org/bugzilla\n" +" user=bugmail@my-project.org\n" +" password=plugh\n" +" version=xmlrpc\n" +" bzemail=bugzilla@my-project.org\n" +" template=Changeset {node|short} in {root|basename}.\n" +" {hgweb}/{webroot}/rev/{node|short}\\n\n" +" {desc}\\n\n" +" strip=5" + +msgid "" +" [usermap]\n" +" user@emaildomain.com=user.name@bugzilladomain.com" +msgstr "" +" [usermap]\n" +" user@emaildomain.com=user.name@bugzilladomain.com" + +msgid "" +"MySQL example configuration. This has a local Bugzilla 3.2 installation\n" +"in ``/opt/bugzilla-3.2``. The MySQL database is on ``localhost``,\n" +"the Bugzilla database name is ``bugs`` and MySQL is\n" +"accessed with MySQL username ``bugs`` password ``XYZZY``. It is used\n" +"with a collection of Mercurial repositories in ``/var/local/hg/repos/``,\n" +"with a web interface at ``http://my-project.org/hg``. ::" msgstr "" msgid "" @@ -478,32 +648,22 @@ " {desc}\\n\n" " strip=5" -msgid "" -" [web]\n" -" baseurl=http://dev.domain.com/hg" -msgstr "" -" [web]\n" -" baseurl=http://dev.domain.com/hg" - -msgid "" -" [usermap]\n" -" user@emaildomain.com=user.name@bugzilladomain.com" -msgstr "" -" [usermap]\n" -" user@emaildomain.com=user.name@bugzilladomain.com" - -msgid "Commits add a comment to the Bugzilla bug record of the form::" +msgid "All the above add a comment to the Bugzilla bug record of the form::" msgstr "" msgid "" " Changeset 3b16791d6642 in repository-name.\n" -" http://dev.domain.com/hg/repository-name/rev/3b16791d6642" +" http://my-project.org/hg/repository-name/rev/3b16791d6642" msgstr "" msgid " Changeset commit comment. Bug 1234.\n" msgstr "" #, python-format +msgid "python mysql support not available: %s" +msgstr "python mysql-understøttelse ikke tilgængelig: %s" + +#, python-format msgid "connecting to %s:%s as %s, password %s\n" msgstr "forbinder til %s:%s som %s, kodeord %s\n" @@ -552,6 +712,13 @@ msgid "cannot find bugzilla user id for %s or %s" msgstr "kan ikke finde bugzilla bruger-id for %s eller %s" +msgid "configuration 'bzemail' missing" +msgstr "konfigurationen af 'bzemail' mangler" + +#, python-format +msgid "default bugzilla user %s email not found" +msgstr "" + #, python-format msgid "bugzilla version %s not supported" msgstr "bugzilla version %s ikke understøttet" @@ -563,16 +730,12 @@ msgstr "" #, python-format -msgid "python mysql support not available: %s" -msgstr "python mysql-understøttelse ikke tilgængelig: %s" - -#, python-format msgid "hook type %s does not pass a changeset id" msgstr "" #, python-format -msgid "database error: %s" -msgstr "databasefejl: %s" +msgid "Bugzilla error: %s" +msgstr "Bugzilla fejl: %s" msgid "command to display child changesets" msgstr "kommando til at vise børne-ændringer" @@ -736,12 +899,11 @@ msgstr "farvelæg output for nogle kommandoer" msgid "" -"This extension modifies the status and resolve commands to add color to " -"their\n" -"output to reflect file status, the qseries command to add color to reflect\n" -"patch status (applied, unapplied, missing), and to diff-related\n" -"commands to highlight additions, removals, diff headers, and trailing\n" -"whitespace." +"This extension modifies the status and resolve commands to add color\n" +"to their output to reflect file status, the qseries command to add\n" +"color to reflect patch status (applied, unapplied, missing), and to\n" +"diff-related commands to highlight additions, removals, diff headers,\n" +"and trailing whitespace." msgstr "" "Denne udvidelse ændrer status- og resolve-kommandoerne så de tilføjer\n" "farve til deres output for at afspejle filstatus, qseries-kommandoen\n" @@ -1332,6 +1494,15 @@ msgid "hg debugcvsps [OPTION]... [PATH]..." msgstr "hg debugcvsps [TILVALG]... [STI]..." +msgid ":svnrev: String. Converted subversion revision number." +msgstr "" + +msgid ":svnpath: String. Converted subversion revision project path." +msgstr "" + +msgid ":svnuuid: String. Converted subversion revision repository identifier." +msgstr "" + #, python-format msgid "%s does not look like a Bazaar repository" msgstr "%s ser ikke ud som et Bazaar depot" @@ -1664,6 +1835,9 @@ msgid "updating tags\n" msgstr "opdaterer mærkater\n" +msgid "updating bookmarks\n" +msgstr "opdaterer bogmærker\n" + #, python-format msgid "%s is not a valid start revision" msgstr "%s er ikke en gyldig startrevision" @@ -1676,10 +1850,45 @@ msgid "%s does not look like a monotone repository" msgstr "%s ser ikke ud som et monotone depot" +msgid "bad mtn packet - no end of commandnbr" +msgstr "" + +#, python-format +msgid "bad mtn packet - bad stream type %s" +msgstr "" + +msgid "bad mtn packet - no divider before size" +msgstr "" + +msgid "bad mtn packet - no end of packet size" +msgstr "" + +#, python-format +msgid "bad mtn packet - bad packet size %s" +msgstr "" + +#, python-format +msgid "bad mtn packet - unable to read full packet read %s of %s" +msgstr "" + +#, python-format +msgid "mtn command '%s' returned %s" +msgstr "mtn kommando '%s' returnerede %s" + #, python-format msgid "copying file in renamed directory from '%s' to '%s'" msgstr "kopierer fil i omdøbt katalog fra '%s' til '%s'" +msgid "unable to determine mtn automate interface version" +msgstr "" + +#, python-format +msgid "mtn automate stdio header unexpected: %s" +msgstr "" + +msgid "failed to reach end of mtn automate stdio headers" +msgstr "" + #, python-format msgid "%s does not look like a P4 repository" msgstr "%s ser ikke ud som et P4 depot" @@ -1935,11 +2144,13 @@ msgid "" "The ``win32text.forbid*`` hooks provided by the win32text extension\n" -"have been unified into a single hook named ``eol.hook``. The hook will\n" -"lookup the expected line endings from the ``.hgeol`` file, which means\n" -"you must migrate to a ``.hgeol`` file first before using the hook.\n" -"Remember to enable the eol extension in the repository where you\n" -"install the hook." +"have been unified into a single hook named ``eol.checkheadshook``. The\n" +"hook will lookup the expected line endings from the ``.hgeol`` file,\n" +"which means you must migrate to a ``.hgeol`` file first before using\n" +"the hook. ``eol.checkheadshook`` only checks heads, intermediate\n" +"invalid revisions will be pushed. To forbid them completely, use the\n" +"``eol.checkallhook`` hook. These hooks are best used as\n" +"``pretxnchangegroup`` hooks." msgstr "" msgid "" @@ -1950,25 +2161,24 @@ "glob-mønstre.\n" #, python-format -msgid "%s should not have CRLF line endings" -msgstr "%s skulle ikke have CRLF linieskift" - -#, python-format -msgid "%s should not have LF line endings" -msgstr "%s skulle ikke have LF linieskift" +msgid "ignoring unknown EOL style '%s' from %s\n" +msgstr "ignorerer ukendt linieskifttype '%s' fra %s\n" #, python-format msgid "warning: ignoring .hgeol file due to parse error at %s: %s\n" msgstr "" +#, python-format +msgid " %s in %s should not have %s line endings" +msgstr " %s i %s skulle ikke have %s linieskift" + +msgid "end-of-line check failed:\n" +msgstr "" + msgid "the eol extension is incompatible with the win32text extension\n" msgstr "eol-udvidelsen er inkompatibel med win32text-udvidelsen\n" #, python-format -msgid "ignoring unknown EOL style '%s' from %s\n" -msgstr "ignorerer ukendt linieskifttype '%s' fra %s\n" - -#, python-format msgid "inconsistent newline style in %s\n" msgstr "inkonsistent linieskift i %s\n" @@ -2873,17 +3083,17 @@ "available templates and filters." msgstr "" -msgid "Three additional date template filters are provided::" -msgstr "" - -msgid "" -" utcdate \"2006/09/18 15:13:13\"\n" -" svnutcdate \"2006-09-18 15:13:13Z\"\n" -" svnisodate \"2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)\"" -msgstr "" -" utcdate \"2006/09/18 15:13:13\"\n" -" svnutcdate \"2006-09-18 15:13:13Z\"\n" -" svnisodate \"2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)\"" +msgid "Three additional date template filters are provided:" +msgstr "" + +msgid "" +":``utcdate``: \"2006/09/18 15:13:13\"\n" +":``svnutcdate``: \"2006-09-18 15:13:13Z\"\n" +":``svnisodate``: \"2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)\"" +msgstr "" +":``utcdate``: \"2006/09/18 15:13:13\"\n" +":``svnutcdate``: \"2006-09-18 15:13:13Z\"\n" +":``svnisodate``: \"2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)\"" msgid "" "The default template mappings (view with :hg:`kwdemo -d`) can be\n" @@ -3609,14 +3819,15 @@ " With -g/--git, patches imported with --rev will use the git diff\n" " format. See the diffs help topic for information on why this is\n" " important for preserving rename/copy information and permission\n" -" changes." -msgstr "" -" En eksisterende ændrin kan blive sat under mq kontrol med -r/--rev\n" +" changes. Use :hg:`qfinish` to remove changesets from mq control." +msgstr "" +" En eksisterende ændring kan blive sat under mq kontrol med -r/--rev\n" " (e.g. qimport --rev tip -n patch vil sætte tip under mq kontrol).\n" " Med -g/--git vil patches importeret med --rev bruge git diff\n" " formatet. Se 'hg help diffs' for mere information om hvorfor dette\n" " er vigtigt for at bevare omdøbnings/kopierings-information og\n" -" ændriner i rettigheder." +" ændriner i rettigheder. Brug :hg:`qfinish` for at fjerne ændringer\n" +" fra mq." msgid "" " To import a patch from standard input, pass - as the patch file.\n" @@ -3929,13 +4140,6 @@ " Når -f/--force er angivet, så vil alle lokale ændringer i de\n" " rettede filer gå tabt." -msgid "" -" Return 0 on succces.\n" -" " -msgstr "" -" Returnerer 0 ved succes.\n" -" " - msgid "no saved queues found, please use -n\n" msgstr "fandt ingen gemte køer, brug venligst -r\n" @@ -4053,12 +4257,12 @@ " eksempel::" msgid "" -" qguard foo.patch -stable (negative guard)\n" -" qguard bar.patch +stable (positive guard)\n" +" qguard foo.patch -- -stable (negative guard)\n" +" qguard bar.patch +stable (positive guard)\n" " qselect stable" msgstr "" -" qguard foo.patch -stable (negativt filter)\n" -" qguard bar.patch +stable (positivt filter)\n" +" qguard foo.patch -- -stable (negativt filter)\n" +" qguard bar.patch +stable (positivt filter)\n" " qselect stable" msgid "" @@ -4805,8 +5009,8 @@ msgstr "" msgid "" -"To configure other defaults, add a section like this to your hgrc\n" -"file::" +"To configure other defaults, add a section like this to your\n" +"configuration file::" msgstr "" msgid "" @@ -5311,6 +5515,9 @@ " " msgstr "" +msgid "message can only be specified with collapse" +msgstr "" + msgid "cannot use both abort and continue" msgstr "abort og continue kan ikke angives samtidig" @@ -5323,6 +5530,9 @@ msgid "abort and continue do not allow specifying revisions" msgstr "abort og continue tillader ikke at der angives revisioner" +msgid "tool option will be ignored\n" +msgstr "" + msgid "cannot specify both a revision and a base" msgstr "man kan ikke angive både en revision og en basis" @@ -5335,9 +5545,6 @@ msgid "nothing to rebase\n" msgstr "" -msgid "cannot use both keepbranches and extrafn" -msgstr "man kan ikke bruge både keepbranches og extrafn" - msgid "rebasing" msgstr "" @@ -5407,6 +5614,12 @@ msgid "collapse the rebased changesets" msgstr "" +msgid "use text as collapse commit message" +msgstr "brug tekst som deponeringsbesked" + +msgid "read collapse commit message from file" +msgstr "læs deponeringsbeskeden fra fil" + msgid "keep original changesets" msgstr "behold de originale ændringer" @@ -5416,6 +5629,9 @@ msgid "force detaching of source from its original branch" msgstr "" +msgid "specify merge tool" +msgstr "angiv sammenføjningsværktøj" + msgid "continue an interrupted rebase" msgstr "" @@ -5536,6 +5752,17 @@ msgid " This command is not available when committing a merge." msgstr "" +msgid "interactively record a new patch" +msgstr "vælg ændringer interaktivt til deponering" + +msgid "" +" See :hg:`help qnew` & :hg:`help record` for more information and\n" +" usage.\n" +" " +msgstr "" +" Se :hg:`help qnew` & :hg:`help record` for mere information.\n" +" " + msgid "'mq' extension not loaded" msgstr "'mq' udvidelsen er ikke indlæst" @@ -5641,7 +5868,7 @@ msgstr "sammenkæder" #, python-format -msgid "relinked %d files (%d bytes reclaimed)\n" +msgid "relinked %d files (%s reclaimed)\n" msgstr "" msgid "[ORIGIN]" @@ -5711,6 +5938,10 @@ "same name.\n" msgstr "" +#, python-format +msgid "custom scheme %s:// conflicts with drive letter %s:\\\n" +msgstr "" + msgid "share a common history between several working directories" msgstr "del en fælles historie mellem flere arbejdsbiblioteker" @@ -5800,6 +6031,9 @@ msgid "commit failed" msgstr "deponering fejlede" +msgid "filter corrupted changeset (no user or date)" +msgstr "filtrer ødelagte ændringer (ingen bruger eller dato)" + msgid "" "y: transplant this changeset\n" "n: skip this changeset\n" @@ -5901,6 +6135,11 @@ " Transplanted changesets in set." msgstr "" +msgid "" +":transplanted: String. The node identifier of the transplanted\n" +" changeset if any." +msgstr "" + msgid "pull patches from REPO" msgstr "hiv rettelser fra DEPOT" @@ -6161,6 +6400,18 @@ msgid "bookmark '%s' contains illegal character" msgstr "" +#, python-format +msgid "branch %s not found" +msgstr "gren %s blev ikke fundet" + +#, python-format +msgid "updating bookmark %s\n" +msgstr "opdaterer bogmærke %s\n" + +#, python-format +msgid "not updating divergent bookmark %s\n" +msgstr "opdaterer ikke divergerende bogmærke %s\n" + msgid "searching for changed bookmarks\n" msgstr "leder efter ændrede bogmærker\n" @@ -6699,7 +6950,8 @@ #, python-format msgid "" "Not all ancestors of this changeset have been checked.\n" -"To check the other ancestors, start from the common ancestor, %s.\n" +"Use bisect --extend to continue the bisection from\n" +"the common ancestor, %s.\n" msgstr "" msgid "Due to skipped revisions, the first good revision could be any of:\n" @@ -6737,6 +6989,13 @@ msgstr "Ændring %d:%s: %s\n" #, python-format +msgid "Extending search to changeset %d:%s\n" +msgstr "Udvider søgning til ændring %d:%s\n" + +msgid "nothing to extend" +msgstr "kan ikke udvidde" + +#, python-format msgid "Testing changeset %d:%s (%d changesets remaining, ~%d tests)\n" msgstr "Tester ændring %d:%s (%d ændringer tilbage, ~%d test)\n" @@ -6781,11 +7040,13 @@ " bookmark-udviddelsen skal være slået til.\n" " " -msgid "a bookmark of this name does not exist" -msgstr "et bogmærke med dette navn findes ikke" - -msgid "a bookmark of the same name already exists" -msgstr "et bogmærke med samme navn findes allerede" +#, python-format +msgid "bookmark '%s' does not exist" +msgstr "mærkaten '%s' eksisterer ikke" + +#, python-format +msgid "bookmark '%s' already exists (use -f to force)" +msgstr "mærkaten '%s' eksisterer allerede (brug -f for at gennemtvinge)" msgid "new bookmark name required" msgstr "nyt bogmærkenavn påkrævet" @@ -7144,6 +7405,10 @@ msgid "can only close branch heads" msgstr "kan kun lukke grenhoveder" +#, python-format +msgid "nothing changed (%d missing files, see 'hg status')\n" +msgstr "" + msgid "nothing changed\n" msgstr "ingen ændringer\n" @@ -7314,6 +7579,29 @@ msgid "only one config item permitted" msgstr "" +msgid "test whether node ids are known to a repo" +msgstr "" + +msgid "" +" Every ID must be a full-length hex node id string. Returns a list of 0s " +"and 1s\n" +" indicating unknown/known.\n" +" " +msgstr "" + +msgid "lists the contents of a bundle" +msgstr "vis indholdet af et bundt" + +msgid "retrieves a bundle from a repo" +msgstr "" + +msgid "" +" Every ID must be a full-length hex node id string. Saves the bundle to " +"the\n" +" given file.\n" +" " +msgstr "" + msgid "access the pushkey key/value protocol" msgstr "" @@ -7412,28 +7700,6 @@ msgid " (templates seem to have been installed incorrectly)\n" msgstr "" -msgid "Checking patch...\n" -msgstr "" - -msgid " patch call failed:\n" -msgstr "" - -msgid " unexpected patch output!\n" -msgstr "" - -msgid " patch test failed!\n" -msgstr "" - -msgid "" -" (Current patch tool may be incompatible with patch, or misconfigured. " -"Please check your configuration file)\n" -msgstr "" - -msgid "" -" Internal patcher failure, please report this error to http://mercurial." -"selenic.com/wiki/BugTracker\n" -msgstr "" - msgid "Checking commit editor...\n" msgstr "" @@ -7742,6 +8008,10 @@ "detaljer" #, python-format +msgid "use \"hg help %s\" to show the full help text" +msgstr "brug \"hg help %s\" for at se udførlig hjælp" + +#, python-format msgid "use \"hg -v help%s\" to show builtin aliases and global options" msgstr "" "brug \"hg -v help%s\" for at vise indbyggede aliaser og globale " @@ -7840,7 +8110,14 @@ msgstr "" msgid "" -" With no revision, print a summary of the current state of the\n" +" Print a summary identifiying the repository state at REV\n" +" using one or two parent hash identifiers, followed by a\n" +" \"+\" if there are uncommitted changes in the working directory,\n" +" the branch name (omitted if default) and a list of tags, bookmarks." +msgstr "" + +msgid "" +" When REV is not given, print a summary of the current state of the\n" " repository." msgstr "" @@ -7849,14 +8126,7 @@ " cause lookup to operate on that repository/bundle." msgstr "" -msgid "" -" This summary identifies the repository state using one or two\n" -" parent hash identifiers, followed by a \"+\" if there are\n" -" uncommitted changes in the working directory, a list of tags for\n" -" this revision and a branch name for non-default branches." -msgstr "" - -msgid "can't query remote revision number, branch, tags, or bookmarks" +msgid "can't query remote revision number, branch, or tags" msgstr "" msgid "import an ordered set of patches" @@ -8099,7 +8369,7 @@ msgid "" " ``--tool`` can be used to specify the merge tool used for file\n" " merges. It overrides the HGMERGE environment variable and your\n" -" configuration files." +" configuration files. See :hg:`help merge-tools` for options." msgstr "" msgid "" @@ -8239,6 +8509,12 @@ msgid "(run 'hg heads' to see heads, 'hg merge' to merge)\n" msgstr "(kør 'hg heads' for at se hoveder, 'hg merge' for at sammenføje)\n" +msgid "(run 'hg heads .' to see heads, 'hg merge' to merge)\n" +msgstr "(kør 'hg heads .' for at se hoveder, 'hg merge' for at sammenføje)\n" + +msgid "(run 'hg heads' to see heads)\n" +msgstr "(kør 'hg heads' for at se hoveder)\n" + msgid "(run 'hg update' to get a working copy)\n" msgstr "(kør 'hg update' for at få en arbejdskopi)\n" @@ -8582,8 +8858,9 @@ msgstr "" msgid "" -" If a file has been deleted, it is restored. If the executable mode\n" -" of a file was changed, it is reset." +" If a file has been deleted, it is restored. Files scheduled for\n" +" addition are just unscheduled and left as they are. If the\n" +" executable mode of a file was changed, it is reset." msgstr "" msgid "" @@ -9333,9 +9610,6 @@ msgid "parent to choose when backing out merge" msgstr "" -msgid "specify merge tool" -msgstr "angiv sammenføjningsværktøj" - msgid "revision to backout" msgstr "revision som skal bakkes ud" @@ -9354,6 +9628,9 @@ msgid "skip testing changeset" msgstr "spring testen af denne ændring over" +msgid "extend the bisect range" +msgstr "udvid halveringsområdet" + msgid "use command to check changeset state" msgstr "brug kommando for at kontrollere tilstanden af ændringen" @@ -9474,6 +9751,9 @@ msgid "[OPTION]... TEXT" msgstr "[TILVALG]... TEKST" +msgid "show all details" +msgstr "vis alle detaljer" + msgid "[COMMAND]" msgstr "[KOMMANDO]" @@ -9510,9 +9790,21 @@ msgid "[PATH]" msgstr "[STI]" +msgid "id of head node" +msgstr "" + +msgid "id of common node" +msgstr "id på fælles knude" + +msgid "REPO FILE [-H|-C ID]..." +msgstr "" + msgid "revlog format" msgstr "" +msgid "REPO ID..." +msgstr "" + msgid "REPO NAMESPACE [KEY OLD NEW]" msgstr "" @@ -9534,12 +9826,18 @@ msgid "do not display the saved mtime" msgstr "vis ikke den gemte mtime" +msgid "sort by saved mtime" +msgstr "sorter efter den gemte mtime" + msgid "[OPTION]..." msgstr "[TILVALG]..." msgid "revision to check" msgstr "revision som skal undersøges" +msgid "REPO [OPTIONS]... [ONE [TWO]]" +msgstr "DEPOT [TILVALG]... [EN [TO]]" + msgid "[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]..." msgstr "[TILVALG]... ([-c REV] | [-r REV1 [-r REV2]]) [FIL]..." @@ -10014,10 +10312,6 @@ msgstr "arbejdskatalogtilstand virker beskadiget!" #, python-format -msgid "'\\n' and '\\r' disallowed in filenames: %r" -msgstr "'\\n' og '\\r' må ikke forekomme i filnavne: %r" - -#, python-format msgid "directory %r already in dirstate" msgstr "katalog %r er allerede i dirstate" @@ -10566,29 +10860,29 @@ msgstr "- ``1165432709 0`` (Ons 6. dec. 13:18:29 2006 UTC)" msgid "" -"This is the internal representation format for dates. unixtime is the\n" -"number of seconds since the epoch (1970-01-01 00:00 UTC). offset is\n" -"the offset of the local timezone, in seconds west of UTC (negative if\n" -"the timezone is east of UTC)." -msgstr "" -"Dette er den interne repræsentation af datoer. unixtime er\n" +"This is the internal representation format for dates. The first number\n" +"is the number of seconds since the epoch (1970-01-01 00:00 UTC). The\n" +"second is the offset of the local timezone, in seconds west of UTC\n" +"(negative if the timezone is east of UTC)." +msgstr "" +"Dette er den interne repræsentation af datoer. Det første tal er\n" "antallet af sekunder siden begyndelsen af epoken (1970-01-01 00:00\n" -"UTC). offset er den lokale tidszone, angivet i antal sekunder vest\n" -"for UTC (negativ hvis tidszonen er øst for UTC)." +"UTC). Det andet tal er den lokale tidszone, angivet i antal sekunder\n" +"vest for UTC (negativ hvis tidszonen er øst for UTC)." msgid "The log command also accepts date ranges:" msgstr "Kommandoen log accepterer også datointervaller:" msgid "" -"- ``<{datetime}`` - at or before a given date/time\n" -"- ``>{datetime}`` - on or after a given date/time\n" -"- ``{datetime} to {datetime}`` - a date range, inclusive\n" -"- ``-{days}`` - within a given number of days of today\n" -msgstr "" -"- ``<{date}`` - på eller før den angivne dato/tidspunkt\n" -"- ``>{date}`` - på eller efter den angivne dato/tidspunkt\n" -"- ``{date} to {date}`` - et datointerval, inklusiv endepunkterne\n" -"- ``-{days}`` - indenfor et angivet antal dage, fra dags dato\n" +"- ``<DATE`` - at or before a given date/time\n" +"- ``>DATE`` - on or after a given date/time\n" +"- ``DATE to DATE`` - a date range, inclusive\n" +"- ``-DAYS`` - within a given number of days of today\n" +msgstr "" +"- ``<DATO`` - på eller før den angivne dato/tidspunkt\n" +"- ``>DATO`` - på eller efter den angivne dato/tidspunkt\n" +"- ``DATO to DATO`` - et datointerval, inklusiv endepunkterne\n" +"- ``-DAGE`` - indenfor et angivet antal dage, fra dags dato\n" msgid "" "Mercurial's default format for showing changes between two versions of\n" @@ -10714,6 +11008,18 @@ msgstr "" msgid "" +"HGPLAINEXCEPT\n" +" This is a comma-separated list of features to preserve when\n" +" HGPLAIN is enabled. Currently the only value supported is \"i18n\",\n" +" which preserves internationalization in plain mode." +msgstr "" + +msgid "" +" Setting HGPLAINEXCEPT to anything (even an empty string) will\n" +" enable plain mode." +msgstr "" + +msgid "" "HGUSER\n" " This is the string used as the author of a commit. If not set,\n" " available values will be considered in this order:" @@ -11367,8 +11673,8 @@ msgstr "" msgid "" -"This file uses the same syntax as hgrc configuration files, but only\n" -"the following sections are recognized:" +"This file uses the same syntax as other Mercurial configuration files,\n" +"but only the following sections are recognized:" msgstr "" msgid "" @@ -11382,7 +11688,8 @@ msgid "" "The ``web`` section can specify all the settings described in the web\n" -"section of the hgrc documentation." +"section of the hgrc(5) documentation. See :hg:`help config` for\n" +"information on where to find the manual page." msgstr "" msgid "" @@ -11889,7 +12196,7 @@ msgid " hg log -r \"1.3::1.5 and keyword(bug) and file('hgext/*')\"" msgstr " hg log -r \"1.3::1.5 and keyword(bug) and file(\"hgext/*\")\"" -msgid "- Changesets in committed May 2008, sorted by user::" +msgid "- Changesets committed in May 2008, sorted by user::" msgstr "" msgid " hg log -r \"sort(date('May 2008'), user)\"" @@ -12134,78 +12441,7 @@ "keywords are usually available for templating a log-like command:" msgstr "" -msgid ":author: String. The unmodified author of the changeset." -msgstr "" - -msgid "" -":branch: String. The name of the branch on which the changeset was\n" -" committed." -msgstr "" - -msgid "" -":branches: List of strings. The name of the branch on which the\n" -" changeset was committed. Will be empty if the branch name was\n" -" default." -msgstr "" - -msgid ":children: List of strings. The children of the changeset." -msgstr "" - -msgid ":date: Date information. The date when the changeset was committed." -msgstr "" - -msgid ":desc: String. The text of the changeset description." -msgstr "" - -msgid "" -":diffstat: String. Statistics of changes with the following format:\n" -" \"modified files: +added/-removed lines\"" -msgstr "" - -msgid "" -":files: List of strings. All files modified, added, or removed by this\n" -" changeset." -msgstr "" - -msgid ":file_adds: List of strings. Files added by this changeset." -msgstr "" - -msgid "" -":file_copies: List of strings. Files copied in this changeset with\n" -" their sources." -msgstr "" - -msgid "" -":file_copies_switch: List of strings. Like \"file_copies\" but displayed\n" -" only if the --copied switch is set." -msgstr "" - -msgid ":file_mods: List of strings. Files modified by this changeset." -msgstr "" - -msgid ":file_dels: List of strings. Files removed by this changeset." -msgstr "" - -msgid "" -":node: String. The changeset identification hash, as a 40 hexadecimal\n" -" digit string." -msgstr "" - -msgid ":parents: List of strings. The parents of the changeset." -msgstr "" - -msgid ":rev: Integer. The repository-local changeset revision number." -msgstr "" - -msgid ":tags: List of strings. Any tags associated with the changeset." -msgstr "" - -msgid "" -":latesttag: String. Most recent global tag in the ancestors of this\n" -" changeset." -msgstr "" - -msgid ":latesttagdistance: Integer. Longest path to the latest tag." +msgid ".. keywordsmarker" msgstr "" msgid "" @@ -12227,131 +12463,7 @@ msgid "List of filters:" msgstr "" -msgid "" -":addbreaks: Any text. Add an XHTML \"<br />\" tag before the end of\n" -" every line except the last." -msgstr "" - -msgid "" -":age: Date. Returns a human-readable date/time difference between the\n" -" given date/time and the current date/time." -msgstr "" - -msgid "" -":basename: Any text. Treats the text as a path, and returns the last\n" -" component of the path after splitting by the path separator\n" -" (ignoring trailing separators). For example, \"foo/bar/baz\" becomes\n" -" \"baz\" and \"foo/bar//\" becomes \"bar\"." -msgstr "" - -msgid "" -":stripdir: Treat the text as path and strip a directory level, if\n" -" possible. For example, \"foo\" and \"foo/bar\" becomes \"foo\"." -msgstr "" - -msgid "" -":date: Date. Returns a date in a Unix date format, including the\n" -" timezone: \"Mon Sep 04 15:13:13 2006 0700\"." -msgstr "" - -msgid "" -":domain: Any text. Finds the first string that looks like an email\n" -" address, and extracts just the domain component. Example: ``User\n" -" <user@example.com>`` becomes ``example.com``." -msgstr "" - -msgid "" -":email: Any text. Extracts the first string that looks like an email\n" -" address. Example: ``User <user@example.com>`` becomes\n" -" ``user@example.com``." -msgstr "" - -msgid "" -":escape: Any text. Replaces the special XML/XHTML characters \"&\", \"<\"\n" -" and \">\" with XML entities." -msgstr "" - -msgid "" -":hex: Any text. Convert a binary Mercurial node identifier into\n" -" its long hexadecimal representation." -msgstr "" - -msgid ":fill68: Any text. Wraps the text to fit in 68 columns." -msgstr "" - -msgid ":fill76: Any text. Wraps the text to fit in 76 columns." -msgstr "" - -msgid ":firstline: Any text. Returns the first line of text." -msgstr "" - -msgid ":nonempty: Any text. Returns '(none)' if the string is empty." -msgstr "" - -msgid "" -":hgdate: Date. Returns the date as a pair of numbers: \"1157407993\n" -" 25200\" (Unix timestamp, timezone offset)." -msgstr "" - -msgid "" -":isodate: Date. Returns the date in ISO 8601 format: \"2009-08-18 13:00\n" -" +0200\"." -msgstr "" - -msgid "" -":isodatesec: Date. Returns the date in ISO 8601 format, including\n" -" seconds: \"2009-08-18 13:00:13 +0200\". See also the rfc3339date\n" -" filter." -msgstr "" - -msgid ":localdate: Date. Converts a date to local date." -msgstr "" - -msgid "" -":obfuscate: Any text. Returns the input text rendered as a sequence of\n" -" XML entities." -msgstr "" - -msgid ":person: Any text. Returns the text before an email address." -msgstr "" - -msgid "" -":rfc822date: Date. Returns a date using the same format used in email\n" -" headers: \"Tue, 18 Aug 2009 13:00:13 +0200\"." -msgstr "" - -msgid "" -":rfc3339date: Date. Returns a date using the Internet date format\n" -" specified in RFC 3339: \"2009-08-18T13:00:13+02:00\"." -msgstr "" - -msgid "" -":short: Changeset hash. Returns the short form of a changeset hash,\n" -" i.e. a 12 hexadecimal digit string." -msgstr "" - -msgid ":shortdate: Date. Returns a date like \"2006-09-18\"." -msgstr "" - -msgid "" -":stringify: Any type. Turns the value into text by converting values into\n" -" text and concatenating them." -msgstr "" - -msgid ":strip: Any text. Strips all leading and trailing whitespace." -msgstr "" - -msgid "" -":tabindent: Any text. Returns the text, with every line except the\n" -" first starting with a tab character." -msgstr "" - -msgid "" -":urlescape: Any text. Escapes all \"special\" characters. For example,\n" -" \"foo bar\" becomes \"foo%20bar\"." -msgstr "" - -msgid ":user: Any text. Returns the user portion of an email address.\n" +msgid ".. filtersmarker\n" msgstr "" msgid "Valid URLs are of the form::" @@ -12800,14 +12912,6 @@ msgstr "" #, python-format -msgid "updating bookmark %s\n" -msgstr "opdaterer bogmærke %s\n" - -#, python-format -msgid "not updating divergent bookmark %s\n" -msgstr "opdaterer ikke divergerende bogmærke %s\n" - -#, python-format msgid "%d changesets found\n" msgstr "fandt %d ændringer\n" @@ -12918,6 +13022,10 @@ msgstr "ugyldig lokal adresse: %s" #, python-format +msgid "'\\n' and '\\r' disallowed in filenames: %r" +msgstr "'\\n' og '\\r' må ikke forekomme i filnavne: %r" + +#, python-format msgid "failed to remove %s from manifest" msgstr "kunne ikke fjerne %s fra manifest" @@ -13004,10 +13112,6 @@ msgid "note: possible conflict - %s was renamed multiple times to:\n" msgstr "" -#, python-format -msgid "branch %s not found" -msgstr "gren %s blev ikke fundet" - msgid "merging with a working directory ancestor has no effect" msgstr "sammenføjning med en forfader til arbejdskataloget har ingen effekt" @@ -13122,10 +13226,6 @@ msgstr "" #, python-format -msgid "malformed patch %s %s" -msgstr "" - -#, python-format msgid "unsupported parser state: %s" msgstr "" @@ -13238,86 +13338,12 @@ msgstr "ikke en funktion: %s" msgid "" -"``id(string)``\n" -" Revision non-ambiguously specified by the given hex string prefix." -msgstr "" - -#. i18n: "id" is a keyword -msgid "id requires one argument" -msgstr "id kræver et argument" - -#. i18n: "id" is a keyword -msgid "id requires a string" -msgstr "id kræver en streng" - -msgid "" -"``rev(number)``\n" -" Revision with the given numeric identifier." -msgstr "" - -#. i18n: "rev" is a keyword -msgid "rev requires one argument" -msgstr "rev kræver et argument" - -#. i18n: "rev" is a keyword -msgid "rev requires a number" -msgstr "rev kræver et tal" - -#. i18n: "rev" is a keyword -msgid "rev expects a number" -msgstr "rev forventer et revisionsnummer" - -msgid "" -"``p1([set])``\n" -" First parent of changesets in set, or the working directory." -msgstr "" - -msgid "" -"``p2([set])``\n" -" Second parent of changesets in set, or the working directory." -msgstr "" - -msgid "" -"``parents([set])``\n" -" The set of all parents for all changesets in set, or the working " -"directory." -msgstr "" - -msgid "" -"``max(set)``\n" -" Changeset with highest revision number in set." -msgstr "" - -msgid "" -"``min(set)``\n" -" Changeset with lowest revision number in set." -msgstr "" - -msgid "" -"``limit(set, n)``\n" -" First n members of set." -msgstr "" - -#. i18n: "limit" is a keyword -msgid "limit requires two arguments" -msgstr "limit kræver to argumenter" - -#. i18n: "limit" is a keyword -msgid "limit requires a number" -msgstr "limit kræver et tal" - -#. i18n: "limit" is a keyword -msgid "limit expects a number" -msgstr "" - -msgid "" -"``children(set)``\n" -" Child changesets of changesets in set." -msgstr "" - -msgid "" -"``branch(set)``\n" -" All changesets belonging to the branches of changesets in set." +"``adds(pattern)``\n" +" Changesets that add a file matching pattern." +msgstr "" + +#. i18n: "adds" is a keyword +msgid "adds requires a pattern" msgstr "" msgid "" @@ -13339,6 +13365,80 @@ msgstr "" msgid "" +"``author(string)``\n" +" Alias for ``user(string)``." +msgstr "" + +#. i18n: "author" is a keyword +msgid "author requires a string" +msgstr "" + +msgid "" +"``bisected(string)``\n" +" Changesets marked in the specified bisect state (good, bad, skip)." +msgstr "" + +msgid "bisect requires a string" +msgstr "bisect kræver en streng" + +msgid "invalid bisect state" +msgstr "ugyldig tilstand for halvering" + +msgid "" +"``bookmark([name])``\n" +" The named bookmark or all bookmarks." +msgstr "" +"``bookmark([name])``\n" +" Det navngivne bogmærke eller alle bogmærker." + +#. i18n: "bookmark" is a keyword +msgid "bookmark takes one or no arguments" +msgstr "bookmark tager et eller to argumenter" + +#. i18n: "bookmark" is a keyword +msgid "the argument to bookmark must be a string" +msgstr "argumentet til bookmark skal være en streng" + +msgid "" +"``branch(string or set)``\n" +" All changesets belonging to the given branch or the branches of the " +"given\n" +" changesets." +msgstr "" + +msgid "" +"``children(set)``\n" +" Child changesets of changesets in set." +msgstr "" + +msgid "" +"``closed()``\n" +" Changeset is closed." +msgstr "" + +#. i18n: "closed" is a keyword +msgid "closed takes no arguments" +msgstr "" + +msgid "" +"``contains(pattern)``\n" +" Revision contains pattern." +msgstr "" + +#. i18n: "contains" is a keyword +msgid "contains requires a pattern" +msgstr "" + +msgid "" +"``date(interval)``\n" +" Changesets within the interval, see :hg:`help dates`." +msgstr "" + +#. i18n: "date" is a keyword +msgid "date requires a string" +msgstr "" + +msgid "" "``descendants(set)``\n" " Changesets which are descendants of changesets in set." msgstr "" @@ -13353,22 +13453,12 @@ msgstr "" msgid "" -"``date(interval)``\n" -" Changesets within the interval, see :hg:`help dates`." -msgstr "" - -#. i18n: "date" is a keyword -msgid "date requires a string" -msgstr "" - -msgid "" -"``keyword(string)``\n" -" Search commit message, user name, and names of changed files for\n" -" string." -msgstr "" - -#. i18n: "keyword" is a keyword -msgid "keyword requires a string" +"``all()``\n" +" All changesets, the same as ``0:tip``." +msgstr "" + +#. i18n: "all" is a keyword +msgid "all takes no arguments" msgstr "" msgid "" @@ -13386,20 +13476,6 @@ msgstr "ugyldigt søgemønster: %s" msgid "" -"``author(string)``\n" -" Alias for ``user(string)``." -msgstr "" - -#. i18n: "author" is a keyword -msgid "author requires a string" -msgstr "" - -msgid "" -"``user(string)``\n" -" User name is string." -msgstr "" - -msgid "" "``file(pattern)``\n" " Changesets affecting files matched by pattern." msgstr "" @@ -13409,12 +13485,63 @@ msgstr "" msgid "" -"``contains(pattern)``\n" -" Revision contains pattern." -msgstr "" - -#. i18n: "contains" is a keyword -msgid "contains requires a pattern" +"``head()``\n" +" Changeset is a named branch head." +msgstr "" + +#. i18n: "head" is a keyword +msgid "head takes no arguments" +msgstr "" + +msgid "" +"``heads(set)``\n" +" Members of set with no children in set." +msgstr "" + +msgid "" +"``keyword(string)``\n" +" Search commit message, user name, and names of changed files for\n" +" string." +msgstr "" + +#. i18n: "keyword" is a keyword +msgid "keyword requires a string" +msgstr "" + +msgid "" +"``limit(set, n)``\n" +" First n members of set." +msgstr "" + +#. i18n: "limit" is a keyword +msgid "limit requires two arguments" +msgstr "limit kræver to argumenter" + +#. i18n: "limit" is a keyword +msgid "limit requires a number" +msgstr "limit kræver et tal" + +#. i18n: "limit" is a keyword +msgid "limit expects a number" +msgstr "" + +msgid "" +"``max(set)``\n" +" Changeset with highest revision number in set." +msgstr "" + +msgid "" +"``merge()``\n" +" Changeset is a merge changeset." +msgstr "" + +#. i18n: "merge" is a keyword +msgid "merge takes no arguments" +msgstr "" + +msgid "" +"``min(set)``\n" +" Changeset with lowest revision number in set." msgstr "" msgid "" @@ -13427,12 +13554,48 @@ msgstr "" msgid "" -"``adds(pattern)``\n" -" Changesets that add a file matching pattern." -msgstr "" - -#. i18n: "adds" is a keyword -msgid "adds requires a pattern" +"``id(string)``\n" +" Revision non-ambiguously specified by the given hex string prefix." +msgstr "" + +#. i18n: "id" is a keyword +msgid "id requires one argument" +msgstr "id kræver et argument" + +#. i18n: "id" is a keyword +msgid "id requires a string" +msgstr "id kræver en streng" + +msgid "" +"``outgoing([path])``\n" +" Changesets not found in the specified destination repository, or the\n" +" default push location." +msgstr "" + +#. i18n: "outgoing" is a keyword +msgid "outgoing requires a repository path" +msgstr "" + +msgid "" +"``p1([set])``\n" +" First parent of changesets in set, or the working directory." +msgstr "" + +msgid "" +"``p2([set])``\n" +" Second parent of changesets in set, or the working directory." +msgstr "" + +msgid "" +"``parents([set])``\n" +" The set of all parents for all changesets in set, or the working " +"directory." +msgstr "" + +msgid "" +"``present(set)``\n" +" An empty set, if any revision in set isn't found; otherwise,\n" +" all revisions in set." msgstr "" msgid "" @@ -13445,31 +13608,21 @@ msgstr "" msgid "" -"``merge()``\n" -" Changeset is a merge changeset." -msgstr "" - -#. i18n: "merge" is a keyword -msgid "merge takes no arguments" -msgstr "" - -msgid "" -"``closed()``\n" -" Changeset is closed." -msgstr "" - -#. i18n: "closed" is a keyword -msgid "closed takes no arguments" -msgstr "" - -msgid "" -"``head()``\n" -" Changeset is a named branch head." -msgstr "" - -#. i18n: "head" is a keyword -msgid "head takes no arguments" -msgstr "" +"``rev(number)``\n" +" Revision with the given numeric identifier." +msgstr "" + +#. i18n: "rev" is a keyword +msgid "rev requires one argument" +msgstr "rev kræver et argument" + +#. i18n: "rev" is a keyword +msgid "rev requires a number" +msgstr "rev kræver et tal" + +#. i18n: "rev" is a keyword +msgid "rev expects a number" +msgstr "rev forventer et revisionsnummer" msgid "" "``reverse(set)``\n" @@ -13477,9 +13630,8 @@ msgstr "" msgid "" -"``present(set)``\n" -" An empty set, if any revision in set isn't found; otherwise,\n" -" all revisions in set." +"``roots(set)``\n" +" Changesets with no parent changeset in set." msgstr "" msgid "" @@ -13511,35 +13663,6 @@ msgstr "ukendt sorteringsnøgle %r" msgid "" -"``all()``\n" -" All changesets, the same as ``0:tip``." -msgstr "" - -#. i18n: "all" is a keyword -msgid "all takes no arguments" -msgstr "" - -msgid "" -"``heads(set)``\n" -" Members of set with no children in set." -msgstr "" - -msgid "" -"``roots(set)``\n" -" Changesets with no parent changeset in set." -msgstr "" - -msgid "" -"``outgoing([path])``\n" -" Changesets not found in the specified destination repository, or the\n" -" default push location." -msgstr "" - -#. i18n: "outgoing" is a keyword -msgid "outgoing requires a repository path" -msgstr "" - -msgid "" "``tag(name)``\n" " The specified tag by name, or all tagged revisions if no name is given." msgstr "" @@ -13556,19 +13679,9 @@ msgstr "argumentet til tag skal være en streng" msgid "" -"``bookmark([name])``\n" -" The named bookmark or all bookmarks." -msgstr "" -"``bookmark([name])``\n" -" Det navngivne bogmærke eller alle bogmærker." - -#. i18n: "bookmark" is a keyword -msgid "bookmark takes one or no arguments" -msgstr "bookmark tager et eller to argumenter" - -#. i18n: "bookmark" is a keyword -msgid "the argument to bookmark must be a string" -msgstr "argumentet til bookmark skal være en streng" +"``user(string)``\n" +" User name is string." +msgstr "" msgid "can't negate that" msgstr "" @@ -13716,12 +13829,8 @@ msgstr "fjerner ikke depotet %s fordi det er ændret.\n" #, python-format -msgid "cloning subrepo %s\n" -msgstr "kloner underdepot %s\n" - -#, python-format -msgid "pulling subrepo %s\n" -msgstr "hiver underdepot %s\n" +msgid "cloning subrepo %s from %s\n" +msgstr "kloner underdepot %s fra %s\n" #, python-format msgid "revision %s does not exist in subrepo %s\n" @@ -13861,6 +13970,9 @@ msgid "edit failed" msgstr "redigering fejlede" +msgid "file:// URLs can only refer to localhost" +msgstr "" + #, python-format msgid "ignoring invalid [auth] key '%s'\n" msgstr "ignorerer ugyldig [auth] nøgle '%s'\n" @@ -13937,6 +14049,18 @@ msgstr "kommandoen '%s' fejlede: %s" #, python-format +msgid "filename contains '%s', which is reserved on Windows" +msgstr "" + +#, python-format +msgid "filename contains %r, which is invalid on Windows" +msgstr "" + +#, python-format +msgid "filename ends with '%s', which is not allowed on Windows" +msgstr "" + +#, python-format msgid "path ends in directory separator: %s" msgstr "" @@ -13945,8 +14069,8 @@ msgstr "stien indeholder ugyldig komponent: %s" #, python-format -msgid "path %r is inside repo %r" -msgstr "stien %r er inde i repo %r" +msgid "path %r is inside nested repo %r" +msgstr "stien %r er inden i depotet %r" #, python-format msgid "path %r traverses symbolic link %r" @@ -13979,11 +14103,24 @@ msgid "impossible time zone offset: %d" msgstr "umuligt tidszone: %d" +msgid "dates cannot consist entirely of whitespace" +msgstr "datoer kan ikke bestå udelukkende af tomrum" + +msgid "invalid day spec, use '<DATE'" +msgstr "ugyldig datospecifikation, brug '<DATO'" + +msgid "invalid day spec, use '>DATE'" +msgstr "ugyldig datospecifikation, brug '>DATO'" + #, python-format msgid "invalid day spec: %s" msgstr "ugyldig datospecifikation: %s" #, python-format +msgid "%s must be nonnegative (see 'hg help dates')" +msgstr "" + +#, python-format msgid "%.0f GB" msgstr "%.0f GB"
--- a/mercurial/ancestor.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/ancestor.py Wed May 18 09:56:27 2011 +0200 @@ -9,9 +9,10 @@ def ancestor(a, b, pfunc): """ - return a minimal-distance ancestor of nodes a and b, or None if there is no - such ancestor. Note that there can be several ancestors with the same - (minimal) distance, and the one returned is arbitrary. + Returns the common ancestor of a and b that is furthest from a + root (as measured by longest path) or None if no ancestor is + found. If there are multiple common ancestors at the same + distance, the first one found is returned. pfunc must return a list of parent vertices for a given vertex """ @@ -22,6 +23,7 @@ a, b = sorted([a, b]) # find depth from root of all ancestors + # depth is stored as a negative for heapq parentcache = {} visit = [a, b] depth = {} @@ -39,6 +41,7 @@ if p not in depth: visit.append(p) if visit[-1] == vertex: + # -(maximum distance of parents + 1) depth[vertex] = min([depth[p] for p in pl]) - 1 visit.pop()
--- a/mercurial/archival.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/archival.py Wed May 18 09:56:27 2011 +0200 @@ -8,8 +8,8 @@ from i18n import _ from node import hex import cmdutil -import util, encoding -import cStringIO, os, stat, tarfile, time, zipfile +import scmutil, util, encoding +import cStringIO, os, tarfile, time, zipfile import zlib, gzip def tidyprefix(dest, kind, prefix): @@ -172,10 +172,10 @@ # unzip will not honor unix file modes unless file creator is # set to unix (id 3). i.create_system = 3 - ftype = stat.S_IFREG + ftype = 0x8000 # UNX_IFREG in unzip source code if islink: mode = 0777 - ftype = stat.S_IFLNK + ftype = 0xa000 # UNX_IFLNK in unzip source code i.external_attr = (mode | ftype) << 16L self.z.writestr(i, data) @@ -187,7 +187,7 @@ def __init__(self, name, mtime): self.basedir = name - self.opener = util.opener(self.basedir) + self.opener = scmutil.opener(self.basedir) def addfile(self, name, mode, islink, data): if islink:
--- a/mercurial/bdiff.c Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/bdiff.c Wed May 18 09:56:27 2011 +0200 @@ -49,7 +49,7 @@ #include "util.h" struct line { - int h, len, n, e; + int hash, len, n, e; const char *l; }; @@ -63,9 +63,10 @@ struct hunk *next; }; -int splitlines(const char *a, int len, struct line **lr) +static int splitlines(const char *a, int len, struct line **lr) { - int h, i; + unsigned hash; + int i; const char *p, *b = a; const char * const plast = a + len - 1; struct line *l; @@ -81,14 +82,14 @@ return -1; /* build the line array and calculate hashes */ - h = 0; + hash = 0; for (p = a; p < a + len; p++) { /* Leonid Yuriev's hash */ - h = (h * 1664525) + *p + 1013904223; + hash = (hash * 1664525) + (unsigned char)*p + 1013904223; if (*p == '\n' || p == plast) { - l->h = h; - h = 0; + l->hash = hash; + hash = 0; l->len = p - b + 1; l->l = b; l->n = INT_MAX; @@ -98,14 +99,15 @@ } /* set up a sentinel */ - l->h = l->len = 0; + l->hash = 0; + l->len = 0; l->l = a + len; return i - 1; } -int inline cmp(struct line *a, struct line *b) +static inline int cmp(struct line *a, struct line *b) { - return a->h != b->h || a->len != b->len || memcmp(a->l, b->l, a->len); + return a->hash != b->hash || a->len != b->len || memcmp(a->l, b->l, a->len); } static int equatelines(struct line *a, int an, struct line *b, int bn) @@ -138,7 +140,7 @@ /* add lines to the hash table chains */ for (i = bn - 1; i >= 0; i--) { /* find the equivalence class */ - for (j = b[i].h & buckets; h[j].pos != INT_MAX; + for (j = b[i].hash & buckets; h[j].pos != INT_MAX; j = (j + 1) & buckets) if (!cmp(b + i, b + h[j].pos)) break; @@ -156,7 +158,7 @@ /* match items in a to their equivalence class in b */ for (i = 0; i < an; i++) { /* find the equivalence class */ - for (j = a[i].h & buckets; h[j].pos != INT_MAX; + for (j = a[i].hash & buckets; h[j].pos != INT_MAX; j = (j + 1) & buckets) if (!cmp(a + i, b + h[j].pos)) break;
--- a/mercurial/bookmarks.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/bookmarks.py Wed May 18 09:56:27 2011 +0200 @@ -6,9 +6,9 @@ # GNU General Public License version 2 or any later version. from mercurial.i18n import _ -from mercurial.node import nullid, nullrev, bin, hex, short -from mercurial import encoding, util -import os +from mercurial.node import hex +from mercurial import encoding, error, util +import errno, os def valid(mark): for c in (':', '\0', '\n', '\r'): @@ -23,14 +23,18 @@ in the .hg/bookmarks file. Read the file and return a (name=>nodeid) dictionary ''' + bookmarks = {} try: - bookmarks = {} for line in repo.opener('bookmarks'): sha, refspec = line.strip().split(' ', 1) refspec = encoding.tolocal(refspec) - bookmarks[refspec] = repo.changelog.lookup(sha) - except: - pass + try: + bookmarks[refspec] = repo.changelog.lookup(sha) + except error.RepoLookupError: + pass + except IOError, inst: + if inst.errno != errno.ENOENT: + raise return bookmarks def readcurrent(repo): @@ -41,12 +45,18 @@ is stored in .hg/bookmarks.current ''' mark = None - if os.path.exists(repo.join('bookmarks.current')): + try: file = repo.opener('bookmarks.current') + except IOError, inst: + if inst.errno != errno.ENOENT: + raise + return None + try: # No readline() in posixfile_nt, reading everything is cheap mark = encoding.tolocal((file.readlines() or [''])[0]) if mark == '' or mark not in repo._bookmarks: mark = None + finally: file.close() return mark @@ -95,13 +105,7 @@ if current == mark: return - refs = repo._bookmarks - - # do not update if we do update to a rev equal to the current bookmark - if (mark and mark not in refs and - current and refs[current] == repo.changectx('.').node()): - return - if mark not in refs: + if mark not in repo._bookmarks: mark = '' if not valid(mark): raise util.Abort(_("bookmark '%s' contains illegal " @@ -116,6 +120,15 @@ wlock.release() repo._bookmarkcurrent = mark +def updatecurrentbookmark(repo, oldnode, curbranch): + try: + update(repo, oldnode, repo.branchtags()[curbranch]) + except KeyError: + if curbranch == "default": # no default branch! + update(repo, oldnode, repo.lookup("tip")) + else: + raise util.Abort(_("branch %s not found") % curbranch) + def update(repo, parents, node): marks = repo._bookmarks update = False @@ -157,6 +170,28 @@ finally: w.release() +def updatefromremote(ui, repo, remote): + ui.debug("checking for updated bookmarks\n") + rb = remote.listkeys('bookmarks') + changed = False + for k in rb.keys(): + if k in repo._bookmarks: + nr, nl = rb[k], repo._bookmarks[k] + if nr in repo: + cr = repo[nr] + cl = repo[nl] + if cl.rev() >= cr.rev(): + continue + if cr in cl.descendants(): + repo._bookmarks[k] = cr.node() + changed = True + ui.status(_("updating bookmark %s\n") % k) + else: + ui.warn(_("not updating divergent" + " bookmark %s\n") % k) + if changed: + write(repo) + def diff(ui, repo, remote): ui.status(_("searching for changed bookmarks\n"))
--- a/mercurial/bundlerepo.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/bundlerepo.py Wed May 18 09:56:27 2011 +0200 @@ -13,13 +13,12 @@ from node import nullid from i18n import _ -import os, struct, tempfile, shutil +import os, tempfile, shutil import changegroup, util, mdiff, discovery import localrepo, changelog, manifest, filelog, revlog, error class bundlerevlog(revlog.revlog): - def __init__(self, opener, indexfile, bundle, - linkmapper=None): + def __init__(self, opener, indexfile, bundle, linkmapper): # How it works: # to retrieve a revision, we need to know the offset of # the revision in the bundle (an unbundle object). @@ -32,43 +31,39 @@ revlog.revlog.__init__(self, opener, indexfile) self.bundle = bundle self.basemap = {} - def chunkpositer(): - while 1: - chunk = bundle.chunk() - if not chunk: - break - pos = bundle.tell() - yield chunk, pos - len(chunk) n = len(self) - prev = None - for chunk, start in chunkpositer(): - size = len(chunk) - if size < 80: - raise util.Abort(_("invalid changegroup")) - start += 80 - size -= 80 - node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80]) + chain = None + while 1: + chunkdata = bundle.deltachunk(chain) + if not chunkdata: + break + node = chunkdata['node'] + p1 = chunkdata['p1'] + p2 = chunkdata['p2'] + cs = chunkdata['cs'] + deltabase = chunkdata['deltabase'] + delta = chunkdata['delta'] + + size = len(delta) + start = bundle.tell() - size + + link = linkmapper(cs) if node in self.nodemap: - prev = node + # this can happen if two branches make the same change + chain = node continue + for p in (p1, p2): if not p in self.nodemap: raise error.LookupError(p, self.indexfile, _("unknown parent")) - if linkmapper is None: - link = n - else: - link = linkmapper(cs) - - if not prev: - prev = p1 # start, size, full unc. size, base (unused), link, p1, p2, node e = (revlog.offset_type(start, 0), size, -1, -1, link, self.rev(p1), self.rev(p2), node) - self.basemap[n] = prev + self.basemap[n] = deltabase self.index.insert(-1, e) self.nodemap[node] = n - prev = node + chain = node n += 1 def inbundle(self, rev): @@ -144,7 +139,9 @@ class bundlechangelog(bundlerevlog, changelog.changelog): def __init__(self, opener, bundle): changelog.changelog.__init__(self, opener) - bundlerevlog.__init__(self, opener, self.indexfile, bundle) + linkmapper = lambda x: x + bundlerevlog.__init__(self, opener, self.indexfile, bundle, + linkmapper) class bundlemanifest(bundlerevlog, manifest.manifest): def __init__(self, opener, bundle, linkmapper): @@ -153,10 +150,14 @@ linkmapper) class bundlefilelog(bundlerevlog, filelog.filelog): - def __init__(self, opener, path, bundle, linkmapper): + def __init__(self, opener, path, bundle, linkmapper, repo): filelog.filelog.__init__(self, opener, path) bundlerevlog.__init__(self, opener, self.indexfile, bundle, linkmapper) + self._repo = repo + + def _file(self, f): + self._repo.file(f) class bundlerepository(localrepo.localrepository): def __init__(self, ui, path, bundlename): @@ -200,6 +201,8 @@ @util.propertycache def changelog(self): + # consume the header if it exists + self.bundle.changelogheader() c = bundlechangelog(self.sopener, self.bundle) self.manstart = self.bundle.tell() return c @@ -207,6 +210,8 @@ @util.propertycache def manifest(self): self.bundle.seek(self.manstart) + # consume the header if it exists + self.bundle.manifestheader() m = bundlemanifest(self.sopener, self.bundle, self.changelog.rev) self.filestart = self.bundle.tell() return m @@ -228,12 +233,13 @@ if not self.bundlefilespos: self.bundle.seek(self.filestart) while 1: - chunk = self.bundle.chunk() - if not chunk: + chunkdata = self.bundle.filelogheader() + if not chunkdata: break - self.bundlefilespos[chunk] = self.bundle.tell() + fname = chunkdata['filename'] + self.bundlefilespos[fname] = self.bundle.tell() while 1: - c = self.bundle.chunk() + c = self.bundle.deltachunk(None) if not c: break @@ -242,7 +248,7 @@ if f in self.bundlefilespos: self.bundle.seek(self.bundlefilespos[f]) return bundlefilelog(self.sopener, f, self.bundle, - self.changelog.rev) + self.changelog.rev, self) else: return filelog.filelog(self.sopener, f) @@ -274,9 +280,9 @@ cwd = os.path.join(cwd,'') if parentpath.startswith(cwd): parentpath = parentpath[len(cwd):] - path = util.drop_scheme('file', path) - if path.startswith('bundle:'): - path = util.drop_scheme('bundle', path) + u = util.url(path) + path = u.localpath() + if u.scheme == 'bundle': s = path.split("+", 1) if len(s) == 1: repopath, bundlename = parentpath, s[0] @@ -286,27 +292,49 @@ repopath, bundlename = parentpath, path return bundlerepository(ui, repopath, bundlename) -def getremotechanges(ui, repo, other, revs=None, bundlename=None, force=False): - tmp = discovery.findcommonincoming(repo, other, heads=revs, force=force) +def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None, + force=False): + '''obtains a bundle of changes incoming from other + + "onlyheads" restricts the returned changes to those reachable from the + specified heads. + "bundlename", if given, stores the bundle to this file path permanently; + otherwise it's stored to a temp file and gets deleted again when you call + the returned "cleanupfn". + "force" indicates whether to proceed on unrelated repos. + + Returns a tuple (local, csets, cleanupfn): + + "local" is a local repo from which to obtain the actual incoming changesets; it + is a bundlerepo for the obtained bundle when the original "other" is remote. + "csets" lists the incoming changeset node ids. + "cleanupfn" must be called without arguments when you're done processing the + changes; it closes both the original "other" and the one returned here. + ''' + tmp = discovery.findcommonincoming(repo, other, heads=onlyheads, force=force) common, incoming, rheads = tmp if not incoming: try: os.unlink(bundlename) - except: + except OSError: pass - return other, None, None + return other, [], other.close bundle = None + bundlerepo = None + localrepo = other if bundlename or not other.local(): # create a bundle (uncompressed if other repo is not local) - if revs is None and other.capable('changegroupsubset'): - revs = rheads + if onlyheads is None and other.capable('changegroupsubset'): + onlyheads = rheads - if revs is None: + if other.capable('getbundle'): + cg = other.getbundle('incoming', common=common, heads=onlyheads) + elif onlyheads is None: cg = other.changegroup(incoming, "incoming") else: - cg = other.changegroupsubset(incoming, revs, 'incoming') + cg = other.changegroupsubset(incoming, onlyheads, 'incoming') bundletype = other.local() and "HG10BZ" or "HG10UN" fname = bundle = changegroup.writebundle(cg, bundlename, bundletype) # keep written bundle? @@ -314,6 +342,18 @@ bundle = None if not other.local(): # use the created uncompressed bundlerepo - other = bundlerepository(ui, repo.root, fname) - return (other, incoming, bundle) + localrepo = bundlerepo = bundlerepository(ui, repo.root, fname) + # this repo contains local and other now, so filter out local again + common = repo.heads() + + csets = localrepo.changelog.findmissing(common, onlyheads) + def cleanup(): + if bundlerepo: + bundlerepo.close() + if bundle: + os.unlink(bundle) + other.close() + + return (localrepo, csets, cleanup) +
--- a/mercurial/changegroup.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/changegroup.py Wed May 18 09:56:27 2011 +0200 @@ -6,9 +6,12 @@ # GNU General Public License version 2 or any later version. from i18n import _ -import util +from node import nullrev +import mdiff, util import struct, os, bz2, zlib, tempfile +_BUNDLE10_DELTA_HEADER = "20s20s20s20s" + def readexactly(stream, n): '''read n bytes from stream.read and abort if less was available''' s = stream.read(n) @@ -43,21 +46,14 @@ return "" bundletypes = { - "": ("", nocompress), + "": ("", nocompress), # only when using unbundle on ssh and old http servers + # since the unification ssh accepts a header but there + # is no capability signaling it. "HG10UN": ("HG10UN", nocompress), "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()), "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()), } -def collector(cl, mmfs, files): - # Gather information about changeset nodes going out in a bundle. - # We want to gather manifests needed and filelogs affected. - def collect(node): - c = cl.read(node) - files.update(c[3]) - mmfs.setdefault(c[0], node) - return collect - # hgweb uses this list to communicate its preferred type bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN'] @@ -135,6 +131,8 @@ return util.chunkbuffer(generator(fh)) class unbundle10(object): + deltaheader = _BUNDLE10_DELTA_HEADER + deltaheadersize = struct.calcsize(deltaheader) def __init__(self, fh, alg): self._stream = decompressor(fh, alg) self._type = alg @@ -161,19 +159,40 @@ self.callback() return l - 4 - def chunk(self): - """return the next chunk from changegroup 'source' as a string""" - l = self.chunklength() - return readexactly(self._stream, l) + def changelogheader(self): + """v10 does not have a changelog header chunk""" + return {} - def parsechunk(self): + def manifestheader(self): + """v10 does not have a manifest header chunk""" + return {} + + def filelogheader(self): + """return the header of the filelogs chunk, v10 only has the filename""" l = self.chunklength() if not l: return {} - h = readexactly(self._stream, 80) - node, p1, p2, cs = struct.unpack("20s20s20s20s", h) - data = readexactly(self._stream, l - 80) - return dict(node=node, p1=p1, p2=p2, cs=cs, data=data) + fname = readexactly(self._stream, l) + return dict(filename=fname) + + def _deltaheader(self, headertuple, prevnode): + node, p1, p2, cs = headertuple + if prevnode is None: + deltabase = p1 + else: + deltabase = prevnode + return node, p1, p2, deltabase, cs + + def deltachunk(self, prevnode): + l = self.chunklength() + if not l: + return {} + headerdata = readexactly(self._stream, self.deltaheadersize) + header = struct.unpack(self.deltaheader, headerdata) + delta = readexactly(self._stream, l - self.deltaheadersize) + node, p1, p2, deltabase, cs = self._deltaheader(header, prevnode) + return dict(node=node, p1=p1, p2=p2, cs=cs, + deltabase=deltabase, delta=delta) class headerlessfixup(object): def __init__(self, fh, h): @@ -203,3 +222,35 @@ if version != '10': raise util.Abort(_('%s: unknown bundle version %s') % (fname, version)) return unbundle10(fh, alg) + +class bundle10(object): + deltaheader = _BUNDLE10_DELTA_HEADER + def __init__(self, lookup): + self._lookup = lookup + def close(self): + return closechunk() + def fileheader(self, fname): + return chunkheader(len(fname)) + fname + def revchunk(self, revlog, rev, prev): + node = revlog.node(rev) + p1, p2 = revlog.parentrevs(rev) + base = prev + + prefix = '' + if base == nullrev: + delta = revlog.revision(node) + prefix = mdiff.trivialdiffheader(len(delta)) + else: + delta = revlog.revdiff(base, rev) + linknode = self._lookup(revlog, node) + p1n, p2n = revlog.parents(node) + basenode = revlog.node(base) + meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode) + meta += prefix + l = len(meta) + len(delta) + yield chunkheader(l) + yield meta + yield delta + def builddeltaheader(self, node, p1n, p2n, basenode, linknode): + # do nothing with basenode, it is implicitly the previous one in HG10 + return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
--- a/mercurial/changelog.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/changelog.py Wed May 18 09:56:27 2011 +0200 @@ -100,6 +100,10 @@ class changelog(revlog.revlog): def __init__(self, opener): revlog.revlog.__init__(self, opener, "00changelog.i") + if self._initempty: + # changelogs don't benefit from generaldelta + self.version &= ~revlog.REVLOGGENERALDELTA + self._generaldelta = False self._realopener = opener self._delayed = False self._divert = False @@ -118,7 +122,9 @@ self.opener = self._realopener # move redirected index data back into place if self._divert: - n = self.opener(self.indexfile + ".a").name + nfile = self.opener(self.indexfile + ".a") + n = nfile.name + nfile.close() util.rename(n, n[:-2]) elif self._delaybuf: fp = self.opener(self.indexfile, 'a') @@ -185,7 +191,7 @@ try: # various tools did silly things with the time zone field. timezone = int(extra_data[0]) - except: + except ValueError: timezone = 0 extra = {} else:
--- a/mercurial/cmdutil.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/cmdutil.py Wed May 18 09:56:27 2011 +0200 @@ -7,12 +7,10 @@ from node import hex, nullid, nullrev, short from i18n import _ -import os, sys, errno, re, glob, tempfile -import util, templater, patch, error, encoding, templatekw +import os, sys, errno, re, tempfile +import util, scmutil, templater, patch, error, templatekw, revlog import match as matchmod -import similar, revset, subrepo - -revrangesep = ':' +import subrepo def parsealiases(cmd): return cmd.lstrip("^").split("|") @@ -71,8 +69,8 @@ return p -def bail_if_changed(repo): - if repo.dirstate.parents()[1] != nullid: +def bailifchanged(repo): + if repo.dirstate.p2() != nullid: raise util.Abort(_('outstanding uncommitted merge')) modified, added, removed, deleted = repo.status()[:4] if modified or added or removed or deleted: @@ -91,7 +89,7 @@ if logfile == '-': message = sys.stdin.read() else: - message = open(logfile).read() + message = '\n'.join(util.readfile(logfile).splitlines()) except IOError, inst: raise util.Abort(_("can't read commit message '%s': %s") % (logfile, inst.strerror)) @@ -111,78 +109,7 @@ limit = None return limit -def revsingle(repo, revspec, default='.'): - if not revspec: - return repo[default] - - l = revrange(repo, [revspec]) - if len(l) < 1: - raise util.Abort(_('empty revision set')) - return repo[l[-1]] - -def revpair(repo, revs): - if not revs: - return repo.dirstate.parents()[0], None - - l = revrange(repo, revs) - - if len(l) == 0: - return repo.dirstate.parents()[0], None - - if len(l) == 1: - return repo.lookup(l[0]), None - - return repo.lookup(l[0]), repo.lookup(l[-1]) - -def revrange(repo, revs): - """Yield revision as strings from a list of revision specifications.""" - - def revfix(repo, val, defval): - if not val and val != 0 and defval is not None: - return defval - return repo.changelog.rev(repo.lookup(val)) - - seen, l = set(), [] - for spec in revs: - # attempt to parse old-style ranges first to deal with - # things like old-tag which contain query metacharacters - try: - if isinstance(spec, int): - seen.add(spec) - l.append(spec) - continue - - if revrangesep in spec: - start, end = spec.split(revrangesep, 1) - start = revfix(repo, start, 0) - end = revfix(repo, end, len(repo) - 1) - step = start > end and -1 or 1 - for rev in xrange(start, end + step, step): - if rev in seen: - continue - seen.add(rev) - l.append(rev) - continue - elif spec and spec in repo: # single unquoted rev - rev = revfix(repo, spec, None) - if rev in seen: - continue - seen.add(rev) - l.append(rev) - continue - except error.RepoLookupError: - pass - - # fall through to new-style queries if old-style fails - m = revset.match(spec) - for r in m(repo, range(len(repo))): - if r not in seen: - l.append(r) - seen.update(l) - - return l - -def make_filename(repo, pat, node, +def makefilename(repo, pat, node, total=None, seqno=None, revwidth=None, pathname=None): node_expander = { 'H': lambda: hex(node), @@ -227,10 +154,10 @@ raise util.Abort(_("invalid format spec '%%%s' in output filename") % inst.args[0]) -def make_file(repo, pat, node=None, - total=None, seqno=None, revwidth=None, mode='wb', pathname=None): +def makefileobj(repo, pat, node=None, total=None, + seqno=None, revwidth=None, mode='wb', pathname=None): - writable = 'w' in mode or 'a' in mode + writable = mode not in ('r', 'rb') if not pat or pat == '-': fp = writable and sys.stdout or sys.stdin @@ -239,160 +166,44 @@ return pat if hasattr(pat, 'read') and 'r' in mode: return pat - return open(make_filename(repo, pat, node, total, seqno, revwidth, + return open(makefilename(repo, pat, node, total, seqno, revwidth, pathname), mode) -def expandpats(pats): - if not util.expandglobs: - return list(pats) - ret = [] - for p in pats: - kind, name = matchmod._patsplit(p, None) - if kind is None: - try: - globbed = glob.glob(name) - except re.error: - globbed = [name] - if globbed: - ret.extend(globbed) - continue - ret.append(p) - return ret - -def match(repo, pats=[], opts={}, globbed=False, default='relpath'): - if pats == ("",): - pats = [] - if not globbed and default == 'relpath': - pats = expandpats(pats or []) - m = matchmod.match(repo.root, repo.getcwd(), pats, - opts.get('include'), opts.get('exclude'), default, - auditor=repo.auditor) - def badfn(f, msg): - repo.ui.warn("%s: %s\n" % (m.rel(f), msg)) - m.bad = badfn - return m - -def matchall(repo): - return matchmod.always(repo.root, repo.getcwd()) - -def matchfiles(repo, files): - return matchmod.exact(repo.root, repo.getcwd(), files) +def openrevlog(repo, cmd, file_, opts): + """opens the changelog, manifest, a filelog or a given revlog""" + cl = opts['changelog'] + mf = opts['manifest'] + msg = None + if cl and mf: + msg = _('cannot specify --changelog and --manifest at the same time') + elif cl or mf: + if file_: + msg = _('cannot specify filename with --changelog or --manifest') + elif not repo: + msg = _('cannot specify --changelog or --manifest ' + 'without a repository') + if msg: + raise util.Abort(msg) -def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None): - if dry_run is None: - dry_run = opts.get('dry_run') - if similarity is None: - similarity = float(opts.get('similarity') or 0) - # we'd use status here, except handling of symlinks and ignore is tricky - added, unknown, deleted, removed = [], [], [], [] - audit_path = util.path_auditor(repo.root) - m = match(repo, pats, opts) - for abs in repo.walk(m): - target = repo.wjoin(abs) - good = True - try: - audit_path(abs) - except: - good = False - rel = m.rel(abs) - exact = m.exact(abs) - if good and abs not in repo.dirstate: - unknown.append(abs) - if repo.ui.verbose or not exact: - repo.ui.status(_('adding %s\n') % ((pats and rel) or abs)) - elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target) - or (os.path.isdir(target) and not os.path.islink(target))): - deleted.append(abs) - if repo.ui.verbose or not exact: - repo.ui.status(_('removing %s\n') % ((pats and rel) or abs)) - # for finding renames - elif repo.dirstate[abs] == 'r': - removed.append(abs) - elif repo.dirstate[abs] == 'a': - added.append(abs) - copies = {} - if similarity > 0: - for old, new, score in similar.findrenames(repo, - added + unknown, removed + deleted, similarity): - if repo.ui.verbose or not m.exact(old) or not m.exact(new): - repo.ui.status(_('recording removal of %s as rename to %s ' - '(%d%% similar)\n') % - (m.rel(old), m.rel(new), score * 100)) - copies[new] = old - - if not dry_run: - wctx = repo[None] - wlock = repo.wlock() - try: - wctx.remove(deleted) - wctx.add(unknown) - for new, old in copies.iteritems(): - wctx.copy(old, new) - finally: - wlock.release() - -def updatedir(ui, repo, patches, similarity=0): - '''Update dirstate after patch application according to metadata''' - if not patches: - return - copies = [] - removes = set() - cfiles = patches.keys() - cwd = repo.getcwd() - if cwd: - cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()] - for f in patches: - gp = patches[f] - if not gp: - continue - if gp.op == 'RENAME': - copies.append((gp.oldpath, gp.path)) - removes.add(gp.oldpath) - elif gp.op == 'COPY': - copies.append((gp.oldpath, gp.path)) - elif gp.op == 'DELETE': - removes.add(gp.path) - - wctx = repo[None] - for src, dst in copies: - dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd) - if (not similarity) and removes: - wctx.remove(sorted(removes), True) - - for f in patches: - gp = patches[f] - if gp and gp.mode: - islink, isexec = gp.mode - dst = repo.wjoin(gp.path) - # patch won't create empty files - if gp.op == 'ADD' and not os.path.lexists(dst): - flags = (isexec and 'x' or '') + (islink and 'l' or '') - repo.wwrite(gp.path, '', flags) - util.set_flags(dst, islink, isexec) - addremove(repo, cfiles, similarity=similarity) - files = patches.keys() - files.extend([r for r in removes if r not in files]) - return sorted(files) - -def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None): - """Update the dirstate to reflect the intent of copying src to dst. For - different reasons it might not end with dst being marked as copied from src. - """ - origsrc = repo.dirstate.copied(src) or src - if dst == origsrc: # copying back a copy? - if repo.dirstate[dst] not in 'mn' and not dryrun: - repo.dirstate.normallookup(dst) - else: - if repo.dirstate[origsrc] == 'a' and origsrc == src: - if not ui.quiet: - ui.warn(_("%s has not been committed yet, so no copy " - "data will be stored for %s.\n") - % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd))) - if repo.dirstate[dst] in '?r' and not dryrun: - wctx.add([dst]) - elif not dryrun: - wctx.copy(origsrc, dst) + r = None + if repo: + if cl: + r = repo.changelog + elif mf: + r = repo.manifest + elif file_: + filelog = repo.file(file_) + if len(filelog): + r = filelog + if not r: + if not file_: + raise error.CommandError(cmd, _('invalid arguments')) + if not os.path.isfile(file_): + raise util.Abort(_("revlog '%s' not found") % file_) + r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), + file_[:-2] + ".i") + return r def copy(ui, repo, pats, opts, rename=False): # called with the repo lock held @@ -408,7 +219,7 @@ def walkpat(pat): srcs = [] badstates = after and '?' or '?r' - m = match(repo, [pat], opts, globbed=True) + m = scmutil.match(repo, [pat], opts, globbed=True) for abs in repo.walk(m): state = repo.dirstate[abs] rel = m.rel(abs) @@ -429,12 +240,14 @@ # relsrc: ossep # otarget: ossep def copyfile(abssrc, relsrc, otarget, exact): - abstarget = util.canonpath(repo.root, cwd, otarget) + abstarget = scmutil.canonpath(repo.root, cwd, otarget) reltarget = repo.pathto(abstarget, cwd) target = repo.wjoin(abstarget) src = repo.wjoin(abssrc) state = repo.dirstate[abstarget] + scmutil.checkportable(ui, abstarget) + # check for collisions prevsrc = targets.get(abstarget) if prevsrc is not None: @@ -485,7 +298,8 @@ targets[abstarget] = abssrc # fix up dirstate - dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd) + scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget, + dryrun=dryrun, cwd=cwd) if rename and not dryrun: wctx.remove([abssrc], not after) @@ -495,7 +309,7 @@ # return: function that takes hgsep and returns ossep def targetpathfn(pat, dest, srcs): if os.path.isdir(pat): - abspfx = util.canonpath(repo.root, cwd, pat) + abspfx = scmutil.canonpath(repo.root, cwd, pat) abspfx = util.localpath(abspfx) if destdirexists: striplen = len(os.path.split(abspfx)[0]) @@ -521,7 +335,7 @@ res = lambda p: os.path.join(dest, os.path.basename(util.localpath(p))) else: - abspfx = util.canonpath(repo.root, cwd, pat) + abspfx = scmutil.canonpath(repo.root, cwd, pat) if len(abspfx) < len(srcs[0][0]): # A directory. Either the target path contains the last # component of the source path or it does not. @@ -556,7 +370,7 @@ return res - pats = expandpats(pats) + pats = scmutil.expandpats(pats) if not pats: raise util.Abort(_('no source or destination specified')) if len(pats) == 1: @@ -683,8 +497,8 @@ shouldclose = False if not fp: - fp = make_file(repo, template, node, total=total, seqno=seqno, - revwidth=revwidth, mode='ab') + fp = makefileobj(repo, template, node, total=total, seqno=seqno, + revwidth=revwidth, mode='ab') if fp != template: shouldclose = True if fp != sys.stdout and hasattr(fp, 'name'): @@ -1020,7 +834,7 @@ # options patch = False if opts.get('patch') or opts.get('stat'): - patch = matchall(repo) + patch = scmutil.matchall(repo) tmpl = opts.get('template') style = None @@ -1061,7 +875,7 @@ """Find the tipmost changeset that matches the given date spec""" df = util.matchdate(date) - m = matchall(repo) + m = scmutil.matchall(repo) results = {} def prep(ctx, fns): @@ -1116,7 +930,7 @@ defrange = '%s:0' % repo['.'].rev() else: defrange = '-1:0' - revs = revrange(repo, opts['rev'] or [defrange]) + revs = scmutil.revrange(repo, opts['rev'] or [defrange]) if not revs: return [] wanted = set() @@ -1312,9 +1126,15 @@ match.bad = lambda x, y: bad.append(x) or oldbad(x, y) names = [] wctx = repo[None] + cca = None + abort, warn = scmutil.checkportabilityalert(ui) + if abort or warn: + cca = scmutil.casecollisionauditor(ui, abort, wctx) for f in repo.walk(match): exact = match.exact(f) if exact or f not in repo.dirstate: + if cca: + cca(f) names.append(f) if ui.verbose or not exact: ui.status(_('adding %s\n') % match.rel(join(f))) @@ -1344,9 +1164,9 @@ # extract addremove carefully -- this function can be called from a command # that doesn't support addremove if opts.get('addremove'): - addremove(repo, pats, opts) + scmutil.addremove(repo, pats, opts) - return commitfunc(ui, repo, message, match(repo, pats, opts), opts) + return commitfunc(ui, repo, message, scmutil.match(repo, pats, opts), opts) def commiteditor(repo, ctx, subs): if ctx.description(): @@ -1387,3 +1207,18 @@ raise util.Abort(_("empty commit message")) return text + +def command(table): + '''returns a function object bound to table which can be used as + a decorator for populating table as a command table''' + + def cmd(name, options, synopsis=None): + def decorator(func): + if synopsis: + table[name] = func, options, synopsis + else: + table[name] = func, options + return func + return decorator + + return cmd
--- a/mercurial/commands.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/commands.py Wed May 18 09:56:27 2011 +0200 @@ -5,19 +5,134 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -from node import hex, nullid, nullrev, short +from node import hex, bin, nullid, nullrev, short from lock import release from i18n import _, gettext import os, re, sys, difflib, time, tempfile -import hg, util, revlog, extensions, copies, error, bookmarks -import patch, help, mdiff, url, encoding, templatekw, discovery +import hg, scmutil, util, revlog, extensions, copies, error, bookmarks +import patch, help, url, encoding, templatekw, discovery import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server import merge as mergemod import minirst, revset -import dagparser +import dagparser, context, simplemerge +import random, setdiscovery, treediscovery, dagutil + +table = {} + +command = cmdutil.command(table) + +# common command options + +globalopts = [ + ('R', 'repository', '', + _('repository root directory or name of overlay bundle file'), + _('REPO')), + ('', 'cwd', '', + _('change working directory'), _('DIR')), + ('y', 'noninteractive', None, + _('do not prompt, assume \'yes\' for any required answers')), + ('q', 'quiet', None, _('suppress output')), + ('v', 'verbose', None, _('enable additional output')), + ('', 'config', [], + _('set/override config option (use \'section.name=value\')'), + _('CONFIG')), + ('', 'debug', None, _('enable debugging output')), + ('', 'debugger', None, _('start debugger')), + ('', 'encoding', encoding.encoding, _('set the charset encoding'), + _('ENCODE')), + ('', 'encodingmode', encoding.encodingmode, + _('set the charset encoding mode'), _('MODE')), + ('', 'traceback', None, _('always print a traceback on exception')), + ('', 'time', None, _('time how long the command takes')), + ('', 'profile', None, _('print command execution profile')), + ('', 'version', None, _('output version information and exit')), + ('h', 'help', None, _('display help and exit')), +] + +dryrunopts = [('n', 'dry-run', None, + _('do not perform actions, just print output'))] + +remoteopts = [ + ('e', 'ssh', '', + _('specify ssh command to use'), _('CMD')), + ('', 'remotecmd', '', + _('specify hg command to run on the remote side'), _('CMD')), + ('', 'insecure', None, + _('do not verify server certificate (ignoring web.cacerts config)')), +] + +walkopts = [ + ('I', 'include', [], + _('include names matching the given patterns'), _('PATTERN')), + ('X', 'exclude', [], + _('exclude names matching the given patterns'), _('PATTERN')), +] + +commitopts = [ + ('m', 'message', '', + _('use text as commit message'), _('TEXT')), + ('l', 'logfile', '', + _('read commit message from file'), _('FILE')), +] + +commitopts2 = [ + ('d', 'date', '', + _('record the specified date as commit date'), _('DATE')), + ('u', 'user', '', + _('record the specified user as committer'), _('USER')), +] + +templateopts = [ + ('', 'style', '', + _('display using template map file'), _('STYLE')), + ('', 'template', '', + _('display with template'), _('TEMPLATE')), +] + +logopts = [ + ('p', 'patch', None, _('show patch')), + ('g', 'git', None, _('use git extended diff format')), + ('l', 'limit', '', + _('limit number of changes displayed'), _('NUM')), + ('M', 'no-merges', None, _('do not show merges')), + ('', 'stat', None, _('output diffstat-style summary of changes')), +] + templateopts + +diffopts = [ + ('a', 'text', None, _('treat all files as text')), + ('g', 'git', None, _('use git extended diff format')), + ('', 'nodates', None, _('omit dates from diff headers')) +] + +diffopts2 = [ + ('p', 'show-function', None, _('show which function each change is in')), + ('', 'reverse', None, _('produce a diff that undoes the changes')), + ('w', 'ignore-all-space', None, + _('ignore white space when comparing lines')), + ('b', 'ignore-space-change', None, + _('ignore changes in the amount of white space')), + ('B', 'ignore-blank-lines', None, + _('ignore changes whose lines are all blank')), + ('U', 'unified', '', + _('number of lines of context to show'), _('NUM')), + ('', 'stat', None, _('output diffstat-style summary of changes')), +] + +similarityopts = [ + ('s', 'similarity', '', + _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY')) +] + +subrepoopts = [ + ('S', 'subrepos', None, + _('recurse into subrepositories')) +] # Commands start here, listed alphabetically +@command('^add', + walkopts + subrepoopts + dryrunopts, + _('[OPTION]... [FILE]...')) def add(ui, repo, *pats, **opts): """add the specified files on the next commit @@ -46,11 +161,14 @@ Returns 0 if all files are successfully added. """ - m = cmdutil.match(repo, pats, opts) + m = scmutil.match(repo, pats, opts) rejected = cmdutil.add(ui, repo, m, opts.get('dry_run'), opts.get('subrepos'), prefix="") return rejected and 1 or 0 +@command('addremove', + similarityopts + walkopts + dryrunopts, + _('[OPTION]... [FILE]...')) def addremove(ui, repo, *pats, **opts): """add all new files, delete all missing files @@ -77,8 +195,22 @@ raise util.Abort(_('similarity must be a number')) if sim < 0 or sim > 100: raise util.Abort(_('similarity must be between 0 and 100')) - return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0) - + return scmutil.addremove(repo, pats, opts, similarity=sim / 100.0) + +@command('^annotate|blame', + [('r', 'rev', '', _('annotate the specified revision'), _('REV')), + ('', 'follow', None, + _('follow copies/renames and list the filename (DEPRECATED)')), + ('', 'no-follow', None, _("don't follow copies and renames")), + ('a', 'text', None, _('treat all files as text')), + ('u', 'user', None, _('list the author (long with -v)')), + ('f', 'file', None, _('list the filename')), + ('d', 'date', None, _('list the date (short with -q)')), + ('n', 'number', None, _('list the revision number (default)')), + ('c', 'changeset', None, _('list the changeset')), + ('l', 'line-number', None, _('show line number at the first appearance')) + ] + walkopts, + _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')) def annotate(ui, repo, *pats, **opts): """show changeset information by line for each file @@ -98,7 +230,7 @@ if opts.get('follow'): # --follow is deprecated and now just an alias for -f/--file # to mimic the behavior of Mercurial before version 1.5 - opts['file'] = 1 + opts['file'] = True datefunc = ui.quiet and util.shortdate or util.datestr getdate = util.cachefunc(lambda x: datefunc(x[0].date())) @@ -115,7 +247,7 @@ if (not opts.get('user') and not opts.get('changeset') and not opts.get('date') and not opts.get('file')): - opts['number'] = 1 + opts['number'] = True linenumber = opts.get('line_number') is not None if linenumber and (not opts.get('changeset')) and (not opts.get('number')): @@ -126,8 +258,12 @@ lastfunc = funcmap[-1] funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1]) - ctx = cmdutil.revsingle(repo, opts.get('rev')) - m = cmdutil.match(repo, pats, opts) + def bad(x, y): + raise util.Abort("%s: %s" % (x, y)) + + ctx = scmutil.revsingle(repo, opts.get('rev')) + m = scmutil.match(repo, pats, opts) + m.bad = bad follow = not opts.get('no_follow') for abs in ctx.walk(m): fctx = ctx[abs] @@ -149,6 +285,14 @@ for p, l in zip(zip(*pieces), lines): ui.write("%s: %s" % (" ".join(p), l[1])) +@command('archive', + [('', 'no-decode', None, _('do not pass files through decoders')), + ('p', 'prefix', '', _('directory prefix for files in archive'), + _('PREFIX')), + ('r', 'rev', '', _('revision to distribute'), _('REV')), + ('t', 'type', '', _('type of distribution to create'), _('TYPE')), + ] + subrepoopts + walkopts, + _('[OPTION]... DEST')) def archive(ui, repo, dest, **opts): '''create an unversioned archive of a repository revision @@ -178,11 +322,11 @@ Returns 0 on success. ''' - ctx = cmdutil.revsingle(repo, opts.get('rev')) + ctx = scmutil.revsingle(repo, opts.get('rev')) if not ctx: raise util.Abort(_('no working directory: please specify a revision')) node = ctx.node() - dest = cmdutil.make_filename(repo, dest, node) + dest = cmdutil.makefilename(repo, dest, node) if os.path.realpath(dest) == repo.root: raise util.Abort(_('repository root cannot be destination')) @@ -196,11 +340,18 @@ if not prefix: prefix = os.path.basename(repo.root) + '-%h' - prefix = cmdutil.make_filename(repo, prefix, node) - matchfn = cmdutil.match(repo, [], opts) + prefix = cmdutil.makefilename(repo, prefix, node) + matchfn = scmutil.match(repo, [], opts) archival.archive(repo, dest, node, kind, not opts.get('no_decode'), matchfn, prefix, subrepos=opts.get('subrepos')) +@command('backout', + [('', 'merge', None, _('merge with old dirstate parent after backout')), + ('', 'parent', '', _('parent to choose when backing out merge'), _('REV')), + ('t', 'tool', '', _('specify merge tool')), + ('r', 'rev', '', _('revision to backout'), _('REV')), + ] + walkopts + commitopts + commitopts2, + _('[OPTION]... [-r] REV')) def backout(ui, repo, node=None, rev=None, **opts): '''reverse effect of earlier changeset @@ -238,8 +389,8 @@ if date: opts['date'] = util.parsedate(date) - cmdutil.bail_if_changed(repo) - node = cmdutil.revsingle(repo, rev).node() + cmdutil.bailifchanged(repo) + node = scmutil.revsingle(repo, rev).node() op1, op2 = repo.dirstate.parents() a = repo.changelog.ancestor(op1, node) @@ -302,8 +453,18 @@ ui.setconfig('ui', 'forcemerge', '') return 0 +@command('bisect', + [('r', 'reset', False, _('reset bisect state')), + ('g', 'good', False, _('mark changeset good')), + ('b', 'bad', False, _('mark changeset bad')), + ('s', 'skip', False, _('skip testing changeset')), + ('e', 'extend', False, _('extend the bisect range')), + ('c', 'command', '', _('use command to check changeset state'), _('CMD')), + ('U', 'noupdate', False, _('do not update to target'))], + _("[-gbsr] [-U] [-c CMD] [REV]")) def bisect(ui, repo, rev=None, extra=None, command=None, - reset=None, good=None, bad=None, skip=None, noupdate=None): + reset=None, good=None, bad=None, skip=None, extend=None, + noupdate=None): """subdivision search of changesets This command helps to find changesets which introduce problems. To @@ -326,6 +487,17 @@ Returns 0 on success. """ + def extendbisectrange(nodes, good): + # bisect is incomplete when it ends on a merge node and + # one of the parent was not checked. + parents = repo[nodes[0]].parents() + if len(parents) > 1: + side = good and state['bad'] or state['good'] + num = len(set(i.node() for i in parents) & set(side)) + if num == 1: + return parents[0].ancestor(parents[1]) + return None + def print_result(nodes, good): displayer = cmdutil.show_changeset(ui, repo, {}) if len(nodes) == 1: @@ -335,15 +507,12 @@ else: ui.write(_("The first bad revision is:\n")) displayer.show(repo[nodes[0]]) - parents = repo[nodes[0]].parents() - if len(parents) > 1: - side = good and state['bad'] or state['good'] - num = len(set(i.node() for i in parents) & set(side)) - if num == 1: - common = parents[0].ancestor(parents[1]) - ui.write(_('Not all ancestors of this changeset have been' - ' checked.\nTo check the other ancestors, start' - ' from the common ancestor, %s.\n' % common)) + extendnode = extendbisectrange(nodes, good) + if extendnode is not None: + ui.write(_('Not all ancestors of this changeset have been' + ' checked.\nUse bisect --extend to continue the ' + 'bisection from\nthe common ancestor, %s.\n') + % extendnode) else: # multiple possible revisions if good: @@ -376,7 +545,7 @@ bad = True else: reset = True - elif extra or good + bad + skip + reset + bool(command) > 1: + elif extra or good + bad + skip + reset + extend + bool(command) > 1: raise util.Abort(_('incompatible arguments')) if reset: @@ -404,7 +573,7 @@ raise util.Abort(_("%s killed") % command) else: transition = "bad" - ctx = cmdutil.revsingle(repo, rev) + ctx = scmutil.revsingle(repo, rev) rev = None # clear for future iterations state[transition].append(ctx.node()) ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition)) @@ -412,7 +581,7 @@ # bisect nodes, changesets, good = hbisect.bisect(repo.changelog, state) # update to next check - cmdutil.bail_if_changed(repo) + cmdutil.bailifchanged(repo) hg.clean(repo, nodes[0], show_stats=False) finally: hbisect.save_state(repo, state) @@ -422,7 +591,7 @@ # update state if rev: - nodes = [repo.lookup(i) for i in cmdutil.revrange(repo, [rev])] + nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])] else: nodes = [repo.lookup('.')] @@ -440,6 +609,18 @@ # actually bisect nodes, changesets, good = hbisect.bisect(repo.changelog, state) + if extend: + if not changesets: + extendnode = extendbisectrange(nodes, good) + if extendnode is not None: + ui.write(_("Extending search to changeset %d:%s\n" + % (extendnode.rev(), extendnode))) + if noupdate: + return + cmdutil.bailifchanged(repo) + return hg.clean(repo, extendnode.node()) + raise util.Abort(_("nothing to extend")) + if changesets == 0: print_result(nodes, good) else: @@ -454,10 +635,18 @@ "(%d changesets remaining, ~%d tests)\n") % (rev, short(node), changesets, tests)) if not noupdate: - cmdutil.bail_if_changed(repo) + cmdutil.bailifchanged(repo) return hg.clean(repo, node) -def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None): +@command('bookmarks', + [('f', 'force', False, _('force')), + ('r', 'rev', '', _('revision'), _('REV')), + ('d', 'delete', False, _('delete a given bookmark')), + ('m', 'rename', '', _('rename a given bookmark'), _('NAME')), + ('i', 'inactive', False, _('do not mark a new bookmark active'))], + _('hg bookmarks [-f] [-d] [-i] [-m NAME] [-r REV] [NAME]')) +def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, + rename=None, inactive=False): '''track a line of development with movable markers Bookmarks are pointers to certain commits that move when @@ -481,13 +670,14 @@ if rename: if rename not in marks: - raise util.Abort(_("a bookmark of this name does not exist")) + raise util.Abort(_("bookmark '%s' does not exist") % rename) if mark in marks and not force: - raise util.Abort(_("a bookmark of the same name already exists")) + raise util.Abort(_("bookmark '%s' already exists " + "(use -f to force)") % mark) if mark is None: raise util.Abort(_("new bookmark name required")) marks[mark] = marks[rename] - if repo._bookmarkcurrent == rename: + if repo._bookmarkcurrent == rename and not inactive: bookmarks.setcurrent(repo, mark) del marks[rename] bookmarks.write(repo) @@ -497,7 +687,7 @@ if mark is None: raise util.Abort(_("bookmark name required")) if mark not in marks: - raise util.Abort(_("a bookmark of this name does not exist")) + raise util.Abort(_("bookmark '%s' does not exist") % mark) if mark == repo._bookmarkcurrent: bookmarks.setcurrent(repo, None) del marks[mark] @@ -511,8 +701,12 @@ if not mark: raise util.Abort(_("bookmark names cannot consist entirely of " "whitespace")) + if inactive and mark == repo._bookmarkcurrent: + bookmarks.setcurrent(repo, None) + return if mark in marks and not force: - raise util.Abort(_("a bookmark of the same name already exists")) + raise util.Abort(_("bookmark '%s' already exists " + "(use -f to force)") % mark) if ((mark in repo.branchtags() or mark == repo.dirstate.branch()) and not force): raise util.Abort( @@ -521,7 +715,7 @@ marks[mark] = repo.lookup(rev) else: marks[mark] = repo.changectx('.').node() - if repo.changectx('.').node() == marks[mark]: + if not inactive and repo.changectx('.').node() == marks[mark]: bookmarks.setcurrent(repo, mark) bookmarks.write(repo) return @@ -547,6 +741,11 @@ label=label) return +@command('branch', + [('f', 'force', None, + _('set branch name even if it shadows an existing branch')), + ('C', 'clean', None, _('reset branch name to parent branch name'))], + _('[-fC] [NAME]')) def branch(ui, repo, label=None, **opts): """set or show the current branch name @@ -570,19 +769,24 @@ """ if opts.get('clean'): - label = repo[None].parents()[0].branch() + label = repo[None].p1().branch() repo.dirstate.setbranch(label) ui.status(_('reset working directory to branch %s\n') % label) elif label: if not opts.get('force') and label in repo.branchtags(): if label not in [p.branch() for p in repo.parents()]: - raise util.Abort(_('a branch of the same name already exists' - " (use 'hg update' to switch to it)")) + raise util.Abort(_('a branch of the same name already exists'), + # i18n: "it" refers to an existing branch + hint=_("use 'hg update' to switch to it")) repo.dirstate.setbranch(label) ui.status(_('marked working directory as branch %s\n') % label) else: ui.write("%s\n" % repo.dirstate.branch()) +@command('branches', + [('a', 'active', False, _('show only branches that have unmerged heads')), + ('c', 'closed', False, _('show normal and closed branches'))], + _('[-ac]')) def branches(ui, repo, active=False, closed=False): """list repository named branches @@ -632,6 +836,19 @@ tag = ui.label(tag, label) ui.write("%s %s%s\n" % (tag, rev, notice)) +@command('bundle', + [('f', 'force', None, _('run even when the destination is unrelated')), + ('r', 'rev', [], _('a changeset intended to be added to the destination'), + _('REV')), + ('b', 'branch', [], _('a specific branch you would like to bundle'), + _('BRANCH')), + ('', 'base', [], + _('a base changeset assumed to be available at the destination'), + _('REV')), + ('a', 'all', None, _('bundle all changesets in the repository')), + ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')), + ] + remoteopts, + _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')) def bundle(ui, repo, fname, dest=None, **opts): """create a changegroup file @@ -659,59 +876,33 @@ """ revs = None if 'rev' in opts: - revs = cmdutil.revrange(repo, opts['rev']) + revs = scmutil.revrange(repo, opts['rev']) if opts.get('all'): base = ['null'] else: - base = cmdutil.revrange(repo, opts.get('base')) + base = scmutil.revrange(repo, opts.get('base')) if base: if dest: raise util.Abort(_("--base is incompatible with specifying " "a destination")) - base = [repo.lookup(rev) for rev in base] - # create the right base - # XXX: nodesbetween / changegroup* should be "fixed" instead - o = [] - has = set((nullid,)) - for n in base: - has.update(repo.changelog.reachable(n)) - if revs: - revs = [repo.lookup(rev) for rev in revs] - visit = revs[:] - has.difference_update(visit) - else: - visit = repo.changelog.heads() - seen = {} - while visit: - n = visit.pop(0) - parents = [p for p in repo.changelog.parents(n) if p not in has] - if len(parents) == 0: - if n not in has: - o.append(n) - else: - for p in parents: - if p not in seen: - seen[p] = 1 - visit.append(p) + common = [repo.lookup(rev) for rev in base] + heads = revs and map(repo.lookup, revs) or revs else: dest = ui.expandpath(dest or 'default-push', dest or 'default') dest, branches = hg.parseurl(dest, opts.get('branch')) other = hg.repository(hg.remoteui(repo, opts), dest) revs, checkout = hg.addbranchrevs(repo, other, branches, revs) - if revs: - revs = [repo.lookup(rev) for rev in revs] - o = discovery.findoutgoing(repo, other, force=opts.get('force')) - - if not o: + heads = revs and map(repo.lookup, revs) or revs + common, outheads = discovery.findcommonoutgoing(repo, other, + onlyheads=heads, + force=opts.get('force')) + + cg = repo.getbundle('bundle', common=common, heads=heads) + if not cg: ui.status(_("no changes found\n")) return 1 - if revs: - cg = repo.changegroupsubset(o, revs, 'bundle') - else: - cg = repo.changegroup(o, 'bundle') - bundletype = opts.get('type', 'bzip2').lower() btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'} bundletype = btypes.get(bundletype) @@ -720,6 +911,13 @@ changegroup.writebundle(cg, fname, bundletype) +@command('cat', + [('o', 'output', '', + _('print output to file with formatted name'), _('FORMAT')), + ('r', 'rev', '', _('print the given revision'), _('REV')), + ('', 'decode', None, _('apply any matching decode filter')), + ] + walkopts, + _('[OPTION]... FILE...')) def cat(ui, repo, file1, *pats, **opts): """output the current or given revision of files @@ -737,11 +935,12 @@ Returns 0 on success. """ - ctx = cmdutil.revsingle(repo, opts.get('rev')) + ctx = scmutil.revsingle(repo, opts.get('rev')) err = 1 - m = cmdutil.match(repo, (file1,) + pats, opts) + m = scmutil.match(repo, (file1,) + pats, opts) for abs in ctx.walk(m): - fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs) + fp = cmdutil.makefileobj(repo, opts.get('output'), ctx.node(), + pathname=abs) data = ctx[abs].data() if opts.get('decode'): data = repo.wwritedata(abs, data) @@ -750,6 +949,16 @@ err = 0 return err +@command('^clone', + [('U', 'noupdate', None, + _('the clone will include an empty working copy (only a repository)')), + ('u', 'updaterev', '', _('revision, tag or branch to check out'), _('REV')), + ('r', 'rev', [], _('include the specified changeset'), _('REV')), + ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')), + ('', 'pull', None, _('use pull protocol to copy metadata')), + ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')), + ] + remoteopts, + _('[OPTION]... SOURCE [DEST]')) def clone(ui, source, dest=None, **opts): """make a copy of an existing repository @@ -826,6 +1035,13 @@ return r is None +@command('^commit|ci', + [('A', 'addremove', None, + _('mark new/missing files as added/removed before committing')), + ('', 'close-branch', None, + _('mark a branch as closed, hiding it from the branch list')), + ] + walkopts + commitopts + commitopts2, + _('[OPTION]... [FILE]...')) def commit(ui, repo, *pats, **opts): """commit the specified files or all outstanding changes @@ -868,7 +1084,12 @@ node = cmdutil.commit(ui, repo, commitfunc, pats, opts) if not node: - ui.status(_("nothing changed\n")) + stat = repo.status(match=scmutil.match(repo, pats, opts)) + if stat[3]: + ui.status(_("nothing changed (%d missing files, see 'hg status')\n") + % len(stat[3])) + else: + ui.status(_("nothing changed\n")) return 1 ctx = repo[node] @@ -916,6 +1137,11 @@ elif ui.verbose: ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx)) +@command('copy|cp', + [('A', 'after', None, _('record a copy that has already occurred')), + ('f', 'force', None, _('forcibly copy over an existing managed file')), + ] + walkopts + dryrunopts, + _('[OPTION]... [SOURCE]... DEST')) def copy(ui, repo, *pats, **opts): """mark files as copied for the next commit @@ -938,11 +1164,12 @@ finally: wlock.release() +@command('debugancestor', [], _('[INDEX] REV1 REV2')) def debugancestor(ui, repo, *args): """find the ancestor revision of two revisions in a given index""" if len(args) == 3: index, rev1, rev2 = args - r = revlog.revlog(util.opener(os.getcwd(), audit=False), index) + r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), index) lookup = r.lookup elif len(args) == 2: if not repo: @@ -956,12 +1183,19 @@ a = r.ancestor(lookup(rev1), lookup(rev2)) ui.write("%d:%s\n" % (r.rev(a), hex(a))) -def debugbuilddag(ui, repo, text, +@command('debugbuilddag', + [('m', 'mergeable-file', None, _('add single file mergeable changes')), + ('o', 'overwritten-file', None, _('add single file all revs overwrite')), + ('n', 'new-file', None, _('add new file at each rev'))], + _('[OPTION]... [TEXT]')) +def debugbuilddag(ui, repo, text=None, mergeable_file=False, - appended_file=False, overwritten_file=False, new_file=False): - """builds a repo with a given dag from scratch in the current empty repo + """builds a repo with a given DAG from scratch in the current empty repo + + The description of the DAG is read from stdin if not given on the + command line. Elements: @@ -975,8 +1209,6 @@ - "/p2" is a merge of the preceding node and p2 - ":tag" defines a local tag for the preceding node - "@branch" sets the named branch for subsequent nodes - - "!command" runs the command using your shell - - "!!my command\\n" is like "!", but to the end of the line - "#...\\n" is a comment up to the end of the line Whitespace between the above elements is ignored. @@ -990,138 +1222,165 @@ All string valued-elements are either strictly alphanumeric, or must be enclosed in double quotes ("..."), with "\\" as escape character. - - Note that the --overwritten-file and --appended-file options imply the - use of "HGMERGE=internal:local" during DAG buildup. """ - if not (mergeable_file or appended_file or overwritten_file or new_file): - raise util.Abort(_('need at least one of -m, -a, -o, -n')) - - if len(repo.changelog) > 0: + if text is None: + ui.status(_("reading DAG from stdin\n")) + text = sys.stdin.read() + + cl = repo.changelog + if len(cl) > 0: raise util.Abort(_('repository is not empty')) - if overwritten_file or appended_file: - # we don't want to fail in merges during buildup - os.environ['HGMERGE'] = 'internal:local' - - def writefile(fname, text, fmode="wb"): - f = open(fname, fmode) - try: - f.write(text) - finally: - f.close() + # determine number of revs in DAG + total = 0 + for type, data in dagparser.parsedag(text): + if type == 'n': + total += 1 if mergeable_file: linesperrev = 2 - # determine number of revs in DAG - n = 0 + # make a file with k lines per rev + initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)] + initialmergedlines.append("") + + tags = [] + + tr = repo.transaction("builddag") + try: + + at = -1 + atbranch = 'default' + nodeids = [] + ui.progress(_('building'), 0, unit=_('revisions'), total=total) for type, data in dagparser.parsedag(text): if type == 'n': - n += 1 - # make a file with k lines per rev - writefile("mf", "\n".join(str(i) for i in xrange(0, n * linesperrev)) - + "\n") - - at = -1 - atbranch = 'default' - for type, data in dagparser.parsedag(text): - if type == 'n': - ui.status('node %s\n' % str(data)) - id, ps = data - p1 = ps[0] - if p1 != at: - update(ui, repo, node=str(p1), clean=True) - at = p1 - if repo.dirstate.branch() != atbranch: - branch(ui, repo, atbranch, force=True) - if len(ps) > 1: - p2 = ps[1] - merge(ui, repo, node=p2) - - if mergeable_file: - f = open("mf", "rb+") - try: - lines = f.read().split("\n") - lines[id * linesperrev] += " r%i" % id - f.seek(0) - f.write("\n".join(lines)) - finally: - f.close() - - if appended_file: - writefile("af", "r%i\n" % id, "ab") - - if overwritten_file: - writefile("of", "r%i\n" % id) - - if new_file: - writefile("nf%i" % id, "r%i\n" % id) - - commit(ui, repo, addremove=True, message="r%i" % id, date=(id, 0)) - at = id - elif type == 'l': - id, name = data - ui.status('tag %s\n' % name) - tag(ui, repo, name, local=True) - elif type == 'a': - ui.status('branch %s\n' % data) - atbranch = data - elif type in 'cC': - r = util.system(data, cwd=repo.root) - if r: - desc, r = util.explain_exit(r) - raise util.Abort(_('%s command %s') % (data, desc)) - -def debugcommands(ui, cmd='', *args): - """list all available commands and options""" - for cmd, vals in sorted(table.iteritems()): - cmd = cmd.split('|')[0].strip('^') - opts = ', '.join([i[1] for i in vals[1]]) - ui.write('%s: %s\n' % (cmd, opts)) - -def debugcomplete(ui, cmd='', **opts): - """returns the completion list associated with the given command""" - - if opts.get('options'): - options = [] - otables = [globalopts] - if cmd: - aliases, entry = cmdutil.findcmd(cmd, table, False) - otables.append(entry[1]) - for t in otables: - for o in t: - if "(DEPRECATED)" in o[3]: - continue - if o[0]: - options.append('-%s' % o[0]) - options.append('--%s' % o[1]) - ui.write("%s\n" % "\n".join(options)) - return - - cmdlist = cmdutil.findpossible(cmd, table) - if ui.verbose: - cmdlist = [' '.join(c[0]) for c in cmdlist.values()] - ui.write("%s\n" % "\n".join(sorted(cmdlist))) - -def debugfsinfo(ui, path = "."): - """show information detected about current filesystem""" - open('.debugfsinfo', 'w').write('') - ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no')) - ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no')) - ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo') - and 'yes' or 'no')) - os.unlink('.debugfsinfo') - -def debugrebuildstate(ui, repo, rev="tip"): - """rebuild the dirstate as it would look like for the given revision""" - ctx = cmdutil.revsingle(repo, rev) - wlock = repo.wlock() + ui.note('node %s\n' % str(data)) + id, ps = data + + files = [] + fctxs = {} + + p2 = None + if mergeable_file: + fn = "mf" + p1 = repo[ps[0]] + if len(ps) > 1: + p2 = repo[ps[1]] + pa = p1.ancestor(p2) + base, local, other = [x[fn].data() for x in pa, p1, p2] + m3 = simplemerge.Merge3Text(base, local, other) + ml = [l.strip() for l in m3.merge_lines()] + ml.append("") + elif at > 0: + ml = p1[fn].data().split("\n") + else: + ml = initialmergedlines + ml[id * linesperrev] += " r%i" % id + mergedtext = "\n".join(ml) + files.append(fn) + fctxs[fn] = context.memfilectx(fn, mergedtext) + + if overwritten_file: + fn = "of" + files.append(fn) + fctxs[fn] = context.memfilectx(fn, "r%i\n" % id) + + if new_file: + fn = "nf%i" % id + files.append(fn) + fctxs[fn] = context.memfilectx(fn, "r%i\n" % id) + if len(ps) > 1: + if not p2: + p2 = repo[ps[1]] + for fn in p2: + if fn.startswith("nf"): + files.append(fn) + fctxs[fn] = p2[fn] + + def fctxfn(repo, cx, path): + return fctxs.get(path) + + if len(ps) == 0 or ps[0] < 0: + pars = [None, None] + elif len(ps) == 1: + pars = [nodeids[ps[0]], None] + else: + pars = [nodeids[p] for p in ps] + cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn, + date=(id, 0), + user="debugbuilddag", + extra={'branch': atbranch}) + nodeid = repo.commitctx(cx) + nodeids.append(nodeid) + at = id + elif type == 'l': + id, name = data + ui.note('tag %s\n' % name) + tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name)) + elif type == 'a': + ui.note('branch %s\n' % data) + atbranch = data + ui.progress(_('building'), id, unit=_('revisions'), total=total) + tr.close() + finally: + ui.progress(_('building'), None) + tr.release() + + if tags: + repo.opener.write("localtags", "".join(tags)) + +@command('debugbundle', [('a', 'all', None, _('show all details'))], _('FILE')) +def debugbundle(ui, bundlepath, all=None, **opts): + """lists the contents of a bundle""" + f = url.open(ui, bundlepath) try: - repo.dirstate.rebuild(ctx.node(), ctx.manifest()) + gen = changegroup.readbundle(f, bundlepath) + if all: + ui.write("format: id, p1, p2, cset, delta base, len(delta)\n") + + def showchunks(named): + ui.write("\n%s\n" % named) + chain = None + while 1: + chunkdata = gen.deltachunk(chain) + if not chunkdata: + break + node = chunkdata['node'] + p1 = chunkdata['p1'] + p2 = chunkdata['p2'] + cs = chunkdata['cs'] + deltabase = chunkdata['deltabase'] + delta = chunkdata['delta'] + ui.write("%s %s %s %s %s %s\n" % + (hex(node), hex(p1), hex(p2), + hex(cs), hex(deltabase), len(delta))) + chain = node + + chunkdata = gen.changelogheader() + showchunks("changelog") + chunkdata = gen.manifestheader() + showchunks("manifest") + while 1: + chunkdata = gen.filelogheader() + if not chunkdata: + break + fname = chunkdata['filename'] + showchunks(fname) + else: + chunkdata = gen.changelogheader() + chain = None + while 1: + chunkdata = gen.deltachunk(chain) + if not chunkdata: + break + node = chunkdata['node'] + ui.write("%s\n" % hex(node)) + chain = node finally: - wlock.release() - + f.close() + +@command('debugcheckstate', [], '') def debugcheckstate(ui, repo): """validate the correctness of the current dirstate""" parent1, parent2 = repo.dirstate.parents() @@ -1149,126 +1408,47 @@ error = _(".hg/dirstate inconsistent with current parent's manifest") raise util.Abort(error) -def showconfig(ui, repo, *values, **opts): - """show combined config settings from all hgrc files - - With no arguments, print names and values of all config items. - - With one argument of the form section.name, print just the value - of that config item. - - With multiple arguments, print names and values of all config - items with matching section names. - - With --debug, the source (filename and line number) is printed - for each config item. - - Returns 0 on success. - """ - - for f in util.rcpath(): - ui.debug(_('read config from: %s\n') % f) - untrusted = bool(opts.get('untrusted')) - if values: - sections = [v for v in values if '.' not in v] - items = [v for v in values if '.' in v] - if len(items) > 1 or items and sections: - raise util.Abort(_('only one config item permitted')) - for section, name, value in ui.walkconfig(untrusted=untrusted): - sectname = section + '.' + name - if values: - for v in values: - if v == section: - ui.debug('%s: ' % - ui.configsource(section, name, untrusted)) - ui.write('%s=%s\n' % (sectname, value)) - elif v == sectname: - ui.debug('%s: ' % - ui.configsource(section, name, untrusted)) - ui.write(value, '\n') - else: - ui.debug('%s: ' % - ui.configsource(section, name, untrusted)) - ui.write('%s=%s\n' % (sectname, value)) - -def debugpushkey(ui, repopath, namespace, *keyinfo): - '''access the pushkey key/value protocol - - With two args, list the keys in the given namespace. - - With five args, set a key to new if it currently is set to old. - Reports success or failure. - ''' - - target = hg.repository(ui, repopath) - if keyinfo: - key, old, new = keyinfo - r = target.pushkey(namespace, key, old, new) - ui.status(str(r) + '\n') - return not r - else: - for k, v in target.listkeys(namespace).iteritems(): - ui.write("%s\t%s\n" % (k.encode('string-escape'), - v.encode('string-escape'))) - -def debugrevspec(ui, repo, expr): - '''parse and apply a revision specification''' +@command('debugcommands', [], _('[COMMAND]')) +def debugcommands(ui, cmd='', *args): + """list all available commands and options""" + for cmd, vals in sorted(table.iteritems()): + cmd = cmd.split('|')[0].strip('^') + opts = ', '.join([i[1] for i in vals[1]]) + ui.write('%s: %s\n' % (cmd, opts)) + +@command('debugcomplete', + [('o', 'options', None, _('show the command options'))], + _('[-o] CMD')) +def debugcomplete(ui, cmd='', **opts): + """returns the completion list associated with the given command""" + + if opts.get('options'): + options = [] + otables = [globalopts] + if cmd: + aliases, entry = cmdutil.findcmd(cmd, table, False) + otables.append(entry[1]) + for t in otables: + for o in t: + if "(DEPRECATED)" in o[3]: + continue + if o[0]: + options.append('-%s' % o[0]) + options.append('--%s' % o[1]) + ui.write("%s\n" % "\n".join(options)) + return + + cmdlist = cmdutil.findpossible(cmd, table) if ui.verbose: - tree = revset.parse(expr) - ui.note(tree, "\n") - func = revset.match(expr) - for c in func(repo, range(len(repo))): - ui.write("%s\n" % c) - -def debugsetparents(ui, repo, rev1, rev2=None): - """manually set the parents of the current working directory - - This is useful for writing repository conversion tools, but should - be used with care. - - Returns 0 on success. - """ - - r1 = cmdutil.revsingle(repo, rev1).node() - r2 = cmdutil.revsingle(repo, rev2, 'null').node() - - wlock = repo.wlock() - try: - repo.dirstate.setparents(r1, r2) - finally: - wlock.release() - -def debugstate(ui, repo, nodates=None): - """show the contents of the current dirstate""" - timestr = "" - showdate = not nodates - for file_, ent in sorted(repo.dirstate._map.iteritems()): - if showdate: - if ent[3] == -1: - # Pad or slice to locale representation - locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", - time.localtime(0))) - timestr = 'unset' - timestr = (timestr[:locale_len] + - ' ' * (locale_len - len(timestr))) - else: - timestr = time.strftime("%Y-%m-%d %H:%M:%S ", - time.localtime(ent[3])) - if ent[1] & 020000: - mode = 'lnk' - else: - mode = '%3o' % (ent[1] & 0777) - ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_)) - for f in repo.dirstate.copies(): - ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f)) - -def debugsub(ui, repo, rev=None): - ctx = cmdutil.revsingle(repo, rev, None) - for k, v in sorted(ctx.substate.items()): - ui.write('path %s\n' % k) - ui.write(' source %s\n' % v[0]) - ui.write(' revision %s\n' % v[1]) - + cmdlist = [' '.join(c[0]) for c in cmdlist.values()] + ui.write("%s\n" % "\n".join(sorted(cmdlist))) + +@command('debugdag', + [('t', 'tags', None, _('use tags as labels')), + ('b', 'branches', None, _('annotate with branch names')), + ('', 'dots', None, _('use dots for runs')), + ('s', 'spaces', None, _('separate elements by spaces'))], + _('[OPTION]... [FILE [REV]...]')) def debugdag(ui, repo, file_=None, *revs, **opts): """format the changelog or an index DAG as a concise textual description @@ -1280,7 +1460,7 @@ spaces = opts.get('spaces') dots = opts.get('dots') if file_: - rlog = revlog.revlog(util.opener(os.getcwd(), audit=False), file_) + rlog = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_) revs = set((int(r) for r in revs)) def events(): for r in rlog: @@ -1322,20 +1502,25 @@ ui.write(line) ui.write("\n") -def debugdata(ui, repo, file_, rev): +@command('debugdata', + [('c', 'changelog', False, _('open changelog')), + ('m', 'manifest', False, _('open manifest'))], + _('-c|-m|FILE REV')) +def debugdata(ui, repo, file_, rev = None, **opts): """dump the contents of a data file revision""" - r = None - if repo: - filelog = repo.file(file_) - if len(filelog): - r = filelog - if not r: - r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i") + if opts.get('changelog') or opts.get('manifest'): + file_, rev = None, file_ + elif rev is None: + raise error.CommandError('debugdata', _('invalid arguments')) + r = cmdutil.openrevlog(repo, 'debugdata', file_, opts) try: ui.write(r.revision(r.lookup(rev))) except KeyError: raise util.Abort(_('invalid revision identifier %s') % rev) +@command('debugdate', + [('e', 'extended', None, _('try extended date formats'))], + _('[-e] DATE [RANGE]')) def debugdate(ui, date, range=None, **opts): """parse and display a date""" if opts["extended"]: @@ -1348,6 +1533,109 @@ m = util.matchdate(range) ui.write("match: %s\n" % m(d[0])) +@command('debugdiscovery', + [('', 'old', None, _('use old-style discovery')), + ('', 'nonheads', None, + _('use old-style discovery with non-heads included')), + ] + remoteopts, + _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]')) +def debugdiscovery(ui, repo, remoteurl="default", **opts): + """runs the changeset discovery protocol in isolation""" + remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl), opts.get('branch')) + remote = hg.repository(hg.remoteui(repo, opts), remoteurl) + ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl)) + + # make sure tests are repeatable + random.seed(12323) + + def doit(localheads, remoteheads): + if opts.get('old'): + if localheads: + raise util.Abort('cannot use localheads with old style discovery') + common, _in, hds = treediscovery.findcommonincoming(repo, remote, + force=True) + common = set(common) + if not opts.get('nonheads'): + ui.write("unpruned common: %s\n" % " ".join([short(n) + for n in common])) + dag = dagutil.revlogdag(repo.changelog) + all = dag.ancestorset(dag.internalizeall(common)) + common = dag.externalizeall(dag.headsetofconnecteds(all)) + else: + common, any, hds = setdiscovery.findcommonheads(ui, repo, remote) + common = set(common) + rheads = set(hds) + lheads = set(repo.heads()) + ui.write("common heads: %s\n" % " ".join([short(n) for n in common])) + if lheads <= common: + ui.write("local is subset\n") + elif rheads <= common: + ui.write("remote is subset\n") + + serverlogs = opts.get('serverlog') + if serverlogs: + for filename in serverlogs: + logfile = open(filename, 'r') + try: + line = logfile.readline() + while line: + parts = line.strip().split(';') + op = parts[1] + if op == 'cg': + pass + elif op == 'cgss': + doit(parts[2].split(' '), parts[3].split(' ')) + elif op == 'unb': + doit(parts[3].split(' '), parts[2].split(' ')) + line = logfile.readline() + finally: + logfile.close() + + else: + remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, + opts.get('remote_head')) + localrevs = opts.get('local_head') + doit(localrevs, remoterevs) + +@command('debugfsinfo', [], _('[PATH]')) +def debugfsinfo(ui, path = "."): + """show information detected about current filesystem""" + util.writefile('.debugfsinfo', '') + ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no')) + ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no')) + ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo') + and 'yes' or 'no')) + os.unlink('.debugfsinfo') + +@command('debuggetbundle', + [('H', 'head', [], _('id of head node'), _('ID')), + ('C', 'common', [], _('id of common node'), _('ID')), + ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))], + _('REPO FILE [-H|-C ID]...')) +def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts): + """retrieves a bundle from a repo + + Every ID must be a full-length hex node id string. Saves the bundle to the + given file. + """ + repo = hg.repository(ui, repopath) + if not repo.capable('getbundle'): + raise util.Abort("getbundle() not supported by target repository") + args = {} + if common: + args['common'] = [bin(s) for s in common] + if head: + args['heads'] = [bin(s) for s in head] + bundle = repo.getbundle('debug', **args) + + bundletype = opts.get('type', 'bzip2').lower() + btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'} + bundletype = btypes.get(bundletype) + if bundletype not in changegroup.bundletypes: + raise util.Abort(_('unknown bundle type specified with --type')) + changegroup.writebundle(bundle, bundlepath, bundletype) + +@command('debugignore', [], '') def debugignore(ui, repo, *values, **opts): """display the combined ignore pattern""" ignore = repo.dirstate._ignore @@ -1356,44 +1644,52 @@ else: raise util.Abort(_("no ignore patterns found")) -def debugindex(ui, repo, file_, **opts): +@command('debugindex', + [('c', 'changelog', False, _('open changelog')), + ('m', 'manifest', False, _('open manifest')), + ('f', 'format', 0, _('revlog format'), _('FORMAT'))], + _('[-f FORMAT] -c|-m|FILE')) +def debugindex(ui, repo, file_ = None, **opts): """dump the contents of an index file""" - r = None - if repo: - filelog = repo.file(file_) - if len(filelog): - r = filelog - + r = cmdutil.openrevlog(repo, 'debugindex', file_, opts) format = opts.get('format', 0) if format not in (0, 1): raise util.Abort(_("unknown format %d") % format) - if not r: - r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_) + generaldelta = r.version & revlog.REVLOGGENERALDELTA + if generaldelta: + basehdr = ' delta' + else: + basehdr = ' base' if format == 0: - ui.write(" rev offset length base linkrev" + ui.write(" rev offset length " + basehdr + " linkrev" " nodeid p1 p2\n") elif format == 1: ui.write(" rev flag offset length" - " size base link p1 p2 nodeid\n") + " size " + basehdr + " link p1 p2 nodeid\n") for i in r: node = r.node(i) + if generaldelta: + base = r.deltaparent(i) + else: + base = r.chainbase(i) if format == 0: try: pp = r.parents(node) except: pp = [nullid, nullid] ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % ( - i, r.start(i), r.length(i), r.base(i), r.linkrev(i), + i, r.start(i), r.length(i), base, r.linkrev(i), short(node), short(pp[0]), short(pp[1]))) elif format == 1: pr = r.parentrevs(i) ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % ( i, r.flags(i), r.start(i), r.length(i), r.rawsize(i), - r.base(i), r.linkrev(i), pr[0], pr[1], short(node))) - + base, r.linkrev(i), pr[0], pr[1], short(node))) + +@command('debugindexdot', [], _('FILE')) def debugindexdot(ui, repo, file_): """dump an index DAG as a graphviz dot file""" r = None @@ -1402,7 +1698,7 @@ if len(filelog): r = filelog if not r: - r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_) + r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False), file_) ui.write("digraph G {\n") for i in r: node = r.node(i) @@ -1412,6 +1708,7 @@ ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i)) ui.write("}\n") +@command('debuginstall', [], '') def debuginstall(ui): '''test Mercurial installation @@ -1457,49 +1754,10 @@ ui.write(_(" (templates seem to have been installed incorrectly)\n")) problems += 1 - # patch - ui.status(_("Checking patch...\n")) - patchproblems = 0 - a = "1\n2\n3\n4\n" - b = "1\n2\n3\ninsert\n4\n" - fa = writetemp(a) - d = mdiff.unidiff(a, None, b, None, os.path.basename(fa), - os.path.basename(fa)) - fd = writetemp(d) - - files = {} - try: - patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files) - except util.Abort, e: - ui.write(_(" patch call failed:\n")) - ui.write(" " + str(e) + "\n") - patchproblems += 1 - else: - if list(files) != [os.path.basename(fa)]: - ui.write(_(" unexpected patch output!\n")) - patchproblems += 1 - a = open(fa).read() - if a != b: - ui.write(_(" patch test failed!\n")) - patchproblems += 1 - - if patchproblems: - if ui.config('ui', 'patch'): - ui.write(_(" (Current patch tool may be incompatible with patch," - " or misconfigured. Please check your configuration" - " file)\n")) - else: - ui.write(_(" Internal patcher failure, please report this error" - " to http://mercurial.selenic.com/wiki/BugTracker\n")) - problems += patchproblems - - os.unlink(fa) - os.unlink(fd) - # editor ui.status(_("Checking commit editor...\n")) editor = ui.geteditor() - cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0]) + cmdpath = util.findexe(editor) or util.findexe(editor.split()[0]) if not cmdpath: if editor == 'vi': ui.write(_(" No commit editor set and can't find vi in PATH\n")) @@ -1528,11 +1786,60 @@ return problems +@command('debugknown', [], _('REPO ID...')) +def debugknown(ui, repopath, *ids, **opts): + """test whether node ids are known to a repo + + Every ID must be a full-length hex node id string. Returns a list of 0s and 1s + indicating unknown/known. + """ + repo = hg.repository(ui, repopath) + if not repo.capable('known'): + raise util.Abort("known() not supported by target repository") + flags = repo.known([bin(s) for s in ids]) + ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags]))) + +@command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]')) +def debugpushkey(ui, repopath, namespace, *keyinfo): + '''access the pushkey key/value protocol + + With two args, list the keys in the given namespace. + + With five args, set a key to new if it currently is set to old. + Reports success or failure. + ''' + + target = hg.repository(ui, repopath) + if keyinfo: + key, old, new = keyinfo + r = target.pushkey(namespace, key, old, new) + ui.status(str(r) + '\n') + return not r + else: + for k, v in target.listkeys(namespace).iteritems(): + ui.write("%s\t%s\n" % (k.encode('string-escape'), + v.encode('string-escape'))) + +@command('debugrebuildstate', + [('r', 'rev', '', _('revision to rebuild to'), _('REV'))], + _('[-r REV] [REV]')) +def debugrebuildstate(ui, repo, rev="tip"): + """rebuild the dirstate as it would look like for the given revision""" + ctx = scmutil.revsingle(repo, rev) + wlock = repo.wlock() + try: + repo.dirstate.rebuild(ctx.node(), ctx.manifest()) + finally: + wlock.release() + +@command('debugrename', + [('r', 'rev', '', _('revision to debug'), _('REV'))], + _('[-r REV] FILE')) def debugrename(ui, repo, file1, *pats, **opts): """dump rename information""" - ctx = cmdutil.revsingle(repo, opts.get('rev')) - m = cmdutil.match(repo, (file1,) + pats, opts) + ctx = scmutil.revsingle(repo, opts.get('rev')) + m = scmutil.match(repo, (file1,) + pats, opts) for abs in ctx.walk(m): fctx = ctx[abs] o = fctx.filelog().renamed(fctx.filenode()) @@ -1542,9 +1849,244 @@ else: ui.write(_("%s not renamed\n") % rel) +@command('debugrevlog', + [('c', 'changelog', False, _('open changelog')), + ('m', 'manifest', False, _('open manifest')), + ('d', 'dump', False, _('dump index data'))], + _('-c|-m|FILE')) +def debugrevlog(ui, repo, file_ = None, **opts): + """show data and statistics about a revlog""" + r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts) + + if opts.get("dump"): + numrevs = len(r) + ui.write("# rev p1rev p2rev start end deltastart base p1 p2" + " rawsize totalsize compression heads\n") + ts = 0 + heads = set() + for rev in xrange(numrevs): + dbase = r.base(rev) + cbase = r.chainbase(rev) + p1, p2 = r.parentrevs(rev) + rs = r.rawsize(rev) + ts = ts + rs + heads -= set(r.parentrevs(rev)) + heads.add(rev) + ui.write("%d %d %d %d %d %d %d %d %d %d %d %d %d\n" % + (rev, p1, p2, r.start(rev), r.end(rev), + r.start(dbase), r.start(cbase), + r.start(p1), r.start(p2), + rs, ts, ts / r.end(rev), len(heads))) + return 0 + + v = r.version + format = v & 0xFFFF + flags = [] + gdelta = False + if v & revlog.REVLOGNGINLINEDATA: + flags.append('inline') + if v & revlog.REVLOGGENERALDELTA: + gdelta = True + flags.append('generaldelta') + if not flags: + flags = ['(none)'] + + nummerges = 0 + numfull = 0 + numprev = 0 + nump1 = 0 + nump2 = 0 + numother = 0 + nump1prev = 0 + nump2prev = 0 + chainlengths = [] + + datasize = [None, 0, 0L] + fullsize = [None, 0, 0L] + deltasize = [None, 0, 0L] + + def addsize(size, l): + if l[0] is None or size < l[0]: + l[0] = size + if size > l[1]: + l[1] = size + l[2] += size + + numrevs = len(r) + for rev in xrange(numrevs): + p1, p2 = r.parentrevs(rev) + delta = r.deltaparent(rev) + if format > 0: + addsize(r.rawsize(rev), datasize) + if p2 != nullrev: + nummerges += 1 + size = r.length(rev) + if delta == nullrev: + chainlengths.append(0) + numfull += 1 + addsize(size, fullsize) + else: + chainlengths.append(chainlengths[delta] + 1) + addsize(size, deltasize) + if delta == rev - 1: + numprev += 1 + if delta == p1: + nump1prev += 1 + elif delta == p2: + nump2prev += 1 + elif delta == p1: + nump1 += 1 + elif delta == p2: + nump2 += 1 + elif delta != nullrev: + numother += 1 + + numdeltas = numrevs - numfull + numoprev = numprev - nump1prev - nump2prev + totalrawsize = datasize[2] + datasize[2] /= numrevs + fulltotal = fullsize[2] + fullsize[2] /= numfull + deltatotal = deltasize[2] + deltasize[2] /= numrevs - numfull + totalsize = fulltotal + deltatotal + avgchainlen = sum(chainlengths) / numrevs + compratio = totalrawsize / totalsize + + basedfmtstr = '%%%dd\n' + basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n' + + def dfmtstr(max): + return basedfmtstr % len(str(max)) + def pcfmtstr(max, padding=0): + return basepcfmtstr % (len(str(max)), ' ' * padding) + + def pcfmt(value, total): + return (value, 100 * float(value) / total) + + ui.write('format : %d\n' % format) + ui.write('flags : %s\n' % ', '.join(flags)) + + ui.write('\n') + fmt = pcfmtstr(totalsize) + fmt2 = dfmtstr(totalsize) + ui.write('revisions : ' + fmt2 % numrevs) + ui.write(' merges : ' + fmt % pcfmt(nummerges, numrevs)) + ui.write(' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs)) + ui.write('revisions : ' + fmt2 % numrevs) + ui.write(' full : ' + fmt % pcfmt(numfull, numrevs)) + ui.write(' deltas : ' + fmt % pcfmt(numdeltas, numrevs)) + ui.write('revision size : ' + fmt2 % totalsize) + ui.write(' full : ' + fmt % pcfmt(fulltotal, totalsize)) + ui.write(' deltas : ' + fmt % pcfmt(deltatotal, totalsize)) + + ui.write('\n') + fmt = dfmtstr(max(avgchainlen, compratio)) + ui.write('avg chain length : ' + fmt % avgchainlen) + ui.write('compression ratio : ' + fmt % compratio) + + if format > 0: + ui.write('\n') + ui.write('uncompressed data size (min/max/avg) : %d / %d / %d\n' + % tuple(datasize)) + ui.write('full revision size (min/max/avg) : %d / %d / %d\n' + % tuple(fullsize)) + ui.write('delta size (min/max/avg) : %d / %d / %d\n' + % tuple(deltasize)) + + if numdeltas > 0: + ui.write('\n') + fmt = pcfmtstr(numdeltas) + fmt2 = pcfmtstr(numdeltas, 4) + ui.write('deltas against prev : ' + fmt % pcfmt(numprev, numdeltas)) + if numprev > 0: + ui.write(' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev)) + ui.write(' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev)) + ui.write(' other : ' + fmt2 % pcfmt(numoprev, numprev)) + if gdelta: + ui.write('deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas)) + ui.write('deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas)) + ui.write('deltas against other : ' + fmt % pcfmt(numother, numdeltas)) + +@command('debugrevspec', [], ('REVSPEC')) +def debugrevspec(ui, repo, expr): + '''parse and apply a revision specification''' + if ui.verbose: + tree = revset.parse(expr)[0] + ui.note(tree, "\n") + newtree = revset.findaliases(ui, tree) + if newtree != tree: + ui.note(newtree, "\n") + func = revset.match(ui, expr) + for c in func(repo, range(len(repo))): + ui.write("%s\n" % c) + +@command('debugsetparents', [], _('REV1 [REV2]')) +def debugsetparents(ui, repo, rev1, rev2=None): + """manually set the parents of the current working directory + + This is useful for writing repository conversion tools, but should + be used with care. + + Returns 0 on success. + """ + + r1 = scmutil.revsingle(repo, rev1).node() + r2 = scmutil.revsingle(repo, rev2, 'null').node() + + wlock = repo.wlock() + try: + repo.dirstate.setparents(r1, r2) + finally: + wlock.release() + +@command('debugstate', + [('', 'nodates', None, _('do not display the saved mtime')), + ('', 'datesort', None, _('sort by saved mtime'))], + _('[OPTION]...')) +def debugstate(ui, repo, nodates=None, datesort=None): + """show the contents of the current dirstate""" + timestr = "" + showdate = not nodates + if datesort: + keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename + else: + keyfunc = None # sort by filename + for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc): + if showdate: + if ent[3] == -1: + # Pad or slice to locale representation + locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", + time.localtime(0))) + timestr = 'unset' + timestr = (timestr[:locale_len] + + ' ' * (locale_len - len(timestr))) + else: + timestr = time.strftime("%Y-%m-%d %H:%M:%S ", + time.localtime(ent[3])) + if ent[1] & 020000: + mode = 'lnk' + else: + mode = '%3o' % (ent[1] & 0777) + ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_)) + for f in repo.dirstate.copies(): + ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f)) + +@command('debugsub', + [('r', 'rev', '', + _('revision to check'), _('REV'))], + _('[-r REV] [REV]')) +def debugsub(ui, repo, rev=None): + ctx = scmutil.revsingle(repo, rev, None) + for k, v in sorted(ctx.substate.items()): + ui.write('path %s\n' % k) + ui.write(' source %s\n' % v[0]) + ui.write(' revision %s\n' % v[1]) + +@command('debugwalk', walkopts, _('[OPTION]... [FILE]...')) def debugwalk(ui, repo, *pats, **opts): """show how files match on given patterns""" - m = cmdutil.match(repo, pats, opts) + m = scmutil.match(repo, pats, opts) items = list(repo.walk(m)) if not items: return @@ -1555,6 +2097,32 @@ line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '') ui.write("%s\n" % line.rstrip()) +@command('debugwireargs', + [('', 'three', '', 'three'), + ('', 'four', '', 'four'), + ('', 'five', '', 'five'), + ] + remoteopts, + _('REPO [OPTIONS]... [ONE [TWO]]')) +def debugwireargs(ui, repopath, *vals, **opts): + repo = hg.repository(hg.remoteui(ui, opts), repopath) + for opt in remoteopts: + del opts[opt[1]] + args = {} + for k, v in opts.iteritems(): + if v: + args[k] = v + # run twice to check that we don't mess up the stream for the next command + res1 = repo.debugwireargs(*vals, **args) + res2 = repo.debugwireargs(*vals, **args) + ui.write("%s\n" % res1) + if res1 != res2: + ui.warn("%s\n" % res2) + +@command('^diff', + [('r', 'rev', [], _('revision'), _('REV')), + ('c', 'change', '', _('change made by revision'), _('REV')) + ] + diffopts + diffopts2 + walkopts + subrepoopts, + _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')) def diff(ui, repo, *pats, **opts): """diff repository (or selected files) @@ -1595,19 +2163,26 @@ msg = _('cannot specify --rev and --change at the same time') raise util.Abort(msg) elif change: - node2 = repo.lookup(change) - node1 = repo[node2].parents()[0].node() + node2 = scmutil.revsingle(repo, change, None).node() + node1 = repo[node2].p1().node() else: - node1, node2 = cmdutil.revpair(repo, revs) + node1, node2 = scmutil.revpair(repo, revs) if reverse: node1, node2 = node2, node1 diffopts = patch.diffopts(ui, opts) - m = cmdutil.match(repo, pats, opts) + m = scmutil.match(repo, pats, opts) cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat, listsubrepos=opts.get('subrepos')) +@command('^export', + [('o', 'output', '', + _('print output to file with formatted name'), _('FORMAT')), + ('', 'switch-parent', None, _('diff against the second parent')), + ('r', 'rev', [], _('revisions to export'), _('REV')), + ] + diffopts, + _('[OPTION]... [-o OUTFILESPEC] REV...')) def export(ui, repo, *changesets, **opts): """dump the header and diffs for one or more changesets @@ -1649,7 +2224,7 @@ changesets += tuple(opts.get('rev', [])) if not changesets: raise util.Abort(_("export requires at least one changeset")) - revs = cmdutil.revrange(repo, changesets) + revs = scmutil.revrange(repo, changesets) if len(revs) > 1: ui.note(_('exporting patches:\n')) else: @@ -1658,6 +2233,7 @@ switch_parent=opts.get('switch_parent'), opts=patch.diffopts(ui, opts)) +@command('^forget', walkopts, _('[OPTION]... FILE...')) def forget(ui, repo, *pats, **opts): """forget the specified files on the next commit @@ -1676,7 +2252,7 @@ if not pats: raise util.Abort(_('no files specified')) - m = cmdutil.match(repo, pats, opts) + m = scmutil.match(repo, pats, opts) s = repo.status(match=m, clean=True) forget = sorted(s[0] + s[1] + s[3] + s[6]) errs = 0 @@ -1694,6 +2270,23 @@ repo[None].remove(forget, unlink=False) return errs +@command('grep', + [('0', 'print0', None, _('end fields with NUL')), + ('', 'all', None, _('print all revisions that match')), + ('a', 'text', None, _('treat all files as text')), + ('f', 'follow', None, + _('follow changeset history,' + ' or file history across copies and renames')), + ('i', 'ignore-case', None, _('ignore case when matching')), + ('l', 'files-with-matches', None, + _('print only filenames and revisions that match')), + ('n', 'line-number', None, _('print matching line numbers')), + ('r', 'rev', [], + _('only search files changed within revision range'), _('REV')), + ('u', 'user', None, _('list the author (long with -v)')), + ('d', 'date', None, _('list the date (short with -q)')), + ] + walkopts, + _('[OPTION]... PATTERN [FILE]...')) def grep(ui, repo, pattern, *pats, **opts): """search for a pattern in specified files and revisions @@ -1782,6 +2375,10 @@ datefunc = ui.quiet and util.shortdate or util.datestr found = False filerevmatches = {} + def binary(): + flog = getfile(fn) + return util.binary(flog.read(ctx.filenode(fn))) + if opts.get('all'): iter = difflinestates(pstates, states) else: @@ -1808,22 +2405,25 @@ after = l.line[l.colend:] ui.write(sep.join(cols)) if before is not None: - ui.write(sep + before) - ui.write(match, label='grep.match') - ui.write(after) + if not opts.get('text') and binary(): + ui.write(sep + " Binary file matches") + else: + ui.write(sep + before) + ui.write(match, label='grep.match') + ui.write(after) ui.write(eol) found = True return found skip = {} revfiles = {} - matchfn = cmdutil.match(repo, pats, opts) + matchfn = scmutil.match(repo, pats, opts) found = False follow = opts.get('follow') def prep(ctx, fns): rev = ctx.rev() - pctx = ctx.parents()[0] + pctx = ctx.p1() parent = pctx.rev() matches.setdefault(rev, {}) matches.setdefault(parent, {}) @@ -1858,7 +2458,7 @@ for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep): rev = ctx.rev() - parent = ctx.parents()[0].rev() + parent = ctx.p1().rev() for fn in sorted(revfiles.get(rev, [])): states = matches[rev][fn] copy = copies.get(rev, {}).get(fn) @@ -1879,6 +2479,14 @@ return not found +@command('heads', + [('r', 'rev', '', + _('show only heads which are descendants of STARTREV'), _('STARTREV')), + ('t', 'topo', False, _('show topological heads only')), + ('a', 'active', False, _('show active branchheads only (DEPRECATED)')), + ('c', 'closed', False, _('show normal and closed branch heads')), + ] + templateopts, + _('[-ac] [-r STARTREV] [REV]...')) def heads(ui, repo, *branchrevs, **opts): """show current repository heads or show branch heads @@ -1906,7 +2514,7 @@ start = None if 'rev' in opts: - start = cmdutil.revsingle(repo, opts['rev'], None).node() + start = scmutil.revsingle(repo, opts['rev'], None).node() if opts.get('topo'): heads = [repo[h] for h in repo.heads(start)] @@ -1951,7 +2559,11 @@ displayer.show(ctx) displayer.close() -def help_(ui, name=None, with_version=False, unknowncmd=False): +@command('help', + [('e', 'extension', None, _('show only help for extensions')), + ('c', 'command', None, _('show only help for commands'))], + _('[-ec] [TOPIC]')) +def help_(ui, name=None, with_version=False, unknowncmd=False, full=True, **opts): """show help for a given topic or a help overview With no arguments, print a list of commands with short help messages. @@ -1962,7 +2574,7 @@ Returns 0 if successful. """ option_lists = [] - textwidth = ui.termwidth() - 2 + textwidth = min(ui.termwidth(), 80) - 2 def addglobalopts(aliases): if ui.verbose: @@ -1974,6 +2586,8 @@ if name == 'shortlist': msg = _('use "hg help" for the full list of commands ' 'or "hg -v" for details') + elif name and not full: + msg = _('use "hg help %s" to show the full help text' % name) elif aliases: msg = _('use "hg -v help%s" to show builtin aliases and ' 'global options') % (name and " " + name or "") @@ -2012,7 +2626,7 @@ ui.write('hg %s\n' % aliases[0]) # aliases - if not ui.quiet and len(aliases) > 1: + if full and not ui.quiet and len(aliases) > 1: ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:])) # description @@ -2024,7 +2638,7 @@ doc = _('shell alias for::\n\n %s') % entry[0].definition[1:] else: doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc) - if ui.quiet: + if ui.quiet or not full: doc = doc.splitlines()[0] keep = ui.verbose and ['verbose'] or [] formatted, pruned = minirst.format(doc, textwidth, keep=keep) @@ -2039,6 +2653,18 @@ addglobalopts(False) + # check if this command shadows a non-trivial (multi-line) + # extension help text + try: + mod = extensions.find(name) + doc = gettext(mod.__doc__) or '' + if '\n' in doc.strip(): + msg = _('use "hg help -e %s" to show help for ' + 'the %s extension') % (name, name) + ui.write('\n%s\n' % msg) + except KeyError: + pass + def helplist(header, select=None): h = {} cmds = {} @@ -2097,6 +2723,12 @@ ui.write("%s\n\n" % header) ui.write("%s\n" % minirst.format(doc, textwidth, indent=4)) + try: + cmdutil.findcmd(name, table) + ui.write(_('\nuse "hg help -c %s" to see help for ' + 'the %s command\n') % (name, name)) + except error.UnknownCommand: + pass def helpext(name): try: @@ -2133,19 +2765,20 @@ doc = gettext(mod.__doc__).splitlines()[0] msg = help.listexts(_("'%s' is provided by the following " - "extension:") % cmd, {ext: doc}, len(ext), - indent=4) + "extension:") % cmd, {ext: doc}, indent=4) ui.write(minirst.format(msg, textwidth)) ui.write('\n\n') ui.write(_('use "hg help extensions" for information on enabling ' 'extensions\n')) - help.addtopichook('revsets', revset.makedoc) - if name and name != 'shortlist': i = None if unknowncmd: queries = (helpextcmd,) + elif opts.get('extension'): + queries = (helpext,) + elif opts.get('command'): + queries = (helpcmd,) else: queries = (helptopic, helpcmd, helpext, helpextcmd) for f in queries: @@ -2174,8 +2807,7 @@ helplist(header) if name != 'shortlist': - exts, maxlength = extensions.enabled() - text = help.listexts(_('enabled extensions:'), exts, maxlength) + text = help.listexts(_('enabled extensions:'), extensions.enabled()) if text: ui.write("\n%s\n" % minirst.format(text, textwidth)) @@ -2240,6 +2872,15 @@ else: ui.write("%s\n" % opt) +@command('identify|id', + [('r', 'rev', '', + _('identify the specified revision'), _('REV')), + ('n', 'num', None, _('show local revision number')), + ('i', 'id', None, _('show global revision id')), + ('b', 'branch', None, _('show branch')), + ('t', 'tags', None, _('show tags')), + ('B', 'bookmarks', None, _('show bookmarks'))], + _('[-nibtB] [-r REV] [SOURCE]')) def identify(ui, repo, source=None, rev=None, num=None, id=None, branch=None, tags=None, bookmarks=None): """identify the working copy or specified revision @@ -2265,67 +2906,103 @@ hexfunc = ui.debugflag and hex or short default = not (num or id or branch or tags or bookmarks) output = [] - revs = [] + if source: source, branches = hg.parseurl(ui.expandpath(source)) repo = hg.repository(ui, source) revs, checkout = hg.addbranchrevs(repo, repo, branches, None) if not repo.local(): + if num or branch or tags: + raise util.Abort( + _("can't query remote revision number, branch, or tags")) if not rev and revs: rev = revs[0] if not rev: rev = "tip" - if num or branch or tags or bookmarks: - raise util.Abort(_("can't query remote revision number," - " branch, tags, or bookmarks")) - output = [hexfunc(repo.lookup(rev))] - elif not rev: - ctx = repo[None] - parents = ctx.parents() - changed = False - if default or id or num: - changed = util.any(repo.status()) - if default or id: - output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]), - (changed) and "+" or "")] - if num: - output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]), - (changed) and "+" or "")) - else: - ctx = cmdutil.revsingle(repo, rev) + + remoterev = repo.lookup(rev) if default or id: - output = [hexfunc(ctx.node())] - if num: - output.append(str(ctx.rev())) - - if repo.local() and default and not ui.quiet: - b = ctx.branch() - if b != 'default': - output.append("(%s)" % b) - - # multiple tags for a single parent separated by '/' - t = "/".join(ctx.tags()) - if t: - output.append(t) - - # multiple bookmarks for a single parent separated by '/' - bm = '/'.join(ctx.bookmarks()) - if bm: - output.append(bm) - - if branch: - output.append(ctx.branch()) - - if tags: - output.extend(ctx.tags()) - - if bookmarks: - output.extend(ctx.bookmarks()) + output = [hexfunc(remoterev)] + + def getbms(): + bms = [] + + if 'bookmarks' in repo.listkeys('namespaces'): + hexremoterev = hex(remoterev) + bms = [bm for bm, bmr in repo.listkeys('bookmarks').iteritems() + if bmr == hexremoterev] + + return bms + + if bookmarks: + output.extend(getbms()) + elif default and not ui.quiet: + # multiple bookmarks for a single parent separated by '/' + bm = '/'.join(getbms()) + if bm: + output.append(bm) + else: + if not rev: + ctx = repo[None] + parents = ctx.parents() + changed = "" + if default or id or num: + changed = util.any(repo.status()) and "+" or "" + if default or id: + output = ["%s%s" % + ('+'.join([hexfunc(p.node()) for p in parents]), changed)] + if num: + output.append("%s%s" % + ('+'.join([str(p.rev()) for p in parents]), changed)) + else: + ctx = scmutil.revsingle(repo, rev) + if default or id: + output = [hexfunc(ctx.node())] + if num: + output.append(str(ctx.rev())) + + if default and not ui.quiet: + b = ctx.branch() + if b != 'default': + output.append("(%s)" % b) + + # multiple tags for a single parent separated by '/' + t = '/'.join(ctx.tags()) + if t: + output.append(t) + + # multiple bookmarks for a single parent separated by '/' + bm = '/'.join(ctx.bookmarks()) + if bm: + output.append(bm) + else: + if branch: + output.append(ctx.branch()) + + if tags: + output.extend(ctx.tags()) + + if bookmarks: + output.extend(ctx.bookmarks()) ui.write("%s\n" % ' '.join(output)) +@command('import|patch', + [('p', 'strip', 1, + _('directory strip option for patch. This has the same ' + 'meaning as the corresponding patch option'), _('NUM')), + ('b', 'base', '', _('base path'), _('PATH')), + ('f', 'force', None, _('skip check for outstanding uncommitted changes')), + ('', 'no-commit', None, + _("don't commit, just update the working directory")), + ('', 'exact', None, + _('apply patch to the nodes from which it was generated')), + ('', 'import-branch', None, + _('use any branch information in patch (implied by --exact)'))] + + commitopts + commitopts2 + similarityopts, + _('[OPTION]... PATCH...')) def import_(ui, repo, patch1, *patches, **opts): """import an ordered set of patches @@ -2376,7 +3053,7 @@ raise util.Abort(_('similarity must be between 0 and 100')) if opts.get('exact') or not opts.get('force'): - cmdutil.bail_if_changed(repo) + cmdutil.bailifchanged(repo) d = opts["base"] strip = opts["strip"] @@ -2426,12 +3103,9 @@ repo.dirstate.setbranch(branch or 'default') files = {} - try: - patch.patch(tmpname, ui, strip=strip, cwd=repo.root, - files=files, eolmode=None) - finally: - files = cmdutil.updatedir(ui, repo, files, - similarity=sim / 100.0) + patch.patch(ui, repo, tmpname, strip=strip, cwd=repo.root, + files=files, eolmode=None, similarity=sim / 100.0) + files = list(files) if opts.get('no_commit'): if message: msgs.append(message) @@ -2439,7 +3113,7 @@ if opts.get('exact'): m = None else: - m = cmdutil.matchfiles(repo, files or []) + m = scmutil.matchfiles(repo, files or []) n = repo.commit(message, opts.get('user') or user, opts.get('date') or date, match=m, editor=cmdutil.commiteditor) @@ -2485,10 +3159,22 @@ raise util.Abort(_('no diffs found')) if msgs: - repo.opener('last-message.txt', 'wb').write('\n* * *\n'.join(msgs)) + repo.opener.write('last-message.txt', '\n* * *\n'.join(msgs)) finally: release(lock, wlock) +@command('incoming|in', + [('f', 'force', None, + _('run even if remote repository is unrelated')), + ('n', 'newest-first', None, _('show newest record first')), + ('', 'bundle', '', + _('file to store the bundles into'), _('FILE')), + ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')), + ('B', 'bookmarks', False, _("compare bookmarks")), + ('b', 'branch', [], + _('a specific branch you would like to pull'), _('BRANCH')), + ] + logopts + remoteopts + subrepoopts, + _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]')) def incoming(ui, repo, source="default", **opts): """show new changesets found in source @@ -2513,12 +3199,13 @@ if 'bookmarks' not in other.listkeys('namespaces'): ui.warn(_("remote doesn't support bookmarks\n")) return 0 - ui.status(_('comparing with %s\n') % url.hidepassword(source)) + ui.status(_('comparing with %s\n') % util.hidepassword(source)) return bookmarks.diff(ui, repo, other) ret = hg.incoming(ui, repo, source, opts) return ret +@command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]')) def init(ui, dest=".", **opts): """create a new repository in the given directory @@ -2534,6 +3221,12 @@ """ hg.repository(hg.remoteui(ui, opts), ui.expandpath(dest), create=1) +@command('locate', + [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')), + ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), + ('f', 'fullpath', None, _('print complete paths from the filesystem root')), + ] + walkopts, + _('[OPTION]... [PATTERN]...')) def locate(ui, repo, *pats, **opts): """locate files matching specific patterns @@ -2555,10 +3248,10 @@ Returns 0 if a match is found, 1 otherwise. """ end = opts.get('print0') and '\0' or '\n' - rev = cmdutil.revsingle(repo, opts.get('rev'), None).node() + rev = scmutil.revsingle(repo, opts.get('rev'), None).node() ret = 1 - m = cmdutil.match(repo, pats, opts, default='relglob') + m = scmutil.match(repo, pats, opts, default='relglob') m.bad = lambda x, y: False for abs in repo[rev].walk(m): if not rev and abs not in repo.dirstate: @@ -2571,6 +3264,28 @@ return ret +@command('^log|history', + [('f', 'follow', None, + _('follow changeset history, or file history across copies and renames')), + ('', 'follow-first', None, + _('only follow the first parent of merge changesets')), + ('d', 'date', '', _('show revisions matching date spec'), _('DATE')), + ('C', 'copies', None, _('show copied files')), + ('k', 'keyword', [], + _('do case-insensitive search for a given text'), _('TEXT')), + ('r', 'rev', [], _('show the specified revision or range'), _('REV')), + ('', 'removed', None, _('include revisions where files were removed')), + ('m', 'only-merges', None, _('show only merges')), + ('u', 'user', [], _('revisions committed by user'), _('USER')), + ('', 'only-branch', [], + _('show only changesets within the given named branch (DEPRECATED)'), + _('BRANCH')), + ('b', 'branch', [], + _('show changesets within the given named branch'), _('BRANCH')), + ('P', 'prune', [], + _('do not display revision or any of its ancestors'), _('REV')), + ] + logopts + walkopts, + _('[OPTION]... [FILE]')) def log(ui, repo, *pats, **opts): """show revision history of entire repository or files @@ -2604,13 +3319,13 @@ Returns 0 on success. """ - matchfn = cmdutil.match(repo, pats, opts) + matchfn = scmutil.match(repo, pats, opts) limit = cmdutil.loglimit(opts) count = 0 endrev = None if opts.get('copies') and opts.get('rev'): - endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1 + endrev = max(scmutil.revrange(repo, opts.get('rev'))) + 1 df = False if opts["date"]: @@ -2657,7 +3372,7 @@ if opts.get('patch') or opts.get('stat'): if opts.get('follow') or opts.get('follow_first'): # note: this might be wrong when following through merges - revmatchfn = cmdutil.match(repo, fns, default='path') + revmatchfn = scmutil.match(repo, fns, default='path') else: revmatchfn = matchfn @@ -2670,6 +3385,9 @@ count += 1 displayer.close() +@command('manifest', + [('r', 'rev', '', _('revision to display'), _('REV'))], + _('[-r REV]')) def manifest(ui, repo, node=None, rev=None): """output the current or given revision of the project manifest @@ -2690,7 +3408,7 @@ node = rev decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '} - ctx = cmdutil.revsingle(repo, node) + ctx = scmutil.revsingle(repo, node) for f in ctx: if ui.debugflag: ui.write("%40s " % hex(ctx.manifest()[f])) @@ -2698,6 +3416,13 @@ ui.write(decor[ctx.flags(f)]) ui.write("%s\n" % f) +@command('^merge', + [('f', 'force', None, _('force a merge with outstanding changes')), + ('t', 'tool', '', _('specify merge tool')), + ('r', 'rev', '', _('revision to merge'), _('REV')), + ('P', 'preview', None, + _('review revisions to merge (no merge is performed)'))], + _('[-P] [-f] [[-r] REV]')) def merge(ui, repo, node=None, **opts): """merge working directory with another revision @@ -2711,7 +3436,7 @@ ``--tool`` can be used to specify the merge tool used for file merges. It overrides the HGMERGE environment variable and your - configuration files. + configuration files. See :hg:`help merge-tools` for options. If no revision is specified, the working directory's parent is a head revision, and the current branch contains exactly one other @@ -2736,31 +3461,30 @@ branch = repo[None].branch() bheads = repo.branchheads(branch) if len(bheads) > 2: - raise util.Abort(_( - 'branch \'%s\' has %d heads - ' - 'please merge with an explicit rev\n' - '(run \'hg heads .\' to see heads)') - % (branch, len(bheads))) - - parent = repo.dirstate.parents()[0] + raise util.Abort(_("branch '%s' has %d heads - " + "please merge with an explicit rev") + % (branch, len(bheads)), + hint=_("run 'hg heads .' to see heads")) + + parent = repo.dirstate.p1() if len(bheads) == 1: if len(repo.heads()) > 1: - raise util.Abort(_( - 'branch \'%s\' has one head - ' - 'please merge with an explicit rev\n' - '(run \'hg heads\' to see all heads)') - % branch) + raise util.Abort(_("branch '%s' has one head - " + "please merge with an explicit rev") + % branch, + hint=_("run 'hg heads' to see all heads")) msg = _('there is nothing to merge') if parent != repo.lookup(repo[None].branch()): msg = _('%s - use "hg update" instead') % msg raise util.Abort(msg) if parent not in bheads: - raise util.Abort(_('working dir not at a head rev - ' - 'use "hg update" or merge with an explicit rev')) + raise util.Abort(_('working directory not at a head revision'), + hint=_("use 'hg update' or merge with an " + "explicit revision")) node = parent == bheads[0] and bheads[-1] or bheads[0] else: - node = cmdutil.revsingle(repo, node).node() + node = scmutil.revsingle(repo, node).node() if opts.get('preview'): # find nodes that are ancestors of p2 but not of p1 @@ -2781,6 +3505,16 @@ finally: ui.setconfig('ui', 'forcemerge', '') +@command('outgoing|out', + [('f', 'force', None, _('run even when the destination is unrelated')), + ('r', 'rev', [], + _('a changeset intended to be included in the destination'), _('REV')), + ('n', 'newest-first', None, _('show newest record first')), + ('B', 'bookmarks', False, _('compare bookmarks')), + ('b', 'branch', [], _('a specific branch you would like to push'), + _('BRANCH')), + ] + logopts + remoteopts + subrepoopts, + _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')) def outgoing(ui, repo, dest=None, **opts): """show changesets not found in the destination @@ -2800,12 +3534,16 @@ if 'bookmarks' not in other.listkeys('namespaces'): ui.warn(_("remote doesn't support bookmarks\n")) return 0 - ui.status(_('comparing with %s\n') % url.hidepassword(dest)) + ui.status(_('comparing with %s\n') % util.hidepassword(dest)) return bookmarks.diff(ui, other, repo) ret = hg.outgoing(ui, repo, dest, opts) return ret +@command('parents', + [('r', 'rev', '', _('show parents of the specified revision'), _('REV')), + ] + templateopts, + _('[-r REV] [FILE]')) def parents(ui, repo, file_=None, **opts): """show the parents of the working directory or revision @@ -2818,10 +3556,10 @@ Returns 0 on success. """ - ctx = cmdutil.revsingle(repo, opts.get('rev'), None) + ctx = scmutil.revsingle(repo, opts.get('rev'), None) if file_: - m = cmdutil.match(repo, (file_,), opts) + m = scmutil.match(repo, (file_,), opts) if m.anypats() or len(m.files()) != 1: raise util.Abort(_('can only specify an explicit filename')) file_ = m.files()[0] @@ -2846,12 +3584,16 @@ displayer.show(repo[n]) displayer.close() +@command('paths', [], _('[NAME]')) def paths(ui, repo, search=None): """show aliases for remote repositories Show definition of symbolic path name NAME. If no name is given, show definition of all available names. + Option -q/--quiet suppresses all output when searching for NAME + and shows only the path names when listing all definitions. + Path names are defined in the [paths] section of your configuration file and in ``/etc/mercurial/hgrc``. If run inside a repository, ``.hg/hgrc`` is used, too. @@ -2874,13 +3616,17 @@ if search: for name, path in ui.configitems("paths"): if name == search: - ui.write("%s\n" % url.hidepassword(path)) + ui.status("%s\n" % util.hidepassword(path)) return - ui.warn(_("not found!\n")) + if not ui.quiet: + ui.warn(_("not found!\n")) return 1 else: for name, path in ui.configitems("paths"): - ui.write("%s = %s\n" % (name, url.hidepassword(path))) + if ui.quiet: + ui.write("%s\n" % name) + else: + ui.write("%s = %s\n" % (name, util.hidepassword(path))) def postincoming(ui, repo, modheads, optupdate, checkout): if modheads == 0: @@ -2891,10 +3637,26 @@ else: ui.status(_("not updating, since new heads added\n")) if modheads > 1: - ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n")) + currentbranchheads = len(repo.branchheads()) + if currentbranchheads == modheads: + ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n")) + elif currentbranchheads > 1: + ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to merge)\n")) + else: + ui.status(_("(run 'hg heads' to see heads)\n")) else: ui.status(_("(run 'hg update' to get a working copy)\n")) +@command('^pull', + [('u', 'update', None, + _('update to new branch head if changesets were pulled')), + ('f', 'force', None, _('run even when remote repository is unrelated')), + ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')), + ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')), + ('b', 'branch', [], _('a specific branch you would like to pull'), + _('BRANCH')), + ] + remoteopts, + _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')) def pull(ui, repo, source="default", **opts): """pull changes from the specified source @@ -2917,7 +3679,7 @@ """ source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch')) other = hg.repository(hg.remoteui(repo, opts), source) - ui.status(_('pulling from %s\n') % url.hidepassword(source)) + ui.status(_('pulling from %s\n') % util.hidepassword(source)) revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev')) if opts.get('bookmark'): @@ -2938,6 +3700,7 @@ raise util.Abort(err) modheads = repo.pull(other, heads=revs, force=opts.get('force')) + bookmarks.updatefromremote(ui, repo, other) if checkout: checkout = str(repo.changelog.rev(other.lookup(checkout))) repo._subtoppath = source @@ -2957,6 +3720,17 @@ return ret +@command('^push', + [('f', 'force', None, _('force push')), + ('r', 'rev', [], + _('a changeset intended to be included in the destination'), + _('REV')), + ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')), + ('b', 'branch', [], + _('a specific branch you would like to push'), _('BRANCH')), + ('', 'new-branch', False, _('allow pushing a new branch')), + ] + remoteopts, + _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')) def push(ui, repo, dest=None, **opts): """push changes to the specified destination @@ -2999,7 +3773,7 @@ dest = ui.expandpath(dest or 'default-push', dest or 'default') dest, branches = hg.parseurl(dest, opts.get('branch')) - ui.status(_('pushing to %s\n') % url.hidepassword(dest)) + ui.status(_('pushing to %s\n') % util.hidepassword(dest)) revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev')) other = hg.repository(hg.remoteui(repo, opts), dest) if revs: @@ -3043,6 +3817,7 @@ return result +@command('recover', []) def recover(ui, repo): """roll back an interrupted transaction @@ -3058,6 +3833,12 @@ return hg.verify(repo) return 1 +@command('^remove|rm', + [('A', 'after', None, _('record delete for missing files')), + ('f', 'force', None, + _('remove (and delete) file even if added or modified')), + ] + walkopts, + _('[OPTION]... FILE...')) def remove(ui, repo, *pats, **opts): """remove the specified files on the next commit @@ -3092,7 +3873,7 @@ if not pats and not after: raise util.Abort(_('no files specified')) - m = cmdutil.match(repo, pats, opts) + m = scmutil.match(repo, pats, opts) s = repo.status(match=m, clean=True) modified, added, deleted, clean = s[0], s[1], s[3], s[6] @@ -3128,6 +3909,11 @@ repo[None].remove(remove, unlink=not after) return ret +@command('rename|move|mv', + [('A', 'after', None, _('record a rename that has already occurred')), + ('f', 'force', None, _('forcibly copy over an existing managed file')), + ] + walkopts + dryrunopts, + _('[OPTION]... SOURCE... DEST')) def rename(ui, repo, *pats, **opts): """rename files; equivalent of copy + remove @@ -3150,6 +3936,15 @@ finally: wlock.release() +@command('resolve', + [('a', 'all', None, _('select all unresolved files')), + ('l', 'list', None, _('list state of files needing merge')), + ('m', 'mark', None, _('mark files as resolved')), + ('u', 'unmark', None, _('mark files as unresolved')), + ('t', 'tool', '', _('specify merge tool')), + ('n', 'no-status', None, _('hide status prefix'))] + + walkopts, + _('[OPTION]... [FILE]...')) def resolve(ui, repo, *pats, **opts): """redo merges or set/view the merge status of files @@ -3198,7 +3993,7 @@ 'use --all to remerge all files')) ms = mergemod.mergestate(repo) - m = cmdutil.match(repo, pats, opts) + m = scmutil.match(repo, pats, opts) ret = 0 for f in ms: @@ -3236,6 +4031,13 @@ ms.commit() return ret +@command('revert', + [('a', 'all', None, _('revert all changes when no arguments given')), + ('d', 'date', '', _('tipmost revision matching date'), _('DATE')), + ('r', 'rev', '', _('revert to the specified revision'), _('REV')), + ('', 'no-backup', None, _('do not save backup copies of files')), + ] + walkopts + dryrunopts, + _('[OPTION]... [-r REV] [NAME]...')) def revert(ui, repo, *pats, **opts): """restore individual files or directories to an earlier state @@ -3265,8 +4067,9 @@ directory, the reverted files will thus appear modified afterwards. - If a file has been deleted, it is restored. If the executable mode - of a file was changed, it is reset. + If a file has been deleted, it is restored. Files scheduled for + addition are just unscheduled and left as they are. If the + executable mode of a file was changed, it is reset. If names are given, all files matching the names are reverted. If no arguments are given, no files are reverted. @@ -3291,7 +4094,7 @@ raise util.Abort(_('no files or directories specified; ' 'use --all to revert the whole repo')) - ctx = cmdutil.revsingle(repo, opts.get('rev')) + ctx = scmutil.revsingle(repo, opts.get('rev')) node = ctx.node() mf = ctx.manifest() if node == parent: @@ -3309,7 +4112,7 @@ try: # walk dirstate. - m = cmdutil.match(repo, pats, opts) + m = scmutil.match(repo, pats, opts) m.bad = lambda x, y: False for abs in repo.walk(m): names[abs] = m.rel(abs), m.exact(abs) @@ -3325,13 +4128,13 @@ return ui.warn("%s: %s\n" % (m.rel(path), msg)) - m = cmdutil.match(repo, pats, opts) + m = scmutil.match(repo, pats, opts) m.bad = badfn for abs in repo[node].walk(m): if abs not in names: names[abs] = m.rel(abs), m.exact(abs) - m = cmdutil.matchfiles(repo, names) + m = scmutil.matchfiles(repo, names) changes = repo.status(match=m)[:4] modified, added, removed, deleted = map(set, changes) @@ -3423,7 +4226,7 @@ fc = ctx[f] repo.wwrite(f, fc.data(), fc.flags()) - audit_path = util.path_auditor(repo.root) + audit_path = scmutil.pathauditor(repo.root) for f in remove[0]: if repo.dirstate[f] == 'a': repo.dirstate.forget(f) @@ -3463,6 +4266,7 @@ finally: wlock.release() +@command('rollback', dryrunopts) def rollback(ui, repo, **opts): """roll back the last transaction (dangerous) @@ -3494,6 +4298,7 @@ """ return repo.rollback(opts.get('dry_run')) +@command('root', []) def root(ui, repo): """print the root (top) of the current working directory @@ -3503,6 +4308,31 @@ """ ui.write(repo.root + "\n") +@command('^serve', + [('A', 'accesslog', '', _('name of access log file to write to'), + _('FILE')), + ('d', 'daemon', None, _('run server in background')), + ('', 'daemon-pipefds', '', _('used internally by daemon mode'), _('NUM')), + ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')), + # use string type, then we can check if something was passed + ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')), + ('a', 'address', '', _('address to listen on (default: all interfaces)'), + _('ADDR')), + ('', 'prefix', '', _('prefix path to serve from (default: server root)'), + _('PREFIX')), + ('n', 'name', '', + _('name to show in web pages (default: working directory)'), _('NAME')), + ('', 'web-conf', '', + _('name of the hgweb config file (see "hg help hgweb")'), _('FILE')), + ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'), + _('FILE')), + ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')), + ('', 'stdio', None, _('for remote clients')), + ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')), + ('', 'style', '', _('template style to use'), _('STYLE')), + ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')), + ('', 'certificate', '', _('SSL certificate file'), _('FILE'))], + _('[OPTION]...')) def serve(ui, repo, **opts): """start stand-alone webserver @@ -3561,7 +4391,7 @@ class service(object): def init(self): - util.set_signal_handler() + util.setsignalhandler() self.httpd = hgweb.server.create_server(ui, app) if opts['port'] and not ui.verbose: @@ -3599,6 +4429,68 @@ cmdutil.service(opts, initfn=service.init, runfn=service.run) +@command('showconfig|debugconfig', + [('u', 'untrusted', None, _('show untrusted configuration options'))], + _('[-u] [NAME]...')) +def showconfig(ui, repo, *values, **opts): + """show combined config settings from all hgrc files + + With no arguments, print names and values of all config items. + + With one argument of the form section.name, print just the value + of that config item. + + With multiple arguments, print names and values of all config + items with matching section names. + + With --debug, the source (filename and line number) is printed + for each config item. + + Returns 0 on success. + """ + + for f in scmutil.rcpath(): + ui.debug(_('read config from: %s\n') % f) + untrusted = bool(opts.get('untrusted')) + if values: + sections = [v for v in values if '.' not in v] + items = [v for v in values if '.' in v] + if len(items) > 1 or items and sections: + raise util.Abort(_('only one config item permitted')) + for section, name, value in ui.walkconfig(untrusted=untrusted): + value = str(value).replace('\n', '\\n') + sectname = section + '.' + name + if values: + for v in values: + if v == section: + ui.debug('%s: ' % + ui.configsource(section, name, untrusted)) + ui.write('%s=%s\n' % (sectname, value)) + elif v == sectname: + ui.debug('%s: ' % + ui.configsource(section, name, untrusted)) + ui.write(value, '\n') + else: + ui.debug('%s: ' % + ui.configsource(section, name, untrusted)) + ui.write('%s=%s\n' % (sectname, value)) + +@command('^status|st', + [('A', 'all', None, _('show status of all files')), + ('m', 'modified', None, _('show only modified files')), + ('a', 'added', None, _('show only added files')), + ('r', 'removed', None, _('show only removed files')), + ('d', 'deleted', None, _('show only deleted (but tracked) files')), + ('c', 'clean', None, _('show only files without changes')), + ('u', 'unknown', None, _('show only unknown (not tracked) files')), + ('i', 'ignored', None, _('show only ignored files')), + ('n', 'no-status', None, _('hide status prefix')), + ('C', 'copies', None, _('show source of copied files')), + ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), + ('', 'rev', [], _('show difference from revision'), _('REV')), + ('', 'change', '', _('list the changed files of a revision'), _('REV')), + ] + walkopts + subrepoopts, + _('[OPTION]... [FILE]...')) def status(ui, repo, *pats, **opts): """show changed files in the working directory @@ -3645,9 +4537,9 @@ raise util.Abort(msg) elif change: node2 = repo.lookup(change) - node1 = repo[node2].parents()[0].node() + node1 = repo[node2].p1().node() else: - node1, node2 = cmdutil.revpair(repo, revs) + node1, node2 = scmutil.revpair(repo, revs) cwd = (pats and repo.getcwd()) or '' end = opts.get('print0') and '\0' or '\n' @@ -3659,7 +4551,7 @@ if not show: show = ui.quiet and states[:4] or states[:5] - stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts), + stat = repo.status(node1, node2, scmutil.match(repo, pats, opts), 'ignored' in show, 'clean' in show, 'unknown' in show, opts.get('subrepos')) changestates = zip(states, 'MAR!?IC', stat) @@ -3691,6 +4583,8 @@ ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end), label='status.copied') +@command('^summary|sum', + [('', 'remote', None, _('check for push and pull'))], '[--remote]') def summary(ui, repo, **opts): """summarize working directory state @@ -3817,21 +4711,25 @@ source, branches = hg.parseurl(ui.expandpath('default')) other = hg.repository(hg.remoteui(repo, {}), source) revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev')) - ui.debug('comparing with %s\n' % url.hidepassword(source)) + ui.debug('comparing with %s\n' % util.hidepassword(source)) repo.ui.pushbuffer() - common, incoming, rheads = discovery.findcommonincoming(repo, other) + commoninc = discovery.findcommonincoming(repo, other) + _common, incoming, _rheads = commoninc repo.ui.popbuffer() if incoming: t.append(_('1 or more incoming')) dest, branches = hg.parseurl(ui.expandpath('default-push', 'default')) revs, checkout = hg.addbranchrevs(repo, repo, branches, None) - other = hg.repository(hg.remoteui(repo, {}), dest) - ui.debug('comparing with %s\n' % url.hidepassword(dest)) + if source != dest: + other = hg.repository(hg.remoteui(repo, {}), dest) + commoninc = None + ui.debug('comparing with %s\n' % util.hidepassword(dest)) repo.ui.pushbuffer() - o = discovery.findoutgoing(repo, other) + common, outheads = discovery.findcommonoutgoing(repo, other, + commoninc=commoninc) repo.ui.popbuffer() - o = repo.changelog.nodesbetween(o, None)[0] + o = repo.changelog.findmissing(common=common, heads=outheads) if o: t.append(_('%d outgoing') % len(o)) if 'bookmarks' in other.listkeys('namespaces'): @@ -3849,6 +4747,16 @@ else: ui.status(_('remote: (synced)\n')) +@command('tag', + [('f', 'force', None, _('force tag')), + ('l', 'local', None, _('make the tag local')), + ('r', 'rev', '', _('revision to tag'), _('REV')), + ('', 'remove', None, _('remove a tag')), + # -l/--local is already there, commitopts cannot be used + ('e', 'edit', None, _('edit commit message')), + ('m', 'message', '', _('use <text> as commit message'), _('TEXT')), + ] + commitopts2, + _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')) def tag(ui, repo, name1, *names, **opts): """add one or more tags for the current or given revision @@ -3888,7 +4796,7 @@ raise util.Abort(_('tag names must be unique')) for n in names: if n in ['tip', '.', 'null']: - raise util.Abort(_('the name \'%s\' is reserved') % n) + raise util.Abort(_("the name '%s' is reserved") % n) if not n: raise util.Abort(_('tag names cannot consist entirely of whitespace')) if opts.get('rev') and opts.get('remove'): @@ -3900,12 +4808,12 @@ expectedtype = opts.get('local') and 'local' or 'global' for n in names: if not repo.tagtype(n): - raise util.Abort(_('tag \'%s\' does not exist') % n) + raise util.Abort(_("tag '%s' does not exist") % n) if repo.tagtype(n) != expectedtype: if expectedtype == 'global': - raise util.Abort(_('tag \'%s\' is not a global tag') % n) + raise util.Abort(_("tag '%s' is not a global tag") % n) else: - raise util.Abort(_('tag \'%s\' is not a local tag') % n) + raise util.Abort(_("tag '%s' is not a local tag") % n) rev_ = nullid if not message: # we don't translate commit messages @@ -3913,8 +4821,8 @@ elif not opts.get('force'): for n in names: if n in repo.tags(): - raise util.Abort(_('tag \'%s\' already exists ' - '(use -f to force)') % n) + raise util.Abort(_("tag '%s' already exists " + "(use -f to force)") % n) if not opts.get('local'): p1, p2 = repo.dirstate.parents() if p2 != nullid: @@ -3922,7 +4830,7 @@ bheads = repo.branchheads() if not opts.get('force') and bheads and p1 not in bheads: raise util.Abort(_('not at a branch head (use -f to force)')) - r = cmdutil.revsingle(repo, rev_).node() + r = scmutil.revsingle(repo, rev_).node() if not message: # we don't translate commit messages @@ -3938,6 +4846,7 @@ repo.tag(names, r, message, opts.get('local'), opts.get('user'), date) +@command('tags', [], '') def tags(ui, repo): """list repository tags @@ -3955,20 +4864,22 @@ ui.write("%s\n" % t) continue - try: - hn = hexfunc(n) - r = "%5d:%s" % (repo.changelog.rev(n), hn) - except error.LookupError: - r = " ?:%s" % hn - else: - spaces = " " * (30 - encoding.colwidth(t)) - if ui.verbose: - if repo.tagtype(t) == 'local': - tagtype = " local" - else: - tagtype = "" - ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype)) - + hn = hexfunc(n) + r = "%5d:%s" % (repo.changelog.rev(n), hn) + spaces = " " * (30 - encoding.colwidth(t)) + + if ui.verbose: + if repo.tagtype(t) == 'local': + tagtype = " local" + else: + tagtype = "" + ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype)) + +@command('tip', + [('p', 'patch', None, _('show patch')), + ('g', 'git', None, _('use git extended diff format')), + ] + templateopts, + _('[-p] [-g]')) def tip(ui, repo, **opts): """show the tip revision @@ -3987,6 +4898,10 @@ displayer.show(repo[len(repo) - 1]) displayer.close() +@command('unbundle', + [('u', 'update', None, + _('update to new branch head if changesets were unbundled'))], + _('[-u] FILE...')) def unbundle(ui, repo, fname1, *fnames, **opts): """apply one or more changegroup files @@ -3998,17 +4913,25 @@ fnames = (fname1,) + fnames lock = repo.lock() + wc = repo['.'] try: for fname in fnames: f = url.open(ui, fname) gen = changegroup.readbundle(f, fname) modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname, lock=lock) + bookmarks.updatecurrentbookmark(repo, wc.node(), wc.branch()) finally: lock.release() - return postincoming(ui, repo, modheads, opts.get('update'), None) +@command('^update|up|checkout|co', + [('C', 'clean', None, _('discard uncommitted changes (no backup)')), + ('c', 'check', None, + _('update across branches if no uncommitted changes')), + ('d', 'date', '', _('tipmost revision matching date'), _('DATE')), + ('r', 'rev', '', _('revision'), _('REV'))], + _('[-c] [-C] [-d DATE] [[-r] REV]')) def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False): """update working directory (or switch revisions) @@ -4053,12 +4976,12 @@ if rev and node: raise util.Abort(_("please specify just one revision")) - if not rev: + if rev is None or rev == '': rev = node # if we defined a bookmark, we have to remember the original bookmark name brev = rev - rev = cmdutil.revsingle(repo, rev, rev).rev() + rev = scmutil.revsingle(repo, rev, rev).rev() if check and clean: raise util.Abort(_("cannot specify both -c/--check and -C/--clean")) @@ -4070,7 +4993,7 @@ raise util.Abort(_("uncommitted local changes")) if date: - if rev: + if rev is not None: raise util.Abort(_("you can't specify a revision and a date")) rev = cmdutil.finddate(ui, repo, date) @@ -4084,6 +5007,7 @@ return ret +@command('verify', []) def verify(ui, repo): """verify the integrity of the repository @@ -4098,6 +5022,7 @@ """ return hg.verify(repo) +@command('version', []) def version_(ui): """output version and copyright information""" ui.write(_("Mercurial Distributed SCM (version %s)\n") @@ -4110,639 +5035,8 @@ "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" )) -# Command options and aliases are listed here, alphabetically - -globalopts = [ - ('R', 'repository', '', - _('repository root directory or name of overlay bundle file'), - _('REPO')), - ('', 'cwd', '', - _('change working directory'), _('DIR')), - ('y', 'noninteractive', None, - _('do not prompt, assume \'yes\' for any required answers')), - ('q', 'quiet', None, _('suppress output')), - ('v', 'verbose', None, _('enable additional output')), - ('', 'config', [], - _('set/override config option (use \'section.name=value\')'), - _('CONFIG')), - ('', 'debug', None, _('enable debugging output')), - ('', 'debugger', None, _('start debugger')), - ('', 'encoding', encoding.encoding, _('set the charset encoding'), - _('ENCODE')), - ('', 'encodingmode', encoding.encodingmode, - _('set the charset encoding mode'), _('MODE')), - ('', 'traceback', None, _('always print a traceback on exception')), - ('', 'time', None, _('time how long the command takes')), - ('', 'profile', None, _('print command execution profile')), - ('', 'version', None, _('output version information and exit')), - ('h', 'help', None, _('display help and exit')), -] - -dryrunopts = [('n', 'dry-run', None, - _('do not perform actions, just print output'))] - -remoteopts = [ - ('e', 'ssh', '', - _('specify ssh command to use'), _('CMD')), - ('', 'remotecmd', '', - _('specify hg command to run on the remote side'), _('CMD')), - ('', 'insecure', None, - _('do not verify server certificate (ignoring web.cacerts config)')), -] - -walkopts = [ - ('I', 'include', [], - _('include names matching the given patterns'), _('PATTERN')), - ('X', 'exclude', [], - _('exclude names matching the given patterns'), _('PATTERN')), -] - -commitopts = [ - ('m', 'message', '', - _('use text as commit message'), _('TEXT')), - ('l', 'logfile', '', - _('read commit message from file'), _('FILE')), -] - -commitopts2 = [ - ('d', 'date', '', - _('record the specified date as commit date'), _('DATE')), - ('u', 'user', '', - _('record the specified user as committer'), _('USER')), -] - -templateopts = [ - ('', 'style', '', - _('display using template map file'), _('STYLE')), - ('', 'template', '', - _('display with template'), _('TEMPLATE')), -] - -logopts = [ - ('p', 'patch', None, _('show patch')), - ('g', 'git', None, _('use git extended diff format')), - ('l', 'limit', '', - _('limit number of changes displayed'), _('NUM')), - ('M', 'no-merges', None, _('do not show merges')), - ('', 'stat', None, _('output diffstat-style summary of changes')), -] + templateopts - -diffopts = [ - ('a', 'text', None, _('treat all files as text')), - ('g', 'git', None, _('use git extended diff format')), - ('', 'nodates', None, _('omit dates from diff headers')) -] - -diffopts2 = [ - ('p', 'show-function', None, _('show which function each change is in')), - ('', 'reverse', None, _('produce a diff that undoes the changes')), - ('w', 'ignore-all-space', None, - _('ignore white space when comparing lines')), - ('b', 'ignore-space-change', None, - _('ignore changes in the amount of white space')), - ('B', 'ignore-blank-lines', None, - _('ignore changes whose lines are all blank')), - ('U', 'unified', '', - _('number of lines of context to show'), _('NUM')), - ('', 'stat', None, _('output diffstat-style summary of changes')), -] - -similarityopts = [ - ('s', 'similarity', '', - _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY')) -] - -subrepoopts = [ - ('S', 'subrepos', None, - _('recurse into subrepositories')) -] - -table = { - "^add": (add, walkopts + subrepoopts + dryrunopts, - _('[OPTION]... [FILE]...')), - "addremove": - (addremove, similarityopts + walkopts + dryrunopts, - _('[OPTION]... [FILE]...')), - "^annotate|blame": - (annotate, - [('r', 'rev', '', - _('annotate the specified revision'), _('REV')), - ('', 'follow', None, - _('follow copies/renames and list the filename (DEPRECATED)')), - ('', 'no-follow', None, _("don't follow copies and renames")), - ('a', 'text', None, _('treat all files as text')), - ('u', 'user', None, _('list the author (long with -v)')), - ('f', 'file', None, _('list the filename')), - ('d', 'date', None, _('list the date (short with -q)')), - ('n', 'number', None, _('list the revision number (default)')), - ('c', 'changeset', None, _('list the changeset')), - ('l', 'line-number', None, - _('show line number at the first appearance')) - ] + walkopts, - _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')), - "archive": - (archive, - [('', 'no-decode', None, _('do not pass files through decoders')), - ('p', 'prefix', '', - _('directory prefix for files in archive'), _('PREFIX')), - ('r', 'rev', '', - _('revision to distribute'), _('REV')), - ('t', 'type', '', - _('type of distribution to create'), _('TYPE')), - ] + subrepoopts + walkopts, - _('[OPTION]... DEST')), - "backout": - (backout, - [('', 'merge', None, - _('merge with old dirstate parent after backout')), - ('', 'parent', '', - _('parent to choose when backing out merge'), _('REV')), - ('t', 'tool', '', - _('specify merge tool')), - ('r', 'rev', '', - _('revision to backout'), _('REV')), - ] + walkopts + commitopts + commitopts2, - _('[OPTION]... [-r] REV')), - "bisect": - (bisect, - [('r', 'reset', False, _('reset bisect state')), - ('g', 'good', False, _('mark changeset good')), - ('b', 'bad', False, _('mark changeset bad')), - ('s', 'skip', False, _('skip testing changeset')), - ('c', 'command', '', - _('use command to check changeset state'), _('CMD')), - ('U', 'noupdate', False, _('do not update to target'))], - _("[-gbsr] [-U] [-c CMD] [REV]")), - "bookmarks": - (bookmark, - [('f', 'force', False, _('force')), - ('r', 'rev', '', _('revision'), _('REV')), - ('d', 'delete', False, _('delete a given bookmark')), - ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))], - _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')), - "branch": - (branch, - [('f', 'force', None, - _('set branch name even if it shadows an existing branch')), - ('C', 'clean', None, _('reset branch name to parent branch name'))], - _('[-fC] [NAME]')), - "branches": - (branches, - [('a', 'active', False, - _('show only branches that have unmerged heads')), - ('c', 'closed', False, - _('show normal and closed branches'))], - _('[-ac]')), - "bundle": - (bundle, - [('f', 'force', None, - _('run even when the destination is unrelated')), - ('r', 'rev', [], - _('a changeset intended to be added to the destination'), - _('REV')), - ('b', 'branch', [], - _('a specific branch you would like to bundle'), - _('BRANCH')), - ('', 'base', [], - _('a base changeset assumed to be available at the destination'), - _('REV')), - ('a', 'all', None, _('bundle all changesets in the repository')), - ('t', 'type', 'bzip2', - _('bundle compression type to use'), _('TYPE')), - ] + remoteopts, - _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')), - "cat": - (cat, - [('o', 'output', '', - _('print output to file with formatted name'), _('FORMAT')), - ('r', 'rev', '', - _('print the given revision'), _('REV')), - ('', 'decode', None, _('apply any matching decode filter')), - ] + walkopts, - _('[OPTION]... FILE...')), - "^clone": - (clone, - [('U', 'noupdate', None, - _('the clone will include an empty working copy (only a repository)')), - ('u', 'updaterev', '', - _('revision, tag or branch to check out'), _('REV')), - ('r', 'rev', [], - _('include the specified changeset'), _('REV')), - ('b', 'branch', [], - _('clone only the specified branch'), _('BRANCH')), - ('', 'pull', None, _('use pull protocol to copy metadata')), - ('', 'uncompressed', None, - _('use uncompressed transfer (fast over LAN)')), - ] + remoteopts, - _('[OPTION]... SOURCE [DEST]')), - "^commit|ci": - (commit, - [('A', 'addremove', None, - _('mark new/missing files as added/removed before committing')), - ('', 'close-branch', None, - _('mark a branch as closed, hiding it from the branch list')), - ] + walkopts + commitopts + commitopts2, - _('[OPTION]... [FILE]...')), - "copy|cp": - (copy, - [('A', 'after', None, _('record a copy that has already occurred')), - ('f', 'force', None, - _('forcibly copy over an existing managed file')), - ] + walkopts + dryrunopts, - _('[OPTION]... [SOURCE]... DEST')), - "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')), - "debugbuilddag": - (debugbuilddag, - [('m', 'mergeable-file', None, _('add single file mergeable changes')), - ('a', 'appended-file', None, _('add single file all revs append to')), - ('o', 'overwritten-file', None, _('add single file all revs overwrite')), - ('n', 'new-file', None, _('add new file at each rev')), - ], - _('[OPTION]... TEXT')), - "debugcheckstate": (debugcheckstate, [], ''), - "debugcommands": (debugcommands, [], _('[COMMAND]')), - "debugcomplete": - (debugcomplete, - [('o', 'options', None, _('show the command options'))], - _('[-o] CMD')), - "debugdag": - (debugdag, - [('t', 'tags', None, _('use tags as labels')), - ('b', 'branches', None, _('annotate with branch names')), - ('', 'dots', None, _('use dots for runs')), - ('s', 'spaces', None, _('separate elements by spaces')), - ], - _('[OPTION]... [FILE [REV]...]')), - "debugdate": - (debugdate, - [('e', 'extended', None, _('try extended date formats'))], - _('[-e] DATE [RANGE]')), - "debugdata": (debugdata, [], _('FILE REV')), - "debugfsinfo": (debugfsinfo, [], _('[PATH]')), - "debugignore": (debugignore, [], ''), - "debugindex": (debugindex, - [('f', 'format', 0, _('revlog format'), _('FORMAT'))], - _('FILE')), - "debugindexdot": (debugindexdot, [], _('FILE')), - "debuginstall": (debuginstall, [], ''), - "debugpushkey": (debugpushkey, [], _('REPO NAMESPACE [KEY OLD NEW]')), - "debugrebuildstate": - (debugrebuildstate, - [('r', 'rev', '', - _('revision to rebuild to'), _('REV'))], - _('[-r REV] [REV]')), - "debugrename": - (debugrename, - [('r', 'rev', '', - _('revision to debug'), _('REV'))], - _('[-r REV] FILE')), - "debugrevspec": - (debugrevspec, [], ('REVSPEC')), - "debugsetparents": - (debugsetparents, [], _('REV1 [REV2]')), - "debugstate": - (debugstate, - [('', 'nodates', None, _('do not display the saved mtime'))], - _('[OPTION]...')), - "debugsub": - (debugsub, - [('r', 'rev', '', - _('revision to check'), _('REV'))], - _('[-r REV] [REV]')), - "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')), - "^diff": - (diff, - [('r', 'rev', [], - _('revision'), _('REV')), - ('c', 'change', '', - _('change made by revision'), _('REV')) - ] + diffopts + diffopts2 + walkopts + subrepoopts, - _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')), - "^export": - (export, - [('o', 'output', '', - _('print output to file with formatted name'), _('FORMAT')), - ('', 'switch-parent', None, _('diff against the second parent')), - ('r', 'rev', [], - _('revisions to export'), _('REV')), - ] + diffopts, - _('[OPTION]... [-o OUTFILESPEC] REV...')), - "^forget": - (forget, - [] + walkopts, - _('[OPTION]... FILE...')), - "grep": - (grep, - [('0', 'print0', None, _('end fields with NUL')), - ('', 'all', None, _('print all revisions that match')), - ('f', 'follow', None, - _('follow changeset history,' - ' or file history across copies and renames')), - ('i', 'ignore-case', None, _('ignore case when matching')), - ('l', 'files-with-matches', None, - _('print only filenames and revisions that match')), - ('n', 'line-number', None, _('print matching line numbers')), - ('r', 'rev', [], - _('only search files changed within revision range'), _('REV')), - ('u', 'user', None, _('list the author (long with -v)')), - ('d', 'date', None, _('list the date (short with -q)')), - ] + walkopts, - _('[OPTION]... PATTERN [FILE]...')), - "heads": - (heads, - [('r', 'rev', '', - _('show only heads which are descendants of STARTREV'), - _('STARTREV')), - ('t', 'topo', False, _('show topological heads only')), - ('a', 'active', False, - _('show active branchheads only (DEPRECATED)')), - ('c', 'closed', False, - _('show normal and closed branch heads')), - ] + templateopts, - _('[-ac] [-r STARTREV] [REV]...')), - "help": (help_, [], _('[TOPIC]')), - "identify|id": - (identify, - [('r', 'rev', '', - _('identify the specified revision'), _('REV')), - ('n', 'num', None, _('show local revision number')), - ('i', 'id', None, _('show global revision id')), - ('b', 'branch', None, _('show branch')), - ('t', 'tags', None, _('show tags')), - ('B', 'bookmarks', None, _('show bookmarks'))], - _('[-nibtB] [-r REV] [SOURCE]')), - "import|patch": - (import_, - [('p', 'strip', 1, - _('directory strip option for patch. This has the same ' - 'meaning as the corresponding patch option'), - _('NUM')), - ('b', 'base', '', - _('base path'), _('PATH')), - ('f', 'force', None, - _('skip check for outstanding uncommitted changes')), - ('', 'no-commit', None, - _("don't commit, just update the working directory")), - ('', 'exact', None, - _('apply patch to the nodes from which it was generated')), - ('', 'import-branch', None, - _('use any branch information in patch (implied by --exact)'))] + - commitopts + commitopts2 + similarityopts, - _('[OPTION]... PATCH...')), - "incoming|in": - (incoming, - [('f', 'force', None, - _('run even if remote repository is unrelated')), - ('n', 'newest-first', None, _('show newest record first')), - ('', 'bundle', '', - _('file to store the bundles into'), _('FILE')), - ('r', 'rev', [], - _('a remote changeset intended to be added'), _('REV')), - ('B', 'bookmarks', False, _("compare bookmarks")), - ('b', 'branch', [], - _('a specific branch you would like to pull'), _('BRANCH')), - ] + logopts + remoteopts + subrepoopts, - _('[-p] [-n] [-M] [-f] [-r REV]...' - ' [--bundle FILENAME] [SOURCE]')), - "^init": - (init, - remoteopts, - _('[-e CMD] [--remotecmd CMD] [DEST]')), - "locate": - (locate, - [('r', 'rev', '', - _('search the repository as it is in REV'), _('REV')), - ('0', 'print0', None, - _('end filenames with NUL, for use with xargs')), - ('f', 'fullpath', None, - _('print complete paths from the filesystem root')), - ] + walkopts, - _('[OPTION]... [PATTERN]...')), - "^log|history": - (log, - [('f', 'follow', None, - _('follow changeset history,' - ' or file history across copies and renames')), - ('', 'follow-first', None, - _('only follow the first parent of merge changesets')), - ('d', 'date', '', - _('show revisions matching date spec'), _('DATE')), - ('C', 'copies', None, _('show copied files')), - ('k', 'keyword', [], - _('do case-insensitive search for a given text'), _('TEXT')), - ('r', 'rev', [], - _('show the specified revision or range'), _('REV')), - ('', 'removed', None, _('include revisions where files were removed')), - ('m', 'only-merges', None, _('show only merges')), - ('u', 'user', [], - _('revisions committed by user'), _('USER')), - ('', 'only-branch', [], - _('show only changesets within the given named branch (DEPRECATED)'), - _('BRANCH')), - ('b', 'branch', [], - _('show changesets within the given named branch'), _('BRANCH')), - ('P', 'prune', [], - _('do not display revision or any of its ancestors'), _('REV')), - ] + logopts + walkopts, - _('[OPTION]... [FILE]')), - "manifest": - (manifest, - [('r', 'rev', '', - _('revision to display'), _('REV'))], - _('[-r REV]')), - "^merge": - (merge, - [('f', 'force', None, _('force a merge with outstanding changes')), - ('t', 'tool', '', _('specify merge tool')), - ('r', 'rev', '', - _('revision to merge'), _('REV')), - ('P', 'preview', None, - _('review revisions to merge (no merge is performed)'))], - _('[-P] [-f] [[-r] REV]')), - "outgoing|out": - (outgoing, - [('f', 'force', None, - _('run even when the destination is unrelated')), - ('r', 'rev', [], - _('a changeset intended to be included in the destination'), - _('REV')), - ('n', 'newest-first', None, _('show newest record first')), - ('B', 'bookmarks', False, _("compare bookmarks")), - ('b', 'branch', [], - _('a specific branch you would like to push'), _('BRANCH')), - ] + logopts + remoteopts + subrepoopts, - _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')), - "parents": - (parents, - [('r', 'rev', '', - _('show parents of the specified revision'), _('REV')), - ] + templateopts, - _('[-r REV] [FILE]')), - "paths": (paths, [], _('[NAME]')), - "^pull": - (pull, - [('u', 'update', None, - _('update to new branch head if changesets were pulled')), - ('f', 'force', None, - _('run even when remote repository is unrelated')), - ('r', 'rev', [], - _('a remote changeset intended to be added'), _('REV')), - ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')), - ('b', 'branch', [], - _('a specific branch you would like to pull'), _('BRANCH')), - ] + remoteopts, - _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')), - "^push": - (push, - [('f', 'force', None, _('force push')), - ('r', 'rev', [], - _('a changeset intended to be included in the destination'), - _('REV')), - ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')), - ('b', 'branch', [], - _('a specific branch you would like to push'), _('BRANCH')), - ('', 'new-branch', False, _('allow pushing a new branch')), - ] + remoteopts, - _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')), - "recover": (recover, []), - "^remove|rm": - (remove, - [('A', 'after', None, _('record delete for missing files')), - ('f', 'force', None, - _('remove (and delete) file even if added or modified')), - ] + walkopts, - _('[OPTION]... FILE...')), - "rename|move|mv": - (rename, - [('A', 'after', None, _('record a rename that has already occurred')), - ('f', 'force', None, - _('forcibly copy over an existing managed file')), - ] + walkopts + dryrunopts, - _('[OPTION]... SOURCE... DEST')), - "resolve": - (resolve, - [('a', 'all', None, _('select all unresolved files')), - ('l', 'list', None, _('list state of files needing merge')), - ('m', 'mark', None, _('mark files as resolved')), - ('u', 'unmark', None, _('mark files as unresolved')), - ('t', 'tool', '', _('specify merge tool')), - ('n', 'no-status', None, _('hide status prefix'))] - + walkopts, - _('[OPTION]... [FILE]...')), - "revert": - (revert, - [('a', 'all', None, _('revert all changes when no arguments given')), - ('d', 'date', '', - _('tipmost revision matching date'), _('DATE')), - ('r', 'rev', '', - _('revert to the specified revision'), _('REV')), - ('', 'no-backup', None, _('do not save backup copies of files')), - ] + walkopts + dryrunopts, - _('[OPTION]... [-r REV] [NAME]...')), - "rollback": (rollback, dryrunopts), - "root": (root, []), - "^serve": - (serve, - [('A', 'accesslog', '', - _('name of access log file to write to'), _('FILE')), - ('d', 'daemon', None, _('run server in background')), - ('', 'daemon-pipefds', '', - _('used internally by daemon mode'), _('NUM')), - ('E', 'errorlog', '', - _('name of error log file to write to'), _('FILE')), - # use string type, then we can check if something was passed - ('p', 'port', '', - _('port to listen on (default: 8000)'), _('PORT')), - ('a', 'address', '', - _('address to listen on (default: all interfaces)'), _('ADDR')), - ('', 'prefix', '', - _('prefix path to serve from (default: server root)'), _('PREFIX')), - ('n', 'name', '', - _('name to show in web pages (default: working directory)'), - _('NAME')), - ('', 'web-conf', '', - _('name of the hgweb config file (see "hg help hgweb")'), - _('FILE')), - ('', 'webdir-conf', '', - _('name of the hgweb config file (DEPRECATED)'), _('FILE')), - ('', 'pid-file', '', - _('name of file to write process ID to'), _('FILE')), - ('', 'stdio', None, _('for remote clients')), - ('t', 'templates', '', - _('web templates to use'), _('TEMPLATE')), - ('', 'style', '', - _('template style to use'), _('STYLE')), - ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')), - ('', 'certificate', '', - _('SSL certificate file'), _('FILE'))], - _('[OPTION]...')), - "showconfig|debugconfig": - (showconfig, - [('u', 'untrusted', None, _('show untrusted configuration options'))], - _('[-u] [NAME]...')), - "^summary|sum": - (summary, - [('', 'remote', None, _('check for push and pull'))], '[--remote]'), - "^status|st": - (status, - [('A', 'all', None, _('show status of all files')), - ('m', 'modified', None, _('show only modified files')), - ('a', 'added', None, _('show only added files')), - ('r', 'removed', None, _('show only removed files')), - ('d', 'deleted', None, _('show only deleted (but tracked) files')), - ('c', 'clean', None, _('show only files without changes')), - ('u', 'unknown', None, _('show only unknown (not tracked) files')), - ('i', 'ignored', None, _('show only ignored files')), - ('n', 'no-status', None, _('hide status prefix')), - ('C', 'copies', None, _('show source of copied files')), - ('0', 'print0', None, - _('end filenames with NUL, for use with xargs')), - ('', 'rev', [], - _('show difference from revision'), _('REV')), - ('', 'change', '', - _('list the changed files of a revision'), _('REV')), - ] + walkopts + subrepoopts, - _('[OPTION]... [FILE]...')), - "tag": - (tag, - [('f', 'force', None, _('force tag')), - ('l', 'local', None, _('make the tag local')), - ('r', 'rev', '', - _('revision to tag'), _('REV')), - ('', 'remove', None, _('remove a tag')), - # -l/--local is already there, commitopts cannot be used - ('e', 'edit', None, _('edit commit message')), - ('m', 'message', '', - _('use <text> as commit message'), _('TEXT')), - ] + commitopts2, - _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')), - "tags": (tags, [], ''), - "tip": - (tip, - [('p', 'patch', None, _('show patch')), - ('g', 'git', None, _('use git extended diff format')), - ] + templateopts, - _('[-p] [-g]')), - "unbundle": - (unbundle, - [('u', 'update', None, - _('update to new branch head if changesets were unbundled'))], - _('[-u] FILE...')), - "^update|up|checkout|co": - (update, - [('C', 'clean', None, _('discard uncommitted changes (no backup)')), - ('c', 'check', None, - _('update across branches if no uncommitted changes')), - ('d', 'date', '', - _('tipmost revision matching date'), _('DATE')), - ('r', 'rev', '', - _('revision'), _('REV'))], - _('[-c] [-C] [-d DATE] [[-r] REV]')), - "verify": (verify, []), - "version": (version_, []), -} - norepo = ("clone init version help debugcommands debugcomplete" - " debugdate debuginstall debugfsinfo debugpushkey") + " debugdate debuginstall debugfsinfo debugpushkey debugwireargs" + " debugknown debuggetbundle debugbundle") optionalrepo = ("identify paths serve showconfig debugancestor debugdag" - " debugdata debugindex debugindexdot") + " debugdata debugindex debugindexdot debugrevlog")
--- a/mercurial/config.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/config.py Wed May 18 09:56:27 2011 +0200 @@ -138,5 +138,5 @@ def read(self, path, fp=None, sections=None, remap=None): if not fp: - fp = open(path) + fp = util.posixfile(path) self.parse(path, fp.read(), sections, remap, self.read)
--- a/mercurial/context.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/context.py Wed May 18 09:56:27 2011 +0200 @@ -7,7 +7,7 @@ from node import nullid, nullrev, short, hex from i18n import _ -import ancestor, bdiff, error, util, subrepo, patch, encoding +import ancestor, bdiff, error, util, scmutil, subrepo, patch, encoding import os, errno, stat propertycache = util.propertycache @@ -402,6 +402,15 @@ return [filectx(self._repo, p, fileid=n, filelog=l) for p, n, l in pl if n != nullid] + def p1(self): + return self.parents()[0] + + def p2(self): + p = self.parents() + if len(p) == 2: + return p[1] + return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog) + def children(self): # hard for renames c = self._filelog.children(self._filenode) @@ -652,6 +661,12 @@ return man + def __iter__(self): + d = self._repo.dirstate + for f in d: + if d[f] != 'r': + yield f + @propertycache def _status(self): return self._repo.status()[:4] @@ -792,10 +807,11 @@ try: rejected = [] for f in list: + scmutil.checkportable(ui, join(f)) p = self._repo.wjoin(f) try: st = os.lstat(p) - except: + except OSError: ui.warn(_("%s does not exist!\n") % join(f)) rejected.append(f) continue
--- a/mercurial/copies.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/copies.py Wed May 18 09:56:27 2011 +0200 @@ -93,7 +93,7 @@ return {}, {} # avoid silly behavior for parent -> working dir - if c2.node() is None and c1.node() == repo.dirstate.parents()[0]: + if c2.node() is None and c1.node() == repo.dirstate.p1(): return repo.dirstate.copies(), {} limit = _findlimit(repo, c1.rev(), c2.rev())
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/dagutil.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,248 @@ +# dagutil.py - dag utilities for mercurial +# +# Copyright 2010 Benoit Boissinot <bboissin@gmail.com> +# and Peter Arrenbrecht <peter@arrenbrecht.ch> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from node import nullrev + + +class basedag(object): + '''generic interface for DAGs + + terms: + "ix" (short for index) identifies a nodes internally, + "id" identifies one externally. + + All params are ixs unless explicitly suffixed otherwise. + Pluralized params are lists or sets. + ''' + + def __init__(self): + self._inverse = None + + def nodeset(self): + '''set of all node idxs''' + raise NotImplementedError() + + def heads(self): + '''list of head ixs''' + raise NotImplementedError() + + def parents(self, ix): + '''list of parents ixs of ix''' + raise NotImplementedError() + + def inverse(self): + '''inverse DAG, where parents becomes children, etc.''' + raise NotImplementedError() + + def ancestorset(self, starts, stops=None): + ''' + set of all ancestors of starts (incl), but stop walk at stops (excl) + ''' + raise NotImplementedError() + + def descendantset(self, starts, stops=None): + ''' + set of all descendants of starts (incl), but stop walk at stops (excl) + ''' + return self.inverse().ancestorset(starts, stops) + + def headsetofconnecteds(self, ixs): + ''' + subset of connected list of ixs so that no node has a descendant in it + + By "connected list" we mean that if an ancestor and a descendant are in + the list, then so is at least one path connecting them. + ''' + raise NotImplementedError() + + def externalize(self, ix): + '''return a list of (or set if given a set) of node ids''' + return self._externalize(ix) + + def externalizeall(self, ixs): + '''return a list of (or set if given a set) of node ids''' + ids = self._externalizeall(ixs) + if isinstance(ixs, set): + return set(ids) + return list(ids) + + def internalize(self, id): + '''return a list of (or set if given a set) of node ixs''' + return self._internalize(id) + + def internalizeall(self, ids, filterunknown=False): + '''return a list of (or set if given a set) of node ids''' + ixs = self._internalizeall(ids, filterunknown) + if isinstance(ids, set): + return set(ixs) + return list(ixs) + + +class genericdag(basedag): + '''generic implementations for DAGs''' + + def ancestorset(self, starts, stops=None): + stops = stops and set(stops) or set() + seen = set() + pending = list(starts) + while pending: + n = pending.pop() + if n not in seen and n not in stops: + seen.add(n) + pending.extend(self.parents(n)) + return seen + + def headsetofconnecteds(self, ixs): + hds = set(ixs) + if not hds: + return hds + for n in ixs: + for p in self.parents(n): + hds.discard(p) + assert hds + return hds + + +class revlogbaseddag(basedag): + '''generic dag interface to a revlog''' + + def __init__(self, revlog, nodeset): + basedag.__init__(self) + self._revlog = revlog + self._heads = None + self._nodeset = nodeset + + def nodeset(self): + return self._nodeset + + def heads(self): + if self._heads is None: + self._heads = self._getheads() + return self._heads + + def _externalize(self, ix): + return self._revlog.index[ix][7] + def _externalizeall(self, ixs): + idx = self._revlog.index + return [idx[i][7] for i in ixs] + + def _internalize(self, id): + ix = self._revlog.rev(id) + if ix == nullrev: + raise LookupError(id, self._revlog.indexfile, _('nullid')) + return ix + def _internalizeall(self, ids, filterunknown): + rl = self._revlog + if filterunknown: + return [r for r in map(rl.nodemap.get, ids) + if r is not None and r != nullrev] + return map(self._internalize, ids) + + +class revlogdag(revlogbaseddag): + '''dag interface to a revlog''' + + def __init__(self, revlog): + revlogbaseddag.__init__(self, revlog, set(xrange(len(revlog)))) + + def _getheads(self): + return [r for r in self._revlog.headrevs() if r != nullrev] + + def parents(self, ix): + rlog = self._revlog + idx = rlog.index + revdata = idx[ix] + prev = revdata[5] + if prev != nullrev: + prev2 = revdata[6] + if prev2 == nullrev: + return [prev] + return [prev, prev2] + prev2 = revdata[6] + if prev2 != nullrev: + return [prev2] + return [] + + def inverse(self): + if self._inverse is None: + self._inverse = inverserevlogdag(self) + return self._inverse + + def ancestorset(self, starts, stops=None): + rlog = self._revlog + idx = rlog.index + stops = stops and set(stops) or set() + seen = set() + pending = list(starts) + while pending: + rev = pending.pop() + if rev not in seen and rev not in stops: + seen.add(rev) + revdata = idx[rev] + for i in [5, 6]: + prev = revdata[i] + if prev != nullrev: + pending.append(prev) + return seen + + def headsetofconnecteds(self, ixs): + if not ixs: + return set() + rlog = self._revlog + idx = rlog.index + headrevs = set(ixs) + for rev in ixs: + revdata = idx[rev] + for i in [5, 6]: + prev = revdata[i] + if prev != nullrev: + headrevs.discard(prev) + assert headrevs + return headrevs + + +class inverserevlogdag(revlogbaseddag, genericdag): + '''inverse of an existing revlog dag; see revlogdag.inverse()''' + + def __init__(self, orig): + revlogbaseddag.__init__(self, orig._revlog, orig._nodeset) + self._orig = orig + self._children = {} + self._roots = [] + self._walkfrom = len(self._revlog) - 1 + + def _walkto(self, walkto): + rev = self._walkfrom + cs = self._children + roots = self._roots + idx = self._revlog.index + while rev >= walkto: + data = idx[rev] + isroot = True + for prev in [data[5], data[6]]: # parent revs + if prev != nullrev: + cs.setdefault(prev, []).append(rev) + isroot = False + if isroot: + roots.append(rev) + rev -= 1 + self._walkfrom = rev - 1 + + def _getheads(self): + self._walkto(nullrev) + return self._roots + + def parents(self, ix): + if ix is None: + return [] + if ix <= self._walkfrom: + self._walkto(ix) + return self._children.get(ix, []) + + def inverse(self): + return self._orig
--- a/mercurial/dirstate.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/dirstate.py Wed May 18 09:56:27 2011 +0200 @@ -7,7 +7,7 @@ from node import nullid from i18n import _ -import util, ignore, osutil, parsers, encoding +import scmutil, util, ignore, osutil, parsers, encoding import struct, os, stat, errno import cStringIO @@ -49,6 +49,7 @@ self._rootdir = os.path.join(root, '') self._dirty = False self._dirtypl = False + self._lastnormaltime = None self._ui = ui @propertycache @@ -73,7 +74,7 @@ @propertycache def _branch(self): try: - return self._opener("branch").read().strip() or "default" + return self._opener.read("branch").strip() or "default" except IOError: return "default" @@ -137,7 +138,7 @@ p = self._join(x) if os.path.islink(p): return 'l' - if util.is_exec(p): + if util.isexec(p): return 'x' return '' return f @@ -152,7 +153,7 @@ def f(x): if 'l' in fallback(x): return 'l' - if util.is_exec(self._join(x)): + if util.isexec(self._join(x)): return 'x' return '' return f @@ -202,6 +203,12 @@ def parents(self): return [self._validate(p) for p in self._pl] + def p1(self): + return self._validate(self._pl[0]) + + def p2(self): + return self._validate(self._pl[1]) + def branch(self): return encoding.tolocal(self._branch) @@ -213,13 +220,13 @@ if branch in ['tip', '.', 'null']: raise util.Abort(_('the name \'%s\' is reserved') % branch) self._branch = encoding.fromlocal(branch) - self._opener("branch", "w").write(self._branch + '\n') + self._opener.write("branch", self._branch + '\n') def _read(self): self._map = {} self._copymap = {} try: - st = self._opener("dirstate").read() + st = self._opener.read("dirstate") except IOError, err: if err.errno != errno.ENOENT: raise @@ -236,6 +243,7 @@ "_ignore"): if a in self.__dict__: delattr(self, a) + self._lastnormaltime = None self._dirty = False def copy(self, source, dest): @@ -261,9 +269,7 @@ def _addpath(self, f, check=False): oldstate = self[f] if check or oldstate == "r": - if '\r' in f or '\n' in f: - raise util.Abort( - _("'\\n' and '\\r' disallowed in filenames: %r") % f) + scmutil.checkfilename(f) if f in self._dirs: raise util.Abort(_('directory %r already in dirstate') % f) # shadows @@ -281,9 +287,15 @@ self._dirty = True self._addpath(f) s = os.lstat(self._join(f)) - self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime)) + mtime = int(s.st_mtime) + self._map[f] = ('n', s.st_mode, s.st_size, mtime) if f in self._copymap: del self._copymap[f] + if mtime > self._lastnormaltime: + # Remember the most recent modification timeslot for status(), + # to make sure we won't miss future size-preserving file content + # modifications that happen within the same timeslot. + self._lastnormaltime = mtime def normallookup(self, f): '''Mark a file normal, but possibly dirty.''' @@ -397,6 +409,7 @@ delattr(self, "_dirs") self._copymap = {} self._pl = [nullid, nullid] + self._lastnormaltime = None self._dirty = True def rebuild(self, parent, files): @@ -444,6 +457,7 @@ write(f) st.write(cs.getvalue()) st.rename() + self._lastnormaltime = None self._dirty = self._dirtypl = False def _dirignore(self, f): @@ -680,6 +694,7 @@ # lines are an expansion of "islink => checklink" # where islink means "is this a link?" and checklink # means "can we check links?". + mtime = int(st.st_mtime) if (size >= 0 and (size != st.st_size or ((mode ^ st.st_mode) & 0100 and self._checkexec)) @@ -687,9 +702,15 @@ or size == -2 # other parent or fn in self._copymap): madd(fn) - elif (time != int(st.st_mtime) + elif (mtime != time and (mode & lnkkind != lnkkind or self._checklink)): ladd(fn) + elif mtime == self._lastnormaltime: + # fn may have been changed in the same timeslot without + # changing its size. This can happen if we quickly do + # multiple commits in a single transaction. + # Force lookup, so we don't miss such a racy file change. + ladd(fn) elif listclean: cadd(fn) elif state == 'm':
--- a/mercurial/discovery.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/discovery.py Wed May 18 09:56:27 2011 +0200 @@ -7,184 +7,59 @@ from node import nullid, short from i18n import _ -import util, error +import util, setdiscovery, treediscovery def findcommonincoming(repo, remote, heads=None, force=False): - """Return a tuple (common, missing roots, heads) used to identify - missing nodes from remote. - - If a list of heads is specified, return only nodes which are heads - or ancestors of these heads. - """ - m = repo.changelog.nodemap - search = [] - fetch = set() - seen = set() - seenbranch = set() - base = set() - - if not heads: - heads = remote.heads() - - if repo.changelog.tip() == nullid: - base.add(nullid) - if heads != [nullid]: - return [nullid], [nullid], list(heads) - return [nullid], [], [] - - # assume we're closer to the tip than the root - # and start by examining the heads - repo.ui.status(_("searching for changes\n")) - - unknown = [] - for h in heads: - if h not in m: - unknown.append(h) - else: - base.add(h) - - heads = unknown - if not unknown: - return list(base), [], [] - - req = set(unknown) - reqcnt = 0 + """Return a tuple (common, anyincoming, heads) used to identify the common + subset of nodes between repo and remote. - # search through remote branches - # a 'branch' here is a linear segment of history, with four parts: - # head, root, first parent, second parent - # (a branch always has two parents (or none) by definition) - unknown = remote.branches(unknown) - while unknown: - r = [] - while unknown: - n = unknown.pop(0) - if n[0] in seen: - continue + "common" is a list of (at least) the heads of the common subset. + "anyincoming" is testable as a boolean indicating if any nodes are missing + locally. If remote does not support getbundle, this actually is a list of + roots of the nodes that would be incoming, to be supplied to + changegroupsubset. No code except for pull should be relying on this fact + any longer. + "heads" is either the supplied heads, or else the remote's heads. - repo.ui.debug("examining %s:%s\n" - % (short(n[0]), short(n[1]))) - if n[0] == nullid: # found the end of the branch - pass - elif n in seenbranch: - repo.ui.debug("branch already found\n") - continue - elif n[1] and n[1] in m: # do we know the base? - repo.ui.debug("found incomplete branch %s:%s\n" - % (short(n[0]), short(n[1]))) - search.append(n[0:2]) # schedule branch range for scanning - seenbranch.add(n) - else: - if n[1] not in seen and n[1] not in fetch: - if n[2] in m and n[3] in m: - repo.ui.debug("found new changeset %s\n" % - short(n[1])) - fetch.add(n[1]) # earliest unknown - for p in n[2:4]: - if p in m: - base.add(p) # latest known + If you pass heads and they are all known locally, the reponse lists justs + these heads in "common" and in "heads". - for p in n[2:4]: - if p not in req and p not in m: - r.append(p) - req.add(p) - seen.add(n[0]) + Please use findcommonoutgoing to compute the set of outgoing nodes to give + extensions a good hook into outgoing. + """ + + if not remote.capable('getbundle'): + return treediscovery.findcommonincoming(repo, remote, heads, force) - if r: - reqcnt += 1 - repo.ui.progress(_('searching'), reqcnt, unit=_('queries')) - repo.ui.debug("request %d: %s\n" % - (reqcnt, " ".join(map(short, r)))) - for p in xrange(0, len(r), 10): - for b in remote.branches(r[p:p + 10]): - repo.ui.debug("received %s:%s\n" % - (short(b[0]), short(b[1]))) - unknown.append(b) + if heads: + allknown = True + nm = repo.changelog.nodemap + for h in heads: + if nm.get(h) is None: + allknown = False + break + if allknown: + return (heads, False, heads) - # do binary search on the branches we found - while search: - newsearch = [] - reqcnt += 1 - repo.ui.progress(_('searching'), reqcnt, unit=_('queries')) - for n, l in zip(search, remote.between(search)): - l.append(n[1]) - p = n[0] - f = 1 - for i in l: - repo.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i))) - if i in m: - if f <= 2: - repo.ui.debug("found new branch changeset %s\n" % - short(p)) - fetch.add(p) - base.add(i) - else: - repo.ui.debug("narrowed branch search to %s:%s\n" - % (short(p), short(i))) - newsearch.append((p, i)) - break - p, f = i, f * 2 - search = newsearch - - # sanity check our fetch list - for f in fetch: - if f in m: - raise error.RepoError(_("already have changeset ") - + short(f[:4])) + res = setdiscovery.findcommonheads(repo.ui, repo, remote, + abortwhenunrelated=not force) + common, anyinc, srvheads = res + return (list(common), anyinc, heads or list(srvheads)) - base = list(base) - if base == [nullid]: - if force: - repo.ui.warn(_("warning: repository is unrelated\n")) - else: - raise util.Abort(_("repository is unrelated")) - - repo.ui.debug("found new changesets starting at " + - " ".join([short(f) for f in fetch]) + "\n") - - repo.ui.progress(_('searching'), None) - repo.ui.debug("%d total queries\n" % reqcnt) - - return base, list(fetch), heads - -def findoutgoing(repo, remote, base=None, remoteheads=None, force=False): - """Return list of nodes that are roots of subsets not in remote +def findcommonoutgoing(repo, other, onlyheads=None, force=False, commoninc=None): + '''Return a tuple (common, anyoutgoing, heads) used to identify the set + of nodes present in repo but not in other. - If base dict is specified, assume that these nodes and their parents - exist on the remote side. - If remotehead is specified, assume it is the list of the heads from - the remote repository. - """ - if base is None: - base = findcommonincoming(repo, remote, heads=remoteheads, - force=force)[0] - else: - base = list(base) - - repo.ui.debug("common changesets up to " - + " ".join(map(short, base)) + "\n") - - remain = set(repo.changelog.nodemap) + If onlyheads is given, only nodes ancestral to nodes in onlyheads (inclusive) + are included. If you already know the local repo's heads, passing them in + onlyheads is faster than letting them be recomputed here. - # prune everything remote has from the tree - remain.remove(nullid) - remove = base - while remove: - n = remove.pop(0) - if n in remain: - remain.remove(n) - for p in repo.changelog.parents(n): - remove.append(p) + If commoninc is given, it must the the result of a prior call to + findcommonincoming(repo, other, force) to avoid recomputing it here. - # find every node whose parents have been pruned - subset = [] - # find every remote head that will get new children - for n in remain: - p1, p2 = repo.changelog.parents(n) - if p1 not in remain and p2 not in remain: - subset.append(n) - - return subset + The returned tuple is meant to be passed to changelog.findmissing.''' + common, _any, _hds = commoninc or findcommonincoming(repo, other, force=force) + return (common, onlyheads or repo.heads()) def prepush(repo, remote, force, revs, newbranch): '''Analyze the local and remote repositories and determine which @@ -200,15 +75,15 @@ changegroup is a readable file-like object whose read() returns successive changegroup chunks ready to be sent over the wire and remoteheads is the list of remote heads.''' - remoteheads = remote.heads() - common, inc, rheads = findcommonincoming(repo, remote, heads=remoteheads, - force=force) + commoninc = findcommonincoming(repo, remote, force=force) + common, revs = findcommonoutgoing(repo, remote, onlyheads=revs, + commoninc=commoninc, force=force) + _common, inc, remoteheads = commoninc cl = repo.changelog - update = findoutgoing(repo, remote, common, remoteheads) - outg, bases, heads = cl.nodesbetween(update, revs) + outg = cl.findmissing(common, revs) - if not bases: + if not outg: repo.ui.status(_("no changes found\n")) return None, 1 @@ -309,8 +184,7 @@ if revs is None: # use the fast path, no race possible on push - nodes = repo.changelog.findmissing(common) - cg = repo._changegroup(nodes, 'push') + cg = repo._changegroup(outg, 'push') else: - cg = repo.changegroupsubset(update, revs, 'push') + cg = repo.getbundle('push', heads=revs, common=common) return cg, remoteheads
--- a/mercurial/dispatch.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/dispatch.py Wed May 18 09:56:27 2011 +0200 @@ -90,7 +90,7 @@ except error.CommandError, inst: if inst.args[0]: ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1])) - commands.help_(ui, inst.args[0]) + commands.help_(ui, inst.args[0], full=False, command=True) else: ui.warn(_("hg: %s\n") % inst.args[1]) commands.help_(ui, 'shortlist') @@ -133,7 +133,8 @@ elif hasattr(inst, "reason"): try: # usually it is in the form (errno, strerror) reason = inst.reason.args[1] - except: # it might be anything, for example a string + except (AttributeError, IndexError): + # it might be anything, for example a string reason = inst.reason ui.warn(_("abort: error: %s\n") % reason) elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE: @@ -181,10 +182,21 @@ return -1 -def aliasargs(fn): - if hasattr(fn, 'args'): - return fn.args - return [] +def aliasargs(fn, givenargs): + args = getattr(fn, 'args', []) + if args and givenargs: + cmd = ' '.join(map(util.shellquote, args)) + + nums = [] + def replacer(m): + num = int(m.group(1)) - 1 + nums.append(num) + return givenargs[num] + cmd = re.sub(r'\$(\d+|\$)', replacer, cmd) + givenargs = [x for i, x in enumerate(givenargs) + if i not in nums] + args = shlex.split(cmd) + return args + givenargs class cmdalias(object): def __init__(self, name, definition, cmdtable): @@ -262,7 +274,7 @@ else: self.fn, self.opts = tableentry - self.args = aliasargs(self.fn) + args + self.args = aliasargs(self.fn, args) if cmd not in commands.norepo.split(' '): self.norepo = False if self.help.startswith("hg " + cmd): @@ -329,7 +341,7 @@ aliases, entry = cmdutil.findcmd(cmd, commands.table, ui.config("ui", "strict")) cmd = aliases[0] - args = aliasargs(entry[0]) + args + args = aliasargs(entry[0], args) defaults = ui.config("defaults", cmd) if defaults: args = map(util.expandpath, shlex.split(defaults)) + args @@ -586,8 +598,8 @@ if guess and repos.count(guess) == len(repos): return _dispatch(ui, ['--repository', guess] + fullargs) if not path: - raise error.RepoError(_("There is no Mercurial repository" - " here (.hg not found)")) + raise error.RepoError(_("no repository found in %r" + " (.hg not found)") % os.getcwd()) raise args.insert(0, repo) elif rpath:
--- a/mercurial/encoding.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/encoding.py Wed May 18 09:56:27 2011 +0200 @@ -148,3 +148,17 @@ return sum([w(c) in wide and 2 or 1 for c in d]) return len(d) +def lower(s): + "best-effort encoding-aware case-folding of local string s" + try: + if isinstance(s, localstr): + u = s._utf8.decode("utf-8") + else: + u = s.decode(encoding, encodingmode) + + lu = u.lower() + if u == lu: + return s # preserve localstring + return lu.encode(encoding) + except UnicodeError: + return s.lower() # we don't know how to fold this except in ASCII
--- a/mercurial/extensions.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/extensions.py Wed May 18 09:56:27 2011 +0200 @@ -6,12 +6,12 @@ # GNU General Public License version 2 or any later version. import imp, os -import util, cmdutil, help, error +import util, cmdutil, error from i18n import _, gettext _extensions = {} _order = [] -_ignore = ['hbisect', 'bookmarks'] +_ignore = ['hbisect', 'bookmarks', 'parentrevspec'] def extensions(): for name in _order: @@ -209,6 +209,38 @@ exts[name] = path return exts +def _moduledoc(file): + '''return the top-level python documentation for the given file + + Loosely inspired by pydoc.source_synopsis(), but rewritten to + handle triple quotes and to return the whole text instead of just + the synopsis''' + result = [] + + line = file.readline() + while line[:1] == '#' or not line.strip(): + line = file.readline() + if not line: + break + + start = line[:3] + if start == '"""' or start == "'''": + line = line[3:] + while line: + if line.rstrip().endswith(start): + line = line.split(start)[0] + if line: + result.append(line) + break + elif not line: + return None # unmatched delimiter + result.append(line) + line = file.readline() + else: + return None + + return ''.join(result) + def _disabledhelp(path): '''retrieve help synopsis of a disabled extension (without importing)''' try: @@ -216,7 +248,7 @@ except IOError: return else: - doc = help.moduledoc(file) + doc = _moduledoc(file) file.close() if doc: # extracting localized synopsis @@ -230,20 +262,15 @@ paths = _disabledpaths() if not paths: - return None, 0 + return None exts = {} - maxlength = 0 for name, path in paths.iteritems(): doc = _disabledhelp(path) - if not doc: - continue + if doc: + exts[name] = doc - exts[name] = doc - if len(name) > maxlength: - maxlength = len(name) - - return exts, maxlength + return exts def disabledext(name): '''find a specific disabled extension from hgext. returns desc''' @@ -299,11 +326,9 @@ def enabled(): '''return a dict of {name: desc} of extensions, and the max name length''' exts = {} - maxlength = 0 for ename, ext in extensions(): doc = (gettext(ext.__doc__) or _('(no help text available)')) ename = ename.split('.')[-1] - maxlength = max(len(ename), maxlength) exts[ename] = doc.splitlines()[0].strip() - return exts, maxlength + return exts
--- a/mercurial/filelog.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/filelog.py Wed May 18 09:56:27 2011 +0200 @@ -6,17 +6,28 @@ # GNU General Public License version 2 or any later version. import revlog +import re +_mdre = re.compile('\1\n') def _parsemeta(text): - if not text.startswith('\1\n'): - return {} - s = text.index('\1\n', 2) - mt = text[2:s] - m = {} - for l in mt.splitlines(): + """return (metadatadict, keylist, metadatasize)""" + # text can be buffer, so we can't use .startswith or .index + if text[:2] != '\1\n': + return None, None, None + s = _mdre.search(text, 2).start() + mtext = text[2:s] + meta = {} + keys = [] + for l in mtext.splitlines(): k, v = l.split(": ", 1) - m[k] = v - return m + meta[k] = v + keys.append(k) + return meta, keys, (s + 2) + +def _packmeta(meta, keys=None): + if not keys: + keys = sorted(meta.iterkeys()) + return "".join("%s: %s\n" % (k, meta[k]) for k in keys) class filelog(revlog.revlog): def __init__(self, opener, path): @@ -32,15 +43,14 @@ def add(self, text, meta, transaction, link, p1=None, p2=None): if meta or text.startswith('\1\n'): - mt = ["%s: %s\n" % (k, v) for k, v in sorted(meta.iteritems())] - text = "\1\n%s\1\n%s" % ("".join(mt), text) + text = "\1\n%s\1\n%s" % (_packmeta(meta), text) return self.addrevision(text, transaction, link, p1, p2) def renamed(self, node): if self.parents(node)[0] != revlog.nullid: return False t = self.revision(node) - m = _parsemeta(t) + m = _parsemeta(t)[0] if m and "copy" in m: return (m["copy"], revlog.bin(m["copyrev"])) return False @@ -77,3 +87,6 @@ return t2 != text return True + + def _file(self, f): + return filelog(self.opener, f)
--- a/mercurial/filemerge.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/filemerge.py Wed May 18 09:56:27 2011 +0200 @@ -29,12 +29,12 @@ k = _toolstr(ui, tool, kn) if not k: continue - p = util.lookup_reg(k, _toolstr(ui, tool, "regname")) + p = util.lookupreg(k, _toolstr(ui, tool, "regname")) if p: - p = util.find_exe(p + _toolstr(ui, tool, "regappend")) + p = util.findexe(p + _toolstr(ui, tool, "regappend")) if p: return p - return util.find_exe(_toolstr(ui, tool, "executable", tool)) + return util.findexe(_toolstr(ui, tool, "executable", tool)) def _picktool(repo, ui, path, binary, symlink): def check(tool, pat, symlink, binary): @@ -113,14 +113,14 @@ def _matcheol(file, origfile): "Convert EOL markers in a file to match origfile" - tostyle = _eoltype(open(origfile, "rb").read()) + tostyle = _eoltype(util.readfile(origfile)) if tostyle: - data = open(file, "rb").read() + data = util.readfile(file) style = _eoltype(data) if style: newdata = data.replace(style, tostyle) if newdata != data: - open(file, "wb").write(newdata) + util.writefile(file, newdata) def filemerge(repo, mynode, orig, fcd, fco, fca): """perform a 3-way merge in the working directory
--- a/mercurial/graphmod.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/graphmod.py Wed May 18 09:56:27 2011 +0200 @@ -21,40 +21,39 @@ CHANGESET = 'C' -def revisions(repo, start, stop): +def dagwalker(repo, revs): """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples - This generator function walks through the revision history from revision - start to revision stop (which must be less than or equal to start). It - returns a tuple for each node. The node and parent ids are arbitrary - integers which identify a node in the context of the graph returned. + This generator function walks through revisions (which should be ordered + from bigger to lower). It returns a tuple for each node. The node and parent + ids are arbitrary integers which identify a node in the context of the graph + returned. """ - cur = start - while cur >= stop: - ctx = repo[cur] - parents = set([p.rev() for p in ctx.parents() if p.rev() != nullrev]) - yield (cur, CHANGESET, ctx, sorted(parents)) - cur -= 1 + if not revs: + return -def filerevs(repo, path, start, stop, limit=None): - """file cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples + cl = repo.changelog + lowestrev = min(revs) + gpcache = {} - This generator function walks through the revision history of a single - file from revision start down to revision stop. - """ - filerev = len(repo.file(path)) - 1 - rev = stop + 1 - count = 0 - while filerev >= 0 and rev > stop: - fctx = repo.filectx(path, fileid=filerev) - parents = set([f.linkrev() for f in fctx.parents() if f.path() == path]) - rev = fctx.rev() - if rev <= start: - yield (rev, CHANGESET, fctx.changectx(), sorted(parents)) - count += 1 - if count == limit: - break - filerev -= 1 + knownrevs = set(revs) + for rev in revs: + ctx = repo[rev] + parents = sorted(set([p.rev() for p in ctx.parents() + if p.rev() in knownrevs])) + mpars = [p.rev() for p in ctx.parents() if + p.rev() != nullrev and p.rev() not in parents] + + for mpar in mpars: + gp = gpcache.get(mpar) + if gp is None: + gp = gpcache[mpar] = grandparent(cl, lowestrev, revs, mpar) + if not gp: + parents.append(mpar) + else: + parents.extend(g for g in gp if g not in parents) + + yield (ctx.rev(), CHANGESET, ctx, parents) def nodes(repo, nodes): """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples @@ -120,3 +119,21 @@ # Yield and move on yield (cur, type, data, (col, color), edges) seen = next + +def grandparent(cl, lowestrev, roots, head): + """Return all ancestors of head in roots which revision is + greater or equal to lowestrev. + """ + pending = set([head]) + seen = set() + kept = set() + llowestrev = max(nullrev, lowestrev) + while pending: + r = pending.pop() + if r >= llowestrev and r not in seen: + if r in roots: + kept.add(r) + else: + pending.update([p for p in cl.parentrevs(r)]) + seen.add(r) + return sorted(kept)
--- a/mercurial/hbisect.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/hbisect.py Wed May 18 09:56:27 2011 +0200 @@ -54,10 +54,10 @@ return badrev, None return badrev, ancestors - good = 0 + good = False badrev, ancestors = buildancestors(state['bad'], state['good']) if not ancestors: # looking for bad to good transition? - good = 1 + good = True badrev, ancestors = buildancestors(state['good'], state['bad']) bad = changelog.node(badrev) if not ancestors: # now we're confused
--- a/mercurial/help.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/help.py Wed May 18 09:56:27 2011 +0200 @@ -7,45 +7,14 @@ from i18n import gettext, _ import sys, os -import extensions - - -def moduledoc(file): - '''return the top-level python documentation for the given file - - Loosely inspired by pydoc.source_synopsis(), but rewritten to - handle triple quotes and to return the whole text instead of just - the synopsis''' - result = [] - - line = file.readline() - while line[:1] == '#' or not line.strip(): - line = file.readline() - if not line: - break +import extensions, revset, templatekw, templatefilters +import util - start = line[:3] - if start == '"""' or start == "'''": - line = line[3:] - while line: - if line.rstrip().endswith(start): - line = line.split(start)[0] - if line: - result.append(line) - break - elif not line: - return None # unmatched delimiter - result.append(line) - line = file.readline() - else: - return None - - return ''.join(result) - -def listexts(header, exts, maxlength, indent=1): +def listexts(header, exts, indent=1): '''return a text listing of the given extensions''' if not exts: return '' + maxlength = max(len(e) for e in exts) result = '\n%s\n\n' % header for name, desc in sorted(exts.iteritems()): result += '%s%-*s %s\n' % (' ' * indent, maxlength + 2, @@ -54,13 +23,8 @@ def extshelp(): doc = loaddoc('extensions')() - - exts, maxlength = extensions.enabled() - doc += listexts(_('enabled extensions:'), exts, maxlength) - - exts, maxlength = extensions.disabled() - doc += listexts(_('disabled extensions:'), exts, maxlength) - + doc += listexts(_('enabled extensions:'), extensions.enabled()) + doc += listexts(_('disabled extensions:'), extensions.disabled()) return doc def loaddoc(topic): @@ -79,14 +43,14 @@ break path = os.path.join(docdir, topic + ".txt") - doc = gettext(open(path).read()) + doc = gettext(util.readfile(path)) for rewriter in helphooks.get(topic, []): doc = rewriter(topic, doc) return doc return loader -helptable = [ +helptable = sorted([ (["config", "hgrc"], _("Configuration Files"), loaddoc('config')), (["dates"], _("Date Formats"), loaddoc('dates')), (["patterns"], _("File Name Patterns"), loaddoc('patterns')), @@ -103,15 +67,41 @@ loaddoc('templates')), (['urls'], _('URL Paths'), loaddoc('urls')), (["extensions"], _("Using additional features"), extshelp), - (["subrepo", "subrepos"], _("Subrepositories"), loaddoc('subrepos')), - (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')), - (["glossary"], _("Glossary"), loaddoc('glossary')), -] + (["subrepo", "subrepos"], _("Subrepositories"), loaddoc('subrepos')), + (["hgweb"], _("Configuring hgweb"), loaddoc('hgweb')), + (["glossary"], _("Glossary"), loaddoc('glossary')), + (["hgignore", "ignore"], _("syntax for Mercurial ignore files"), + loaddoc('hgignore')), +]) # Map topics to lists of callable taking the current topic help and # returning the updated version -helphooks = { -} +helphooks = {} def addtopichook(topic, rewriter): helphooks.setdefault(topic, []).append(rewriter) + +def makeitemsdoc(topic, doc, marker, items): + """Extract docstring from the items key to function mapping, build a + .single documentation block and use it to overwrite the marker in doc + """ + entries = [] + for name in sorted(items): + text = (items[name].__doc__ or '').rstrip() + if not text: + continue + text = gettext(text) + lines = text.splitlines() + lines[1:] = [(' ' + l.strip()) for l in lines[1:]] + entries.append('\n'.join(lines)) + entries = '\n\n'.join(entries) + return doc.replace(marker, entries) + +def addtopicsymbols(topic, marker, symbols): + def add(topic, doc): + return makeitemsdoc(topic, doc, marker, symbols) + addtopichook(topic, add) + +addtopicsymbols('revsets', '.. predicatesmarker', revset.symbols) +addtopicsymbols('templates', '.. keywordsmarker', templatekw.keywords) +addtopicsymbols('templates', '.. filtersmarker', templatefilters.filters)
--- a/mercurial/help/dates.txt Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/help/dates.txt Wed May 18 09:56:27 2011 +0200 @@ -30,7 +30,7 @@ The log command also accepts date ranges: -- ``<{datetime}`` - at or before a given date/time -- ``>{datetime}`` - on or after a given date/time -- ``{datetime} to {datetime}`` - a date range, inclusive -- ``-{days}`` - within a given number of days of today +- ``<DATE`` - at or before a given date/time +- ``>DATE`` - on or after a given date/time +- ``DATE to DATE`` - a date range, inclusive +- ``-DAYS`` - within a given number of days of today
--- a/mercurial/help/environment.txt Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/help/environment.txt Wed May 18 09:56:27 2011 +0200 @@ -59,6 +59,14 @@ Equivalent options set via command line flags or environment variables are not overridden. +HGPLAINEXCEPT + This is a comma-separated list of features to preserve when + HGPLAIN is enabled. Currently the only value supported is "i18n", + which preserves internationalization in plain mode. + + Setting HGPLAINEXCEPT to anything (even an empty string) will + enable plain mode. + HGUSER This is the string used as the author of a commit. If not set, available values will be considered in this order:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/help/hgignore.txt Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,81 @@ +Synopsis +-------- + +The Mercurial system uses a file called ``.hgignore`` in the root +directory of a repository to control its behavior when it searches +for files that it is not currently tracking. + +Description +----------- + +The working directory of a Mercurial repository will often contain +files that should not be tracked by Mercurial. These include backup +files created by editors and build products created by compilers. +These files can be ignored by listing them in a ``.hgignore`` file in +the root of the working directory. The ``.hgignore`` file must be +created manually. It is typically put under version control, so that +the settings will propagate to other repositories with push and pull. + +An untracked file is ignored if its path relative to the repository +root directory, or any prefix path of that path, is matched against +any pattern in ``.hgignore``. + +For example, say we have an untracked file, ``file.c``, at +``a/b/file.c`` inside our repository. Mercurial will ignore ``file.c`` +if any pattern in ``.hgignore`` matches ``a/b/file.c``, ``a/b`` or ``a``. + +In addition, a Mercurial configuration file can reference a set of +per-user or global ignore files. See the +``http://www.selenic.com/mercurial/hgrc.5.html`` man page for details +of how to configure these files. Look for the "ignore" entry in the +"ui" section. + +To control Mercurial's handling of files that it manages, see the +``http://www.selenic.com/mercurial/hg.1.html`` man page. Look for +the ``-I`` and ``-X`` options. + +Syntax +------ + +An ignore file is a plain text file consisting of a list of patterns, +with one pattern per line. Empty lines are skipped. The ``#`` +character is treated as a comment character, and the ``\`` character +is treated as an escape character. + +Mercurial supports several pattern syntaxes. The default syntax used +is Python/Perl-style regular expressions. + +To change the syntax used, use a line of the following form:: + + syntax: NAME + +where ``NAME`` is one of the following: + +``regexp`` + Regular expression, Python/Perl syntax. +``glob`` + Shell-style glob. + +The chosen syntax stays in effect when parsing all patterns that +follow, until another syntax is selected. + +Neither glob nor regexp patterns are rooted. A glob-syntax pattern of +the form ``*.c`` will match a file ending in ``.c`` in any directory, +and a regexp pattern of the form ``\.c$`` will do the same. To root a +regexp pattern, start it with ``^``. + +Example +------- + +Here is an example ignore file. :: + + # use glob syntax. + syntax: glob + + *.elc + *.pyc + *~ + + # switch to regexp syntax. + syntax: regexp + ^\.pc/
--- a/mercurial/help/revsets.txt Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/help/revsets.txt Wed May 18 09:56:27 2011 +0200 @@ -42,11 +42,45 @@ ``x - y`` Changesets in x but not in y. + +``x^n`` + The nth parent of x, n == 0, 1, or 2. + For n == 0, x; for n == 1, the first parent of each changeset in x; + for n == 2, the second parent of changeset in x. + +``x~n`` + The nth first ancestor of x; ``x~0`` is x; ``x~3`` is ``x^^^``. + +There is a single postfix operator: + +``x^`` + Equivalent to ``x^1``, the first parent of each changeset in x. + The following predicates are supported: .. predicatesmarker +New predicates (known as "aliases") can be defined, using any combination of +existing predicates or other aliases. An alias definition looks like:: + + <alias> = <definition> + +in the ``revsetalias`` section of ``.hgrc``. Arguments of the form `$1`, `$2`, +etc. are substituted from the alias into the definition. + +For example, + +:: + + [revsetalias] + h = heads() + d($1) = sort($1, date) + rs($1, $2) = reverse(sort($1, $2)) + +defines three aliases, ``h``, ``d``, and ``rs``. ``rs(0:tip, author)`` is +exactly equivalent to ``reverse(sort(0:tip, author))``. + Command line equivalents for :hg:`log`:: -f -> ::.
--- a/mercurial/help/templates.txt Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/help/templates.txt Wed May 18 09:56:27 2011 +0200 @@ -23,52 +23,7 @@ keywords depends on the exact context of the templater. These keywords are usually available for templating a log-like command: -:author: String. The unmodified author of the changeset. - -:branch: String. The name of the branch on which the changeset was - committed. - -:branches: List of strings. The name of the branch on which the - changeset was committed. Will be empty if the branch name was - default. - -:children: List of strings. The children of the changeset. - -:date: Date information. The date when the changeset was committed. - -:desc: String. The text of the changeset description. - -:diffstat: String. Statistics of changes with the following format: - "modified files: +added/-removed lines" - -:files: List of strings. All files modified, added, or removed by this - changeset. - -:file_adds: List of strings. Files added by this changeset. - -:file_copies: List of strings. Files copied in this changeset with - their sources. - -:file_copies_switch: List of strings. Like "file_copies" but displayed - only if the --copied switch is set. - -:file_mods: List of strings. Files modified by this changeset. - -:file_dels: List of strings. Files removed by this changeset. - -:node: String. The changeset identification hash, as a 40 hexadecimal - digit string. - -:parents: List of strings. The parents of the changeset. - -:rev: Integer. The repository-local changeset revision number. - -:tags: List of strings. Any tags associated with the changeset. - -:latesttag: String. Most recent global tag in the ancestors of this - changeset. - -:latesttagdistance: Integer. Longest path to the latest tag. +.. keywordsmarker The "date" keyword does not produce human-readable output. If you want to use a date in your output, you can use a filter to process @@ -82,82 +37,4 @@ List of filters: -:addbreaks: Any text. Add an XHTML "<br />" tag before the end of - every line except the last. - -:age: Date. Returns a human-readable date/time difference between the - given date/time and the current date/time. - -:basename: Any text. Treats the text as a path, and returns the last - component of the path after splitting by the path separator - (ignoring trailing separators). For example, "foo/bar/baz" becomes - "baz" and "foo/bar//" becomes "bar". - -:stripdir: Treat the text as path and strip a directory level, if - possible. For example, "foo" and "foo/bar" becomes "foo". - -:date: Date. Returns a date in a Unix date format, including the - timezone: "Mon Sep 04 15:13:13 2006 0700". - -:domain: Any text. Finds the first string that looks like an email - address, and extracts just the domain component. Example: ``User - <user@example.com>`` becomes ``example.com``. - -:email: Any text. Extracts the first string that looks like an email - address. Example: ``User <user@example.com>`` becomes - ``user@example.com``. - -:escape: Any text. Replaces the special XML/XHTML characters "&", "<" - and ">" with XML entities. - -:hex: Any text. Convert a binary Mercurial node identifier into - its long hexadecimal representation. - -:fill68: Any text. Wraps the text to fit in 68 columns. - -:fill76: Any text. Wraps the text to fit in 76 columns. - -:firstline: Any text. Returns the first line of text. - -:nonempty: Any text. Returns '(none)' if the string is empty. - -:hgdate: Date. Returns the date as a pair of numbers: "1157407993 - 25200" (Unix timestamp, timezone offset). - -:isodate: Date. Returns the date in ISO 8601 format: "2009-08-18 13:00 - +0200". - -:isodatesec: Date. Returns the date in ISO 8601 format, including - seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date - filter. - -:localdate: Date. Converts a date to local date. - -:obfuscate: Any text. Returns the input text rendered as a sequence of - XML entities. - -:person: Any text. Returns the text before an email address. - -:rfc822date: Date. Returns a date using the same format used in email - headers: "Tue, 18 Aug 2009 13:00:13 +0200". - -:rfc3339date: Date. Returns a date using the Internet date format - specified in RFC 3339: "2009-08-18T13:00:13+02:00". - -:short: Changeset hash. Returns the short form of a changeset hash, - i.e. a 12 hexadecimal digit string. - -:shortdate: Date. Returns a date like "2006-09-18". - -:stringify: Any type. Turns the value into text by converting values into - text and concatenating them. - -:strip: Any text. Strips all leading and trailing whitespace. - -:tabindent: Any text. Returns the text, with every line except the - first starting with a tab character. - -:urlescape: Any text. Escapes all "special" characters. For example, - "foo bar" becomes "foo%20bar". - -:user: Any text. Returns the user portion of an email address. +.. filtersmarker
--- a/mercurial/hg.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/hg.py Wed May 18 09:56:27 2011 +0200 @@ -8,16 +8,16 @@ from i18n import _ from lock import release -from node import hex, nullid, nullrev, short -import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo -import lock, util, extensions, error, encoding, node -import cmdutil, discovery, url +from node import hex, nullid +import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo, bookmarks +import lock, util, extensions, error, node +import cmdutil, discovery import merge as mergemod import verify as verifymod import errno, os, shutil def _local(path): - path = util.expandpath(util.drop_scheme('file', path)) + path = util.expandpath(util.localpath(path)) return (os.path.isfile(path) and bundlerepo or localrepo) def addbranchrevs(lrepo, repo, branches, revs): @@ -51,13 +51,15 @@ revs.append(hashbranch) return revs, revs[0] -def parseurl(url, branches=None): +def parseurl(path, branches=None): '''parse url#branch, returning (url, (branch, branches))''' - if '#' not in url: - return url, (None, branches or []) - url, branch = url.split('#', 1) - return url, (branch, branches or []) + u = util.url(path) + branch = None + if u.fragment: + branch = u.fragment + u.fragment = None + return str(u), (branch, branches or []) schemes = { 'bundle': bundlerepo, @@ -69,11 +71,8 @@ } def _lookup(path): - scheme = 'file' - if path: - c = path.find(':') - if c > 0: - scheme = path[:c] + u = util.url(path) + scheme = u.scheme or 'file' thing = schemes.get(scheme) or schemes['file'] try: return thing(path) @@ -103,15 +102,6 @@ '''return default destination of clone if none is given''' return os.path.basename(os.path.normpath(source)) -def localpath(path): - if path.startswith('file://localhost/'): - return path[16:] - if path.startswith('file://'): - return path[7:] - if path.startswith('file:'): - return path[5:] - return path - def share(ui, source, dest=None, update=True): '''create a shared repository''' @@ -143,26 +133,27 @@ if not os.path.isdir(root): os.mkdir(root) - os.mkdir(roothg) + util.makedir(roothg, notindexed=True) requirements = '' try: - requirements = srcrepo.opener('requires').read() + requirements = srcrepo.opener.read('requires') except IOError, inst: if inst.errno != errno.ENOENT: raise requirements += 'shared\n' - file(os.path.join(roothg, 'requires'), 'w').write(requirements) - file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath) + util.writefile(os.path.join(roothg, 'requires'), requirements) + util.writefile(os.path.join(roothg, 'sharedpath'), sharedpath) + + r = repository(ui, root) default = srcrepo.ui.config('paths', 'default') if default: - f = file(os.path.join(roothg, 'hgrc'), 'w') - f.write('[paths]\ndefault = %s\n' % default) - f.close() - - r = repository(ui, root) + fp = r.opener("hgrc", "w", text=True) + fp.write("[paths]\n") + fp.write("default = %s\n" % default) + fp.close() if update: r.ui.status(_("updating working directory\n")) @@ -231,8 +222,8 @@ else: dest = ui.expandpath(dest) - dest = localpath(dest) - source = localpath(source) + dest = util.localpath(dest) + source = util.localpath(source) if os.path.exists(dest): if not os.path.isdir(dest): @@ -258,7 +249,7 @@ abspath = origsource copy = False if src_repo.cancopy() and islocal(dest): - abspath = os.path.abspath(util.drop_scheme('file', origsource)) + abspath = os.path.abspath(util.localpath(origsource)) copy = not pull and not rev if copy: @@ -281,7 +272,7 @@ dir_cleanup.dir_ = hgdir try: dest_path = hgdir - os.mkdir(dest_path) + util.makedir(dest_path, notindexed=True) except OSError, inst: if inst.errno == errno.EEXIST: dir_cleanup.close() @@ -366,6 +357,21 @@ dest_repo.ui.status(_("updating to branch %s\n") % bn) _update(dest_repo, uprev) + # clone all bookmarks + if dest_repo.local() and src_repo.capable("pushkey"): + rb = src_repo.listkeys('bookmarks') + for k, n in rb.iteritems(): + try: + m = dest_repo.lookup(n) + dest_repo._bookmarks[k] = m + except error.RepoLookupError: + pass + if rb: + bookmarks.write(dest_repo) + elif src_repo.local() and dest_repo.capable("pushkey"): + for k, n in src_repo._bookmarks.iteritems(): + dest_repo.pushkey('bookmarks', k, '', hex(n)) + return src_repo, dest_repo finally: release(src_lock, dest_lock) @@ -416,19 +422,18 @@ """ source, branches = parseurl(ui.expandpath(source), opts.get('branch')) other = repository(remoteui(repo, opts), source) - ui.status(_('comparing with %s\n') % url.hidepassword(source)) + ui.status(_('comparing with %s\n') % util.hidepassword(source)) revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev')) if revs: revs = [other.lookup(rev) for rev in revs] - other, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other, revs, - opts["bundle"], opts["force"]) - if incoming is None: - ui.status(_("no changes found\n")) - return subreporecurse() + other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other, + revs, opts["bundle"], opts["force"]) + try: + if not chlist: + ui.status(_("no changes found\n")) + return subreporecurse() - try: - chlist = other.changelog.nodesbetween(incoming, revs)[0] displayer = cmdutil.show_changeset(ui, other, opts, buffered) # XXX once graphlog extension makes it into core, @@ -437,10 +442,7 @@ displayer.close() finally: - if hasattr(other, 'close'): - other.close() - if bundle: - os.unlink(bundle) + cleanupfn() subreporecurse() return 0 # exit code is zero since we found incoming changes @@ -472,18 +474,19 @@ def _outgoing(ui, repo, dest, opts): dest = ui.expandpath(dest or 'default-push', dest or 'default') dest, branches = parseurl(dest, opts.get('branch')) - ui.status(_('comparing with %s\n') % url.hidepassword(dest)) + ui.status(_('comparing with %s\n') % util.hidepassword(dest)) revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev')) if revs: revs = [repo.lookup(rev) for rev in revs] other = repository(remoteui(repo, opts), dest) - o = discovery.findoutgoing(repo, other, force=opts.get('force')) + common, outheads = discovery.findcommonoutgoing(repo, other, revs, + force=opts.get('force')) + o = repo.changelog.findmissing(common, outheads) if not o: ui.status(_("no changes found\n")) return None - - return repo.changelog.nodesbetween(o, revs)[0] + return o def outgoing(ui, repo, dest, opts): def recurse():
--- a/mercurial/hgweb/common.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/hgweb/common.py Wed May 18 09:56:27 2011 +0200 @@ -17,12 +17,6 @@ HTTP_METHOD_NOT_ALLOWED = 405 HTTP_SERVER_ERROR = 500 -# Hooks for hgweb permission checks; extensions can add hooks here. Each hook -# is invoked like this: hook(hgweb, request, operation), where operation is -# either read, pull or push. Hooks should either raise an ErrorResponse -# exception, or just return. -# It is possible to do both authentication and authorization through this. -permhooks = [] def checkauthz(hgweb, req, op): '''Check permission for operation based on request data (including @@ -65,18 +59,43 @@ if not result: raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized') -# Add the default permhook, which provides simple authorization. -permhooks.append(checkauthz) +# Hooks for hgweb permission checks; extensions can add hooks here. +# Each hook is invoked like this: hook(hgweb, request, operation), +# where operation is either read, pull or push. Hooks should either +# raise an ErrorResponse exception, or just return. +# +# It is possible to do both authentication and authorization through +# this. +permhooks = [checkauthz] class ErrorResponse(Exception): def __init__(self, code, message=None, headers=[]): if message is None: message = _statusmessage(code) - Exception.__init__(self, code, message) + Exception.__init__(self) self.code = code self.message = message self.headers = headers + def __str__(self): + return self.message + +class continuereader(object): + def __init__(self, f, write): + self.f = f + self._write = write + self.continued = False + + def read(self, amt=-1): + if not self.continued: + self.continued = True + self._write('HTTP/1.1 100 Continue\r\n\r\n') + return self.f.read(amt) + + def __getattr__(self, attr): + if attr in ('close', 'readline', 'readlines', '__iter__'): + return getattr(self.f, attr) + raise AttributeError() def _statusmessage(code): from BaseHTTPServer import BaseHTTPRequestHandler
--- a/mercurial/hgweb/hgweb_mod.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/hgweb/hgweb_mod.py Wed May 18 09:56:27 2011 +0200 @@ -17,6 +17,7 @@ perms = { 'changegroup': 'pull', 'changegroupsubset': 'pull', + 'getbundle': 'pull', 'stream_out': 'pull', 'listkeys': 'pull', 'unbundle': 'push', @@ -125,7 +126,11 @@ self.check_perm(req, perms[cmd]) return protocol.call(self.repo, req, cmd) except ErrorResponse, inst: - if cmd == 'unbundle': + # 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'): req.drain() req.respond(inst, protocol.HGTYPE) return '0\n%s\n' % inst.message @@ -183,7 +188,8 @@ req.form['cmd'] = [tmpl.cache['default']] cmd = req.form['cmd'][0] - caching(self, req) # sets ETag header or raises NOT_MODIFIED + if self.configbool('web', 'cache', True): + caching(self, req) # sets ETag header or raises NOT_MODIFIED if cmd not in webcommands.__all__: msg = 'no such method: %s' % cmd raise ErrorResponse(HTTP_BAD_REQUEST, msg) @@ -228,6 +234,7 @@ port = req.env["SERVER_PORT"] port = port != default_port and (":" + port) or "" urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port) + logourl = self.config("web", "logourl", "http://mercurial.selenic.com/") staticurl = self.config("web", "staticurl") or req.url + 'static/' if not staticurl.endswith('/'): staticurl += '/' @@ -267,6 +274,7 @@ tmpl = templater.templater(mapfile, defaults={"url": req.url, + "logourl": logourl, "staticurl": staticurl, "urlbase": urlbase, "repo": self.reponame,
--- a/mercurial/hgweb/hgwebdir_mod.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/hgweb/hgwebdir_mod.py Wed May 18 09:56:27 2011 +0200 @@ -6,9 +6,9 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import os, re, time, urlparse +import os, re, time from mercurial.i18n import _ -from mercurial import ui, hg, util, templater +from mercurial import ui, hg, scmutil, util, templater from mercurial import error, encoding from common import ErrorResponse, get_mtime, staticfile, paritygen, \ get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR @@ -33,16 +33,17 @@ repos.append((prefix, root)) continue roothead = os.path.normpath(os.path.abspath(roothead)) - paths = util.walkrepos(roothead, followsym=True, recurse=recurse) + paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse) repos.extend(urlrepos(prefix, roothead, paths)) return repos def urlrepos(prefix, roothead, paths): """yield url paths and filesystem paths from a list of repo paths - >>> list(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt'])) + >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq] + >>> conv(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt'])) [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')] - >>> list(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt'])) + >>> conv(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt'])) [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')] """ for path in paths: @@ -76,7 +77,10 @@ if not os.path.exists(self.conf): raise util.Abort(_('config file %s not found!') % self.conf) u.readconfig(self.conf, remap=map, trust=True) - paths = u.configitems('hgweb-paths') + paths = [] + for name, ignored in u.configitems('hgweb-paths'): + for path in u.configlist('hgweb-paths', name): + paths.append((name, path)) elif isinstance(self.conf, (list, tuple)): paths = self.conf elif isinstance(self.conf, dict): @@ -85,7 +89,7 @@ repos = findrepos(paths) for prefix, root in u.configitems('collections'): prefix = util.pconvert(prefix) - for path in util.walkrepos(root, followsym=True): + for path in scmutil.walkrepos(root, followsym=True): repo = os.path.normpath(path) name = util.pconvert(repo) if name.startswith(prefix): @@ -247,6 +251,9 @@ # update time with local timezone try: r = hg.repository(self.ui, path) + except IOError: + u.warn(_('error accessing repository at %s\n') % path) + continue except error.RepoError: u.warn(_('error accessing repository at %s\n') % path) continue @@ -340,6 +347,7 @@ start = url[-1] == '?' and '&' or '?' sessionvars = webutil.sessionvars(vars, start) + logourl = config('web', 'logourl', 'http://mercurial.selenic.com/') staticurl = config('web', 'staticurl') or url + 'static/' if not staticurl.endswith('/'): staticurl += '/' @@ -349,22 +357,15 @@ "footer": footer, "motd": motd, "url": url, + "logourl": logourl, "staticurl": staticurl, "sessionvars": sessionvars}) return tmpl def updatereqenv(self, env): - def splitnetloc(netloc): - if ':' in netloc: - return netloc.split(':', 1) - else: - return (netloc, None) - if self._baseurl is not None: - urlcomp = urlparse.urlparse(self._baseurl) - host, port = splitnetloc(urlcomp[1]) - path = urlcomp[2] - env['SERVER_NAME'] = host - if port: - env['SERVER_PORT'] = port - env['SCRIPT_NAME'] = path + u = util.url(self._baseurl) + env['SERVER_NAME'] = u.host + if u.port: + env['SERVER_PORT'] = u.port + env['SCRIPT_NAME'] = '/' + u.path
--- a/mercurial/hgweb/protocol.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/hgweb/protocol.py Wed May 18 09:56:27 2011 +0200 @@ -5,7 +5,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import cStringIO, zlib, sys, urllib +import cgi, cStringIO, zlib, sys, urllib from mercurial import util, wireproto from common import HTTP_OK @@ -16,18 +16,31 @@ self.req = req self.response = '' def getargs(self, args): + knownargs = self._args() data = {} keys = args.split() for k in keys: if k == '*': star = {} - for key in self.req.form.keys(): - if key not in keys: - star[key] = self.req.form[key][0] + for key in knownargs.keys(): + if key != 'cmd' and key not in keys: + star[key] = knownargs[key][0] data['*'] = star else: - data[k] = self.req.form[k][0] + data[k] = knownargs[k][0] return [data[k] for k in keys] + def _args(self): + args = self.req.form.copy() + chunks = [] + i = 1 + while 1: + h = self.req.env.get('HTTP_X_HGARG_' + str(i)) + if h is None: + break + chunks += [h] + i += 1 + args.update(cgi.parse_qs(''.join(chunks), keep_blank_values=True)) + return args def getfile(self, fp): length = int(self.req.env['CONTENT_LENGTH']) for s in util.filechunkiter(self.req, limit=length):
--- a/mercurial/hgweb/server.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/hgweb/server.py Wed May 18 09:56:27 2011 +0200 @@ -8,6 +8,7 @@ import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback from mercurial import util, error +from mercurial.hgweb import common from mercurial.i18n import _ def _splitURI(uri): @@ -58,6 +59,12 @@ def log_message(self, format, *args): self._log_any(self.server.accesslog, format, *args) + def log_request(self, code='-', size='-'): + xheaders = [h for h in self.headers.items() if h[0].startswith('x-')] + self.log_message('"%s" %s %s%s', + self.requestline, str(code), str(size), + ''.join([' %s:%s' % h for h in sorted(xheaders)])) + def do_write(self): try: self.do_hgweb() @@ -111,6 +118,9 @@ env['SERVER_PROTOCOL'] = self.request_version env['wsgi.version'] = (1, 0) env['wsgi.url_scheme'] = self.url_scheme + if env.get('HTTP_EXPECT', '').lower() == '100-continue': + self.rfile = common.continuereader(self.rfile, self.wfile.write) + env['wsgi.input'] = self.rfile env['wsgi.errors'] = _error_logger(self) env['wsgi.multithread'] = isinstance(self.server,
--- a/mercurial/hgweb/webcommands.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/hgweb/webcommands.py Wed May 18 09:56:27 2011 +0200 @@ -80,7 +80,7 @@ path=webutil.up(f), text=lines(), rev=fctx.rev(), - node=hex(fctx.node()), + node=fctx.hex(), author=fctx.user(), date=fctx.date(), desc=fctx.description(), @@ -108,10 +108,11 @@ revcount = web.maxchanges if 'revcount' in req.form: revcount = int(req.form.get('revcount', [revcount])[0]) + revcount = max(revcount, 1) tmpl.defaults['sessionvars']['revcount'] = revcount lessvars = copy.copy(tmpl.defaults['sessionvars']) - lessvars['revcount'] = revcount / 2 + lessvars['revcount'] = max(revcount / 2, 1) lessvars['rev'] = query morevars = copy.copy(tmpl.defaults['sessionvars']) morevars['revcount'] = revcount * 2 @@ -220,10 +221,11 @@ revcount = shortlog and web.maxshortchanges or web.maxchanges if 'revcount' in req.form: revcount = int(req.form.get('revcount', [revcount])[0]) + revcount = max(revcount, 1) tmpl.defaults['sessionvars']['revcount'] = revcount lessvars = copy.copy(tmpl.defaults['sessionvars']) - lessvars['revcount'] = revcount / 2 + lessvars['revcount'] = max(revcount / 2, 1) morevars = copy.copy(tmpl.defaults['sessionvars']) morevars['revcount'] = revcount * 2 @@ -237,7 +239,7 @@ changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx) return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav, - node=hex(ctx.node()), rev=pos, changesets=count, + node=ctx.hex(), rev=pos, changesets=count, entries=lambda **x: changelist(limit=0,**x), latestentry=lambda **x: changelist(limit=1,**x), archives=web.archivelist("tip"), revcount=revcount, @@ -393,14 +395,11 @@ def bookmarks(web, req, tmpl): i = web.repo._bookmarks.items() - i.reverse() parity = paritygen(web.stripecount) - def entries(notip=False, limit=0, **map): + def entries(limit=0, **map): count = 0 - for k, n in i: - if notip and k == "tip": - continue + for k, n in sorted(i): if limit > 0 and count >= limit: continue count = count + 1 @@ -411,9 +410,8 @@ return tmpl("bookmarks", node=hex(web.repo.changelog.tip()), - entries=lambda **x: entries(False, 0, **x), - entriesnotip=lambda **x: entries(True, 0, **x), - latestentry=lambda **x: entries(True, 1, **x)) + entries=lambda **x: entries(0, **x), + latestentry=lambda **x: entries(1, **x)) def branches(web, req, tmpl): tips = (web.repo[n] for t, n in web.repo.branchtags().iteritems()) @@ -464,6 +462,15 @@ node=hex(n), date=web.repo[n].date()) + def bookmarks(**map): + parity = paritygen(web.stripecount) + b = web.repo._bookmarks.items() + for k, n in sorted(b)[:10]: # limit to 10 bookmarks + yield {'parity': parity.next(), + 'bookmark': k, + 'date': web.repo[n].date(), + 'node': hex(n)} + def branches(**map): parity = paritygen(web.stripecount) @@ -508,6 +515,7 @@ owner=get_contact(web.config) or "unknown", lastchange=tip.date(), tags=tagentries, + bookmarks=bookmarks, branches=branches, shortlog=changelist, node=tip.hex(), @@ -574,7 +582,7 @@ last = fnode yield {"parity": parity.next(), - "node": hex(f.node()), + "node": f.hex(), "rev": f.rev(), "author": f.user(), "desc": f.description(), @@ -590,7 +598,7 @@ annotate=annotate, path=webutil.up(f), rev=fctx.rev(), - node=hex(fctx.node()), + node=fctx.hex(), author=fctx.user(), date=fctx.date(), desc=fctx.description(), @@ -624,10 +632,11 @@ revcount = web.maxshortchanges if 'revcount' in req.form: revcount = int(req.form.get('revcount', [revcount])[0]) + revcount = max(revcount, 1) tmpl.defaults['sessionvars']['revcount'] = revcount lessvars = copy.copy(tmpl.defaults['sessionvars']) - lessvars['revcount'] = revcount / 2 + lessvars['revcount'] = max(revcount / 2, 1) morevars = copy.copy(tmpl.defaults['sessionvars']) morevars['revcount'] = revcount * 2 @@ -646,7 +655,7 @@ l.insert(0, {"parity": parity.next(), "filerev": i, "file": f, - "node": hex(iterfctx.node()), + "node": iterfctx.hex(), "author": iterfctx.user(), "date": iterfctx.date(), "rename": webutil.renamelink(iterfctx), @@ -668,7 +677,7 @@ nodefunc = lambda x: fctx.filectx(fileid=x) nav = webutil.revnavgen(end - 1, revcount, count, nodefunc) - return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav, + return tmpl("filelog", file=f, node=fctx.hex(), nav=nav, entries=lambda **x: entries(limit=0, **x), latestentry=lambda **x: entries(limit=1, **x), revcount=revcount, morevars=morevars, lessvars=lessvars) @@ -725,10 +734,11 @@ revcount = web.maxshortchanges if 'revcount' in req.form: revcount = int(req.form.get('revcount', [revcount])[0]) + revcount = max(revcount, 1) tmpl.defaults['sessionvars']['revcount'] = revcount lessvars = copy.copy(tmpl.defaults['sessionvars']) - lessvars['revcount'] = revcount / 2 + lessvars['revcount'] = max(revcount / 2, 1) morevars = copy.copy(tmpl.defaults['sessionvars']) morevars['revcount'] = revcount * 2 @@ -745,14 +755,14 @@ if rev < web.maxshortchanges: startrev = uprev - dag = graphmod.revisions(web.repo, startrev, downrev) + dag = graphmod.dagwalker(web.repo, range(startrev, downrev - 1, -1)) tree = list(graphmod.colored(dag)) canvasheight = (len(tree) + 1) * bg_height - 27 data = [] for (id, type, ctx, vtx, edges) in tree: if type != graphmod.CHANGESET: continue - node = short(ctx.node()) + node = str(ctx) age = templatefilters.age(ctx.date()) desc = templatefilters.firstline(ctx.description()) desc = cgi.escape(templatefilters.nonempty(desc)) @@ -780,8 +790,6 @@ topicname = req.form.get('node', [None])[0] if not topicname: - topic = [] - def topics(**map): for entries, summary, _ in helpmod.helptable: entries = sorted(entries, key=len)
--- a/mercurial/hgweb/webutil.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/hgweb/webutil.py Wed May 18 09:56:27 2011 +0200 @@ -7,7 +7,7 @@ # GNU General Public License version 2 or any later version. import os, copy -from mercurial import match, patch, util, error, ui +from mercurial import match, patch, scmutil, error, ui from mercurial.node import hex, nullid def up(p): @@ -66,7 +66,7 @@ if len(siblings) == 1 and siblings[0].rev() == hiderev: return for s in siblings: - d = {'node': hex(s.node()), 'rev': s.rev()} + d = {'node': s.hex(), 'rev': s.rev()} d['user'] = s.user() d['date'] = s.date() d['description'] = s.description() @@ -127,7 +127,7 @@ def cleanpath(repo, path): path = path.lstrip('/') - return util.canonpath(repo.root, '', path) + return scmutil.canonpath(repo.root, '', path) def changectx(repo, req): changeid = "tip"
--- a/mercurial/hgweb/wsgicgi.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/hgweb/wsgicgi.py Wed May 18 09:56:27 2011 +0200 @@ -10,10 +10,11 @@ import os, sys from mercurial import util +from mercurial.hgweb import common def launch(application): - util.set_binary(sys.stdin) - util.set_binary(sys.stdout) + util.setbinary(sys.stdin) + util.setbinary(sys.stdout) environ = dict(os.environ.iteritems()) environ.setdefault('PATH_INFO', '') @@ -23,7 +24,11 @@ if environ['PATH_INFO'].startswith(scriptname): environ['PATH_INFO'] = environ['PATH_INFO'][len(scriptname):] - environ['wsgi.input'] = sys.stdin + stdin = sys.stdin + if environ.get('HTTP_EXPECT', '').lower() == '100-continue': + stdin = common.continuereader(stdin, sys.stdout.write) + + environ['wsgi.input'] = stdin environ['wsgi.errors'] = sys.stderr environ['wsgi.version'] = (1, 0) environ['wsgi.multithread'] = False
--- a/mercurial/hook.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/hook.py Wed May 18 09:56:27 2011 +0200 @@ -109,7 +109,7 @@ else: r = util.system(cmd, environ=env, cwd=cwd) if r: - desc, r = util.explain_exit(r) + desc, r = util.explainexit(r) if throw: raise util.Abort(_('%s hook %s') % (name, desc)) ui.warn(_('warning: %s hook %s\n') % (name, desc))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/httpclient/__init__.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,665 @@ +# Copyright 2010, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Improved HTTP/1.1 client library + +This library contains an HTTPConnection which is similar to the one in +httplib, but has several additional features: + + * supports keepalives natively + * uses select() to block for incoming data + * notices when the server responds early to a request + * implements ssl inline instead of in a different class +""" + +import cStringIO +import errno +import httplib +import logging +import rfc822 +import select +import socket + +import socketutil + +logger = logging.getLogger(__name__) + +__all__ = ['HTTPConnection', 'HTTPResponse'] + +HTTP_VER_1_0 = 'HTTP/1.0' +HTTP_VER_1_1 = 'HTTP/1.1' + +_LEN_CLOSE_IS_END = -1 + +OUTGOING_BUFFER_SIZE = 1 << 15 +INCOMING_BUFFER_SIZE = 1 << 20 + +HDR_ACCEPT_ENCODING = 'accept-encoding' +HDR_CONNECTION_CTRL = 'connection' +HDR_CONTENT_LENGTH = 'content-length' +HDR_XFER_ENCODING = 'transfer-encoding' + +XFER_ENCODING_CHUNKED = 'chunked' + +CONNECTION_CLOSE = 'close' + +EOL = '\r\n' +_END_HEADERS = EOL * 2 + +# Based on some searching around, 1 second seems like a reasonable +# default here. +TIMEOUT_ASSUME_CONTINUE = 1 +TIMEOUT_DEFAULT = None + + +class HTTPResponse(object): + """Response from an HTTP server. + + The response will continue to load as available. If you need the + complete response before continuing, check the .complete() method. + """ + def __init__(self, sock, timeout): + self.sock = sock + self.raw_response = '' + self._body = None + self._headers_len = 0 + self._content_len = 0 + self.headers = None + self.will_close = False + self.status_line = '' + self.status = None + self.http_version = None + self.reason = None + self._chunked = False + self._chunked_done = False + self._chunked_until_next = 0 + self._chunked_skip_bytes = 0 + self._chunked_preloaded_block = None + + self._read_location = 0 + self._eol = EOL + + self._timeout = timeout + + @property + def _end_headers(self): + return self._eol * 2 + + def complete(self): + """Returns true if this response is completely loaded. + """ + if self._chunked: + return self._chunked_done + if self._content_len == _LEN_CLOSE_IS_END: + return False + return self._body is not None and len(self._body) >= self._content_len + + def readline(self): + """Read a single line from the response body. + + This may block until either a line ending is found or the + response is complete. + """ + eol = self._body.find('\n', self._read_location) + while eol == -1 and not self.complete(): + self._select() + eol = self._body.find('\n', self._read_location) + if eol != -1: + eol += 1 + else: + eol = len(self._body) + data = self._body[self._read_location:eol] + self._read_location = eol + return data + + def read(self, length=None): + # if length is None, unbounded read + while (not self.complete() # never select on a finished read + and (not length # unbounded, so we wait for complete() + or (self._read_location + length) > len(self._body))): + self._select() + if not length: + length = len(self._body) - self._read_location + elif len(self._body) < (self._read_location + length): + length = len(self._body) - self._read_location + r = self._body[self._read_location:self._read_location + length] + self._read_location += len(r) + if self.complete() and self.will_close: + self.sock.close() + return r + + def _select(self): + r, _, _ = select.select([self.sock], [], [], self._timeout) + if not r: + # socket was not readable. If the response is not complete + # and we're not a _LEN_CLOSE_IS_END response, raise a timeout. + # If we are a _LEN_CLOSE_IS_END response and we have no data, + # raise a timeout. + if not (self.complete() or + (self._content_len == _LEN_CLOSE_IS_END and self._body)): + logger.info('timed out with timeout of %s', self._timeout) + raise HTTPTimeoutException('timeout reading data') + logger.info('cl: %r body: %r', self._content_len, self._body) + try: + data = self.sock.recv(INCOMING_BUFFER_SIZE) + except socket.sslerror, e: + if e.args[0] != socket.SSL_ERROR_WANT_READ: + raise + logger.debug('SSL_WANT_READ in _select, should retry later') + return True + logger.debug('response read %d data during _select', len(data)) + if not data: + if not self.headers: + self._load_response(self._end_headers) + self._content_len = 0 + elif self._content_len == _LEN_CLOSE_IS_END: + self._content_len = len(self._body) + return False + else: + self._load_response(data) + return True + + def _chunked_parsedata(self, data): + if self._chunked_preloaded_block: + data = self._chunked_preloaded_block + data + self._chunked_preloaded_block = None + while data: + logger.debug('looping with %d data remaining', len(data)) + # Slice out anything we should skip + if self._chunked_skip_bytes: + if len(data) <= self._chunked_skip_bytes: + self._chunked_skip_bytes -= len(data) + data = '' + break + else: + data = data[self._chunked_skip_bytes:] + self._chunked_skip_bytes = 0 + + # determine how much is until the next chunk + if self._chunked_until_next: + amt = self._chunked_until_next + logger.debug('reading remaining %d of existing chunk', amt) + self._chunked_until_next = 0 + body = data + else: + try: + amt, body = data.split(self._eol, 1) + except ValueError: + self._chunked_preloaded_block = data + logger.debug('saving %r as a preloaded block for chunked', + self._chunked_preloaded_block) + return + amt = int(amt, base=16) + logger.debug('reading chunk of length %d', amt) + if amt == 0: + self._chunked_done = True + + # read through end of what we have or the chunk + self._body += body[:amt] + if len(body) >= amt: + data = body[amt:] + self._chunked_skip_bytes = len(self._eol) + else: + self._chunked_until_next = amt - len(body) + self._chunked_skip_bytes = 0 + data = '' + + def _load_response(self, data): + if self._chunked: + self._chunked_parsedata(data) + return + elif self._body is not None: + self._body += data + return + + # We haven't seen end of headers yet + self.raw_response += data + # This is a bogus server with bad line endings + if self._eol not in self.raw_response: + for bad_eol in ('\n', '\r'): + if (bad_eol in self.raw_response + # verify that bad_eol is not the end of the incoming data + # as this could be a response line that just got + # split between \r and \n. + and (self.raw_response.index(bad_eol) < + (len(self.raw_response) - 1))): + logger.info('bogus line endings detected, ' + 'using %r for EOL', bad_eol) + self._eol = bad_eol + break + # exit early if not at end of headers + if self._end_headers not in self.raw_response or self.headers: + return + + # handle 100-continue response + hdrs, body = self.raw_response.split(self._end_headers, 1) + http_ver, status = hdrs.split(' ', 1) + if status.startswith('100'): + self.raw_response = body + logger.debug('continue seen, setting body to %r', body) + return + + # arriving here means we should parse response headers + # as all headers have arrived completely + hdrs, body = self.raw_response.split(self._end_headers, 1) + del self.raw_response + if self._eol in hdrs: + self.status_line, hdrs = hdrs.split(self._eol, 1) + else: + self.status_line = hdrs + hdrs = '' + # TODO HTTP < 1.0 support + (self.http_version, self.status, + self.reason) = self.status_line.split(' ', 2) + self.status = int(self.status) + if self._eol != EOL: + hdrs = hdrs.replace(self._eol, '\r\n') + headers = rfc822.Message(cStringIO.StringIO(hdrs)) + if HDR_CONTENT_LENGTH in headers: + self._content_len = int(headers[HDR_CONTENT_LENGTH]) + if self.http_version == HTTP_VER_1_0: + self.will_close = True + elif HDR_CONNECTION_CTRL in headers: + self.will_close = ( + headers[HDR_CONNECTION_CTRL].lower() == CONNECTION_CLOSE) + if self._content_len == 0: + self._content_len = _LEN_CLOSE_IS_END + if (HDR_XFER_ENCODING in headers + and headers[HDR_XFER_ENCODING].lower() == XFER_ENCODING_CHUNKED): + self._body = '' + self._chunked_parsedata(body) + self._chunked = True + if self._body is None: + self._body = body + self.headers = headers + + +class HTTPConnection(object): + """Connection to a single http server. + + Supports 100-continue and keepalives natively. Uses select() for + non-blocking socket operations. + """ + http_version = HTTP_VER_1_1 + response_class = HTTPResponse + + def __init__(self, host, port=None, use_ssl=None, ssl_validator=None, + timeout=TIMEOUT_DEFAULT, + continue_timeout=TIMEOUT_ASSUME_CONTINUE, + proxy_hostport=None, **ssl_opts): + """Create a new HTTPConnection. + + Args: + host: The host to which we'll connect. + port: Optional. The port over which we'll connect. Default 80 for + non-ssl, 443 for ssl. + use_ssl: Optional. Wether to use ssl. Defaults to False if port is + not 443, true if port is 443. + ssl_validator: a function(socket) to validate the ssl cert + timeout: Optional. Connection timeout, default is TIMEOUT_DEFAULT. + continue_timeout: Optional. Timeout for waiting on an expected + "100 Continue" response. Default is TIMEOUT_ASSUME_CONTINUE. + proxy_hostport: Optional. Tuple of (host, port) to use as an http + proxy for the connection. Default is to not use a proxy. + """ + if port is None and host.count(':') == 1 or ']:' in host: + host, port = host.rsplit(':', 1) + port = int(port) + if '[' in host: + host = host[1:-1] + if use_ssl is None and port is None: + use_ssl = False + port = 80 + elif use_ssl is None: + use_ssl = (port == 443) + elif port is None: + port = (use_ssl and 443 or 80) + self.port = port + if use_ssl and not socketutil.have_ssl: + raise Exception('ssl requested but unavailable on this Python') + self.ssl = use_ssl + self.ssl_opts = ssl_opts + self._ssl_validator = ssl_validator + self.host = host + self.sock = None + self._current_response = None + self._current_response_taken = False + if proxy_hostport is None: + self._proxy_host = self._proxy_port = None + else: + self._proxy_host, self._proxy_port = proxy_hostport + + self.timeout = timeout + self.continue_timeout = continue_timeout + + def _connect(self): + """Connect to the host and port specified in __init__.""" + if self.sock: + return + if self._proxy_host is not None: + logger.info('Connecting to http proxy %s:%s', + self._proxy_host, self._proxy_port) + sock = socketutil.create_connection((self._proxy_host, + self._proxy_port)) + if self.ssl: + # TODO proxy header support + data = self.buildheaders('CONNECT', '%s:%d' % (self.host, + self.port), + {}, HTTP_VER_1_0) + sock.send(data) + sock.setblocking(0) + r = self.response_class(sock, self.timeout) + timeout_exc = HTTPTimeoutException( + 'Timed out waiting for CONNECT response from proxy') + while not r.complete(): + try: + if not r._select(): + raise timeout_exc + except HTTPTimeoutException: + # This raise/except pattern looks goofy, but + # _select can raise the timeout as well as the + # loop body. I wish it wasn't this convoluted, + # but I don't have a better solution + # immediately handy. + raise timeout_exc + if r.status != 200: + raise HTTPProxyConnectFailedException( + 'Proxy connection failed: %d %s' % (r.status, + r.read())) + logger.info('CONNECT (for SSL) to %s:%s via proxy succeeded.', + self.host, self.port) + else: + sock = socketutil.create_connection((self.host, self.port)) + if self.ssl: + logger.debug('wrapping socket for ssl with options %r', + self.ssl_opts) + sock = socketutil.wrap_socket(sock, **self.ssl_opts) + if self._ssl_validator: + self._ssl_validator(sock) + sock.setblocking(0) + self.sock = sock + + def buildheaders(self, method, path, headers, http_ver): + if self.ssl and self.port == 443 or self.port == 80: + # default port for protocol, so leave it out + hdrhost = self.host + else: + # include nonstandard port in header + if ':' in self.host: # must be IPv6 + hdrhost = '[%s]:%d' % (self.host, self.port) + else: + hdrhost = '%s:%d' % (self.host, self.port) + if self._proxy_host and not self.ssl: + # When talking to a regular http proxy we must send the + # full URI, but in all other cases we must not (although + # technically RFC 2616 says servers must accept our + # request if we screw up, experimentally few do that + # correctly.) + assert path[0] == '/', 'path must start with a /' + path = 'http://%s%s' % (hdrhost, path) + outgoing = ['%s %s %s%s' % (method, path, http_ver, EOL)] + headers['host'] = ('Host', hdrhost) + headers[HDR_ACCEPT_ENCODING] = (HDR_ACCEPT_ENCODING, 'identity') + for hdr, val in headers.itervalues(): + outgoing.append('%s: %s%s' % (hdr, val, EOL)) + outgoing.append(EOL) + return ''.join(outgoing) + + def close(self): + """Close the connection to the server. + + This is a no-op if the connection is already closed. The + connection may automatically close if requessted by the server + or required by the nature of a response. + """ + if self.sock is None: + return + self.sock.close() + self.sock = None + logger.info('closed connection to %s on %s', self.host, self.port) + + def busy(self): + """Returns True if this connection object is currently in use. + + If a response is still pending, this will return True, even if + the request has finished sending. In the future, + HTTPConnection may transparently juggle multiple connections + to the server, in which case this will be useful to detect if + any of those connections is ready for use. + """ + cr = self._current_response + if cr is not None: + if self._current_response_taken: + if cr.will_close: + self.sock = None + self._current_response = None + return False + elif cr.complete(): + self._current_response = None + return False + return True + return False + + def request(self, method, path, body=None, headers={}, + expect_continue=False): + """Send a request to the server. + + For increased flexibility, this does not return the response + object. Future versions of HTTPConnection that juggle multiple + sockets will be able to send (for example) 5 requests all at + once, and then let the requests arrive as data is + available. Use the `getresponse()` method to retrieve the + response. + """ + if self.busy(): + raise httplib.CannotSendRequest( + 'Can not send another request before ' + 'current response is read!') + self._current_response_taken = False + + logger.info('sending %s request for %s to %s on port %s', + method, path, self.host, self.port) + hdrs = dict((k.lower(), (k, v)) for k, v in headers.iteritems()) + if hdrs.get('expect', ('', ''))[1].lower() == '100-continue': + expect_continue = True + elif expect_continue: + hdrs['expect'] = ('Expect', '100-Continue') + + chunked = False + if body and HDR_CONTENT_LENGTH not in hdrs: + if getattr(body, '__len__', False): + hdrs[HDR_CONTENT_LENGTH] = (HDR_CONTENT_LENGTH, len(body)) + elif getattr(body, 'read', False): + hdrs[HDR_XFER_ENCODING] = (HDR_XFER_ENCODING, + XFER_ENCODING_CHUNKED) + chunked = True + else: + raise BadRequestData('body has no __len__() nor read()') + + self._connect() + outgoing_headers = self.buildheaders( + method, path, hdrs, self.http_version) + response = None + first = True + + def reconnect(where): + logger.info('reconnecting during %s', where) + self.close() + self._connect() + + while ((outgoing_headers or body) + and not (response and response.complete())): + select_timeout = self.timeout + out = outgoing_headers or body + blocking_on_continue = False + if expect_continue and not outgoing_headers and not ( + response and response.headers): + logger.info( + 'waiting up to %s seconds for' + ' continue response from server', + self.continue_timeout) + select_timeout = self.continue_timeout + blocking_on_continue = True + out = False + if out: + w = [self.sock] + else: + w = [] + r, w, x = select.select([self.sock], w, [], select_timeout) + # if we were expecting a 100 continue and it's been long + # enough, just go ahead and assume it's ok. This is the + # recommended behavior from the RFC. + if r == w == x == []: + if blocking_on_continue: + expect_continue = False + logger.info('no response to continue expectation from ' + 'server, optimistically sending request body') + else: + raise HTTPTimeoutException('timeout sending data') + # TODO exceptional conditions with select? (what are those be?) + # TODO if the response is loading, must we finish sending at all? + # + # Certainly not if it's going to close the connection and/or + # the response is already done...I think. + was_first = first + + # incoming data + if r: + try: + try: + data = r[0].recv(INCOMING_BUFFER_SIZE) + except socket.sslerror, e: + if e.args[0] != socket.SSL_ERROR_WANT_READ: + raise + logger.debug( + 'SSL_WANT_READ while sending data, retrying...') + continue + if not data: + logger.info('socket appears closed in read') + outgoing_headers = body = None + break + if response is None: + response = self.response_class(r[0], self.timeout) + response._load_response(data) + if (response._content_len == _LEN_CLOSE_IS_END + and len(data) == 0): + response._content_len = len(response._body) + if response.complete(): + w = [] + response.will_close = True + except socket.error, e: + if e[0] != errno.EPIPE and not was_first: + raise + if (response._content_len + and response._content_len != _LEN_CLOSE_IS_END): + outgoing_headers = sent_data + outgoing_headers + reconnect('read') + + # outgoing data + if w and out: + try: + if getattr(out, 'read', False): + data = out.read(OUTGOING_BUFFER_SIZE) + if not data: + continue + if len(data) < OUTGOING_BUFFER_SIZE: + if chunked: + body = '0' + EOL + EOL + else: + body = None + if chunked: + out = hex(len(data))[2:] + EOL + data + EOL + else: + out = data + amt = w[0].send(out) + except socket.error, e: + if e[0] == socket.SSL_ERROR_WANT_WRITE and self.ssl: + # This means that SSL hasn't flushed its buffer into + # the socket yet. + # TODO: find a way to block on ssl flushing its buffer + # similar to selecting on a raw socket. + continue + elif (e[0] not in (errno.ECONNRESET, errno.EPIPE) + and not first): + raise + reconnect('write') + amt = self.sock.send(out) + logger.debug('sent %d', amt) + first = False + # stash data we think we sent in case the socket breaks + # when we read from it + if was_first: + sent_data = out[:amt] + if out is body: + body = out[amt:] + else: + outgoing_headers = out[amt:] + + # close if the server response said to or responded before eating + # the whole request + if response is None: + response = self.response_class(self.sock, self.timeout) + complete = response.complete() + data_left = bool(outgoing_headers or body) + if data_left: + logger.info('stopped sending request early, ' + 'will close the socket to be safe.') + response.will_close = True + if response.will_close: + # The socket will be closed by the response, so we disown + # the socket + self.sock = None + self._current_response = response + + def getresponse(self): + if self._current_response is None: + raise httplib.ResponseNotReady() + r = self._current_response + while r.headers is None: + r._select() + if r.will_close: + self.sock = None + self._current_response = None + elif r.complete(): + self._current_response = None + else: + self._current_response_taken = True + return r + + +class HTTPTimeoutException(httplib.HTTPException): + """A timeout occurred while waiting on the server.""" + + +class BadRequestData(httplib.HTTPException): + """Request body object has neither __len__ nor read.""" + + +class HTTPProxyConnectFailedException(httplib.HTTPException): + """Connecting to the HTTP proxy failed.""" +# no-check-code
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/httpclient/socketutil.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,134 @@ +# Copyright 2010, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Abstraction to simplify socket use for Python < 2.6 + +This will attempt to use the ssl module and the new +socket.create_connection method, but fall back to the old +methods if those are unavailable. +""" +import logging +import socket + +logger = logging.getLogger(__name__) + +try: + import ssl + ssl.wrap_socket # make demandimporters load the module + have_ssl = True +except ImportError: + import httplib + import urllib2 + have_ssl = getattr(urllib2, 'HTTPSHandler', False) + ssl = False + + +try: + create_connection = socket.create_connection +except AttributeError: + def create_connection(address): + host, port = address + msg = "getaddrinfo returns an empty list" + sock = None + for res in socket.getaddrinfo(host, port, 0, + socket.SOCK_STREAM): + af, socktype, proto, _canonname, sa = res + try: + sock = socket.socket(af, socktype, proto) + logger.info("connect: (%s, %s)", host, port) + sock.connect(sa) + except socket.error, msg: + logger.info('connect fail: %s %s', host, port) + if sock: + sock.close() + sock = None + continue + break + if not sock: + raise socket.error, msg + return sock + +if ssl: + wrap_socket = ssl.wrap_socket + CERT_NONE = ssl.CERT_NONE + CERT_OPTIONAL = ssl.CERT_OPTIONAL + CERT_REQUIRED = ssl.CERT_REQUIRED + PROTOCOL_SSLv2 = ssl.PROTOCOL_SSLv2 + PROTOCOL_SSLv3 = ssl.PROTOCOL_SSLv3 + PROTOCOL_SSLv23 = ssl.PROTOCOL_SSLv23 + PROTOCOL_TLSv1 = ssl.PROTOCOL_TLSv1 +else: + class FakeSocket(httplib.FakeSocket): + """Socket wrapper that supports SSL. + """ + # backport the behavior from Python 2.6, which is to busy wait + # on the socket instead of anything nice. Sigh. + # See http://bugs.python.org/issue3890 for more info. + def recv(self, buflen=1024, flags=0): + """ssl-aware wrapper around socket.recv + """ + if flags != 0: + raise ValueError( + "non-zero flags not allowed in calls to recv() on %s" % + self.__class__) + while True: + try: + return self._ssl.read(buflen) + except socket.sslerror, x: + if x.args[0] == socket.SSL_ERROR_WANT_READ: + continue + else: + raise x + + PROTOCOL_SSLv2 = 0 + PROTOCOL_SSLv3 = 1 + PROTOCOL_SSLv23 = 2 + PROTOCOL_TLSv1 = 3 + + CERT_NONE = 0 + CERT_OPTIONAL = 1 + CERT_REQUIRED = 2 + + def wrap_socket(sock, keyfile=None, certfile=None, + server_side=False, cert_reqs=CERT_NONE, + ssl_version=PROTOCOL_SSLv23, ca_certs=None, + do_handshake_on_connect=True, + suppress_ragged_eofs=True): + if cert_reqs != CERT_NONE and ca_certs: + raise CertificateValidationUnsupported( + 'SSL certificate validation requires the ssl module' + '(included in Python 2.6 and later.)') + sslob = socket.ssl(sock) + # borrow httplib's workaround for no ssl.wrap_socket + sock = FakeSocket(sock, sslob) + return sock + + +class CertificateValidationUnsupported(Exception): + """Exception raised when cert validation is requested but unavailable.""" +# no-check-code
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/httpclient/tests/__init__.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,1 @@ +# no-check-code
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/httpclient/tests/simple_http_test.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,356 @@ +# Copyright 2010, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import unittest + +import http + +# relative import to ease embedding the library +import util + + +class SimpleHttpTest(util.HttpTestBase, unittest.TestCase): + + def _run_simple_test(self, host, server_data, expected_req, expected_data): + con = http.HTTPConnection(host) + con._connect() + con.sock.data.extend(server_data) + con.request('GET', '/') + + self.assertStringEqual(expected_req, con.sock.sent) + self.assertEqual(expected_data, con.getresponse().read()) + + def test_broken_data_obj(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + self.assertRaises(http.BadRequestData, + con.request, 'POST', '/', body=1) + + def test_no_keepalive_http_1_0(self): + expected_request_one = """GET /remote/.hg/requires HTTP/1.1 +Host: localhost:9999 +range: bytes=0- +accept-encoding: identity +accept: application/mercurial-0.1 +user-agent: mercurial/proto-1.0 + +""".replace('\n', '\r\n') + expected_response_headers = """HTTP/1.0 200 OK +Server: SimpleHTTP/0.6 Python/2.6.1 +Date: Sun, 01 May 2011 13:56:57 GMT +Content-type: application/octet-stream +Content-Length: 33 +Last-Modified: Sun, 01 May 2011 13:56:56 GMT + +""".replace('\n', '\r\n') + expected_response_body = """revlogv1 +store +fncache +dotencode +""" + con = http.HTTPConnection('localhost:9999') + con._connect() + con.sock.data = [expected_response_headers, expected_response_body] + con.request('GET', '/remote/.hg/requires', + headers={'accept-encoding': 'identity', + 'range': 'bytes=0-', + 'accept': 'application/mercurial-0.1', + 'user-agent': 'mercurial/proto-1.0', + }) + self.assertStringEqual(expected_request_one, con.sock.sent) + self.assertEqual(con.sock.closed, False) + self.assertNotEqual(con.sock.data, []) + self.assert_(con.busy()) + resp = con.getresponse() + self.assertStringEqual(resp.read(), expected_response_body) + self.failIf(con.busy()) + self.assertEqual(con.sock, None) + self.assertEqual(resp.sock.data, []) + self.assert_(resp.sock.closed) + + def test_multiline_header(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + con.sock.data = ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'Multiline: Value\r\n', + ' Rest of value\r\n', + 'Content-Length: 10\r\n', + '\r\n' + '1234567890' + ] + con.request('GET', '/') + + expected_req = ('GET / HTTP/1.1\r\n' + 'Host: 1.2.3.4\r\n' + 'accept-encoding: identity\r\n\r\n') + + self.assertEqual(('1.2.3.4', 80), con.sock.sa) + self.assertEqual(expected_req, con.sock.sent) + resp = con.getresponse() + self.assertEqual('1234567890', resp.read()) + self.assertEqual(['Value\n Rest of value'], + resp.headers.getheaders('multiline')) + # Socket should not be closed + self.assertEqual(resp.sock.closed, False) + self.assertEqual(con.sock.closed, False) + + def testSimpleRequest(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + con.sock.data = ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'MultiHeader: Value\r\n' + 'MultiHeader: Other Value\r\n' + 'MultiHeader: One More!\r\n' + 'Content-Length: 10\r\n', + '\r\n' + '1234567890' + ] + con.request('GET', '/') + + expected_req = ('GET / HTTP/1.1\r\n' + 'Host: 1.2.3.4\r\n' + 'accept-encoding: identity\r\n\r\n') + + self.assertEqual(('1.2.3.4', 80), con.sock.sa) + self.assertEqual(expected_req, con.sock.sent) + resp = con.getresponse() + self.assertEqual('1234567890', resp.read()) + self.assertEqual(['Value', 'Other Value', 'One More!'], + resp.headers.getheaders('multiheader')) + self.assertEqual(['BogusServer 1.0'], + resp.headers.getheaders('server')) + + def testHeaderlessResponse(self): + con = http.HTTPConnection('1.2.3.4', use_ssl=False) + con._connect() + con.sock.data = ['HTTP/1.1 200 OK\r\n', + '\r\n' + '1234567890' + ] + con.request('GET', '/') + + expected_req = ('GET / HTTP/1.1\r\n' + 'Host: 1.2.3.4\r\n' + 'accept-encoding: identity\r\n\r\n') + + self.assertEqual(('1.2.3.4', 80), con.sock.sa) + self.assertEqual(expected_req, con.sock.sent) + resp = con.getresponse() + self.assertEqual('1234567890', resp.read()) + self.assertEqual({}, dict(resp.headers)) + self.assertEqual(resp.status, 200) + + def testReadline(self): + con = http.HTTPConnection('1.2.3.4') + con._connect() + # make sure it trickles in one byte at a time + # so that we touch all the cases in readline + con.sock.data = list(''.join( + ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'Connection: Close\r\n', + '\r\n' + '1\n2\nabcdefg\n4\n5'])) + + expected_req = ('GET / HTTP/1.1\r\n' + 'Host: 1.2.3.4\r\n' + 'accept-encoding: identity\r\n\r\n') + + con.request('GET', '/') + self.assertEqual(('1.2.3.4', 80), con.sock.sa) + self.assertEqual(expected_req, con.sock.sent) + r = con.getresponse() + for expected in ['1\n', '2\n', 'abcdefg\n', '4\n', '5']: + actual = r.readline() + self.assertEqual(expected, actual, + 'Expected %r, got %r' % (expected, actual)) + + def testIPv6(self): + self._run_simple_test('[::1]:8221', + ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'Content-Length: 10', + '\r\n\r\n' + '1234567890'], + ('GET / HTTP/1.1\r\n' + 'Host: [::1]:8221\r\n' + 'accept-encoding: identity\r\n\r\n'), + '1234567890') + self._run_simple_test('::2', + ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'Content-Length: 10', + '\r\n\r\n' + '1234567890'], + ('GET / HTTP/1.1\r\n' + 'Host: ::2\r\n' + 'accept-encoding: identity\r\n\r\n'), + '1234567890') + self._run_simple_test('[::3]:443', + ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'Content-Length: 10', + '\r\n\r\n' + '1234567890'], + ('GET / HTTP/1.1\r\n' + 'Host: ::3\r\n' + 'accept-encoding: identity\r\n\r\n'), + '1234567890') + + def testEarlyContinueResponse(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + sock = con.sock + sock.data = ['HTTP/1.1 403 Forbidden\r\n', + 'Server: BogusServer 1.0\r\n', + 'Content-Length: 18', + '\r\n\r\n' + "You can't do that."] + expected_req = self.doPost(con, expect_body=False) + self.assertEqual(('1.2.3.4', 80), sock.sa) + self.assertStringEqual(expected_req, sock.sent) + self.assertEqual("You can't do that.", con.getresponse().read()) + self.assertEqual(sock.closed, True) + + def testDeniedAfterContinueTimeoutExpires(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + sock = con.sock + sock.data = ['HTTP/1.1 403 Forbidden\r\n', + 'Server: BogusServer 1.0\r\n', + 'Content-Length: 18\r\n', + 'Connection: close', + '\r\n\r\n' + "You can't do that."] + sock.read_wait_sentinel = 'Dear server, send response!' + sock.close_on_empty = True + # send enough data out that we'll chunk it into multiple + # blocks and the socket will close before we can send the + # whole request. + post_body = ('This is some POST data\n' * 1024 * 32 + + 'Dear server, send response!\n' + + 'This is some POST data\n' * 1024 * 32) + expected_req = self.doPost(con, expect_body=False, + body_to_send=post_body) + self.assertEqual(('1.2.3.4', 80), sock.sa) + self.assert_('POST data\n' in sock.sent) + self.assert_('Dear server, send response!\n' in sock.sent) + # We expect not all of our data was sent. + self.assertNotEqual(sock.sent, expected_req) + self.assertEqual("You can't do that.", con.getresponse().read()) + self.assertEqual(sock.closed, True) + + def testPostData(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + sock = con.sock + sock.read_wait_sentinel = 'POST data' + sock.early_data = ['HTTP/1.1 100 Co', 'ntinue\r\n\r\n'] + sock.data = ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'Content-Length: 16', + '\r\n\r\n', + "You can do that."] + expected_req = self.doPost(con, expect_body=True) + self.assertEqual(('1.2.3.4', 80), sock.sa) + self.assertEqual(expected_req, sock.sent) + self.assertEqual("You can do that.", con.getresponse().read()) + self.assertEqual(sock.closed, False) + + def testServerWithoutContinue(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + sock = con.sock + sock.read_wait_sentinel = 'POST data' + sock.data = ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'Content-Length: 16', + '\r\n\r\n', + "You can do that."] + expected_req = self.doPost(con, expect_body=True) + self.assertEqual(('1.2.3.4', 80), sock.sa) + self.assertEqual(expected_req, sock.sent) + self.assertEqual("You can do that.", con.getresponse().read()) + self.assertEqual(sock.closed, False) + + def testServerWithSlowContinue(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + sock = con.sock + sock.read_wait_sentinel = 'POST data' + sock.data = ['HTTP/1.1 100 ', 'Continue\r\n\r\n', + 'HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'Content-Length: 16', + '\r\n\r\n', + "You can do that."] + expected_req = self.doPost(con, expect_body=True) + self.assertEqual(('1.2.3.4', 80), sock.sa) + self.assertEqual(expected_req, sock.sent) + resp = con.getresponse() + self.assertEqual("You can do that.", resp.read()) + self.assertEqual(200, resp.status) + self.assertEqual(sock.closed, False) + + def testSlowConnection(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + # simulate one byte arriving at a time, to check for various + # corner cases + con.sock.data = list('HTTP/1.1 200 OK\r\n' + 'Server: BogusServer 1.0\r\n' + 'Content-Length: 10' + '\r\n\r\n' + '1234567890') + con.request('GET', '/') + + expected_req = ('GET / HTTP/1.1\r\n' + 'Host: 1.2.3.4\r\n' + 'accept-encoding: identity\r\n\r\n') + + self.assertEqual(('1.2.3.4', 80), con.sock.sa) + self.assertEqual(expected_req, con.sock.sent) + self.assertEqual('1234567890', con.getresponse().read()) + + def testTimeout(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + con.sock.data = [] + con.request('GET', '/') + self.assertRaises(http.HTTPTimeoutException, + con.getresponse) + + expected_req = ('GET / HTTP/1.1\r\n' + 'Host: 1.2.3.4\r\n' + 'accept-encoding: identity\r\n\r\n') + + self.assertEqual(('1.2.3.4', 80), con.sock.sa) + self.assertEqual(expected_req, con.sock.sent) +# no-check-code
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/httpclient/tests/test_bogus_responses.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,68 @@ +# Copyright 2010, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Tests against malformed responses. + +Server implementations that respond with only LF instead of CRLF have +been observed. Checking against ones that use only CR is a hedge +against that potential insanit.y +""" +import unittest + +import http + +# relative import to ease embedding the library +import util + + +class SimpleHttpTest(util.HttpTestBase, unittest.TestCase): + + def bogusEOL(self, eol): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + con.sock.data = ['HTTP/1.1 200 OK%s' % eol, + 'Server: BogusServer 1.0%s' % eol, + 'Content-Length: 10', + eol * 2, + '1234567890'] + con.request('GET', '/') + + expected_req = ('GET / HTTP/1.1\r\n' + 'Host: 1.2.3.4\r\n' + 'accept-encoding: identity\r\n\r\n') + + self.assertEqual(('1.2.3.4', 80), con.sock.sa) + self.assertEqual(expected_req, con.sock.sent) + self.assertEqual('1234567890', con.getresponse().read()) + + def testOnlyLinefeed(self): + self.bogusEOL('\n') + + def testOnlyCarriageReturn(self): + self.bogusEOL('\r') +# no-check-code
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/httpclient/tests/test_chunked_transfer.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,137 @@ +# Copyright 2010, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import cStringIO +import unittest + +import http + +# relative import to ease embedding the library +import util + + +def chunkedblock(x, eol='\r\n'): + r"""Make a chunked transfer-encoding block. + + >>> chunkedblock('hi') + '2\r\nhi\r\n' + >>> chunkedblock('hi' * 10) + '14\r\nhihihihihihihihihihi\r\n' + >>> chunkedblock('hi', eol='\n') + '2\nhi\n' + """ + return ''.join((hex(len(x))[2:], eol, x, eol)) + + +class ChunkedTransferTest(util.HttpTestBase, unittest.TestCase): + def testChunkedUpload(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + sock = con.sock + sock.read_wait_sentinel = 'end-of-body' + sock.data = ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'Content-Length: 6', + '\r\n\r\n', + "Thanks"] + + zz = 'zz\n' + con.request('POST', '/', body=cStringIO.StringIO( + (zz * (0x8010 / 3)) + 'end-of-body')) + expected_req = ('POST / HTTP/1.1\r\n' + 'transfer-encoding: chunked\r\n' + 'Host: 1.2.3.4\r\n' + 'accept-encoding: identity\r\n\r\n') + expected_req += chunkedblock('zz\n' * (0x8000 / 3) + 'zz') + expected_req += chunkedblock( + '\n' + 'zz\n' * ((0x1b - len('end-of-body')) / 3) + 'end-of-body') + expected_req += '0\r\n\r\n' + self.assertEqual(('1.2.3.4', 80), sock.sa) + self.assertStringEqual(expected_req, sock.sent) + self.assertEqual("Thanks", con.getresponse().read()) + self.assertEqual(sock.closed, False) + + def testChunkedDownload(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + sock = con.sock + sock.data = ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'transfer-encoding: chunked', + '\r\n\r\n', + chunkedblock('hi '), + chunkedblock('there'), + chunkedblock(''), + ] + con.request('GET', '/') + self.assertStringEqual('hi there', con.getresponse().read()) + + def testChunkedDownloadBadEOL(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + sock = con.sock + sock.data = ['HTTP/1.1 200 OK\n', + 'Server: BogusServer 1.0\n', + 'transfer-encoding: chunked', + '\n\n', + chunkedblock('hi ', eol='\n'), + chunkedblock('there', eol='\n'), + chunkedblock('', eol='\n'), + ] + con.request('GET', '/') + self.assertStringEqual('hi there', con.getresponse().read()) + + def testChunkedDownloadPartialChunkBadEOL(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + sock = con.sock + sock.data = ['HTTP/1.1 200 OK\n', + 'Server: BogusServer 1.0\n', + 'transfer-encoding: chunked', + '\n\n', + chunkedblock('hi ', eol='\n'), + ] + list(chunkedblock('there\n' * 5, eol='\n')) + [ + chunkedblock('', eol='\n')] + con.request('GET', '/') + self.assertStringEqual('hi there\nthere\nthere\nthere\nthere\n', + con.getresponse().read()) + + def testChunkedDownloadPartialChunk(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + sock = con.sock + sock.data = ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'transfer-encoding: chunked', + '\r\n\r\n', + chunkedblock('hi '), + ] + list(chunkedblock('there\n' * 5)) + [chunkedblock('')] + con.request('GET', '/') + self.assertStringEqual('hi there\nthere\nthere\nthere\nthere\n', + con.getresponse().read()) +# no-check-code
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/httpclient/tests/test_proxy_support.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,132 @@ +# Copyright 2010, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import unittest +import socket + +import http + +# relative import to ease embedding the library +import util + + +def make_preloaded_socket(data): + """Make a socket pre-loaded with data so it can be read during connect. + + Useful for https proxy tests because we have to read from the + socket during _connect rather than later on. + """ + def s(*args, **kwargs): + sock = util.MockSocket(*args, **kwargs) + sock.data = data[:] + return sock + return s + + +class ProxyHttpTest(util.HttpTestBase, unittest.TestCase): + + def _run_simple_test(self, host, server_data, expected_req, expected_data): + con = http.HTTPConnection(host) + con._connect() + con.sock.data = server_data + con.request('GET', '/') + + self.assertEqual(expected_req, con.sock.sent) + self.assertEqual(expected_data, con.getresponse().read()) + + def testSimpleRequest(self): + con = http.HTTPConnection('1.2.3.4:80', + proxy_hostport=('magicproxy', 4242)) + con._connect() + con.sock.data = ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'MultiHeader: Value\r\n' + 'MultiHeader: Other Value\r\n' + 'MultiHeader: One More!\r\n' + 'Content-Length: 10\r\n', + '\r\n' + '1234567890' + ] + con.request('GET', '/') + + expected_req = ('GET http://1.2.3.4/ HTTP/1.1\r\n' + 'Host: 1.2.3.4\r\n' + 'accept-encoding: identity\r\n\r\n') + + self.assertEqual(('127.0.0.42', 4242), con.sock.sa) + self.assertStringEqual(expected_req, con.sock.sent) + resp = con.getresponse() + self.assertEqual('1234567890', resp.read()) + self.assertEqual(['Value', 'Other Value', 'One More!'], + resp.headers.getheaders('multiheader')) + self.assertEqual(['BogusServer 1.0'], + resp.headers.getheaders('server')) + + def testSSLRequest(self): + con = http.HTTPConnection('1.2.3.4:443', + proxy_hostport=('magicproxy', 4242)) + socket.socket = make_preloaded_socket( + ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'Content-Length: 10\r\n', + '\r\n' + '1234567890']) + con._connect() + con.sock.data.extend(['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'Content-Length: 10\r\n', + '\r\n' + '1234567890' + ]) + con.request('GET', '/') + + expected_req = ('CONNECT 1.2.3.4:443 HTTP/1.0\r\n' + 'Host: 1.2.3.4\r\n' + 'accept-encoding: identity\r\n' + '\r\n' + 'GET / HTTP/1.1\r\n' + 'Host: 1.2.3.4\r\n' + 'accept-encoding: identity\r\n\r\n') + + self.assertEqual(('127.0.0.42', 4242), con.sock.sa) + self.assertStringEqual(expected_req, con.sock.sent) + resp = con.getresponse() + self.assertEqual(resp.status, 200) + self.assertEqual('1234567890', resp.read()) + self.assertEqual(['BogusServer 1.0'], + resp.headers.getheaders('server')) + + def testSSLProxyFailure(self): + con = http.HTTPConnection('1.2.3.4:443', + proxy_hostport=('magicproxy', 4242)) + socket.socket = make_preloaded_socket( + ['HTTP/1.1 407 Proxy Authentication Required\r\n\r\n']) + self.assertRaises(http.HTTPProxyConnectFailedException, con._connect) + self.assertRaises(http.HTTPProxyConnectFailedException, + con.request, 'GET', '/') +# no-check-code
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/httpclient/tests/test_ssl.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,94 @@ +# Copyright 2011, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import unittest + +import http + +# relative import to ease embedding the library +import util + + + +class HttpSslTest(util.HttpTestBase, unittest.TestCase): + def testSslRereadRequired(self): + con = http.HTTPConnection('1.2.3.4:443') + con._connect() + # extend the list instead of assign because of how + # MockSSLSocket works. + con.sock.data.extend(['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'MultiHeader: Value\r\n' + 'MultiHeader: Other Value\r\n' + 'MultiHeader: One More!\r\n' + 'Content-Length: 10\r\n', + '\r\n' + '1234567890' + ]) + con.request('GET', '/') + + expected_req = ('GET / HTTP/1.1\r\n' + 'Host: 1.2.3.4\r\n' + 'accept-encoding: identity\r\n\r\n') + + self.assertEqual(('1.2.3.4', 443), con.sock.sa) + self.assertEqual(expected_req, con.sock.sent) + resp = con.getresponse() + self.assertEqual('1234567890', resp.read()) + self.assertEqual(['Value', 'Other Value', 'One More!'], + resp.headers.getheaders('multiheader')) + self.assertEqual(['BogusServer 1.0'], + resp.headers.getheaders('server')) + + def testSslRereadInEarlyResponse(self): + con = http.HTTPConnection('1.2.3.4:443') + con._connect() + # extend the list instead of assign because of how + # MockSSLSocket works. + con.sock.early_data.extend(['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'MultiHeader: Value\r\n' + 'MultiHeader: Other Value\r\n' + 'MultiHeader: One More!\r\n' + 'Content-Length: 10\r\n', + '\r\n' + '1234567890' + ]) + + expected_req = self.doPost(con, False) + self.assertEqual(None, con.sock, + 'Connection should have disowned socket') + + resp = con.getresponse() + self.assertEqual(('1.2.3.4', 443), resp.sock.sa) + self.assertEqual(expected_req, resp.sock.sent) + self.assertEqual('1234567890', resp.read()) + self.assertEqual(['Value', 'Other Value', 'One More!'], + resp.headers.getheaders('multiheader')) + self.assertEqual(['BogusServer 1.0'], + resp.headers.getheaders('server'))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/httpclient/tests/util.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,190 @@ +# Copyright 2010, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import difflib +import socket + +import http + + +class MockSocket(object): + """Mock non-blocking socket object. + + This is ONLY capable of mocking a nonblocking socket. + + Attributes: + early_data: data to always send as soon as end of headers is seen + data: a list of strings to return on recv(), with the + assumption that the socket would block between each + string in the list. + read_wait_sentinel: data that must be written to the socket before + beginning the response. + close_on_empty: If true, close the socket when it runs out of data + for the client. + """ + def __init__(self, af, socktype, proto): + self.af = af + self.socktype = socktype + self.proto = proto + + self.early_data = [] + self.data = [] + self.remote_closed = self.closed = False + self.close_on_empty = False + self.sent = '' + self.read_wait_sentinel = http._END_HEADERS + + def close(self): + self.closed = True + + def connect(self, sa): + self.sa = sa + + def setblocking(self, timeout): + assert timeout == 0 + + def recv(self, amt=-1): + if self.early_data: + datalist = self.early_data + elif not self.data: + return '' + else: + datalist = self.data + if amt == -1: + return datalist.pop(0) + data = datalist.pop(0) + if len(data) > amt: + datalist.insert(0, data[amt:]) + if not self.data and not self.early_data and self.close_on_empty: + self.remote_closed = True + return data[:amt] + + @property + def ready_for_read(self): + return ((self.early_data and http._END_HEADERS in self.sent) + or (self.read_wait_sentinel in self.sent and self.data) + or self.closed) + + def send(self, data): + # this is a horrible mock, but nothing needs us to raise the + # correct exception yet + assert not self.closed, 'attempted to write to a closed socket' + assert not self.remote_closed, ('attempted to write to a' + ' socket closed by the server') + if len(data) > 8192: + data = data[:8192] + self.sent += data + return len(data) + + +def mockselect(r, w, x, timeout=0): + """Simple mock for select() + """ + readable = filter(lambda s: s.ready_for_read, r) + return readable, w[:], [] + + +class MockSSLSocket(object): + def __init__(self, sock): + self._sock = sock + self._fail_recv = True + + def __getattr__(self, key): + return getattr(self._sock, key) + + def recv(self, amt=-1): + try: + if self._fail_recv: + raise socket.sslerror(socket.SSL_ERROR_WANT_READ) + return self._sock.recv(amt=amt) + finally: + self._fail_recv = not self._fail_recv + + +def mocksslwrap(sock, keyfile=None, certfile=None, + server_side=False, cert_reqs=http.socketutil.CERT_NONE, + ssl_version=http.socketutil.PROTOCOL_SSLv23, ca_certs=None, + do_handshake_on_connect=True, + suppress_ragged_eofs=True): + return MockSSLSocket(sock) + + +def mockgetaddrinfo(host, port, unused, streamtype): + assert unused == 0 + assert streamtype == socket.SOCK_STREAM + if host.count('.') != 3: + host = '127.0.0.42' + return [(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, '', + (host, port))] + + +class HttpTestBase(object): + def setUp(self): + self.orig_socket = socket.socket + socket.socket = MockSocket + + self.orig_getaddrinfo = socket.getaddrinfo + socket.getaddrinfo = mockgetaddrinfo + + self.orig_select = http.select.select + http.select.select = mockselect + + self.orig_sslwrap = http.socketutil.wrap_socket + http.socketutil.wrap_socket = mocksslwrap + + def tearDown(self): + socket.socket = self.orig_socket + http.select.select = self.orig_select + http.socketutil.wrap_socket = self.orig_sslwrap + socket.getaddrinfo = self.orig_getaddrinfo + + def assertStringEqual(self, l, r): + try: + self.assertEqual(l, r, ('failed string equality check, ' + 'see stdout for details')) + except: + add_nl = lambda li: map(lambda x: x + '\n', li) + print 'failed expectation:' + print ''.join(difflib.unified_diff( + add_nl(l.splitlines()), add_nl(r.splitlines()), + fromfile='expected', tofile='got')) + raise + + def doPost(self, con, expect_body, body_to_send='This is some POST data'): + con.request('POST', '/', body=body_to_send, + expect_continue=True) + expected_req = ('POST / HTTP/1.1\r\n' + 'Host: 1.2.3.4\r\n' + 'content-length: %d\r\n' + 'Expect: 100-Continue\r\n' + 'accept-encoding: identity\r\n\r\n' % + len(body_to_send)) + if expect_body: + expected_req += body_to_send + return expected_req +# no-check-code
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/httpconnection.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,265 @@ +# httpconnection.py - urllib2 handler for new http support +# +# Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com> +# Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br> +# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> +# Copyright 2011 Google, Inc. +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. +import logging +import socket +import urllib +import urllib2 +import os + +from mercurial import httpclient +from mercurial import sslutil +from mercurial import util +from mercurial.i18n import _ + +# moved here from url.py to avoid a cycle +class httpsendfile(object): + """This is a wrapper around the objects returned by python's "open". + + Its purpose is to send file-like objects via HTTP and, to do so, it + defines a __len__ attribute to feed the Content-Length header. + """ + + def __init__(self, ui, *args, **kwargs): + # We can't just "self._data = open(*args, **kwargs)" here because there + # is an "open" function defined in this module that shadows the global + # one + self.ui = ui + self._data = open(*args, **kwargs) + self.seek = self._data.seek + self.close = self._data.close + self.write = self._data.write + self._len = os.fstat(self._data.fileno()).st_size + self._pos = 0 + self._total = len(self) / 1024 * 2 + + def read(self, *args, **kwargs): + try: + ret = self._data.read(*args, **kwargs) + except EOFError: + self.ui.progress(_('sending'), None) + self._pos += len(ret) + # We pass double the max for total because we currently have + # to send the bundle twice in the case of a server that + # requires authentication. Since we can't know until we try + # once whether authentication will be required, just lie to + # the user and maybe the push succeeds suddenly at 50%. + self.ui.progress(_('sending'), self._pos / 1024, + unit=_('kb'), total=self._total) + return ret + + def __len__(self): + return self._len + +# moved here from url.py to avoid a cycle +def readauthforuri(ui, uri): + # Read configuration + config = dict() + for key, val in ui.configitems('auth'): + if '.' not in key: + ui.warn(_("ignoring invalid [auth] key '%s'\n") % key) + continue + group, setting = key.rsplit('.', 1) + gdict = config.setdefault(group, dict()) + if setting in ('username', 'cert', 'key'): + val = util.expandpath(val) + gdict[setting] = val + + # Find the best match + scheme, hostpath = uri.split('://', 1) + bestlen = 0 + bestauth = None + for group, auth in config.iteritems(): + prefix = auth.get('prefix') + if not prefix: + continue + p = prefix.split('://', 1) + if len(p) > 1: + schemes, prefix = [p[0]], p[1] + else: + schemes = (auth.get('schemes') or 'https').split() + if (prefix == '*' or hostpath.startswith(prefix)) and \ + len(prefix) > bestlen and scheme in schemes: + bestlen = len(prefix) + bestauth = group, auth + return bestauth + +# Mercurial (at least until we can remove the old codepath) requires +# that the http response object be sufficiently file-like, so we +# provide a close() method here. +class HTTPResponse(httpclient.HTTPResponse): + def close(self): + pass + +class HTTPConnection(httpclient.HTTPConnection): + response_class = HTTPResponse + def request(self, method, uri, body=None, headers={}): + if isinstance(body, httpsendfile): + body.seek(0) + httpclient.HTTPConnection.request(self, method, uri, body=body, + headers=headers) + + +_configuredlogging = False +# Subclass BOTH of these because otherwise urllib2 "helpfully" +# reinserts them since it notices we don't include any subclasses of +# them. +class http2handler(urllib2.HTTPHandler, urllib2.HTTPSHandler): + def __init__(self, ui, pwmgr): + global _configuredlogging + urllib2.AbstractHTTPHandler.__init__(self) + self.ui = ui + self.pwmgr = pwmgr + self._connections = {} + loglevel = ui.config('ui', 'http2debuglevel', default=None) + if loglevel and not _configuredlogging: + _configuredlogging = True + logger = logging.getLogger('mercurial.httpclient') + logger.setLevel(getattr(logging, loglevel.upper())) + logger.addHandler(logging.StreamHandler()) + + def close_all(self): + """Close and remove all connection objects being kept for reuse.""" + for openconns in self._connections.values(): + for conn in openconns: + conn.close() + self._connections = {} + + # shamelessly borrowed from urllib2.AbstractHTTPHandler + def do_open(self, http_class, req, use_ssl): + """Return an addinfourl object for the request, using http_class. + + http_class must implement the HTTPConnection API from httplib. + The addinfourl return value is a file-like object. It also + has methods and attributes including: + - info(): return a mimetools.Message object for the headers + - geturl(): return the original request URL + - code: HTTP status code + """ + # If using a proxy, the host returned by get_host() is + # actually the proxy. On Python 2.6.1, the real destination + # hostname is encoded in the URI in the urllib2 request + # object. On Python 2.6.5, it's stored in the _tunnel_host + # attribute which has no accessor. + tunhost = getattr(req, '_tunnel_host', None) + host = req.get_host() + if tunhost: + proxyhost = host + host = tunhost + elif req.has_proxy(): + proxyhost = req.get_host() + host = req.get_selector().split('://', 1)[1].split('/', 1)[0] + else: + proxyhost = None + + if proxyhost: + if ':' in proxyhost: + # Note: this means we'll explode if we try and use an + # IPv6 http proxy. This isn't a regression, so we + # won't worry about it for now. + proxyhost, proxyport = proxyhost.rsplit(':', 1) + else: + proxyport = 3128 # squid default + proxy = (proxyhost, proxyport) + else: + proxy = None + + if not host: + raise urllib2.URLError('no host given') + + connkey = use_ssl, host, proxy + allconns = self._connections.get(connkey, []) + conns = [c for c in allconns if not c.busy()] + if conns: + h = conns[0] + else: + if allconns: + self.ui.debug('all connections for %s busy, making a new ' + 'one\n' % host) + timeout = None + if req.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: + timeout = req.timeout + h = http_class(host, timeout=timeout, proxy_hostport=proxy) + self._connections.setdefault(connkey, []).append(h) + + headers = dict(req.headers) + headers.update(req.unredirected_hdrs) + headers = dict( + (name.title(), val) for name, val in headers.items()) + try: + path = req.get_selector() + if '://' in path: + path = path.split('://', 1)[1].split('/', 1)[1] + if path[0] != '/': + path = '/' + path + h.request(req.get_method(), path, req.data, headers) + r = h.getresponse() + except socket.error, err: # XXX what error? + raise urllib2.URLError(err) + + # Pick apart the HTTPResponse object to get the addinfourl + # object initialized properly. + r.recv = r.read + + resp = urllib.addinfourl(r, r.headers, req.get_full_url()) + resp.code = r.status + resp.msg = r.reason + return resp + + # httplib always uses the given host/port as the socket connect + # target, and then allows full URIs in the request path, which it + # then observes and treats as a signal to do proxying instead. + def http_open(self, req): + if req.get_full_url().startswith('https'): + return self.https_open(req) + return self.do_open(HTTPConnection, req, False) + + def https_open(self, req): + res = readauthforuri(self.ui, req.get_full_url()) + if res: + group, auth = res + self.auth = auth + self.ui.debug("using auth.%s.* for authentication\n" % group) + else: + self.auth = None + return self.do_open(self._makesslconnection, req, True) + + def _makesslconnection(self, host, port=443, *args, **kwargs): + keyfile = None + certfile = None + + if args: # key_file + keyfile = args.pop(0) + if args: # cert_file + certfile = args.pop(0) + + # if the user has specified different key/cert files in + # hgrc, we prefer these + if self.auth and 'key' in self.auth and 'cert' in self.auth: + keyfile = self.auth['key'] + certfile = self.auth['cert'] + + # let host port take precedence + if ':' in host and '[' not in host or ']:' in host: + host, port = host.rsplit(':', 1) + port = int(port) + if '[' in host: + host = host[1:-1] + + if keyfile: + kwargs['keyfile'] = keyfile + if certfile: + kwargs['certfile'] = certfile + + kwargs.update(sslutil.sslkwargs(self.ui, host)) + + con = HTTPConnection(host, port, use_ssl=True, + ssl_validator=sslutil.validator(self.ui, host), + **kwargs) + return con
--- a/mercurial/httprepo.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/httprepo.py Wed May 18 09:56:27 2011 +0200 @@ -8,8 +8,8 @@ from node import nullid from i18n import _ -import changegroup, statichttprepo, error, url, util, wireproto -import os, urllib, urllib2, urlparse, zlib, httplib +import changegroup, statichttprepo, error, httpconnection, url, util, wireproto +import os, urllib, urllib2, zlib, httplib import errno, socket def zgenerator(f): @@ -28,13 +28,13 @@ self.path = path self.caps = None self.handler = None - scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path) - if query or frag: + u = util.url(path) + if u.query or u.fragment: raise util.Abort(_('unsupported URL component: "%s"') % - (query or frag)) + (u.query or u.fragment)) # urllib cannot handle URLs with embedded user or passwd - self._url, authinfo = url.getauthinfo(path) + self._url, authinfo = u.authinfo() self.ui = ui self.ui.debug('using %s\n' % self._url) @@ -52,10 +52,13 @@ # look up capabilities only when needed + def _fetchcaps(self): + self.caps = set(self._call('capabilities').split()) + def get_caps(self): if self.caps is None: try: - self.caps = set(self._call('capabilities').split()) + self._fetchcaps() except error.RepoError: self.caps = set() self.ui.debug('capabilities: %s\n' % @@ -72,9 +75,31 @@ args['data'] = '' data = args.pop('data', None) headers = args.pop('headers', {}) + + if data and self.ui.configbool('ui', 'usehttp2', False): + headers['Expect'] = '100-Continue' + self.ui.debug("sending %s command\n" % cmd) - q = {"cmd": cmd} - q.update(args) + q = [('cmd', cmd)] + headersize = 0 + if len(args) > 0: + httpheader = self.capable('httpheader') + if httpheader: + headersize = int(httpheader.split(',')[0]) + if headersize > 0: + # The headers can typically carry more data than the URL. + encargs = urllib.urlencode(sorted(args.items())) + headerfmt = 'X-HgArg-%s' + contentlen = headersize - len(headerfmt % '000' + ': \r\n') + headernum = 0 + for i in xrange(0, len(encargs), contentlen): + headernum += 1 + header = headerfmt % str(headernum) + headers[header] = encargs[i:i + contentlen] + varyheaders = [headerfmt % str(h) for h in range(1, headernum + 1)] + headers['Vary'] = ','.join(varyheaders) + else: + q += sorted(args.items()) qs = '?%s' % urllib.urlencode(q) cu = "%s%s" % (self._url, qs) req = urllib2.Request(cu, data, headers) @@ -107,18 +132,18 @@ try: proto = resp.getheader('content-type') except AttributeError: - proto = resp.headers['content-type'] + proto = resp.headers.get('content-type', '') - safeurl = url.hidepassword(self._url) + safeurl = util.hidepassword(self._url) # accept old "text/plain" and "application/hg-changegroup" for now if not (proto.startswith('application/mercurial-') or proto.startswith('text/plain') or proto.startswith('application/hg-changegroup')): - self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu)) + self.ui.debug("requested URL: '%s'\n" % util.hidepassword(cu)) raise error.RepoError( _("'%s' does not appear to be an hg repository:\n" "---%%<--- (%s)\n%s\n---%%<---\n") - % (safeurl, proto, resp.read())) + % (safeurl, proto or 'no content-type', resp.read())) if proto.startswith('application/mercurial-'): try: @@ -145,22 +170,21 @@ # have to stream bundle to a temp file because we do not have # http 1.1 chunked transfer. - type = "" types = self.capable('unbundle') - # servers older than d1b16a746db6 will send 'unbundle' as a - # boolean capability try: types = types.split(',') except AttributeError: + # servers older than d1b16a746db6 will send 'unbundle' as a + # boolean capability. They only support headerless/uncompressed + # bundles. types = [""] - if types: - for x in types: - if x in changegroup.bundletypes: - type = x - break + for x in types: + if x in changegroup.bundletypes: + type = x + break tempname = changegroup.writebundle(cg, None, type) - fp = url.httpsendfile(self.ui, tempname, "rb") + fp = httpconnection.httpsendfile(self.ui, tempname, "rb") headers = {'Content-Type': 'application/mercurial-0.1'} try: @@ -196,8 +220,18 @@ inst = httpsrepository(ui, path) else: inst = httprepository(ui, path) - inst.between([(nullid, nullid)]) + 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)]) return inst - except error.RepoError: - ui.note('(falling back to static-http)\n') - return statichttprepo.instance(ui, "static-" + path, create) + except error.RepoError, httpexception: + try: + r = statichttprepo.instance(ui, "static-" + path, create) + ui.note('(falling back to static-http)\n') + return r + except error.RepoError: + raise httpexception # use the original http RepoError instead
--- a/mercurial/i18n.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/i18n.py Wed May 18 09:56:27 2011 +0200 @@ -51,7 +51,13 @@ # An unknown encoding results in a LookupError. return message -if 'HGPLAIN' in os.environ: +def _plain(): + if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ: + return False + exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',') + return 'i18n' not in exceptions + +if _plain(): _ = lambda message: message else: _ = gettext
--- a/mercurial/localrepo.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/localrepo.py Wed May 18 09:56:27 2011 +0200 @@ -10,18 +10,18 @@ import repo, changegroup, subrepo, discovery, pushkey import changelog, dirstate, filelog, manifest, context, bookmarks import lock, transaction, store, encoding -import util, extensions, hook, error +import scmutil, util, extensions, hook, error import match as matchmod import merge as mergemod import tags as tagsmod -import url as urlmod from lock import release import weakref, errno, os, time, inspect propertycache = util.propertycache class localrepository(repo.repository): - capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey')) - supportedformats = set(('revlogv1', 'parentdelta')) + capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey', + 'known', 'getbundle')) + supportedformats = set(('revlogv1', 'generaldelta')) supported = supportedformats | set(('store', 'fncache', 'shared', 'dotencode')) @@ -30,9 +30,9 @@ self.root = os.path.realpath(util.expandpath(path)) self.path = os.path.join(self.root, ".hg") self.origroot = path - self.auditor = util.path_auditor(self.root, self._checknested) - self.opener = util.opener(self.path) - self.wopener = util.opener(self.root) + self.auditor = scmutil.pathauditor(self.root, self._checknested) + self.opener = scmutil.opener(self.path) + self.wopener = scmutil.opener(self.root) self.baseui = baseui self.ui = baseui.copy() @@ -46,7 +46,7 @@ if create: if not os.path.exists(path): util.makedirs(path) - os.mkdir(self.path) + util.makedir(self.path, notindexed=True) requirements = ["revlogv1"] if self.ui.configbool('format', 'usestore', True): os.mkdir(os.path.join(self.path, "store")) @@ -56,12 +56,13 @@ if self.ui.configbool('format', 'dotencode', True): requirements.append('dotencode') # create an invalid changelog - self.opener("00changelog.i", "a").write( + self.opener.append( + "00changelog.i", '\0\0\0\2' # represents revlogv2 ' dummy changelog to prevent using the old repo layout' ) - if self.ui.configbool('format', 'parentdelta', False): - requirements.append("parentdelta") + if self.ui.configbool('format', 'generaldelta', False): + requirements.append("generaldelta") else: raise error.RepoError(_("repository %s not found") % path) elif create: @@ -70,7 +71,7 @@ # find requirements requirements = set() try: - requirements = set(self.opener("requires").read().splitlines()) + requirements = set(self.opener.read("requires").splitlines()) except IOError, inst: if inst.errno != errno.ENOENT: raise @@ -80,7 +81,7 @@ self.sharedpath = self.path try: - s = os.path.realpath(self.opener("sharedpath").read()) + s = os.path.realpath(self.opener.read("sharedpath")) if not os.path.exists(s): raise error.RepoError( _('.hg/sharedpath points to nonexistent directory %s') % s) @@ -89,7 +90,7 @@ if inst.errno != errno.ENOENT: raise - self.store = store.store(requirements, self.sharedpath, util.opener) + self.store = store.store(requirements, self.sharedpath, scmutil.opener) self.spath = self.store.path self.sopener = self.store.opener self.sjoin = self.store.join @@ -115,9 +116,9 @@ def _applyrequirements(self, requirements): self.requirements = requirements - self.sopener.options = {} - if 'parentdelta' in requirements: - self.sopener.options['parentdelta'] = 1 + openerreqs = set(('revlogv1', 'generaldelta')) + self.sopener.options = dict((r, 1) for r in requirements + if r in openerreqs) def _writerequirements(self): reqfile = self.opener("requires", "w") @@ -177,7 +178,6 @@ p = os.environ['HG_PENDING'] if p.startswith(self.root): c.readpending('00changelog.i.a') - self.sopener.options['defversion'] = c.version return c @propertycache @@ -189,7 +189,7 @@ warned = [0] def validate(node): try: - r = self.changelog.rev(node) + self.changelog.rev(node) return node except error.LookupError: if not warned[0]: @@ -361,7 +361,12 @@ tags = {} for (name, (node, hist)) in alltags.iteritems(): if node != nullid: - tags[encoding.tolocal(name)] = node + try: + # ignore tags to unknown nodes + self.changelog.lookup(node) + tags[encoding.tolocal(name)] = node + except error.LookupError: + pass tags['tip'] = self.changelog.tip() tagtypes = dict([(encoding.tolocal(name), value) for (name, value) in tagtypes.iteritems()]) @@ -384,10 +389,7 @@ '''return a list of tags ordered by revision''' l = [] for t, n in self.tags().iteritems(): - try: - r = self.changelog.rev(n) - except: - r = -2 # sort to the beginning of the list if unknown + r = self.changelog.rev(n) l.append((r, t, n)) return [(t, n) for r, t, n in sorted(l)] @@ -506,22 +508,24 @@ bheads.extend(newnodes) if len(bheads) <= 1: continue + bheads = sorted(bheads, key=lambda x: self[x].rev()) # starting from tip means fewer passes over reachable while newnodes: latest = newnodes.pop() if latest not in bheads: continue - minbhrev = self[min([self[bh].rev() for bh in bheads])].node() + minbhrev = self[bheads[0]].node() reachable = self.changelog.reachable(latest, minbhrev) reachable.remove(latest) - bheads = [b for b in bheads if b not in reachable] + if reachable: + bheads = [b for b in bheads if b not in reachable] partial[branch] = bheads def lookup(self, key): if isinstance(key, int): return self.changelog.node(key) elif key == '.': - return self.dirstate.parents()[0] + return self.dirstate.p1() elif key == 'null': return nullid elif key == 'tip': @@ -546,7 +550,7 @@ try: if len(key) == 20: key = hex(key) - except: + except TypeError: pass raise error.RepoLookupError(_("unknown revision '%s'") % key) @@ -558,6 +562,10 @@ repo = (remote and remote.local()) and remote or self return repo[key].branch() + def known(self, nodes): + nm = self.changelog.nodemap + return [(n in nm) for n in nodes] + def local(self): return True @@ -644,7 +652,7 @@ if self._link(filename): data = os.readlink(self.wjoin(filename)) else: - data = self.wopener(filename, 'r').read() + data = self.wopener.read(filename) return self._filter(self._encodefilterpats, filename, data) def wwrite(self, filename, data, flags): @@ -652,9 +660,9 @@ if 'l' in flags: self.wopener.symlink(data, filename) else: - self.wopener(filename, 'w').write(data) + self.wopener.write(filename, data) if 'x' in flags: - util.set_flags(self.wjoin(filename), False, True) + util.setflags(self.wjoin(filename), False, True) def wwritedata(self, filename, data): return self._filter(self._decodefilterpats, filename, data) @@ -682,19 +690,20 @@ def _writejournal(self, desc): # save dirstate for rollback try: - ds = self.opener("dirstate").read() + ds = self.opener.read("dirstate") except IOError: ds = "" - self.opener("journal.dirstate", "w").write(ds) - self.opener("journal.branch", "w").write( - encoding.fromlocal(self.dirstate.branch())) - self.opener("journal.desc", "w").write("%d\n%s\n" % (len(self), desc)) + self.opener.write("journal.dirstate", ds) + self.opener.write("journal.branch", + encoding.fromlocal(self.dirstate.branch())) + self.opener.write("journal.desc", + "%d\n%s\n" % (len(self), desc)) bkname = self.join('bookmarks') if os.path.exists(bkname): util.copyfile(bkname, self.join('journal.bookmarks')) else: - self.opener('journal.bookmarks', 'w').write('') + self.opener.write('journal.bookmarks', '') return (self.sjoin('journal'), self.join('journal.dirstate'), self.join('journal.branch'), self.join('journal.desc'), @@ -722,7 +731,7 @@ lock = self.lock() if os.path.exists(self.sjoin("undo")): try: - args = self.opener("undo.desc", "r").read().splitlines() + args = self.opener.read("undo.desc").splitlines() if len(args) >= 3 and self.ui.verbose: desc = _("repository tip rolled back to revision %s" " (undo %s: %s)\n") % ( @@ -743,7 +752,7 @@ util.rename(self.join('undo.bookmarks'), self.join('bookmarks')) try: - branch = self.opener("undo.branch").read() + branch = self.opener.read("undo.branch") self.dirstate.setbranch(branch) except IOError: self.ui.warn(_("named branch could not be reset, " @@ -1024,10 +1033,7 @@ raise # update bookmarks, dirstate and mergestate - parents = (p1, p2) - if p2 == nullid: - parents = (p1,) - bookmarks.update(self, parents, ret) + bookmarks.update(self, p1, ret) for f in changes[0] + changes[1]: self.dirstate.normal(f) for f in changes[2]: @@ -1048,8 +1054,6 @@ tr = lock = None removed = list(ctx.removed()) p1, p2 = ctx.p1(), ctx.p2() - m1 = p1.manifest().copy() - m2 = p2.manifest() user = ctx.user() lock = self.lock() @@ -1057,40 +1061,48 @@ tr = self.transaction("commit") trp = weakref.proxy(tr) - # check in files - new = {} - changed = [] - linkrev = len(self) - for f in sorted(ctx.modified() + ctx.added()): - self.ui.note(f + "\n") - try: - fctx = ctx[f] - new[f] = self._filecommit(fctx, m1, m2, linkrev, trp, - changed) - m1.set(f, fctx.flags()) - except OSError, inst: - self.ui.warn(_("trouble committing %s!\n") % f) - raise - except IOError, inst: - errcode = getattr(inst, 'errno', errno.ENOENT) - if error or errcode and errcode != errno.ENOENT: + if ctx.files(): + m1 = p1.manifest().copy() + m2 = p2.manifest() + + # check in files + new = {} + changed = [] + linkrev = len(self) + for f in sorted(ctx.modified() + ctx.added()): + self.ui.note(f + "\n") + try: + fctx = ctx[f] + new[f] = self._filecommit(fctx, m1, m2, linkrev, trp, + changed) + m1.set(f, fctx.flags()) + except OSError, inst: self.ui.warn(_("trouble committing %s!\n") % f) raise - else: - removed.append(f) + except IOError, inst: + errcode = getattr(inst, 'errno', errno.ENOENT) + if error or errcode and errcode != errno.ENOENT: + self.ui.warn(_("trouble committing %s!\n") % f) + raise + else: + removed.append(f) - # update manifest - m1.update(new) - removed = [f for f in sorted(removed) if f in m1 or f in m2] - drop = [f for f in removed if f in m1] - for f in drop: - del m1[f] - mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(), - p2.manifestnode(), (new, drop)) + # update manifest + m1.update(new) + removed = [f for f in sorted(removed) if f in m1 or f in m2] + drop = [f for f in removed if f in m1] + for f in drop: + del m1[f] + mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(), + p2.manifestnode(), (new, drop)) + files = changed + removed + else: + mn = p1.manifestnode() + files = [] # update changelog self.changelog.delayupdate() - n = self.changelog.add(mn, changed + removed, ctx.description(), + n = self.changelog.add(mn, files, ctx.description(), trp, p1.node(), p2.node(), user, ctx.date(), ctx.extra().copy()) p = lambda: self.changelog.writepending() and self.root or "" @@ -1231,14 +1243,15 @@ modified, added, clean = [], [], [] for fn in mf2: if fn in mf1: - if (mf1.flags(fn) != mf2.flags(fn) or - (mf1[fn] != mf2[fn] and - (mf2[fn] or ctx1[fn].cmp(ctx2[fn])))): + if (fn not in deleted and + (mf1.flags(fn) != mf2.flags(fn) or + (mf1[fn] != mf2[fn] and + (mf2[fn] or ctx1[fn].cmp(ctx2[fn]))))): modified.append(fn) elif listclean: clean.append(fn) del mf1[fn] - else: + elif fn not in deleted: added.append(fn) removed = mf1.keys() @@ -1337,13 +1350,16 @@ self.ui.status(_("no changes found\n")) result = 0 else: - if heads is None and fetch == [nullid]: + if heads is None and list(common) == [nullid]: self.ui.status(_("requesting all changes\n")) elif heads is None and remote.capable('changegroupsubset'): # issue1320, avoid a race if remote changed after discovery heads = rheads - if heads is None: + if remote.capable('getbundle'): + cg = remote.getbundle('pull', common=common, + heads=heads or rheads) + elif heads is None: cg = remote.changegroup(fetch, 'pull') elif not remote.capable('changegroupsubset'): raise util.Abort(_("partial pull cannot be done because " @@ -1356,27 +1372,6 @@ finally: lock.release() - self.ui.debug("checking for updated bookmarks\n") - rb = remote.listkeys('bookmarks') - changed = False - for k in rb.keys(): - if k in self._bookmarks: - nr, nl = rb[k], self._bookmarks[k] - if nr in self: - cr = self[nr] - cl = self[nl] - if cl.rev() >= cr.rev(): - continue - if cr in cl.descendants(): - self._bookmarks[k] = cr.node() - changed = True - self.ui.status(_("updating bookmark %s\n") % k) - else: - self.ui.warn(_("not updating divergent" - " bookmark %s\n") % k) - if changed: - bookmarks.write(self) - return result def checkpush(self, force, revs): @@ -1456,7 +1451,7 @@ for node in nodes: self.ui.debug("%s\n" % hex(node)) - def changegroupsubset(self, bases, heads, source, extranodes=None): + def changegroupsubset(self, bases, heads, source): """Compute a changegroup consisting of all the nodes that are descendents of any of the bases and ancestors of any of the heads. Return a chunkbuffer object whose read() method will return @@ -1468,214 +1463,129 @@ Another wrinkle is doing the reverse, figuring out which changeset in the changegroup a particular filenode or manifestnode belongs to. - - The caller can specify some nodes that must be included in the - changegroup using the extranodes argument. It should be a dict - where the keys are the filenames (or 1 for the manifest), and the - values are lists of (node, linknode) tuples, where node is a wanted - node and linknode is the changelog node that should be transmitted as - the linkrev. """ - - # Set up some initial variables - # Make it easy to refer to self.changelog cl = self.changelog - # Compute the list of changesets in this changegroup. - # Some bases may turn out to be superfluous, and some heads may be - # too. nodesbetween will return the minimal set of bases and heads - # necessary to re-create the changegroup. if not bases: bases = [nullid] - msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads) + csets, bases, heads = cl.nodesbetween(bases, heads) + # We assume that all ancestors of bases are known + common = set(cl.ancestors(*[cl.rev(n) for n in bases])) + return self._changegroupsubset(common, csets, heads, source) + + def getbundle(self, source, heads=None, common=None): + """Like changegroupsubset, but returns the set difference between the + ancestors of heads and the ancestors common. + + If heads is None, use the local heads. If common is None, use [nullid]. - if extranodes is None: - # can we go through the fast path ? - heads.sort() - allheads = self.heads() - allheads.sort() - if heads == allheads: - return self._changegroup(msng_cl_lst, source) + The nodes in common might not all be known locally due to the way the + current discovery protocol works. + """ + cl = self.changelog + if common: + nm = cl.nodemap + common = [n for n in common if n in nm] + else: + common = [nullid] + if not heads: + heads = cl.heads() + common, missing = cl.findcommonmissing(common, heads) + if not missing: + return None + return self._changegroupsubset(common, missing, heads, source) + + def _changegroupsubset(self, commonrevs, csets, heads, source): + + cl = self.changelog + mf = self.manifest + mfs = {} # needed manifests + fnodes = {} # needed file nodes + changedfiles = set() + fstate = ['', {}] + count = [0] + + # can we go through the fast path ? + heads.sort() + if heads == sorted(self.heads()): + return self._changegroup(csets, source) # slow path self.hook('preoutgoing', throw=True, source=source) - - self.changegroupinfo(msng_cl_lst, source) - - # We assume that all ancestors of bases are known - commonrevs = set(cl.ancestors(*[cl.rev(n) for n in bases])) + self.changegroupinfo(csets, source) - # Make it easy to refer to self.manifest - mnfst = self.manifest - # We don't know which manifests are missing yet - msng_mnfst_set = {} - # Nor do we know which filenodes are missing. - msng_filenode_set = {} - - # A changeset always belongs to itself, so the changenode lookup - # function for a changenode is identity. - def identity(x): - return x + # filter any nodes that claim to be part of the known set + def prune(revlog, missing): + for n in missing: + if revlog.linkrev(revlog.rev(n)) not in commonrevs: + yield n - # A function generating function that sets up the initial environment - # the inner function. - def filenode_collector(changedfiles): - # This gathers information from each manifestnode included in the - # changegroup about which filenodes the manifest node references - # so we can include those in the changegroup too. - # - # It also remembers which changenode each filenode belongs to. It - # does this by assuming the a filenode belongs to the changenode - # the first manifest that references it belongs to. - def collect_msng_filenodes(mnfstnode): - r = mnfst.rev(mnfstnode) - if mnfst.deltaparent(r) in mnfst.parentrevs(r): - # If the previous rev is one of the parents, - # we only need to see a diff. - deltamf = mnfst.readdelta(mnfstnode) - # For each line in the delta - for f, fnode in deltamf.iteritems(): - # And if the file is in the list of files we care - # about. - if f in changedfiles: - # Get the changenode this manifest belongs to - clnode = msng_mnfst_set[mnfstnode] - # Create the set of filenodes for the file if - # there isn't one already. - ndset = msng_filenode_set.setdefault(f, {}) - # And set the filenode's changelog node to the - # manifest's if it hasn't been set already. - ndset.setdefault(fnode, clnode) - else: - # Otherwise we need a full manifest. - m = mnfst.read(mnfstnode) - # For every file in we care about. - for f in changedfiles: - fnode = m.get(f, None) - # If it's in the manifest - if fnode is not None: - # See comments above. - clnode = msng_mnfst_set[mnfstnode] - ndset = msng_filenode_set.setdefault(f, {}) - ndset.setdefault(fnode, clnode) - return collect_msng_filenodes + def lookup(revlog, x): + if revlog == cl: + c = cl.read(x) + changedfiles.update(c[3]) + mfs.setdefault(c[0], x) + count[0] += 1 + self.ui.progress(_('bundling'), count[0], unit=_('changesets')) + return x + elif revlog == mf: + clnode = mfs[x] + mdata = mf.readfast(x) + for f in changedfiles: + if f in mdata: + fnodes.setdefault(f, {}).setdefault(mdata[f], clnode) + count[0] += 1 + self.ui.progress(_('bundling'), count[0], + unit=_('manifests'), total=len(mfs)) + return mfs[x] + else: + self.ui.progress( + _('bundling'), count[0], item=fstate[0], + unit=_('files'), total=len(changedfiles)) + return fstate[1][x] - # If we determine that a particular file or manifest node must be a - # node that the recipient of the changegroup will already have, we can - # also assume the recipient will have all the parents. This function - # prunes them from the set of missing nodes. - def prune(revlog, missingnodes): - hasset = set() - # If a 'missing' filenode thinks it belongs to a changenode we - # assume the recipient must have, then the recipient must have - # that filenode. - for n in missingnodes: - clrev = revlog.linkrev(revlog.rev(n)) - if clrev in commonrevs: - hasset.add(n) - for n in hasset: - missingnodes.pop(n, None) - for r in revlog.ancestors(*[revlog.rev(n) for n in hasset]): - missingnodes.pop(revlog.node(r), None) + bundler = changegroup.bundle10(lookup) - # Add the nodes that were explicitly requested. - def add_extra_nodes(name, nodes): - if not extranodes or name not in extranodes: - return - - for node, linknode in extranodes[name]: - if node not in nodes: - nodes[node] = linknode - - # Now that we have all theses utility functions to help out and - # logically divide up the task, generate the group. def gengroup(): - # The set of changed files starts empty. - changedfiles = set() - collect = changegroup.collector(cl, msng_mnfst_set, changedfiles) - # Create a changenode group generator that will call our functions # back to lookup the owning changenode and collect information. - group = cl.group(msng_cl_lst, identity, collect) - for cnt, chnk in enumerate(group): - yield chnk - # revlog.group yields three entries per node, so - # dividing by 3 gives an approximation of how many - # nodes have been processed. - self.ui.progress(_('bundling'), cnt / 3, - unit=_('changesets')) - changecount = cnt / 3 + for chunk in cl.group(csets, bundler): + yield chunk self.ui.progress(_('bundling'), None) - prune(mnfst, msng_mnfst_set) - add_extra_nodes(1, msng_mnfst_set) - msng_mnfst_lst = msng_mnfst_set.keys() - # Sort the manifestnodes by revision number. - msng_mnfst_lst.sort(key=mnfst.rev) # Create a generator for the manifestnodes that calls our lookup # and data collection functions back. - group = mnfst.group(msng_mnfst_lst, - lambda mnode: msng_mnfst_set[mnode], - filenode_collector(changedfiles)) - efiles = {} - for cnt, chnk in enumerate(group): - if cnt % 3 == 1: - mnode = chnk[:20] - efiles.update(mnfst.readdelta(mnode)) - yield chnk - # see above comment for why we divide by 3 - self.ui.progress(_('bundling'), cnt / 3, - unit=_('manifests'), total=changecount) + count[0] = 0 + for chunk in mf.group(prune(mf, mfs), bundler): + yield chunk self.ui.progress(_('bundling'), None) - efiles = len(efiles) - # These are no longer needed, dereference and toss the memory for - # them. - msng_mnfst_lst = None - msng_mnfst_set.clear() + mfs.clear() - if extranodes: - for fname in extranodes: - if isinstance(fname, int): - continue - msng_filenode_set.setdefault(fname, {}) - changedfiles.add(fname) # Go through all our files in order sorted by name. - for idx, fname in enumerate(sorted(changedfiles)): + count[0] = 0 + for fname in sorted(changedfiles): filerevlog = self.file(fname) if not len(filerevlog): raise util.Abort(_("empty or missing revlog for %s") % fname) - # Toss out the filenodes that the recipient isn't really - # missing. - missingfnodes = msng_filenode_set.pop(fname, {}) - prune(filerevlog, missingfnodes) - add_extra_nodes(fname, missingfnodes) - # If any filenodes are left, generate the group for them, - # otherwise don't bother. - if missingfnodes: - yield changegroup.chunkheader(len(fname)) - yield fname - # Sort the filenodes by their revision # (topological order) - nodeiter = list(missingfnodes) - nodeiter.sort(key=filerevlog.rev) - # Create a group generator and only pass in a changenode - # lookup function as we need to collect no information - # from filenodes. - group = filerevlog.group(nodeiter, - lambda fnode: missingfnodes[fnode]) - for chnk in group: - # even though we print the same progress on - # most loop iterations, put the progress call - # here so that time estimates (if any) can be updated - self.ui.progress( - _('bundling'), idx, item=fname, - unit=_('files'), total=efiles) - yield chnk + fstate[0] = fname + fstate[1] = fnodes.pop(fname, {}) + first = True + + for chunk in filerevlog.group(prune(filerevlog, fstate[1]), + bundler): + if first: + if chunk == bundler.close(): + break + count[0] += 1 + yield bundler.fileheader(fname) + first = False + yield chunk # Signal that no more groups are left. - yield changegroup.closechunk() + yield bundler.close() self.ui.progress(_('bundling'), None) - if msng_cl_lst: - self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source) + if csets: + self.hook('outgoing', node=hex(csets[0]), source=source) return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN') @@ -1693,75 +1603,75 @@ nodes is the set of nodes to send""" - self.hook('preoutgoing', throw=True, source=source) + cl = self.changelog + mf = self.manifest + mfs = {} + changedfiles = set() + fstate = [''] + count = [0] - cl = self.changelog - revset = set([cl.rev(n) for n in nodes]) + self.hook('preoutgoing', throw=True, source=source) self.changegroupinfo(nodes, source) - def identity(x): - return x + revset = set([cl.rev(n) for n in nodes]) def gennodelst(log): for r in log: if log.linkrev(r) in revset: yield log.node(r) - def lookuplinkrev_func(revlog): - def lookuplinkrev(n): - return cl.node(revlog.linkrev(revlog.rev(n))) - return lookuplinkrev + def lookup(revlog, x): + if revlog == cl: + c = cl.read(x) + changedfiles.update(c[3]) + mfs.setdefault(c[0], x) + count[0] += 1 + self.ui.progress(_('bundling'), count[0], unit=_('changesets')) + return x + elif revlog == mf: + count[0] += 1 + self.ui.progress(_('bundling'), count[0], + unit=_('manifests'), total=len(mfs)) + return cl.node(revlog.linkrev(revlog.rev(x))) + else: + self.ui.progress( + _('bundling'), count[0], item=fstate[0], + total=len(changedfiles), unit=_('files')) + return cl.node(revlog.linkrev(revlog.rev(x))) + + bundler = changegroup.bundle10(lookup) def gengroup(): '''yield a sequence of changegroup chunks (strings)''' # construct a list of all changed files - changedfiles = set() - mmfs = {} - collect = changegroup.collector(cl, mmfs, changedfiles) - for cnt, chnk in enumerate(cl.group(nodes, identity, collect)): - # revlog.group yields three entries per node, so - # dividing by 3 gives an approximation of how many - # nodes have been processed. - self.ui.progress(_('bundling'), cnt / 3, unit=_('changesets')) - yield chnk - changecount = cnt / 3 + for chunk in cl.group(nodes, bundler): + yield chunk self.ui.progress(_('bundling'), None) - mnfst = self.manifest - nodeiter = gennodelst(mnfst) - efiles = {} - for cnt, chnk in enumerate(mnfst.group(nodeiter, - lookuplinkrev_func(mnfst))): - if cnt % 3 == 1: - mnode = chnk[:20] - efiles.update(mnfst.readdelta(mnode)) - # see above comment for why we divide by 3 - self.ui.progress(_('bundling'), cnt / 3, - unit=_('manifests'), total=changecount) - yield chnk - efiles = len(efiles) + count[0] = 0 + for chunk in mf.group(gennodelst(mf), bundler): + yield chunk self.ui.progress(_('bundling'), None) - for idx, fname in enumerate(sorted(changedfiles)): + count[0] = 0 + for fname in sorted(changedfiles): filerevlog = self.file(fname) if not len(filerevlog): raise util.Abort(_("empty or missing revlog for %s") % fname) - nodeiter = gennodelst(filerevlog) - nodeiter = list(nodeiter) - if nodeiter: - yield changegroup.chunkheader(len(fname)) - yield fname - lookup = lookuplinkrev_func(filerevlog) - for chnk in filerevlog.group(nodeiter, lookup): - self.ui.progress( - _('bundling'), idx, item=fname, - total=efiles, unit=_('files')) - yield chnk + fstate[0] = fname + first = True + for chunk in filerevlog.group(gennodelst(filerevlog), bundler): + if first: + if chunk == bundler.close(): + break + count[0] += 1 + yield bundler.fileheader(fname) + first = False + yield chunk + yield bundler.close() self.ui.progress(_('bundling'), None) - yield changegroup.closechunk() - if nodes: self.hook('outgoing', node=hex(nodes[0]), source=source) @@ -1799,9 +1709,9 @@ # inconsistent view cl = self.changelog cl.delayupdate() - oldheads = len(cl.heads()) + oldheads = cl.heads() - tr = self.transaction("\n".join([srctype, urlmod.hidepassword(url)])) + tr = self.transaction("\n".join([srctype, util.hidepassword(url)])) try: trp = weakref.proxy(tr) # pull off the changeset group @@ -1819,6 +1729,7 @@ pr = prog() source.callback = pr + source.changelogheader() if (cl.addgroup(source, csmap, trp) is None and not emptyok): raise util.Abort(_("received changelog group is empty")) @@ -1838,6 +1749,7 @@ # if the result of the merge of 1 and 2 is the same in 3 and 4, # no new manifest will be created and the manifest group will # be empty during the pull + source.manifestheader() self.manifest.addgroup(source, revmap, trp) self.ui.progress(_('manifests'), None) @@ -1859,9 +1771,10 @@ source.callback = None while 1: - f = source.chunk() - if not f: + chunkdata = source.filelogheader() + if not chunkdata: break + f = chunkdata["filename"] self.ui.debug("adding %s revisions\n" % f) pr() fl = self.file(f) @@ -1890,14 +1803,20 @@ _('missing file data for %s:%s - run hg verify') % (f, hex(n))) - newheads = len(cl.heads()) - heads = "" - if oldheads and newheads != oldheads: - heads = _(" (%+d heads)") % (newheads - oldheads) + dh = 0 + if oldheads: + heads = cl.heads() + dh = len(heads) - len(oldheads) + for h in heads: + if h not in oldheads and 'close' in self[h].extra(): + dh -= 1 + htext = "" + if dh: + htext = _(" (%+d heads)") % dh self.ui.status(_("added %d changesets" " with %d changes to %d files%s\n") - % (changesets, revisions, files, heads)) + % (changesets, revisions, files, htext)) if changesets > 0: p = lambda: cl.writepending() and self.root or "" @@ -1925,16 +1844,11 @@ self.hook("incoming", node=hex(cl.node(i)), source=srctype, url=url) - # FIXME - why does this care about tip? - if newheads == oldheads: - bookmarks.update(self, self.dirstate.parents(), self['tip'].node()) - # never return 0 here: - if newheads < oldheads: - return newheads - oldheads - 1 + if dh < 0: + return dh - 1 else: - return newheads - oldheads + 1 - + return dh + 1 def stream_in(self, remote, requirements): lock = self.lock() @@ -2024,10 +1938,22 @@ return self.pull(remote, heads) def pushkey(self, namespace, key, old, new): - return pushkey.push(self, namespace, key, old, new) + self.hook('prepushkey', throw=True, namespace=namespace, key=key, + old=old, new=new) + ret = pushkey.push(self, namespace, key, old, new) + self.hook('pushkey', namespace=namespace, key=key, old=old, new=new, + ret=ret) + return ret def listkeys(self, namespace): - return pushkey.list(self, namespace) + self.hook('prelistkeys', throw=True, namespace=namespace) + values = pushkey.list(self, namespace) + self.hook('listkeys', namespace=namespace, values=values) + return values + + def debugwireargs(self, one, two, three=None, four=None, five=None): + '''used to test argument passing over the wire''' + return "%s %s %s %s %s" % (one, two, three, four, five) # used to avoid circular references so destructors work def aftertrans(files): @@ -2043,7 +1969,7 @@ return os.path.join(base, name.replace('journal', 'undo', 1)) def instance(ui, path, create): - return localrepository(ui, util.drop_scheme('file', path), create) + return localrepository(ui, util.localpath(path), create) def islocal(path): return True
--- a/mercurial/mail.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/mail.py Wed May 18 09:56:27 2011 +0200 @@ -91,7 +91,7 @@ if ret: raise util.Abort('%s %s' % ( os.path.basename(program.split(None, 1)[0]), - util.explain_exit(ret)[0])) + util.explainexit(ret)[0])) def connect(ui): '''make a mail connection. return a function to send mail. @@ -112,7 +112,7 @@ raise util.Abort(_('smtp specified as email transport, ' 'but no smtp host configured')) else: - if not util.find_exe(method): + if not util.findexe(method): raise util.Abort(_('%r specified as email transport, ' 'but not in PATH') % method)
--- a/mercurial/manifest.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/manifest.py Wed May 18 09:56:27 2011 +0200 @@ -38,6 +38,14 @@ r = self.rev(node) return self.parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r))) + def readfast(self, node): + '''use the faster of readdelta or read''' + r = self.rev(node) + deltaparent = self.deltaparent(r) + if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r): + return self.readdelta(node) + return self.read(node) + def read(self, node): if node == revlog.nullid: return manifestdict() # don't upset local cache
--- a/mercurial/match.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/match.py Wed May 18 09:56:27 2011 +0200 @@ -6,7 +6,7 @@ # GNU General Public License version 2 or any later version. import re -import util +import scmutil, util from i18n import _ class match(object): @@ -269,13 +269,16 @@ pats = [] for kind, name in [_patsplit(p, default) for p in names]: if kind in ('glob', 'relpath'): - name = util.canonpath(root, cwd, name, auditor) + name = scmutil.canonpath(root, cwd, name, auditor) elif kind in ('relglob', 'path'): name = util.normpath(name) elif kind in ('listfile', 'listfile0'): - delimiter = kind == 'listfile0' and '\0' or '\n' try: - files = open(name, 'r').read().split(delimiter) + files = util.readfile(name) + if kind == 'listfile0': + files = files.split('\0') + else: + files = files.splitlines() files = [f for f in files if f] except EnvironmentError: raise util.Abort(_("unable to read file list (%s)") % name)
--- a/mercurial/merge.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/merge.py Wed May 18 09:56:27 2011 +0200 @@ -7,7 +7,7 @@ from node import nullid, nullrev, hex, bin from i18n import _ -import util, filemerge, copies, subrepo +import scmutil, util, filemerge, copies, subrepo import errno, os, shutil class mergestate(object): @@ -47,7 +47,7 @@ self._dirty = False def add(self, fcl, fco, fca, fd, flags): hash = util.sha1(fcl.path()).hexdigest() - self._repo.opener("merge/" + hash, "w").write(fcl.data()) + self._repo.opener.write("merge/" + hash, fcl.data()) self._state[fd] = ['u', hash, fcl.path(), fca.path(), hex(fca.filenode()), fco.path(), flags] self._dirty = True @@ -268,10 +268,9 @@ updated, merged, removed, unresolved = 0, 0, 0, 0 ms = mergestate(repo) - ms.reset(wctx.parents()[0].node()) + ms.reset(wctx.p1().node()) moves = [] action.sort(key=actionkey) - substate = wctx.substate # prime # prescan for merges u = repo.ui @@ -286,7 +285,7 @@ fco = mctx[f2] if mctx == actx: # backwards, use working dir parent as ancestor if fcl.parents(): - fca = fcl.parents()[0] + fca = fcl.p1() else: fca = repo.filectx(f, fileid=nullrev) else: @@ -303,7 +302,7 @@ repo.ui.debug("removing %s\n" % f) os.unlink(repo.wjoin(f)) - audit_path = util.path_auditor(repo.root) + audit_path = scmutil.pathauditor(repo.root) numupdates = len(action) for i, a in enumerate(action): @@ -337,7 +336,7 @@ updated += 1 else: merged += 1 - util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags) + util.setflags(repo.wjoin(fd), 'l' in flags, 'x' in flags) if (move and repo.dirstate.normalize(fd) != f and os.path.lexists(repo.wjoin(f))): repo.ui.debug("removing %s\n" % f) @@ -371,7 +370,7 @@ repo.ui.warn(" %s\n" % nf) elif m == "e": # exec flags = a[2] - util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags) + util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags) ms.commit() u.progress(_('updating'), None, total=numupdates, unit=_('files')) @@ -439,7 +438,7 @@ if f: repo.dirstate.forget(f) -def update(repo, node, branchmerge, force, partial): +def update(repo, node, branchmerge, force, partial, ancestor=None): """ Perform a merge between the working directory and the given node @@ -492,9 +491,12 @@ overwrite = force and not branchmerge pl = wc.parents() p1, p2 = pl[0], repo[node] - pa = p1.ancestor(p2) + if ancestor: + pa = repo[ancestor] + else: + pa = p1.ancestor(p2) + fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2) - fastforward = False ### check phase if not overwrite and len(pl) > 1: @@ -504,9 +506,7 @@ raise util.Abort(_("merging with a working directory ancestor" " has no effect")) elif pa == p1: - if p1.branch() != p2.branch(): - fastforward = True - else: + if p1.branch() == p2.branch(): raise util.Abort(_("nothing to merge (use 'hg update'" " or check 'hg heads')")) if not force and (wc.files() or wc.deleted()): @@ -551,7 +551,7 @@ if not partial: repo.dirstate.setparents(fp1, fp2) recordupdates(repo, action, branchmerge) - if not branchmerge and not fastforward: + if not branchmerge: repo.dirstate.setbranch(p2.branch()) finally: wlock.release()
--- a/mercurial/minirst.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/minirst.py Wed May 18 09:56:27 2011 +0200 @@ -467,7 +467,7 @@ print return blocks - text = open(sys.argv[1]).read() + text = util.readfile(sys.argv[1]) blocks = debug(findblocks, text) blocks = debug(findliteralblocks, blocks) blocks, pruned = debug(prunecontainers, blocks, sys.argv[2:])
--- a/mercurial/osutil.c Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/osutil.c Wed May 18 09:56:27 2011 +0200 @@ -514,6 +514,22 @@ } #endif +#ifdef __APPLE__ +#include <ApplicationServices/ApplicationServices.h> + +static PyObject *isgui(PyObject *self) +{ + CFDictionaryRef dict = CGSessionCopyCurrentDictionary(); + + if (dict != NULL) { + CFRelease(dict); + return Py_True; + } else { + return Py_False; + } +} +#endif + static char osutil_doc[] = "Native operating system services."; static PyMethodDef methods[] = { @@ -524,6 +540,12 @@ "Open a file with POSIX-like semantics.\n" "On error, this function may raise either a WindowsError or an IOError."}, #endif +#ifdef __APPLE__ + { + "isgui", (PyCFunction)isgui, METH_NOARGS, + "Is a CoreGraphics session available?" + }, +#endif {NULL, NULL} };
--- a/mercurial/parser.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/parser.py Wed May 18 09:56:27 2011 +0200 @@ -78,7 +78,9 @@ 'generate a parse tree from a message' self._iter = self._tokenizer(message) self._advance() - return self._parse() + res = self._parse() + token, value, pos = self.current + return res, pos def eval(self, tree): 'recursively evaluate a parse tree using node methods' if not isinstance(tree, tuple):
--- a/mercurial/patch.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/patch.py Wed May 18 09:56:27 2011 +0200 @@ -11,31 +11,13 @@ from i18n import _ from node import hex, nullid, short -import base85, mdiff, util, diffhelpers, copies, encoding +import base85, mdiff, scmutil, util, diffhelpers, copies, encoding gitre = re.compile('diff --git a/(.*) b/(.*)') class PatchError(Exception): pass -# helper functions - -def copyfile(src, dst, basedir): - abssrc, absdst = [util.canonpath(basedir, basedir, x) for x in [src, dst]] - if os.path.lexists(absdst): - raise util.Abort(_("cannot create %s: destination already exists") % - dst) - - dstdir = os.path.dirname(absdst) - if dstdir and not os.path.isdir(dstdir): - try: - os.makedirs(dstdir) - except IOError: - raise util.Abort( - _("cannot create %s: unable to create destination directory") - % dst) - - util.copyfile(abssrc, absdst) # public functions @@ -380,24 +362,142 @@ break yield l +class abstractbackend(object): + def __init__(self, ui): + self.ui = ui + + def readlines(self, fname): + """Return target file lines, or its content as a single line + for symlinks. + """ + raise NotImplementedError + + def writelines(self, fname, lines): + """Write lines to target file.""" + raise NotImplementedError + + def unlink(self, fname): + """Unlink target file.""" + raise NotImplementedError + + def writerej(self, fname, failed, total, lines): + """Write rejected lines for fname. total is the number of hunks + which failed to apply and total the total number of hunks for this + files. + """ + pass + + def copy(self, src, dst): + """Copy src file into dst file. Create intermediate directories if + necessary. Files are specified relatively to the patching base + directory. + """ + raise NotImplementedError + + def exists(self, fname): + raise NotImplementedError + +class fsbackend(abstractbackend): + def __init__(self, ui, basedir): + super(fsbackend, self).__init__(ui) + self.opener = scmutil.opener(basedir) + + def readlines(self, fname): + if os.path.islink(fname): + return [os.readlink(fname)] + fp = self.opener(fname, 'r') + try: + return list(fp) + finally: + fp.close() + + def writelines(self, fname, lines): + # Ensure supplied data ends in fname, being a regular file or + # a symlink. _updatedir will -too magically- take care + # of setting it to the proper type afterwards. + st_mode = None + islink = os.path.islink(fname) + if islink: + fp = cStringIO.StringIO() + else: + try: + st_mode = os.lstat(fname).st_mode & 0777 + except OSError, e: + if e.errno != errno.ENOENT: + raise + fp = self.opener(fname, 'w') + try: + fp.writelines(lines) + if islink: + self.opener.symlink(fp.getvalue(), fname) + if st_mode is not None: + os.chmod(fname, st_mode) + finally: + fp.close() + + def unlink(self, fname): + os.unlink(fname) + + def writerej(self, fname, failed, total, lines): + fname = fname + ".rej" + self.ui.warn( + _("%d out of %d hunks FAILED -- saving rejects to file %s\n") % + (failed, total, fname)) + fp = self.opener(fname, 'w') + fp.writelines(lines) + fp.close() + + def copy(self, src, dst): + basedir = self.opener.base + abssrc, absdst = [scmutil.canonpath(basedir, basedir, x) + for x in [src, dst]] + if os.path.lexists(absdst): + raise util.Abort(_("cannot create %s: destination already exists") + % dst) + dstdir = os.path.dirname(absdst) + if dstdir and not os.path.isdir(dstdir): + try: + os.makedirs(dstdir) + except IOError: + raise util.Abort( + _("cannot create %s: unable to create destination directory") + % dst) + util.copyfile(abssrc, absdst) + + def exists(self, fname): + return os.path.lexists(fname) + # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@') contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)') eolmodes = ['strict', 'crlf', 'lf', 'auto'] class patchfile(object): - def __init__(self, ui, fname, opener, missing=False, eolmode='strict'): + def __init__(self, ui, fname, backend, missing=False, eolmode='strict'): self.fname = fname self.eolmode = eolmode self.eol = None - self.opener = opener + self.backend = backend self.ui = ui self.lines = [] self.exists = False self.missing = missing if not missing: try: - self.lines = self.readlines(fname) + self.lines = self.backend.readlines(fname) + if self.lines: + # Normalize line endings + if self.lines[0].endswith('\r\n'): + self.eol = '\r\n' + elif self.lines[0].endswith('\n'): + self.eol = '\n' + if eolmode != 'strict': + nlines = [] + for l in self.lines: + if l.endswith('\r\n'): + l = l[:-2] + '\n' + nlines.append(l) + self.lines = nlines self.exists = True except IOError: pass @@ -413,57 +513,23 @@ self.printfile(False) self.hunks = 0 - def readlines(self, fname): - if os.path.islink(fname): - return [os.readlink(fname)] - fp = self.opener(fname, 'r') - try: - lr = linereader(fp, self.eolmode != 'strict') - lines = list(lr) - self.eol = lr.eol - return lines - finally: - fp.close() - def writelines(self, fname, lines): - # Ensure supplied data ends in fname, being a regular file or - # a symlink. cmdutil.updatedir will -too magically- take care - # of setting it to the proper type afterwards. - st_mode = None - islink = os.path.islink(fname) - if islink: - fp = cStringIO.StringIO() + if self.eolmode == 'auto': + eol = self.eol + elif self.eolmode == 'crlf': + eol = '\r\n' else: - try: - st_mode = os.lstat(fname).st_mode & 0777 - except OSError, e: - if e.errno != errno.ENOENT: - raise - fp = self.opener(fname, 'w') - try: - if self.eolmode == 'auto': - eol = self.eol - elif self.eolmode == 'crlf': - eol = '\r\n' - else: - eol = '\n' + eol = '\n' - if self.eolmode != 'strict' and eol and eol != '\n': - for l in lines: - if l and l[-1] == '\n': - l = l[:-1] + eol - fp.write(l) - else: - fp.writelines(lines) - if islink: - self.opener.symlink(fp.getvalue(), fname) - if st_mode is not None: - os.chmod(fname, st_mode) - finally: - fp.close() + if self.eolmode != 'strict' and eol and eol != '\n': + rawlines = [] + for l in lines: + if l and l[-1] == '\n': + l = l[:-1] + eol + rawlines.append(l) + lines = rawlines - def unlink(self, fname): - os.unlink(fname) + self.backend.writelines(fname, lines) def printfile(self, warn): if self.fileprinted: @@ -488,37 +554,21 @@ cand.sort(key=lambda x: abs(x - linenum)) return cand - def hashlines(self): - self.hash = {} - for x, s in enumerate(self.lines): - self.hash.setdefault(s, []).append(x) - - def makerejlines(self, fname): - base = os.path.basename(fname) - yield "--- %s\n+++ %s\n" % (base, base) - for x in self.rej: - for l in x.hunk: - yield l - if l[-1] != '\n': - yield "\n\ No newline at end of file\n" - def write_rej(self): # our rejects are a little different from patch(1). This always # creates rejects in the same form as the original patch. A file # header is inserted so that you can run the reject through patch again # without having to type the filename. - if not self.rej: return - - fname = self.fname + ".rej" - self.ui.warn( - _("%d out of %d hunks FAILED -- saving rejects to file %s\n") % - (len(self.rej), self.hunks, fname)) - - fp = self.opener(fname, 'w') - fp.writelines(self.makerejlines(self.fname)) - fp.close() + base = os.path.basename(self.fname) + lines = ["--- %s\n+++ %s\n" % (base, base)] + for x in self.rej: + for l in x.hunk: + lines.append(l) + if l[-1] != '\n': + lines.append("\n\ No newline at end of file\n") + self.backend.writerej(self.fname, len(self.rej), self.hunks, lines) def apply(self, h): if not h.complete(): @@ -539,11 +589,11 @@ if isinstance(h, binhunk): if h.rmfile(): - self.unlink(self.fname) + self.backend.unlink(self.fname) else: self.lines[:] = h.new() self.offset += len(h.new()) - self.dirty = 1 + self.dirty = True return 0 horig = h @@ -567,15 +617,17 @@ # fast case code if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0: if h.rmfile(): - self.unlink(self.fname) + self.backend.unlink(self.fname) else: self.lines[start : start + h.lena] = h.new() self.offset += h.lenb - h.lena - self.dirty = 1 + self.dirty = True return 0 - # ok, we couldn't match the hunk. Lets look for offsets and fuzz it - self.hashlines() + # ok, we couldn't match the hunk. Lets look for offsets and fuzz it + self.hash = {} + for x, s in enumerate(self.lines): + self.hash.setdefault(s, []).append(x) if h.hunk[-1][0] != ' ': # if the hunk tried to put something at the bottom of the file # override the start line and use eof here @@ -594,7 +646,7 @@ self.lines[l : l + len(old)] = newlines self.offset += len(newlines) - len(old) self.skew = l - orig_start - self.dirty = 1 + self.dirty = True offset = l - orig_start - fuzzlen if fuzzlen: msg = _("Hunk #%d succeeded at %d " @@ -613,6 +665,12 @@ self.rej.append(horig) return -1 + def close(self): + if self.dirty: + self.writelines(self.fname, self.lines) + self.write_rej() + return len(self.rej) + class hunk(object): def __init__(self, desc, num, lr, context, create=False, remove=False): self.number = num @@ -680,6 +738,7 @@ del self.b[-1] self.lena -= 1 self.lenb -= 1 + self._fixnewline(lr) def read_context_hunk(self, lr): self.desc = lr.readline() @@ -782,9 +841,14 @@ self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena, self.startb, self.lenb) self.hunk[0] = self.desc + self._fixnewline(lr) - def fix_newline(self): - diffhelpers.fix_newline(self.hunk, self.a, self.b) + def _fixnewline(self, lr): + l = lr.readline() + if l.startswith('\ '): + diffhelpers.fix_newline(self.hunk, self.a, self.b) + else: + lr.push(l) def complete(self): return len(self.a) == self.lena and len(self.b) == self.lenb @@ -912,16 +976,16 @@ count -= 1 return path[:i].lstrip(), path[i:].rstrip() -def selectfile(afile_orig, bfile_orig, hunk, strip): +def selectfile(backend, afile_orig, bfile_orig, hunk, strip): nulla = afile_orig == "/dev/null" nullb = bfile_orig == "/dev/null" abase, afile = pathstrip(afile_orig, strip) - gooda = not nulla and os.path.lexists(afile) + gooda = not nulla and backend.exists(afile) bbase, bfile = pathstrip(bfile_orig, strip) if afile == bfile: goodb = gooda else: - goodb = not nullb and os.path.lexists(bfile) + goodb = not nullb and backend.exists(bfile) createfunc = hunk.createfile missing = not goodb and not gooda and not createfunc() @@ -984,7 +1048,7 @@ fp.seek(pos) return gitpatches -def iterhunks(ui, fp): +def iterhunks(fp): """Read a patch and yield the following events: - ("file", afile, bfile, firsthunk): select a new target file. - ("hunk", hunk): a new hunk is ready to be applied, follows a @@ -993,12 +1057,11 @@ maps filenames to gitpatch records. Unique event. """ changed = {} - current_hunk = None afile = "" bfile = "" state = None hunknum = 0 - emitfile = False + emitfile = newfile = False git = False # our states @@ -1007,15 +1070,9 @@ lr = linereader(fp) while True: - newfile = newgitfile = False x = lr.readline() if not x: break - if current_hunk: - if x.startswith('\ '): - current_hunk.fix_newline() - yield 'hunk', current_hunk - current_hunk = None if (state == BFILE and ((not context and x[0] == '@') or ((context is not False) and x.startswith('***************')))): if context is None and x.startswith('***************'): @@ -1023,18 +1080,20 @@ gpatch = changed.get(bfile) create = afile == '/dev/null' or gpatch and gpatch.op == 'ADD' remove = bfile == '/dev/null' or gpatch and gpatch.op == 'DELETE' - current_hunk = hunk(x, hunknum + 1, lr, context, create, remove) + h = hunk(x, hunknum + 1, lr, context, create, remove) hunknum += 1 if emitfile: emitfile = False - yield 'file', (afile, bfile, current_hunk) + yield 'file', (afile, bfile, h) + yield 'hunk', h elif state == BFILE and x.startswith('GIT binary patch'): - current_hunk = binhunk(changed[bfile]) + h = binhunk(changed[bfile]) hunknum += 1 if emitfile: emitfile = False - yield 'file', ('a/' + afile, 'b/' + bfile, current_hunk) - current_hunk.extract(lr) + yield 'file', ('a/' + afile, 'b/' + bfile, h) + h.extract(lr) + yield 'hunk', h elif x.startswith('diff --git'): # check for git diff, scanning the whole patch file if needed m = gitre.match(x) @@ -1052,7 +1111,7 @@ if gp and (gp.op in ('COPY', 'DELETE', 'RENAME', 'ADD') or gp.mode): afile = bfile - newgitfile = True + newfile = True elif x.startswith('---'): # check for a unified diff l2 = lr.readline() @@ -1079,16 +1138,11 @@ afile = parsefilename(x) bfile = parsefilename(l2) - if newgitfile or newfile: + if newfile: + newfile = False emitfile = True state = BFILE hunknum = 0 - if current_hunk: - if current_hunk.complete(): - yield 'hunk', current_hunk - else: - raise PatchError(_("malformed patch %s %s") % (afile, - current_hunk.desc)) def applydiff(ui, fp, changed, strip=1, eolmode='strict'): """Reads a patch from fp and tries to apply it. @@ -1101,28 +1155,19 @@ read in binary mode. Otherwise, line endings are ignored when patching then normalized according to 'eolmode'. - Callers probably want to call 'cmdutil.updatedir' after this to + Callers probably want to call '_updatedir' after this to apply certain categories of changes not done by this function. """ - return _applydiff(ui, fp, patchfile, copyfile, changed, strip=strip, + return _applydiff(ui, fp, patchfile, changed, strip=strip, eolmode=eolmode) -def _applydiff(ui, fp, patcher, copyfn, changed, strip=1, eolmode='strict'): +def _applydiff(ui, fp, patcher, changed, strip=1, eolmode='strict'): rejects = 0 err = 0 current_file = None - cwd = os.getcwd() - opener = util.opener(cwd) + backend = fsbackend(ui, os.getcwd()) - def closefile(): - if not current_file: - return 0 - if current_file.dirty: - current_file.writelines(current_file.fname, current_file.lines) - current_file.write_rej() - return len(current_file.rej) - - for state, values in iterhunks(ui, fp): + for state, values in iterhunks(fp): if state == 'hunk': if not current_file: continue @@ -1132,15 +1177,16 @@ if ret > 0: err = 1 elif state == 'file': - rejects += closefile() + if current_file: + rejects += current_file.close() afile, bfile, first_hunk = values try: - current_file, missing = selectfile(afile, bfile, + current_file, missing = selectfile(backend, afile, bfile, first_hunk, strip) - current_file = patcher(ui, current_file, opener, + current_file = patcher(ui, current_file, backend, missing=missing, eolmode=eolmode) - except PatchError, err: - ui.warn(str(err) + '\n') + except PatchError, inst: + ui.warn(str(inst) + '\n') current_file = None rejects += 1 continue @@ -1152,18 +1198,62 @@ # Binary patches really overwrite target files, copying them # will just make it fails with "target file exists" if gp.op in ('COPY', 'RENAME') and not gp.binary: - copyfn(gp.oldpath, gp.path, cwd) + backend.copy(gp.oldpath, gp.path) changed[gp.path] = gp else: raise util.Abort(_('unsupported parser state: %s') % state) - rejects += closefile() + if current_file: + rejects += current_file.close() if rejects: return -1 return err -def externalpatch(patcher, patchname, ui, strip, cwd, files): +def _updatedir(ui, repo, patches, similarity=0): + '''Update dirstate after patch application according to metadata''' + if not patches: + return [] + copies = [] + removes = set() + cfiles = patches.keys() + cwd = repo.getcwd() + if cwd: + cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()] + for f in patches: + gp = patches[f] + if not gp: + continue + if gp.op == 'RENAME': + copies.append((gp.oldpath, gp.path)) + removes.add(gp.oldpath) + elif gp.op == 'COPY': + copies.append((gp.oldpath, gp.path)) + elif gp.op == 'DELETE': + removes.add(gp.path) + + wctx = repo[None] + for src, dst in copies: + scmutil.dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd) + if (not similarity) and removes: + wctx.remove(sorted(removes), True) + + for f in patches: + gp = patches[f] + if gp and gp.mode: + islink, isexec = gp.mode + dst = repo.wjoin(gp.path) + # patch won't create empty files + if gp.op == 'ADD' and not os.path.lexists(dst): + flags = (isexec and 'x' or '') + (islink and 'l' or '') + repo.wwrite(gp.path, '', flags) + util.setflags(dst, islink, isexec) + scmutil.addremove(repo, cfiles, similarity=similarity) + files = patches.keys() + files.extend([r for r in removes if r not in files]) + return sorted(files) + +def _externalpatch(patcher, patchname, ui, strip, cwd, files): """use <patcher> to apply <patchname> to the working directory. returns whether patch was applied with fuzz factor.""" @@ -1178,7 +1268,7 @@ line = line.rstrip() ui.note(line + '\n') if line.startswith('patching file '): - pf = util.parse_patch_output(line) + pf = util.parsepatchoutput(line) printed_file = False files.setdefault(pf, None) elif line.find('with fuzz') >= 0: @@ -1197,10 +1287,11 @@ code = fp.close() if code: raise PatchError(_("patch command failed: %s") % - util.explain_exit(code)[0]) + util.explainexit(code)[0]) return fuzz -def internalpatch(patchobj, ui, strip, cwd, files=None, eolmode='strict'): +def internalpatch(ui, repo, patchobj, strip, cwd, files=None, eolmode='strict', + similarity=0): """use builtin patch to apply <patchobj> to the working directory. returns whether patch was applied with fuzz factor.""" @@ -1226,11 +1317,14 @@ os.chdir(curdir) if fp != patchobj: fp.close() + touched = _updatedir(ui, repo, files, similarity) + files.update(dict.fromkeys(touched)) if ret < 0: raise PatchError(_('patch failed to apply')) return ret > 0 -def patch(patchname, ui, strip=1, cwd=None, files=None, eolmode='strict'): +def patch(ui, repo, patchname, strip=1, cwd=None, files=None, eolmode='strict', + similarity=0): """Apply <patchname> to the working directory. 'eolmode' specifies how end of lines should be handled. It can be: @@ -1247,11 +1341,44 @@ files = {} try: if patcher: - return externalpatch(patcher, patchname, ui, strip, cwd, files) - return internalpatch(patchname, ui, strip, cwd, files, eolmode) + try: + return _externalpatch(patcher, patchname, ui, strip, cwd, + files) + finally: + touched = _updatedir(ui, repo, files, similarity) + files.update(dict.fromkeys(touched)) + return internalpatch(ui, repo, patchname, strip, cwd, files, eolmode, + similarity) except PatchError, err: raise util.Abort(str(err)) +def changedfiles(ui, repo, patchpath, strip=1): + backend = fsbackend(ui, repo.root) + fp = open(patchpath, 'rb') + try: + changed = set() + for state, values in iterhunks(fp): + if state == 'hunk': + continue + elif state == 'file': + afile, bfile, first_hunk = values + current_file, missing = selectfile(backend, afile, bfile, + first_hunk, strip) + changed.add(current_file) + elif state == 'git': + for gp in values: + gp.path = pathstrip(gp.path, strip - 1)[1] + changed.add(gp.path) + if gp.oldpath: + gp.oldpath = pathstrip(gp.oldpath, strip - 1)[1] + if gp.op == 'RENAME': + changed.add(gp.oldpath) + else: + raise util.Abort(_('unsupported parser state: %s') % state) + return changed + finally: + fp.close() + def b85diff(to, tn): '''print base85-encoded binary diff''' def gitindex(text): @@ -1331,7 +1458,7 @@ opts = mdiff.defaultopts if not node1 and not node2: - node1 = repo.dirstate.parents()[0] + node1 = repo.dirstate.p1() def lrugetfilectx(): cache = {}
--- a/mercurial/posix.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/posix.py Wed May 18 09:56:27 2011 +0200 @@ -6,14 +6,13 @@ # GNU General Public License version 2 or any later version. from i18n import _ -import osutil -import os, sys, errno, stat, getpass, pwd, grp +import os, sys, errno, stat, getpass, pwd, grp, tempfile posixfile = open nulldev = '/dev/null' normpath = os.path.normpath samestat = os.path.samestat -os_link = os.link +oslink = os.link unlink = os.unlink rename = os.rename expandglobs = False @@ -29,30 +28,7 @@ '''return number of hardlinks for the given file''' return os.lstat(name).st_nlink -def rcfiles(path): - rcs = [os.path.join(path, 'hgrc')] - rcdir = os.path.join(path, 'hgrc.d') - try: - rcs.extend([os.path.join(rcdir, f) - for f, kind in osutil.listdir(rcdir) - if f.endswith(".rc")]) - except OSError: - pass - return rcs - -def system_rcpath(): - path = [] - # old mod_python does not set sys.argv - if len(getattr(sys, 'argv', [])) > 0: - path.extend(rcfiles(os.path.dirname(sys.argv[0]) + - '/../etc/mercurial')) - path.extend(rcfiles('/etc/mercurial')) - return path - -def user_rcpath(): - return [os.path.expanduser('~/.hgrc')] - -def parse_patch_output(output_line): +def parsepatchoutput(output_line): """parses the output produced by patch and returns the filename""" pf = output_line[14:] if os.sys.platform == 'OpenVMS': @@ -68,11 +44,11 @@ args = user and ("%s@%s" % (user, host)) or host return port and ("%s -p %s" % (args, port)) or args -def is_exec(f): +def isexec(f): """check whether a file is executable""" return (os.lstat(f).st_mode & 0100 != 0) -def set_flags(f, l, x): +def setflags(f, l, x): s = os.lstat(f).st_mode if l: if not stat.S_ISLNK(s): @@ -83,7 +59,7 @@ os.unlink(f) try: os.symlink(data, f) - except: + except OSError: # failed to make a link, rewrite file fp = open(f, "w") fp.write(data) @@ -108,7 +84,51 @@ # Turn off all +x bits os.chmod(f, s & 0666) -def set_binary(fd): +def checkexec(path): + """ + Check whether the given path is on a filesystem with UNIX-like exec flags + + Requires a directory (like /foo/.hg) + """ + + # VFAT on some Linux versions can flip mode but it doesn't persist + # a FS remount. Frequently we can detect it if files are created + # with exec bit on. + + try: + EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH + fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-') + try: + os.close(fh) + m = os.stat(fn).st_mode & 0777 + new_file_has_exec = m & EXECFLAGS + os.chmod(fn, m ^ EXECFLAGS) + exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m) + finally: + os.unlink(fn) + except (IOError, OSError): + # we don't care, the user probably won't be able to commit anyway + return False + return not (new_file_has_exec or exec_flags_cannot_flip) + +def checklink(path): + """check whether the given path is on a symlink-capable filesystem""" + # mktemp is not racy because symlink creation will fail if the + # file already exists + name = tempfile.mktemp(dir=path, prefix='hg-checklink-') + try: + os.symlink(".", name) + os.unlink(name) + return True + except (OSError, AttributeError): + return False + +def checkosfilename(path): + '''Check that the base-relative path is a valid filename on this platform. + Returns None if the path is ok, or a UI string describing the problem.''' + pass # on posix platforms, every path is ok + +def setbinary(fd): pass def pconvert(path): @@ -190,7 +210,7 @@ except OSError, inst: return inst.errno != errno.ESRCH -def explain_exit(code): +def explainexit(code): """return a 2-tuple (desc, code) describing a subprocess status (codes from kill are negative - not os.system/wait encoding)""" if code >= 0: @@ -201,7 +221,7 @@ """Return True if the stat object st is from the current user.""" return st.st_uid == os.getuid() -def find_exe(command): +def findexe(command): '''Find executable for command searching like which does. If command is a basename then PATH is searched for command. PATH isn't searched if command is an absolute or relative path. @@ -224,7 +244,7 @@ return executable return None -def set_signal_handler(): +def setsignalhandler(): pass def statfiles(files): @@ -243,10 +263,6 @@ '''return name of current user''' return getpass.getuser() -def expand_glob(pats): - '''On Windows, expand the implicit globs in a list of patterns''' - return list(pats) - def username(uid=None): """Return the name of the user with the given uid. @@ -296,7 +312,9 @@ if not os.isatty(fd): continue arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8) - return array.array('h', arri)[1] + width = array.array('h', arri)[1] + if width > 0: + return width except ValueError: pass except IOError, e:
--- a/mercurial/pure/bdiff.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/pure/bdiff.py Wed May 18 09:56:27 2011 +0200 @@ -19,6 +19,7 @@ def _normalizeblocks(a, b, blocks): prev = None + r = [] for curr in blocks: if prev is None: prev = curr @@ -40,9 +41,10 @@ while (b1end + shift < b2end and a[a1end + shift] == b[b1end + shift]): shift += 1 - yield a1, b1, l1 + shift + r.append((a1, b1, l1 + shift)) prev = a2 + shift, b2 + shift, l2 - shift - yield prev + r.append(prev) + return r def bdiff(a, b): a = str(a).splitlines(True)
--- a/mercurial/pure/mpatch.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/pure/mpatch.py Wed May 18 09:56:27 2011 +0200 @@ -56,9 +56,9 @@ def pull(dst, src, l): # pull l bytes from src while l: - f = src.pop(0) + f = src.pop() if f[0] > l: # do we need to split? - src.insert(0, (f[0] - l, f[1] + l)) + src.append((f[0] - l, f[1] + l)) dst.append((l, f[1])) return dst.append(f) @@ -66,7 +66,7 @@ def collect(buf, list): start = buf - for l, p in list: + for l, p in reversed(list): move(buf, p, l) buf += l return (buf - start, start) @@ -88,7 +88,7 @@ new.append((l, pos + 12)) # what got added pos += l + 12 last = p2 - frags = new + frags # what was left at the end + frags.extend(reversed(new)) # what was left at the end t = collect(b2, frags)
--- a/mercurial/pure/parsers.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/pure/parsers.py Wed May 18 09:56:27 2011 +0200 @@ -5,7 +5,7 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -from mercurial.node import bin, nullid, nullrev +from mercurial.node import bin, nullid from mercurial import util import struct, zlib
--- a/mercurial/repair.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/repair.py Wed May 18 09:56:27 2011 +0200 @@ -6,14 +6,14 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import changegroup, bookmarks -from node import nullrev, short -from i18n import _ +from mercurial import changegroup, bookmarks +from mercurial.node import short +from mercurial.i18n import _ import os -def _bundle(repo, bases, heads, node, suffix, extranodes=None, compress=True): +def _bundle(repo, bases, heads, node, suffix, compress=True): """create a bundle with the specified revisions as a backup""" - cg = repo.changegroupsubset(bases, heads, 'strip', extranodes) + cg = repo.changegroupsubset(bases, heads, 'strip') backupdir = repo.join("strip-backup") if not os.path.isdir(backupdir): os.mkdir(backupdir) @@ -33,40 +33,26 @@ return sorted(files) -def _collectextranodes(repo, files, link): - """return the nodes that have to be saved before the strip""" - def collectone(cl, revlog): - extra = [] - startrev = count = len(revlog) +def _collectbrokencsets(repo, files, striprev): + """return the changesets which will be broken by the truncation""" + s = set() + def collectone(revlog): + links = (revlog.linkrev(i) for i in revlog) # find the truncation point of the revlog - for i in xrange(count): - lrev = revlog.linkrev(i) - if lrev >= link: - startrev = i + 1 + for lrev in links: + if lrev >= striprev: break + # see if any revision after this point has a linkrev + # less than striprev (those will be broken by strip) + for lrev in links: + if lrev < striprev: + s.add(lrev) - # see if any revision after that point has a linkrev less than link - # (we have to manually save these guys) - for i in xrange(startrev, count): - node = revlog.node(i) - lrev = revlog.linkrev(i) - if lrev < link: - extra.append((node, cl.node(lrev))) - - return extra + collectone(repo.manifest) + for fname in files: + collectone(repo.file(fname)) - extranodes = {} - cl = repo.changelog - extra = collectone(cl, repo.manifest) - if extra: - extranodes[1] = extra - for fname in files: - f = repo.file(fname) - extra = collectone(cl, f) - if extra: - extranodes[fname] = extra - - return extranodes + return s def strip(ui, repo, node, backup="all"): cl = repo.changelog @@ -82,28 +68,26 @@ # the list of heads and bases of the set of interesting revisions. # (head = revision in the set that has no descendant in the set; # base = revision in the set that has no ancestor in the set) - tostrip = set((striprev,)) - saveheads = set() - savebases = [] + tostrip = set(cl.descendants(striprev)) + tostrip.add(striprev) + + files = _collectfiles(repo, striprev) + saverevs = _collectbrokencsets(repo, files, striprev) + + # compute heads + saveheads = set(saverevs) for r in xrange(striprev + 1, len(cl)): - parents = cl.parentrevs(r) - if parents[0] in tostrip or parents[1] in tostrip: - # r is a descendant of striprev - tostrip.add(r) - # if this is a merge and one of the parents does not descend - # from striprev, mark that parent as a savehead. - if parents[1] != nullrev: - for p in parents: - if p not in tostrip and p > striprev: - saveheads.add(p) - else: - # if no parents of this revision will be stripped, mark it as - # a savebase - if parents[0] < striprev and parents[1] < striprev: - savebases.append(cl.node(r)) + if r not in tostrip: + saverevs.add(r) + saveheads.difference_update(cl.parentrevs(r)) + saveheads.add(r) + saveheads = [cl.node(r) for r in saveheads] - saveheads.difference_update(parents) - saveheads.add(r) + # compute base nodes + if saverevs: + descendants = set(cl.descendants(*saverevs)) + saverevs.difference_update(descendants) + savebases = [cl.node(r) for r in saverevs] bm = repo._bookmarks updatebm = [] @@ -112,20 +96,15 @@ if rev in tostrip: updatebm.append(m) - saveheads = [cl.node(r) for r in saveheads] - files = _collectfiles(repo, striprev) - - extranodes = _collectextranodes(repo, files, striprev) - # create a changegroup for all the branches we need to keep backupfile = None if backup == "all": backupfile = _bundle(repo, [node], cl.heads(), node, 'backup') repo.ui.status(_("saved backup bundle to %s\n") % backupfile) - if saveheads or extranodes: + if saveheads or savebases: # do not compress partial bundle if we remove it from disk later chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp', - extranodes=extranodes, compress=keeppartialbundle) + compress=keeppartialbundle) mfst = repo.manifest @@ -149,7 +128,7 @@ tr.abort() raise - if saveheads or extranodes: + if saveheads or savebases: ui.note(_("adding branch\n")) f = open(chgrpfile, "rb") gen = changegroup.readbundle(f, chgrpfile)
--- a/mercurial/revlog.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/revlog.py Wed May 18 09:56:27 2011 +0200 @@ -14,7 +14,7 @@ # import stuff from node for others to import from revlog from node import bin, hex, nullid, nullrev, short #@UnusedImport from i18n import _ -import changegroup, ancestor, mdiff, parsers, error, util +import ancestor, mdiff, parsers, error, util import struct, zlib, errno _pack = struct.pack @@ -27,16 +27,14 @@ REVLOGV0 = 0 REVLOGNG = 1 REVLOGNGINLINEDATA = (1 << 16) -REVLOGSHALLOW = (1 << 17) +REVLOGGENERALDELTA = (1 << 17) REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA REVLOG_DEFAULT_FORMAT = REVLOGNG REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS -REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGSHALLOW +REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA # revlog index flags -REVIDX_PARENTDELTA = 1 -REVIDX_PUNCHED_FLAG = 2 -REVIDX_KNOWN_FLAGS = REVIDX_PUNCHED_FLAG | REVIDX_PARENTDELTA +REVIDX_KNOWN_FLAGS = 0 # max size of revlog with inline data _maxinline = 131072 @@ -209,7 +207,7 @@ remove data, and can use some simple techniques to avoid the need for locking while reading. """ - def __init__(self, opener, indexfile, shallowroot=None): + def __init__(self, opener, indexfile): """ create a revlog object @@ -220,39 +218,37 @@ self.datafile = indexfile[:-2] + ".d" self.opener = opener self._cache = None + self._basecache = (0, 0) self._chunkcache = (0, '') self.index = [] - self._shallowroot = shallowroot - self._parentdelta = 0 self._pcache = {} self._nodecache = {nullid: nullrev} self._nodepos = None v = REVLOG_DEFAULT_VERSION - if hasattr(opener, 'options') and 'defversion' in opener.options: - v = opener.options['defversion'] - if v & REVLOGNG: - v |= REVLOGNGINLINEDATA - if v & REVLOGNG and 'parentdelta' in opener.options: - self._parentdelta = 1 - - if shallowroot: - v |= REVLOGSHALLOW + if hasattr(opener, 'options'): + if 'revlogv1' in opener.options: + if 'generaldelta' in opener.options: + v |= REVLOGGENERALDELTA + else: + v = 0 i = '' + self._initempty = True try: f = self.opener(self.indexfile) i = f.read() f.close() if len(i) > 0: v = struct.unpack(versionformat, i[:4])[0] + self._initempty = False except IOError, inst: if inst.errno != errno.ENOENT: raise self.version = v self._inline = v & REVLOGNGINLINEDATA - self._shallow = v & REVLOGSHALLOW + self._generaldelta = v & REVLOGGENERALDELTA flags = v & ~0xFFFF fmt = v & 0xFFFF if fmt == REVLOGV0 and flags: @@ -288,7 +284,7 @@ @util.propertycache def nodemap(self): - n = self.rev(self.node(0)) + self.rev(self.node(0)) return self._nodecache def rev(self, node): @@ -326,6 +322,13 @@ return self.index[rev][1] def base(self, rev): return self.index[rev][3] + def chainbase(self, rev): + index = self.index + base = index[rev][3] + while base != rev: + rev = base + base = index[rev][3] + return base def flags(self, rev): return self.index[rev][0] & 0xFFFF def rawsize(self, rev): @@ -399,11 +402,12 @@ yield i break - def findmissing(self, common=None, heads=None): - """Return the ancestors of heads that are not ancestors of common. + def findcommonmissing(self, common=None, heads=None): + """Return a tuple of the ancestors of common and the ancestors of heads + that are not ancestors of common. - More specifically, return a list of nodes N such that every N - satisfies the following constraints: + More specifically, the second element is a list of nodes N such that + every N satisfies the following constraints: 1. N is an ancestor of some node in 'heads' 2. N is not an ancestor of any node in 'common' @@ -441,7 +445,25 @@ visit.append(p) missing = list(missing) missing.sort() - return [self.node(r) for r in missing] + return has, [self.node(r) for r in missing] + + def findmissing(self, common=None, heads=None): + """Return the ancestors of heads that are not ancestors of common. + + More specifically, return a list of nodes N such that every N + satisfies the following constraints: + + 1. N is an ancestor of some node in 'heads' + 2. N is not an ancestor of any node in 'common' + + The list is sorted by revision number, meaning it is + topologically sorted. + + 'heads' and 'common' are both lists of node IDs. If heads is + not supplied, uses all of the revlog's heads. If common is not + supplied, uses nullid.""" + _common, missing = self.findcommonmissing(common, heads) + return missing def nodesbetween(self, roots=None, heads=None): """Return a topological path from 'roots' to 'heads'. @@ -492,7 +514,7 @@ # Turn heads into a dictionary so we can remove 'fake' heads. # Also, later we will be using it to filter out the heads we can't # find from roots. - heads = dict.fromkeys(heads, 0) + heads = dict.fromkeys(heads, False) # Start at the top and keep marking parents until we're done. nodestotag = set(heads) # Remember where the top was so we can use it as a limit later. @@ -582,22 +604,33 @@ # We're trying to figure out which heads are reachable # from roots. # Mark this head as having been reached - heads[n] = 1 + heads[n] = True elif ancestors is None: # Otherwise, we're trying to discover the heads. # Assume this is a head because if it isn't, the next step # will eventually remove it. - heads[n] = 1 + heads[n] = True # But, obviously its parents aren't. for p in self.parents(n): heads.pop(p, None) - heads = [n for n in heads.iterkeys() if heads[n] != 0] + heads = [n for n, flag in heads.iteritems() if flag] roots = list(roots) assert orderedout assert roots assert heads return (orderedout, roots, heads) + def headrevs(self): + count = len(self) + if not count: + return [nullrev] + ishead = [1] * (count + 1) + index = self.index + for r in xrange(count): + e = index[r] + ishead[e[5]] = ishead[e[6]] = 0 + return [r for r in xrange(count) if ishead[r]] + def heads(self, start=None, stop=None): """return the list of all nodes that have no children @@ -607,15 +640,9 @@ as if they had no children """ if start is None and stop is None: - count = len(self) - if not count: + if not len(self): return [nullid] - ishead = [1] * (count + 1) - index = self.index - for r in xrange(count): - e = index[r] - ishead[e[5]] = ishead[e[6]] = 0 - return [self.node(r) for r in xrange(count) if ishead[r]] + return [self.node(r) for r in self.headrevs()] if start is None: start = nullid @@ -803,19 +830,25 @@ def _chunk(self, rev): return decompress(self._chunkraw(rev, rev)) + def _chunkbase(self, rev): + return self._chunk(rev) + def _chunkclear(self): self._chunkcache = (0, '') def deltaparent(self, rev): - """return previous revision or parentrev according to flags""" - if self.flags(rev) & REVIDX_PARENTDELTA: - return self.parentrevs(rev)[0] + """return deltaparent of the given revision""" + base = self.index[rev][3] + if base == rev: + return nullrev + elif self._generaldelta: + return base else: return rev - 1 def revdiff(self, rev1, rev2): """return or calculate a delta between two revisions""" - if self.base(rev2) != rev2 and self.deltaparent(rev2) == rev1: + if rev1 != nullrev and self.deltaparent(rev2) == rev1: return self._chunk(rev2) return mdiff.textdiff(self.revision(self.node(rev1)), @@ -834,7 +867,6 @@ # look up what we need to read text = None rev = self.rev(node) - base = self.base(rev) # check rev flags if self.flags(rev) & ~REVIDX_KNOWN_FLAGS: @@ -844,12 +876,13 @@ # build delta chain chain = [] index = self.index # for performance + generaldelta = self._generaldelta iterrev = rev e = index[iterrev] - while iterrev != base and iterrev != cachedrev: + while iterrev != e[3] and iterrev != cachedrev: chain.append(iterrev) - if e[0] & REVIDX_PARENTDELTA: - iterrev = e[5] + if generaldelta: + iterrev = e[3] else: iterrev -= 1 e = index[iterrev] @@ -865,7 +898,7 @@ self._chunkraw(base, rev) if text is None: - text = self._chunk(base) + text = self._chunkbase(base) bins = [self._chunk(r) for r in chain] text = mdiff.patches(text, bins) @@ -877,8 +910,7 @@ def _checkhash(self, text, node, rev): p1, p2 = self.parents(node) - if (node != hash(text, p1, p2) and - not (self.flags(rev) & REVIDX_PUNCHED_FLAG)): + if node != hash(text, p1, p2): raise RevlogError(_("integrity check failed on %s:%d") % (self.indexfile, rev)) return text @@ -932,8 +964,7 @@ cachedelta - an optional precomputed delta """ node = hash(text, p1, p2) - if (node in self.nodemap and - (not self.flags(self.rev(node)) & REVIDX_PUNCHED_FLAG)): + if node in self.nodemap: return node dfh = None @@ -950,7 +981,13 @@ def _addrevision(self, node, text, transaction, link, p1, p2, cachedelta, ifh, dfh): + """internal function to add revisions to the log + see addrevision for argument descriptions. + invariants: + - text is optional (can be None); if not set, cachedelta must be set. + if both are set, they must correspond to eachother. + """ btext = [text] def buildtext(): if btext[0] is not None: @@ -976,42 +1013,51 @@ delta = mdiff.textdiff(ptext, t) data = compress(delta) l = len(data[1]) + len(data[0]) - base = self.base(rev) - dist = l + offset - self.start(base) - return dist, l, data, base + if basecache[0] == rev: + chainbase = basecache[1] + else: + chainbase = self.chainbase(rev) + dist = l + offset - self.start(chainbase) + if self._generaldelta: + base = rev + else: + base = chainbase + return dist, l, data, base, chainbase curr = len(self) prev = curr - 1 - base = curr + base = chainbase = curr offset = self.end(prev) flags = 0 d = None + basecache = self._basecache p1r, p2r = self.rev(p1), self.rev(p2) # should we try to build a delta? if prev != nullrev: - d = builddelta(prev) - if self._parentdelta and prev != p1r: - d2 = builddelta(p1r) - if d2 < d: - d = d2 - flags = REVIDX_PARENTDELTA - dist, l, data, base = d + if self._generaldelta: + if p1r >= basecache[1]: + d = builddelta(p1r) + elif p2r >= basecache[1]: + d = builddelta(p2r) + else: + d = builddelta(prev) + else: + d = builddelta(prev) + dist, l, data, base, chainbase = d # full versions are inserted when the needed deltas # become comparable to the uncompressed text - # or the base revision is punched if text is None: textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]), cachedelta[1]) else: textlen = len(text) - if (d is None or dist > textlen * 2 or - (self.flags(base) & REVIDX_PUNCHED_FLAG)): + if d is None or dist > textlen * 2: text = buildtext() data = compress(text) l = len(data[1]) + len(data[0]) - base = curr + base = chainbase = curr e = (offset_type(offset, flags), l, textlen, base, link, p1r, p2r, node) @@ -1037,9 +1083,10 @@ if type(text) == str: # only accept immutable objects self._cache = (node, curr, text) + self._basecache = (curr, chainbase) return node - def group(self, nodelist, lookup, infocollect=None, fullrev=False): + def group(self, nodelist, bundler): """Calculate a delta group, yielding a sequence of changegroup chunks (strings). @@ -1049,45 +1096,26 @@ guaranteed to have this parent as it has all history before these changesets. In the case firstparent is nullrev the changegroup starts with a full revision. - fullrev forces the insertion of the full revision, necessary - in the case of shallow clones where the first parent might - not exist at the reciever. """ - revs = [self.rev(n) for n in nodelist] + revs = sorted([self.rev(n) for n in nodelist]) # if we don't have any revisions touched by these changesets, bail if not revs: - yield changegroup.closechunk() + yield bundler.close() return # add the parent of the first rev p = self.parentrevs(revs[0])[0] revs.insert(0, p) - if p == nullrev: - fullrev = True # build deltas - for d in xrange(len(revs) - 1): - a, b = revs[d], revs[d + 1] - nb = self.node(b) - - if infocollect is not None: - infocollect(nb) + for r in xrange(len(revs) - 1): + prev, curr = revs[r], revs[r + 1] + for c in bundler.revchunk(self, curr, prev): + yield c - p = self.parents(nb) - meta = nb + p[0] + p[1] + lookup(nb) - if fullrev: - d = self.revision(nb) - meta += mdiff.trivialdiffheader(len(d)) - fullrev = False - else: - d = self.revdiff(a, b) - yield changegroup.chunkheader(len(meta) + len(d)) - yield meta - yield d - - yield changegroup.closechunk() + yield bundler.close() def addgroup(self, bundle, linkmapper, transaction): """ @@ -1119,52 +1147,34 @@ # loop through our set of deltas chain = None while 1: - chunkdata = bundle.parsechunk() + chunkdata = bundle.deltachunk(chain) if not chunkdata: break node = chunkdata['node'] p1 = chunkdata['p1'] p2 = chunkdata['p2'] cs = chunkdata['cs'] - delta = chunkdata['data'] + deltabase = chunkdata['deltabase'] + delta = chunkdata['delta'] link = linkmapper(cs) - if (node in self.nodemap and - (not self.flags(self.rev(node)) & REVIDX_PUNCHED_FLAG)): + if node in self.nodemap: # this can happen if two branches make the same change chain = node continue for p in (p1, p2): if not p in self.nodemap: - if self._shallow: - # add null entries for missing parents - # XXX FIXME - #if base == nullrev: - # base = len(self) - #e = (offset_type(end, REVIDX_PUNCHED_FLAG), - # 0, 0, base, nullrev, nullrev, nullrev, p) - #self.index.insert(-1, e) - #self.nodemap[p] = r - #entry = self._io.packentry(e, self.node, - # self.version, r) - #ifh.write(entry) - #t, r = r, r + 1 - raise LookupError(p, self.indexfile, - _('unknown parent')) - else: - raise LookupError(p, self.indexfile, - _('unknown parent')) + raise LookupError(p, self.indexfile, + _('unknown parent')) - if not chain: - # retrieve the parent revision of the delta chain - chain = p1 - if not chain in self.nodemap: - raise LookupError(chain, self.indexfile, _('unknown base')) + if deltabase not in self.nodemap: + raise LookupError(deltabase, self.indexfile, + _('unknown delta base')) - chainrev = self.rev(chain) + baserev = self.rev(deltabase) chain = self._addrevision(node, None, transaction, link, - p1, p2, (chainrev, delta), ifh, dfh) + p1, p2, (baserev, delta), ifh, dfh) if not dfh and not self._inline: # addrevision switched from inline to conventional # reopen the index
--- a/mercurial/revset.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/revset.py Wed May 18 09:56:27 2011 +0200 @@ -6,13 +6,15 @@ # GNU General Public License version 2 or any later version. import re -import parser, util, error, discovery +import parser, util, error, discovery, hbisect import bookmarks as bookmarksmod import match as matchmod -from i18n import _, gettext +from i18n import _ elements = { "(": (20, ("group", 1, ")"), ("func", 1, ")")), + "~": (18, None, ("ancestor", 18)), + "^": (18, None, ("parent", 18), ("parentpost", 18)), "-": (5, ("negate", 19), ("minus", 5)), "::": (17, ("dagrangepre", 17), ("dagrange", 17), ("dagrangepost", 17)), @@ -47,7 +49,7 @@ elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully yield ('..', None, pos) pos += 1 # skip ahead - elif c in "():,-|&+!": # handle simple operators + elif c in "():,-|&+!~^": # handle simple operators yield (c, None, pos) elif (c in '"\'' or c == 'r' and program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings @@ -123,7 +125,7 @@ x = repo[x].rev() if x == -1 and len(subset) == len(repo): return [-1] - if x in subset: + if len(subset) == len(repo) or x in subset: return [x] return [] @@ -156,9 +158,10 @@ return getset(repo, getset(repo, subset, x), y) def orset(repo, subset, x, y): - s = set(getset(repo, subset, x)) - s |= set(getset(repo, [r for r in subset if r not in s], y)) - return [r for r in subset if r in s] + xl = getset(repo, subset, x) + s = set(xl) + yl = getset(repo, [r for r in subset if r not in s], y) + return xl + yl def notset(repo, subset, x): s = set(getset(repo, subset, x)) @@ -174,6 +177,399 @@ # functions +def adds(repo, subset, x): + """``adds(pattern)`` + Changesets that add a file matching pattern. + """ + # i18n: "adds" is a keyword + pat = getstring(x, _("adds requires a pattern")) + return checkstatus(repo, subset, pat, 1) + +def ancestor(repo, subset, x): + """``ancestor(single, single)`` + Greatest common ancestor of the two changesets. + """ + # i18n: "ancestor" is a keyword + l = getargs(x, 2, 2, _("ancestor requires two arguments")) + r = range(len(repo)) + a = getset(repo, r, l[0]) + b = getset(repo, r, l[1]) + if len(a) != 1 or len(b) != 1: + # i18n: "ancestor" is a keyword + raise error.ParseError(_("ancestor arguments must be single revisions")) + an = [repo[a[0]].ancestor(repo[b[0]]).rev()] + + return [r for r in an if r in subset] + +def ancestors(repo, subset, x): + """``ancestors(set)`` + Changesets that are ancestors of a changeset in set. + """ + args = getset(repo, range(len(repo)), x) + if not args: + return [] + s = set(repo.changelog.ancestors(*args)) | set(args) + return [r for r in subset if r in s] + +def ancestorspec(repo, subset, x, n): + """``set~n`` + Changesets that are the Nth ancestor (first parents only) of a changeset in set. + """ + try: + n = int(n[1]) + except ValueError: + raise error.ParseError(_("~ expects a number")) + ps = set() + cl = repo.changelog + for r in getset(repo, subset, x): + for i in range(n): + r = cl.parentrevs(r)[0] + ps.add(r) + return [r for r in subset if r in ps] + +def author(repo, subset, x): + """``author(string)`` + Alias for ``user(string)``. + """ + # i18n: "author" is a keyword + n = getstring(x, _("author requires a string")).lower() + return [r for r in subset if n in repo[r].user().lower()] + +def bisected(repo, subset, x): + """``bisected(string)`` + Changesets marked in the specified bisect state (good, bad, skip). + """ + state = getstring(x, _("bisect requires a string")).lower() + if state not in ('good', 'bad', 'skip', 'unknown'): + raise error.ParseError(_('invalid bisect state')) + marked = set(repo.changelog.rev(n) for n in hbisect.load_state(repo)[state]) + return [r for r in subset if r in marked] + +def bookmark(repo, subset, x): + """``bookmark([name])`` + The named bookmark or all bookmarks. + """ + # i18n: "bookmark" is a keyword + args = getargs(x, 0, 1, _('bookmark takes one or no arguments')) + if args: + bm = getstring(args[0], + # i18n: "bookmark" is a keyword + _('the argument to bookmark must be a string')) + bmrev = bookmarksmod.listbookmarks(repo).get(bm, None) + if not bmrev: + raise util.Abort(_("bookmark '%s' does not exist") % bm) + bmrev = repo[bmrev].rev() + return [r for r in subset if r == bmrev] + bms = set([repo[r].rev() + for r in bookmarksmod.listbookmarks(repo).values()]) + return [r for r in subset if r in bms] + +def branch(repo, subset, x): + """``branch(string or set)`` + All changesets belonging to the given branch or the branches of the given + changesets. + """ + try: + b = getstring(x, '') + if b in repo.branchmap(): + return [r for r in subset if repo[r].branch() == b] + except error.ParseError: + # not a string, but another revspec, e.g. tip() + pass + + s = getset(repo, range(len(repo)), x) + b = set() + for r in s: + b.add(repo[r].branch()) + s = set(s) + return [r for r in subset if r in s or repo[r].branch() in b] + +def checkstatus(repo, subset, pat, field): + m = matchmod.match(repo.root, repo.getcwd(), [pat]) + s = [] + fast = (m.files() == [pat]) + for r in subset: + c = repo[r] + if fast: + if pat not in c.files(): + continue + else: + for f in c.files(): + if m(f): + break + else: + continue + files = repo.status(c.p1().node(), c.node())[field] + if fast: + if pat in files: + s.append(r) + else: + for f in files: + if m(f): + s.append(r) + break + return s + +def children(repo, subset, x): + """``children(set)`` + Child changesets of changesets in set. + """ + cs = set() + cl = repo.changelog + s = set(getset(repo, range(len(repo)), x)) + for r in xrange(0, len(repo)): + for p in cl.parentrevs(r): + if p in s: + cs.add(r) + return [r for r in subset if r in cs] + +def closed(repo, subset, x): + """``closed()`` + Changeset is closed. + """ + # i18n: "closed" is a keyword + getargs(x, 0, 0, _("closed takes no arguments")) + return [r for r in subset if repo[r].extra().get('close')] + +def contains(repo, subset, x): + """``contains(pattern)`` + Revision contains a file matching pattern. See :hg:`help patterns` + for information about file patterns. + """ + # i18n: "contains" is a keyword + pat = getstring(x, _("contains requires a pattern")) + m = matchmod.match(repo.root, repo.getcwd(), [pat]) + s = [] + if m.files() == [pat]: + for r in subset: + if pat in repo[r]: + s.append(r) + else: + for r in subset: + for f in repo[r].manifest(): + if m(f): + s.append(r) + break + return s + +def date(repo, subset, x): + """``date(interval)`` + Changesets within the interval, see :hg:`help dates`. + """ + # i18n: "date" is a keyword + ds = getstring(x, _("date requires a string")) + dm = util.matchdate(ds) + return [r for r in subset if dm(repo[r].date()[0])] + +def descendants(repo, subset, x): + """``descendants(set)`` + Changesets which are descendants of changesets in set. + """ + args = getset(repo, range(len(repo)), x) + if not args: + return [] + s = set(repo.changelog.descendants(*args)) | set(args) + return [r for r in subset if r in s] + +def filelog(repo, subset, x): + """``filelog(pattern)`` + Changesets connected to the specified filelog. + """ + + pat = getstring(x, _("filelog requires a pattern")) + m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath') + s = set() + + if not m.anypats(): + for f in m.files(): + fl = repo.file(f) + for fr in fl: + s.add(fl.linkrev(fr)) + else: + for f in repo[None]: + if m(f): + fl = repo.file(f) + for fr in fl: + s.add(fl.linkrev(fr)) + + return [r for r in subset if r in s] + +def follow(repo, subset, x): + """``follow([file])`` + An alias for ``::.`` (ancestors of the working copy's first parent). + If a filename is specified, the history of the given file is followed, + including copies. + """ + # i18n: "follow" is a keyword + l = getargs(x, 0, 1, _("follow takes no arguments or a filename")) + p = repo['.'].rev() + if l: + x = getstring(l[0], "follow expected a filename") + s = set(ctx.rev() for ctx in repo['.'][x].ancestors()) + else: + s = set(repo.changelog.ancestors(p)) + + s |= set([p]) + return [r for r in subset if r in s] + +def followfile(repo, subset, f): + """``follow()`` + An alias for ``::.`` (ancestors of the working copy's first parent). + """ + # i18n: "follow" is a keyword + getargs(x, 0, 0, _("follow takes no arguments")) + p = repo['.'].rev() + s = set(repo.changelog.ancestors(p)) | set([p]) + return [r for r in subset if r in s] + +def getall(repo, subset, x): + """``all()`` + All changesets, the same as ``0:tip``. + """ + # i18n: "all" is a keyword + getargs(x, 0, 0, _("all takes no arguments")) + return subset + +def grep(repo, subset, x): + """``grep(regex)`` + Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')`` + to ensure special escape characters are handled correctly. Unlike + ``keyword(string)``, the match is case-sensitive. + """ + try: + # i18n: "grep" is a keyword + gr = re.compile(getstring(x, _("grep requires a string"))) + except re.error, e: + raise error.ParseError(_('invalid match pattern: %s') % e) + l = [] + for r in subset: + c = repo[r] + for e in c.files() + [c.user(), c.description()]: + if gr.search(e): + l.append(r) + break + return l + +def hasfile(repo, subset, x): + """``file(pattern)`` + Changesets affecting files matched by pattern. + """ + # i18n: "file" is a keyword + pat = getstring(x, _("file requires a pattern")) + m = matchmod.match(repo.root, repo.getcwd(), [pat]) + s = [] + for r in subset: + for f in repo[r].files(): + if m(f): + s.append(r) + break + return s + +def head(repo, subset, x): + """``head()`` + Changeset is a named branch head. + """ + # i18n: "head" is a keyword + getargs(x, 0, 0, _("head takes no arguments")) + hs = set() + for b, ls in repo.branchmap().iteritems(): + hs.update(repo[h].rev() for h in ls) + return [r for r in subset if r in hs] + +def heads(repo, subset, x): + """``heads(set)`` + Members of set with no children in set. + """ + s = getset(repo, subset, x) + ps = set(parents(repo, subset, x)) + return [r for r in s if r not in ps] + +def keyword(repo, subset, x): + """``keyword(string)`` + Search commit message, user name, and names of changed files for + string. The match is case-insensitive. + """ + # i18n: "keyword" is a keyword + kw = getstring(x, _("keyword requires a string")).lower() + l = [] + for r in subset: + c = repo[r] + t = " ".join(c.files() + [c.user(), c.description()]) + if kw in t.lower(): + l.append(r) + return l + +def limit(repo, subset, x): + """``limit(set, n)`` + First n members of set. + """ + # i18n: "limit" is a keyword + l = getargs(x, 2, 2, _("limit requires two arguments")) + try: + # i18n: "limit" is a keyword + lim = int(getstring(l[1], _("limit requires a number"))) + except ValueError: + # i18n: "limit" is a keyword + raise error.ParseError(_("limit expects a number")) + ss = set(subset) + os = getset(repo, range(len(repo)), l[0])[:lim] + return [r for r in os if r in ss] + +def last(repo, subset, x): + """``last(set, n)`` + Last n members of set. + """ + # i18n: "last" is a keyword + l = getargs(x, 2, 2, _("last requires two arguments")) + try: + # i18n: "last" is a keyword + lim = int(getstring(l[1], _("last requires a number"))) + except ValueError: + # i18n: "last" is a keyword + raise error.ParseError(_("last expects a number")) + ss = set(subset) + os = getset(repo, range(len(repo)), l[0])[-lim:] + return [r for r in os if r in ss] + +def maxrev(repo, subset, x): + """``max(set)`` + Changeset with highest revision number in set. + """ + os = getset(repo, range(len(repo)), x) + if os: + m = max(os) + if m in subset: + return [m] + return [] + +def merge(repo, subset, x): + """``merge()`` + Changeset is a merge changeset. + """ + # i18n: "merge" is a keyword + getargs(x, 0, 0, _("merge takes no arguments")) + cl = repo.changelog + return [r for r in subset if cl.parentrevs(r)[1] != -1] + +def minrev(repo, subset, x): + """``min(set)`` + Changeset with lowest revision number in set. + """ + os = getset(repo, range(len(repo)), x) + if os: + m = min(os) + if m in subset: + return [m] + return [] + +def modifies(repo, subset, x): + """``modifies(pattern)`` + Changesets modifying files matched by pattern. + """ + # i18n: "modifies" is a keyword + pat = getstring(x, _("modifies requires a pattern")) + return checkstatus(repo, subset, pat, 0) + def node(repo, subset, x): """``id(string)`` Revision non-ambiguously specified by the given hex string prefix. @@ -188,26 +584,35 @@ rn = repo.changelog.rev(repo.changelog._partialmatch(n)) return [r for r in subset if r == rn] -def rev(repo, subset, x): - """``rev(number)`` - Revision with the given numeric identifier. +def outgoing(repo, subset, x): + """``outgoing([path])`` + Changesets not found in the specified destination repository, or the + default push location. """ - # i18n: "rev" is a keyword - l = getargs(x, 1, 1, _("rev requires one argument")) - try: - # i18n: "rev" is a keyword - l = int(getstring(l[0], _("rev requires a number"))) - except ValueError: - # i18n: "rev" is a keyword - raise error.ParseError(_("rev expects a number")) - return [r for r in subset if r == l] + import hg # avoid start-up nasties + # i18n: "outgoing" is a keyword + l = getargs(x, 0, 1, _("outgoing requires a repository path")) + # i18n: "outgoing" is a keyword + dest = l and getstring(l[0], _("outgoing requires a repository path")) or '' + dest = repo.ui.expandpath(dest or 'default-push', dest or 'default') + dest, branches = hg.parseurl(dest) + revs, checkout = hg.addbranchrevs(repo, repo, branches, []) + if revs: + revs = [repo.lookup(rev) for rev in revs] + other = hg.repository(hg.remoteui(repo, {}), dest) + repo.ui.pushbuffer() + common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs) + repo.ui.popbuffer() + cl = repo.changelog + o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)]) + return [r for r in subset if r in o] def p1(repo, subset, x): """``p1([set])`` First parent of changesets in set, or the working directory. """ if x is None: - p = repo[x].parents()[0].rev() + p = repo[x].p1().rev() return [r for r in subset if r == p] ps = set() @@ -248,293 +653,30 @@ ps.update(cl.parentrevs(r)) return [r for r in subset if r in ps] -def maxrev(repo, subset, x): - """``max(set)`` - Changeset with highest revision number in set. - """ - s = getset(repo, subset, x) - if s: - m = max(s) - if m in subset: - return [m] - return [] - -def minrev(repo, subset, x): - """``min(set)`` - Changeset with lowest revision number in set. - """ - s = getset(repo, subset, x) - if s: - m = min(s) - if m in subset: - return [m] - return [] - -def limit(repo, subset, x): - """``limit(set, n)`` - First n members of set. - """ - # i18n: "limit" is a keyword - l = getargs(x, 2, 2, _("limit requires two arguments")) - try: - # i18n: "limit" is a keyword - lim = int(getstring(l[1], _("limit requires a number"))) - except ValueError: - # i18n: "limit" is a keyword - raise error.ParseError(_("limit expects a number")) - return getset(repo, subset, l[0])[:lim] - -def children(repo, subset, x): - """``children(set)`` - Child changesets of changesets in set. - """ - cs = set() - cl = repo.changelog - s = set(getset(repo, range(len(repo)), x)) - for r in xrange(0, len(repo)): - for p in cl.parentrevs(r): - if p in s: - cs.add(r) - return [r for r in subset if r in cs] - -def branch(repo, subset, x): - """``branch(set)`` - All changesets belonging to the branches of changesets in set. - """ - s = getset(repo, range(len(repo)), x) - b = set() - for r in s: - b.add(repo[r].branch()) - s = set(s) - return [r for r in subset if r in s or repo[r].branch() in b] - -def ancestor(repo, subset, x): - """``ancestor(single, single)`` - Greatest common ancestor of the two changesets. - """ - # i18n: "ancestor" is a keyword - l = getargs(x, 2, 2, _("ancestor requires two arguments")) - r = range(len(repo)) - a = getset(repo, r, l[0]) - b = getset(repo, r, l[1]) - if len(a) != 1 or len(b) != 1: - # i18n: "ancestor" is a keyword - raise error.ParseError(_("ancestor arguments must be single revisions")) - an = [repo[a[0]].ancestor(repo[b[0]]).rev()] - - return [r for r in an if r in subset] - -def ancestors(repo, subset, x): - """``ancestors(set)`` - Changesets that are ancestors of a changeset in set. - """ - args = getset(repo, range(len(repo)), x) - if not args: - return [] - s = set(repo.changelog.ancestors(*args)) | set(args) - return [r for r in subset if r in s] - -def descendants(repo, subset, x): - """``descendants(set)`` - Changesets which are descendants of changesets in set. - """ - args = getset(repo, range(len(repo)), x) - if not args: - return [] - s = set(repo.changelog.descendants(*args)) | set(args) - return [r for r in subset if r in s] - -def follow(repo, subset, x): - """``follow()`` - An alias for ``::.`` (ancestors of the working copy's first parent). - """ - # i18n: "follow" is a keyword - getargs(x, 0, 0, _("follow takes no arguments")) - p = repo['.'].rev() - s = set(repo.changelog.ancestors(p)) | set([p]) - return [r for r in subset if r in s] - -def date(repo, subset, x): - """``date(interval)`` - Changesets within the interval, see :hg:`help dates`. - """ - # i18n: "date" is a keyword - ds = getstring(x, _("date requires a string")) - dm = util.matchdate(ds) - return [r for r in subset if dm(repo[r].date()[0])] - -def keyword(repo, subset, x): - """``keyword(string)`` - Search commit message, user name, and names of changed files for - string. The match is case-insensitive. - """ - # i18n: "keyword" is a keyword - kw = getstring(x, _("keyword requires a string")).lower() - l = [] - for r in subset: - c = repo[r] - t = " ".join(c.files() + [c.user(), c.description()]) - if kw in t.lower(): - l.append(r) - return l - -def grep(repo, subset, x): - """``grep(regex)`` - Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')`` - to ensure special escape characters are handled correctly. Unlike - ``keyword(string)``, the match is case-sensitive. +def parentspec(repo, subset, x, n): + """``set^0`` + The set. + ``set^1`` (or ``set^``), ``set^2`` + First or second parent, respectively, of all changesets in set. """ try: - # i18n: "grep" is a keyword - gr = re.compile(getstring(x, _("grep requires a string"))) - except re.error, e: - raise error.ParseError(_('invalid match pattern: %s') % e) - l = [] - for r in subset: - c = repo[r] - for e in c.files() + [c.user(), c.description()]: - if gr.search(e): - l.append(r) - continue - return l - -def author(repo, subset, x): - """``author(string)`` - Alias for ``user(string)``. - """ - # i18n: "author" is a keyword - n = getstring(x, _("author requires a string")).lower() - return [r for r in subset if n in repo[r].user().lower()] - -def user(repo, subset, x): - """``user(string)`` - User name contains string. The match is case-insensitive. - """ - return author(repo, subset, x) - -def hasfile(repo, subset, x): - """``file(pattern)`` - Changesets affecting files matched by pattern. - """ - # i18n: "file" is a keyword - pat = getstring(x, _("file requires a pattern")) - m = matchmod.match(repo.root, repo.getcwd(), [pat]) - s = [] - for r in subset: - for f in repo[r].files(): - if m(f): - s.append(r) - continue - return s - -def contains(repo, subset, x): - """``contains(pattern)`` - Revision contains a file matching pattern. See :hg:`help patterns` - for information about file patterns. - """ - # i18n: "contains" is a keyword - pat = getstring(x, _("contains requires a pattern")) - m = matchmod.match(repo.root, repo.getcwd(), [pat]) - s = [] - if m.files() == [pat]: - for r in subset: - if pat in repo[r]: - s.append(r) - continue - else: - for r in subset: - for f in repo[r].manifest(): - if m(f): - s.append(r) - continue - return s - -def checkstatus(repo, subset, pat, field): - m = matchmod.match(repo.root, repo.getcwd(), [pat]) - s = [] - fast = (m.files() == [pat]) - for r in subset: - c = repo[r] - if fast: - if pat not in c.files(): - continue - else: - for f in c.files(): - if m(f): - break - else: - continue - files = repo.status(c.p1().node(), c.node())[field] - if fast: - if pat in files: - s.append(r) - continue - else: - for f in files: - if m(f): - s.append(r) - continue - return s - -def modifies(repo, subset, x): - """``modifies(pattern)`` - Changesets modifying files matched by pattern. - """ - # i18n: "modifies" is a keyword - pat = getstring(x, _("modifies requires a pattern")) - return checkstatus(repo, subset, pat, 0) - -def adds(repo, subset, x): - """``adds(pattern)`` - Changesets that add a file matching pattern. - """ - # i18n: "adds" is a keyword - pat = getstring(x, _("adds requires a pattern")) - return checkstatus(repo, subset, pat, 1) - -def removes(repo, subset, x): - """``removes(pattern)`` - Changesets which remove files matching pattern. - """ - # i18n: "removes" is a keyword - pat = getstring(x, _("removes requires a pattern")) - return checkstatus(repo, subset, pat, 2) - -def merge(repo, subset, x): - """``merge()`` - Changeset is a merge changeset. - """ - # i18n: "merge" is a keyword - getargs(x, 0, 0, _("merge takes no arguments")) + n = int(n[1]) + if n not in (0, 1, 2): + raise ValueError + except ValueError: + raise error.ParseError(_("^ expects a number 0, 1, or 2")) + ps = set() cl = repo.changelog - return [r for r in subset if cl.parentrevs(r)[1] != -1] - -def closed(repo, subset, x): - """``closed()`` - Changeset is closed. - """ - # i18n: "closed" is a keyword - getargs(x, 0, 0, _("closed takes no arguments")) - return [r for r in subset if repo[r].extra().get('close')] - -def head(repo, subset, x): - """``head()`` - Changeset is a named branch head. - """ - # i18n: "head" is a keyword - getargs(x, 0, 0, _("head takes no arguments")) - hs = set() - for b, ls in repo.branchmap().iteritems(): - hs.update(repo[h].rev() for h in ls) - return [r for r in subset if r in hs] - -def reverse(repo, subset, x): - """``reverse(set)`` - Reverse order of set. - """ - l = getset(repo, subset, x) - l.reverse() - return l + for r in getset(repo, subset, x): + if n == 0: + ps.add(r) + elif n == 1: + ps.add(cl.parentrevs(r)[0]) + elif n == 2: + parents = cl.parentrevs(r) + if len(parents) > 1: + ps.add(parents[1]) + return [r for r in subset if r in ps] def present(repo, subset, x): """``present(set)`` @@ -546,6 +688,44 @@ except error.RepoLookupError: return [] +def removes(repo, subset, x): + """``removes(pattern)`` + Changesets which remove files matching pattern. + """ + # i18n: "removes" is a keyword + pat = getstring(x, _("removes requires a pattern")) + return checkstatus(repo, subset, pat, 2) + +def rev(repo, subset, x): + """``rev(number)`` + Revision with the given numeric identifier. + """ + # i18n: "rev" is a keyword + l = getargs(x, 1, 1, _("rev requires one argument")) + try: + # i18n: "rev" is a keyword + l = int(getstring(l[0], _("rev requires a number"))) + except ValueError: + # i18n: "rev" is a keyword + raise error.ParseError(_("rev expects a number")) + return [r for r in subset if r == l] + +def reverse(repo, subset, x): + """``reverse(set)`` + Reverse order of set. + """ + l = getset(repo, subset, x) + l.reverse() + return l + +def roots(repo, subset, x): + """``roots(set)`` + Changesets with no parent changeset in set. + """ + s = getset(repo, subset, x) + cs = set(children(repo, subset, x)) + return [r for r in s if r not in cs] + def sort(repo, subset, x): """``sort(set[, [-]key...])`` Sort set by keys. The default sort order is ascending, specify a key @@ -601,53 +781,6 @@ l.sort() return [e[-1] for e in l] -def getall(repo, subset, x): - """``all()`` - All changesets, the same as ``0:tip``. - """ - # i18n: "all" is a keyword - getargs(x, 0, 0, _("all takes no arguments")) - return subset - -def heads(repo, subset, x): - """``heads(set)`` - Members of set with no children in set. - """ - s = getset(repo, subset, x) - ps = set(parents(repo, subset, x)) - return [r for r in s if r not in ps] - -def roots(repo, subset, x): - """``roots(set)`` - Changesets with no parent changeset in set. - """ - s = getset(repo, subset, x) - cs = set(children(repo, subset, x)) - return [r for r in s if r not in cs] - -def outgoing(repo, subset, x): - """``outgoing([path])`` - Changesets not found in the specified destination repository, or the - default push location. - """ - import hg # avoid start-up nasties - # i18n: "outgoing" is a keyword - l = getargs(x, 0, 1, _("outgoing requires a repository path")) - # i18n: "outgoing" is a keyword - dest = l and getstring(l[0], _("outgoing requires a repository path")) or '' - dest = repo.ui.expandpath(dest or 'default-push', dest or 'default') - dest, branches = hg.parseurl(dest) - revs, checkout = hg.addbranchrevs(repo, repo, branches, []) - if revs: - revs = [repo.lookup(rev) for rev in revs] - other = hg.repository(hg.remoteui(repo, {}), dest) - repo.ui.pushbuffer() - o = discovery.findoutgoing(repo, other) - repo.ui.popbuffer() - cl = repo.changelog - o = set([cl.rev(r) for r in repo.changelog.nodesbetween(o, revs)[0]]) - return [r for r in subset if r in o] - def tag(repo, subset, x): """``tag([name])`` The specified tag by name, or all tagged revisions if no name is given. @@ -659,6 +792,8 @@ tn = getstring(args[0], # i18n: "tag" is a keyword _('the argument to tag must be a string')) + if not repo.tags().get(tn, None): + raise util.Abort(_("tag '%s' does not exist") % tn) s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn]) else: s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip']) @@ -667,23 +802,11 @@ def tagged(repo, subset, x): return tag(repo, subset, x) -def bookmark(repo, subset, x): - """``bookmark([name])`` - The named bookmark or all bookmarks. +def user(repo, subset, x): + """``user(string)`` + User name contains string. The match is case-insensitive. """ - # i18n: "bookmark" is a keyword - args = getargs(x, 0, 1, _('bookmark takes one or no arguments')) - if args: - bm = getstring(args[0], - # i18n: "bookmark" is a keyword - _('the argument to bookmark must be a string')) - bmrev = bookmarksmod.listbookmarks(repo).get(bm, None) - if bmrev: - bmrev = repo[bmrev].rev() - return [r for r in subset if r == bmrev] - bms = set([repo[r].rev() - for r in bookmarksmod.listbookmarks(repo).values()]) - return [r for r in subset if r in bms] + return author(repo, subset, x) symbols = { "adds": adds, @@ -691,6 +814,7 @@ "ancestor": ancestor, "ancestors": ancestors, "author": author, + "bisected": bisected, "bookmark": bookmark, "branch": branch, "children": children, @@ -699,11 +823,13 @@ "date": date, "descendants": descendants, "file": hasfile, + "filelog": filelog, "follow": follow, "grep": grep, "head": head, "heads": heads, "keyword": keyword, + "last": last, "limit": limit, "max": maxrev, "min": minrev, @@ -734,6 +860,9 @@ "not": notset, "list": listset, "func": func, + "ancestor": ancestorspec, + "parent": parentspec, + "parentpost": p1, } def optimize(x, small): @@ -779,16 +908,19 @@ elif op == 'not': o = optimize(x[1], not small) return o[0], (op, o[1]) + elif op == 'parentpost': + o = optimize(x[1], small) + return o[0], (op, o[1]) elif op == 'group': return optimize(x[1], small) - elif op in 'range list': + elif op in 'range list parent ancestorspec': wa, ta = optimize(x[1], small) wb, tb = optimize(x[2], small) return wa + wb, (op, ta, tb) elif op == 'func': f = getstring(x[1], _("not a symbol")) wa, ta = optimize(x[2], small) - if f in "grep date user author keyword branch file outgoing": + if f in "grep date user author keyword branch file outgoing closed": w = 10 # slow elif f in "modifies adds removes": w = 30 # slower @@ -805,31 +937,93 @@ return w + wa, (op, x[1], ta) return 1, x +class revsetalias(object): + funcre = re.compile('^([^(]+)\(([^)]+)\)$') + args = () + + def __init__(self, token, value): + '''Aliases like: + + h = heads(default) + b($1) = ancestors($1) - ancestors(default) + ''' + if isinstance(token, tuple): + self.type, self.name = token + else: + m = self.funcre.search(token) + if m: + self.type = 'func' + self.name = m.group(1) + self.args = [x.strip() for x in m.group(2).split(',')] + else: + self.type = 'symbol' + self.name = token + + if isinstance(value, str): + for arg in self.args: + value = value.replace(arg, repr(arg)) + self.replacement, pos = parse(value) + if pos != len(value): + raise error.ParseError('invalid token', pos) + else: + self.replacement = value + + def match(self, tree): + if not tree: + return False + if tree == (self.type, self.name): + return True + if tree[0] != self.type: + return False + if len(tree) > 1 and tree[1] != ('symbol', self.name): + return False + # 'func' + funcname + args + if ((self.args and len(tree) != 3) or + (len(self.args) == 1 and tree[2][0] == 'list') or + (len(self.args) > 1 and (tree[2][0] != 'list' or + len(tree[2]) - 1 != len(self.args)))): + raise error.ParseError('invalid amount of arguments', len(tree) - 2) + return True + + def replace(self, tree): + if tree == (self.type, self.name): + return self.replacement + result = self.replacement + def getsubtree(i): + if tree[2][0] == 'list': + return tree[2][i + 1] + return tree[i + 2] + for i, v in enumerate(self.args): + valalias = revsetalias(('string', v), getsubtree(i)) + result = valalias.process(result) + return result + + def process(self, tree): + if self.match(tree): + return self.replace(tree) + if isinstance(tree, tuple): + return tuple(map(self.process, tree)) + return tree + +def findaliases(ui, tree): + for k, v in ui.configitems('revsetalias'): + alias = revsetalias(k, v) + tree = alias.process(tree) + return tree + parse = parser.parser(tokenize, elements).parse -def match(spec): +def match(ui, spec): if not spec: raise error.ParseError(_("empty query")) - tree = parse(spec) + tree, pos = parse(spec) + if (pos != len(spec)): + raise error.ParseError("invalid token", pos) + tree = findaliases(ui, tree) weight, tree = optimize(tree, True) def mfunc(repo, subset): return getset(repo, subset, tree) return mfunc -def makedoc(topic, doc): - """Generate and include predicates help in revsets topic.""" - predicates = [] - for name in sorted(symbols): - text = symbols[name].__doc__ - if not text: - continue - text = gettext(text.rstrip()) - lines = text.splitlines() - lines[1:] = [(' ' + l.strip()) for l in lines[1:]] - predicates.append('\n'.join(lines)) - predicates = '\n\n'.join(predicates) - doc = doc.replace('.. predicatesmarker', predicates) - return doc - # tell hggettext to extract docstrings from these functions: i18nfunctions = symbols.values()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/scmutil.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,690 @@ +# scmutil.py - Mercurial core utility functions +# +# Copyright Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from i18n import _ +import util, error, osutil, revset, similar +import match as matchmod +import os, errno, stat, sys, glob + +def checkfilename(f): + '''Check that the filename f is an acceptable filename for a tracked file''' + if '\r' in f or '\n' in f: + raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f) + +def checkportable(ui, f): + '''Check if filename f is portable and warn or abort depending on config''' + checkfilename(f) + abort, warn = checkportabilityalert(ui) + if abort or warn: + msg = util.checkwinfilename(f) + if msg: + msg = "%s: %r" % (msg, f) + if abort: + raise util.Abort(msg) + ui.warn(_("warning: %s\n") % msg) + +def checkportabilityalert(ui): + '''check if the user's config requests nothing, a warning, or abort for + non-portable filenames''' + val = ui.config('ui', 'portablefilenames', 'warn') + lval = val.lower() + bval = util.parsebool(val) + abort = os.name == 'nt' or lval == 'abort' + warn = bval or lval == 'warn' + if bval is None and not (warn or abort or lval == 'ignore'): + raise error.ConfigError( + _("ui.portablefilenames value is invalid ('%s')") % val) + return abort, warn + +class casecollisionauditor(object): + def __init__(self, ui, abort, existingiter): + self._ui = ui + self._abort = abort + self._map = {} + for f in existingiter: + self._map[f.lower()] = f + + def __call__(self, f): + fl = f.lower() + map = self._map + if fl in map and map[fl] != f: + msg = _('possible case-folding collision for %s') % f + if self._abort: + raise util.Abort(msg) + self._ui.warn(_("warning: %s\n") % msg) + map[fl] = f + +class pathauditor(object): + '''ensure that a filesystem path contains no banned components. + the following properties of a path are checked: + + - ends with a directory separator + - under top-level .hg + - starts at the root of a windows drive + - contains ".." + - traverses a symlink (e.g. a/symlink_here/b) + - inside a nested repository (a callback can be used to approve + some nested repositories, e.g., subrepositories) + ''' + + def __init__(self, root, callback=None): + self.audited = set() + self.auditeddir = set() + self.root = root + self.callback = callback + + def __call__(self, path): + '''Check the relative path. + path may contain a pattern (e.g. foodir/**.txt)''' + + if path in self.audited: + return + # AIX ignores "/" at end of path, others raise EISDIR. + if util.endswithsep(path): + raise util.Abort(_("path ends in directory separator: %s") % path) + normpath = os.path.normcase(path) + parts = util.splitpath(normpath) + if (os.path.splitdrive(path)[0] + or parts[0].lower() in ('.hg', '.hg.', '') + or os.pardir in parts): + raise util.Abort(_("path contains illegal component: %s") % path) + if '.hg' in path.lower(): + lparts = [p.lower() for p in parts] + for p in '.hg', '.hg.': + if p in lparts[1:]: + pos = lparts.index(p) + base = os.path.join(*parts[:pos]) + raise util.Abort(_('path %r is inside nested repo %r') + % (path, base)) + + parts.pop() + prefixes = [] + while parts: + prefix = os.sep.join(parts) + if prefix in self.auditeddir: + break + curpath = os.path.join(self.root, prefix) + try: + st = os.lstat(curpath) + except OSError, err: + # EINVAL can be raised as invalid path syntax under win32. + # They must be ignored for patterns can be checked too. + if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL): + raise + else: + if stat.S_ISLNK(st.st_mode): + raise util.Abort( + _('path %r traverses symbolic link %r') + % (path, prefix)) + elif (stat.S_ISDIR(st.st_mode) and + os.path.isdir(os.path.join(curpath, '.hg'))): + if not self.callback or not self.callback(curpath): + raise util.Abort(_('path %r is inside nested repo %r') % + (path, prefix)) + prefixes.append(prefix) + parts.pop() + + self.audited.add(path) + # only add prefixes to the cache after checking everything: we don't + # want to add "foo/bar/baz" before checking if there's a "foo/.hg" + self.auditeddir.update(prefixes) + +class abstractopener(object): + """Abstract base class; cannot be instantiated""" + + def __init__(self, *args, **kwargs): + '''Prevent instantiation; don't call this from subclasses.''' + raise NotImplementedError('attempted instantiating ' + str(type(self))) + + def read(self, path): + fp = self(path, 'rb') + try: + return fp.read() + finally: + fp.close() + + def write(self, path, data): + fp = self(path, 'wb') + try: + return fp.write(data) + finally: + fp.close() + + def append(self, path, data): + fp = self(path, 'ab') + try: + return fp.write(data) + finally: + fp.close() + +class opener(abstractopener): + '''Open files relative to a base directory + + This class is used to hide the details of COW semantics and + remote file access from higher level code. + ''' + def __init__(self, base, audit=True): + self.base = base + if audit: + self.auditor = pathauditor(base) + else: + self.auditor = util.always + self.createmode = None + self._trustnlink = None + + @util.propertycache + def _cansymlink(self): + return util.checklink(self.base) + + def _fixfilemode(self, name): + if self.createmode is None: + return + os.chmod(name, self.createmode & 0666) + + def __call__(self, path, mode="r", text=False, atomictemp=False): + r = util.checkosfilename(path) + if r: + raise util.Abort("%s: %r" % (r, path)) + self.auditor(path) + f = os.path.join(self.base, path) + + if not text and "b" not in mode: + mode += "b" # for that other OS + + nlink = -1 + dirname, basename = os.path.split(f) + # If basename is empty, then the path is malformed because it points + # to a directory. Let the posixfile() call below raise IOError. + if basename and mode not in ('r', 'rb'): + if atomictemp: + if not os.path.isdir(dirname): + util.makedirs(dirname, self.createmode) + return util.atomictempfile(f, mode, self.createmode) + try: + if 'w' in mode: + util.unlink(f) + nlink = 0 + else: + # nlinks() may behave differently for files on Windows + # shares if the file is open. + fd = util.posixfile(f) + nlink = util.nlinks(f) + if nlink < 1: + nlink = 2 # force mktempcopy (issue1922) + fd.close() + except (OSError, IOError), e: + if e.errno != errno.ENOENT: + raise + nlink = 0 + if not os.path.isdir(dirname): + util.makedirs(dirname, self.createmode) + if nlink > 0: + if self._trustnlink is None: + self._trustnlink = nlink > 1 or util.checknlink(f) + if nlink > 1 or not self._trustnlink: + util.rename(util.mktempcopy(f), f) + fp = util.posixfile(f, mode) + if nlink == 0: + self._fixfilemode(f) + return fp + + def symlink(self, src, dst): + self.auditor(dst) + linkname = os.path.join(self.base, dst) + try: + os.unlink(linkname) + except OSError: + pass + + dirname = os.path.dirname(linkname) + if not os.path.exists(dirname): + util.makedirs(dirname, self.createmode) + + if self._cansymlink: + try: + os.symlink(src, linkname) + except OSError, err: + raise OSError(err.errno, _('could not symlink to %r: %s') % + (src, err.strerror), linkname) + else: + f = self(dst, "w") + f.write(src) + f.close() + self._fixfilemode(dst) + +class filteropener(abstractopener): + '''Wrapper opener for filtering filenames with a function.''' + + def __init__(self, opener, filter): + self._filter = filter + self._orig = opener + + def __call__(self, path, *args, **kwargs): + return self._orig(self._filter(path), *args, **kwargs) + +def canonpath(root, cwd, myname, auditor=None): + '''return the canonical path of myname, given cwd and root''' + if util.endswithsep(root): + rootsep = root + else: + rootsep = root + os.sep + name = myname + if not os.path.isabs(name): + name = os.path.join(root, cwd, name) + name = os.path.normpath(name) + if auditor is None: + auditor = pathauditor(root) + if name != rootsep and name.startswith(rootsep): + name = name[len(rootsep):] + auditor(name) + return util.pconvert(name) + elif name == root: + return '' + else: + # Determine whether `name' is in the hierarchy at or beneath `root', + # by iterating name=dirname(name) until that causes no change (can't + # check name == '/', because that doesn't work on windows). For each + # `name', compare dev/inode numbers. If they match, the list `rel' + # holds the reversed list of components making up the relative file + # name we want. + root_st = os.stat(root) + rel = [] + while True: + try: + name_st = os.stat(name) + except OSError: + break + if util.samestat(name_st, root_st): + if not rel: + # name was actually the same as root (maybe a symlink) + return '' + rel.reverse() + name = os.path.join(*rel) + auditor(name) + return util.pconvert(name) + dirname, basename = os.path.split(name) + rel.append(basename) + if dirname == name: + break + name = dirname + + raise util.Abort('%s not under root' % myname) + +def walkrepos(path, followsym=False, seen_dirs=None, recurse=False): + '''yield every hg repository under path, recursively.''' + def errhandler(err): + if err.filename == path: + raise err + if followsym and hasattr(os.path, 'samestat'): + def adddir(dirlst, dirname): + match = False + samestat = os.path.samestat + dirstat = os.stat(dirname) + for lstdirstat in dirlst: + if samestat(dirstat, lstdirstat): + match = True + break + if not match: + dirlst.append(dirstat) + return not match + else: + followsym = False + + if (seen_dirs is None) and followsym: + seen_dirs = [] + adddir(seen_dirs, path) + for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler): + dirs.sort() + if '.hg' in dirs: + yield root # found a repository + qroot = os.path.join(root, '.hg', 'patches') + if os.path.isdir(os.path.join(qroot, '.hg')): + yield qroot # we have a patch queue repo here + if recurse: + # avoid recursing inside the .hg directory + dirs.remove('.hg') + else: + dirs[:] = [] # don't descend further + elif followsym: + newdirs = [] + for d in dirs: + fname = os.path.join(root, d) + if adddir(seen_dirs, fname): + if os.path.islink(fname): + for hgname in walkrepos(fname, True, seen_dirs): + yield hgname + else: + newdirs.append(d) + dirs[:] = newdirs + +def osrcpath(): + '''return default os-specific hgrc search path''' + path = systemrcpath() + path.extend(userrcpath()) + path = [os.path.normpath(f) for f in path] + return path + +_rcpath = None + +def rcpath(): + '''return hgrc search path. if env var HGRCPATH is set, use it. + for each item in path, if directory, use files ending in .rc, + else use item. + make HGRCPATH empty to only look in .hg/hgrc of current repo. + if no HGRCPATH, use default os-specific path.''' + global _rcpath + if _rcpath is None: + if 'HGRCPATH' in os.environ: + _rcpath = [] + for p in os.environ['HGRCPATH'].split(os.pathsep): + if not p: + continue + p = util.expandpath(p) + if os.path.isdir(p): + for f, kind in osutil.listdir(p): + if f.endswith('.rc'): + _rcpath.append(os.path.join(p, f)) + else: + _rcpath.append(p) + else: + _rcpath = osrcpath() + return _rcpath + +if os.name != 'nt': + + def rcfiles(path): + rcs = [os.path.join(path, 'hgrc')] + rcdir = os.path.join(path, 'hgrc.d') + try: + rcs.extend([os.path.join(rcdir, f) + for f, kind in osutil.listdir(rcdir) + if f.endswith(".rc")]) + except OSError: + pass + return rcs + + def systemrcpath(): + path = [] + # old mod_python does not set sys.argv + if len(getattr(sys, 'argv', [])) > 0: + path.extend(rcfiles(os.path.dirname(sys.argv[0]) + + '/../etc/mercurial')) + path.extend(rcfiles('/etc/mercurial')) + return path + + def userrcpath(): + return [os.path.expanduser('~/.hgrc')] + +else: + + _HKEY_LOCAL_MACHINE = 0x80000002L + + def systemrcpath(): + '''return default os-specific hgrc search path''' + rcpath = [] + filename = util.executablepath() + # Use mercurial.ini found in directory with hg.exe + progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini') + if os.path.isfile(progrc): + rcpath.append(progrc) + return rcpath + # Use hgrc.d found in directory with hg.exe + progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d') + if os.path.isdir(progrcd): + for f, kind in osutil.listdir(progrcd): + if f.endswith('.rc'): + rcpath.append(os.path.join(progrcd, f)) + return rcpath + # else look for a system rcpath in the registry + value = util.lookupreg('SOFTWARE\\Mercurial', None, + _HKEY_LOCAL_MACHINE) + if not isinstance(value, str) or not value: + return rcpath + value = value.replace('/', os.sep) + for p in value.split(os.pathsep): + if p.lower().endswith('mercurial.ini'): + rcpath.append(p) + elif os.path.isdir(p): + for f, kind in osutil.listdir(p): + if f.endswith('.rc'): + rcpath.append(os.path.join(p, f)) + return rcpath + + def userrcpath(): + '''return os-specific hgrc search path to the user dir''' + home = os.path.expanduser('~') + path = [os.path.join(home, 'mercurial.ini'), + os.path.join(home, '.hgrc')] + userprofile = os.environ.get('USERPROFILE') + if userprofile: + path.append(os.path.join(userprofile, 'mercurial.ini')) + path.append(os.path.join(userprofile, '.hgrc')) + return path + +def revsingle(repo, revspec, default='.'): + if not revspec: + return repo[default] + + l = revrange(repo, [revspec]) + if len(l) < 1: + raise util.Abort(_('empty revision set')) + return repo[l[-1]] + +def revpair(repo, revs): + if not revs: + return repo.dirstate.p1(), None + + l = revrange(repo, revs) + + if len(l) == 0: + return repo.dirstate.p1(), None + + if len(l) == 1: + return repo.lookup(l[0]), None + + return repo.lookup(l[0]), repo.lookup(l[-1]) + +_revrangesep = ':' + +def revrange(repo, revs): + """Yield revision as strings from a list of revision specifications.""" + + def revfix(repo, val, defval): + if not val and val != 0 and defval is not None: + return defval + return repo.changelog.rev(repo.lookup(val)) + + seen, l = set(), [] + for spec in revs: + # attempt to parse old-style ranges first to deal with + # things like old-tag which contain query metacharacters + try: + if isinstance(spec, int): + seen.add(spec) + l.append(spec) + continue + + if _revrangesep in spec: + start, end = spec.split(_revrangesep, 1) + start = revfix(repo, start, 0) + end = revfix(repo, end, len(repo) - 1) + step = start > end and -1 or 1 + for rev in xrange(start, end + step, step): + if rev in seen: + continue + seen.add(rev) + l.append(rev) + continue + elif spec and spec in repo: # single unquoted rev + rev = revfix(repo, spec, None) + if rev in seen: + continue + seen.add(rev) + l.append(rev) + continue + except error.RepoLookupError: + pass + + # fall through to new-style queries if old-style fails + m = revset.match(repo.ui, spec) + for r in m(repo, range(len(repo))): + if r not in seen: + l.append(r) + seen.update(l) + + return l + +def expandpats(pats): + if not util.expandglobs: + return list(pats) + ret = [] + for p in pats: + kind, name = matchmod._patsplit(p, None) + if kind is None: + try: + globbed = glob.glob(name) + except re.error: + globbed = [name] + if globbed: + ret.extend(globbed) + continue + ret.append(p) + return ret + +def match(repo, pats=[], opts={}, globbed=False, default='relpath'): + if pats == ("",): + pats = [] + if not globbed and default == 'relpath': + pats = expandpats(pats or []) + m = matchmod.match(repo.root, repo.getcwd(), pats, + opts.get('include'), opts.get('exclude'), default, + auditor=repo.auditor) + def badfn(f, msg): + repo.ui.warn("%s: %s\n" % (m.rel(f), msg)) + m.bad = badfn + return m + +def matchall(repo): + return matchmod.always(repo.root, repo.getcwd()) + +def matchfiles(repo, files): + return matchmod.exact(repo.root, repo.getcwd(), files) + +def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None): + if dry_run is None: + dry_run = opts.get('dry_run') + if similarity is None: + similarity = float(opts.get('similarity') or 0) + # we'd use status here, except handling of symlinks and ignore is tricky + added, unknown, deleted, removed = [], [], [], [] + audit_path = pathauditor(repo.root) + m = match(repo, pats, opts) + for abs in repo.walk(m): + target = repo.wjoin(abs) + good = True + try: + audit_path(abs) + except (OSError, util.Abort): + good = False + rel = m.rel(abs) + exact = m.exact(abs) + if good and abs not in repo.dirstate: + unknown.append(abs) + if repo.ui.verbose or not exact: + repo.ui.status(_('adding %s\n') % ((pats and rel) or abs)) + elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target) + or (os.path.isdir(target) and not os.path.islink(target))): + deleted.append(abs) + if repo.ui.verbose or not exact: + repo.ui.status(_('removing %s\n') % ((pats and rel) or abs)) + # for finding renames + elif repo.dirstate[abs] == 'r': + removed.append(abs) + elif repo.dirstate[abs] == 'a': + added.append(abs) + copies = {} + if similarity > 0: + for old, new, score in similar.findrenames(repo, + added + unknown, removed + deleted, similarity): + if repo.ui.verbose or not m.exact(old) or not m.exact(new): + repo.ui.status(_('recording removal of %s as rename to %s ' + '(%d%% similar)\n') % + (m.rel(old), m.rel(new), score * 100)) + copies[new] = old + + if not dry_run: + wctx = repo[None] + wlock = repo.wlock() + try: + wctx.remove(deleted) + wctx.add(unknown) + for new, old in copies.iteritems(): + wctx.copy(old, new) + finally: + wlock.release() + +def updatedir(ui, repo, patches, similarity=0): + '''Update dirstate after patch application according to metadata''' + if not patches: + return [] + copies = [] + removes = set() + cfiles = patches.keys() + cwd = repo.getcwd() + if cwd: + cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()] + for f in patches: + gp = patches[f] + if not gp: + continue + if gp.op == 'RENAME': + copies.append((gp.oldpath, gp.path)) + removes.add(gp.oldpath) + elif gp.op == 'COPY': + copies.append((gp.oldpath, gp.path)) + elif gp.op == 'DELETE': + removes.add(gp.path) + + wctx = repo[None] + for src, dst in copies: + dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd) + if (not similarity) and removes: + wctx.remove(sorted(removes), True) + + for f in patches: + gp = patches[f] + if gp and gp.mode: + islink, isexec = gp.mode + dst = repo.wjoin(gp.path) + # patch won't create empty files + if gp.op == 'ADD' and not os.path.lexists(dst): + flags = (isexec and 'x' or '') + (islink and 'l' or '') + repo.wwrite(gp.path, '', flags) + util.setflags(dst, islink, isexec) + addremove(repo, cfiles, similarity=similarity) + files = patches.keys() + files.extend([r for r in removes if r not in files]) + return sorted(files) + +def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None): + """Update the dirstate to reflect the intent of copying src to dst. For + different reasons it might not end with dst being marked as copied from src. + """ + origsrc = repo.dirstate.copied(src) or src + if dst == origsrc: # copying back a copy? + if repo.dirstate[dst] not in 'mn' and not dryrun: + repo.dirstate.normallookup(dst) + else: + if repo.dirstate[origsrc] == 'a' and origsrc == src: + if not ui.quiet: + ui.warn(_("%s has not been committed yet, so no copy " + "data will be stored for %s.\n") + % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd))) + if repo.dirstate[dst] in '?r' and not dryrun: + wctx.add([dst]) + elif not dryrun: + wctx.copy(origsrc, dst)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/setdiscovery.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,178 @@ +# setdiscovery.py - improved discovery of common nodeset for mercurial +# +# Copyright 2010 Benoit Boissinot <bboissin@gmail.com> +# and Peter Arrenbrecht <peter@arrenbrecht.ch> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from node import nullid +from i18n import _ +import random, collections, util, dagutil + +def _updatesample(dag, nodes, sample, always, quicksamplesize=0): + # if nodes is empty we scan the entire graph + if nodes: + heads = dag.headsetofconnecteds(nodes) + else: + heads = dag.heads() + dist = {} + visit = collections.deque(heads) + seen = set() + factor = 1 + while visit: + curr = visit.popleft() + if curr in seen: + continue + d = dist.setdefault(curr, 1) + if d > factor: + factor *= 2 + if d == factor: + if curr not in always: # need this check for the early exit below + sample.add(curr) + if quicksamplesize and (len(sample) >= quicksamplesize): + return + seen.add(curr) + for p in dag.parents(curr): + if not nodes or p in nodes: + dist.setdefault(p, d + 1) + visit.append(p) + +def _setupsample(dag, nodes, size): + if len(nodes) <= size: + return set(nodes), None, 0 + always = set(dag.heads()) + desiredlen = size - len(always) + if desiredlen <= 0: + # This could be bad if there are very many heads, all unknown to the + # server. We're counting on long request support here. + return always, None, desiredlen + return always, set(), desiredlen + +def _takequicksample(dag, nodes, size, initial): + always, sample, desiredlen = _setupsample(dag, nodes, size) + if sample is None: + return always + if initial: + fromset = None + else: + fromset = nodes + _updatesample(dag, fromset, sample, always, quicksamplesize=desiredlen) + sample.update(always) + return sample + +def _takefullsample(dag, nodes, size): + always, sample, desiredlen = _setupsample(dag, nodes, size) + if sample is None: + return always + # update from heads + _updatesample(dag, nodes, sample, always) + # update from roots + _updatesample(dag.inverse(), nodes, sample, always) + assert sample + if len(sample) > desiredlen: + sample = set(random.sample(sample, desiredlen)) + elif len(sample) < desiredlen: + more = desiredlen - len(sample) + sample.update(random.sample(list(nodes - sample - always), more)) + sample.update(always) + return sample + +def findcommonheads(ui, local, remote, + initialsamplesize=100, + fullsamplesize=200, + abortwhenunrelated=True): + '''Return a tuple (common, anyincoming, remoteheads) used to identify + missing nodes from or in remote. + + shortcutlocal determines whether we try use direct access to localrepo if + remote is actually local. + ''' + roundtrips = 0 + cl = local.changelog + dag = dagutil.revlogdag(cl) + nodes = dag.nodeset() + + # early exit if we know all the specified server heads already + ui.debug("query 1; heads\n") + roundtrips += 1 + srvheadhashes = remote.heads() + + ## TODO We might want to request an additional random sample of the server's + ## nodes batched with the heads query here. + + if cl.tip() == nullid: + if srvheadhashes != [nullid]: + return [nullid], True, srvheadhashes + return [nullid], False, [] + + # start actual discovery (we note this before the next "if" for + # compatibility reasons) + ui.status(_("searching for changes\n")) + + srvheads = dag.internalizeall(srvheadhashes, filterunknown=True) + if len(srvheads) == len(srvheadhashes): + ui.note("all remote heads known locally\n") + return (srvheadhashes, False, srvheadhashes,) + + # full blown discovery + undecided = nodes # own nodes where I don't know if the server knows them + common = set() # own nodes I know we both know + missing = set() # own nodes I know the server lacks + + # treat remote heads as a first implicit sample response + common.update(dag.ancestorset(srvheads)) + undecided.difference_update(common) + # use cheapish initial sample + if common: + ui.debug("taking initial sample\n") + sample = _takefullsample(dag, undecided, size=fullsamplesize) + else: + ui.debug("taking quick initial sample\n") + sample = _takequicksample(dag, nodes, size=initialsamplesize, + initial=True) + + roundtrips += 1 + ui.progress(_('searching'), roundtrips, unit=_('queries')) + ui.debug("query %i; still undecided: %i, sample size is: %i\n" + % (roundtrips, len(undecided), len(sample))) + # indices between sample and externalized version must match + sample = list(sample) + yesno = remote.known(dag.externalizeall(sample)) + + while undecided: + commoninsample = set(n for i, n in enumerate(sample) if yesno[i]) + common.update(dag.ancestorset(commoninsample, common)) + + missinginsample = [n for i, n in enumerate(sample) if not yesno[i]] + missing.update(dag.descendantset(missinginsample, missing)) + + undecided.difference_update(missing) + undecided.difference_update(common) + + if not undecided: + break + + ui.note("sampling from both directions\n") + sample = _takefullsample(dag, undecided, size=fullsamplesize) + + roundtrips += 1 + ui.progress(_('searching'), roundtrips, unit=_('queries')) + ui.debug("query %i; still undecided: %i, sample size is: %i\n" + % (roundtrips, len(undecided), len(sample))) + # indices between sample and externalized version must match + sample = list(sample) + yesno = remote.known(dag.externalizeall(sample)) + + result = dag.headsetofconnecteds(common) + ui.progress(_('searching'), None) + ui.debug("%d total queries\n" % roundtrips) + + if not result and srvheadhashes != [nullid]: + if abortwhenunrelated: + raise util.Abort(_("repository is unrelated")) + else: + ui.warn(_("warning: repository is unrelated\n")) + return (set([nullid]), True, srvheadhashes,) + + return (dag.externalizeall(result), True, srvheadhashes,)
--- a/mercurial/simplemerge.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/simplemerge.py Wed May 18 09:56:27 2011 +0200 @@ -18,7 +18,7 @@ # s: "i hate that." from i18n import _ -import util, mdiff +import scmutil, util, mdiff import sys, os class CantReprocessAndShowBase(Exception): @@ -432,7 +432,7 @@ local = os.path.realpath(local) if not opts.get('print'): - opener = util.opener(os.path.dirname(local)) + opener = scmutil.opener(os.path.dirname(local)) out = opener(os.path.basename(local), "w", atomictemp=True) else: out = sys.stdout
--- a/mercurial/sshrepo.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/sshrepo.py Wed May 18 09:56:27 2011 +0200 @@ -7,7 +7,6 @@ from i18n import _ import util, error, wireproto -import re class remotelock(object): def __init__(self, repo): @@ -24,16 +23,16 @@ self._url = path self.ui = ui - m = re.match(r'^ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?$', path) - if not m: + 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)) - self.user = m.group(2) - if self.user and ':' in self.user: + self.user = u.user + if u.passwd is not None: self._abort(error.RepoError(_("password in URL not supported"))) - self.host = m.group(3) - self.port = m.group(5) - self.path = m.group(7) or "." + self.host = u.host + self.port = u.port + self.path = u.path or "." sshcmd = self.ui.config("ui", "ssh", "ssh") remotecmd = self.ui.config("ui", "remotecmd", "hg") @@ -119,9 +118,24 @@ def _callstream(self, cmd, **args): self.ui.debug("sending %s command\n" % cmd) self.pipeo.write("%s\n" % cmd) - for k, v in sorted(args.iteritems()): + _func, names = wireproto.commands[cmd] + keys = names.split() + wireargs = {} + for k in keys: + if k == '*': + wireargs['*'] = args + break + else: + wireargs[k] = args[k] + del args[k] + for k, v in sorted(wireargs.iteritems()): self.pipeo.write("%s %d\n" % (k, len(v))) - self.pipeo.write(v) + if isinstance(v, dict): + for dk, dv in v.iteritems(): + self.pipeo.write("%s %d\n" % (dk, len(dv))) + self.pipeo.write(dv) + else: + self.pipeo.write(v) self.pipeo.flush() return self.pipei @@ -153,7 +167,7 @@ self.readerr() try: l = int(l) - except: + except ValueError: self._abort(error.ResponseError(_("unexpected response:"), l)) return self.pipei.read(l) @@ -194,7 +208,7 @@ return 1 try: return int(r) - except: + except ValueError: self._abort(error.ResponseError(_("unexpected response:"), r)) instance = sshrepository
--- a/mercurial/sshserver.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/sshserver.py Wed May 18 09:56:27 2011 +0200 @@ -21,27 +21,27 @@ sys.stdout = sys.stderr # Prevent insertion/deletion of CRs - util.set_binary(self.fin) - util.set_binary(self.fout) + util.setbinary(self.fin) + util.setbinary(self.fout) def getargs(self, args): data = {} keys = args.split() - count = len(keys) for n in xrange(len(keys)): argline = self.fin.readline()[:-1] arg, l = argline.split() - val = self.fin.read(int(l)) if arg not in keys: raise util.Abort("unexpected parameter %r" % arg) if arg == '*': star = {} - for n in xrange(int(l)): + 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]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/sslutil.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,126 @@ +# sslutil.py - SSL handling for mercurial +# +# Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com> +# Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br> +# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. +import os + +from mercurial import util +from mercurial.i18n import _ +try: + # avoid using deprecated/broken FakeSocket in python 2.6 + import ssl + ssl_wrap_socket = ssl.wrap_socket + CERT_REQUIRED = ssl.CERT_REQUIRED +except ImportError: + CERT_REQUIRED = 2 + + def ssl_wrap_socket(sock, key_file, cert_file, + cert_reqs=CERT_REQUIRED, ca_certs=None): + if ca_certs: + raise util.Abort(_( + 'certificate checking requires Python 2.6')) + + ssl = socket.ssl(sock, key_file, cert_file) + return httplib.FakeSocket(sock, ssl) + +def _verifycert(cert, hostname): + '''Verify that cert (in socket.getpeercert() format) matches hostname. + CRLs is not handled. + + Returns error message if any problems are found and None on success. + ''' + if not cert: + return _('no certificate received') + dnsname = hostname.lower() + def matchdnsname(certname): + return (certname == dnsname or + '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]) + + san = cert.get('subjectAltName', []) + if san: + certnames = [value.lower() for key, value in san if key == 'DNS'] + for name in certnames: + if matchdnsname(name): + return None + return _('certificate is for %s') % ', '.join(certnames) + + # subject is only checked when subjectAltName is empty + for s in cert.get('subject', []): + key, value = s[0] + if key == 'commonName': + try: + # 'subject' entries are unicode + certname = value.lower().encode('ascii') + except UnicodeEncodeError: + return _('IDN in certificate not supported') + if matchdnsname(certname): + return None + return _('certificate is for %s') % certname + return _('no commonName or subjectAltName found in certificate') + + +# CERT_REQUIRED means fetch the cert from the server all the time AND +# validate it against the CA store provided in web.cacerts. +# +# We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally +# busted on those versions. + +def sslkwargs(ui, host): + cacerts = ui.config('web', 'cacerts') + hostfingerprint = ui.config('hostfingerprints', host) + if cacerts and not hostfingerprint: + cacerts = util.expandpath(cacerts) + if not os.path.exists(cacerts): + raise util.Abort(_('could not find web.cacerts: %s') % cacerts) + return {'ca_certs': cacerts, + 'cert_reqs': CERT_REQUIRED, + } + return {} + +class validator(object): + def __init__(self, ui, host): + self.ui = ui + self.host = host + + def __call__(self, sock): + host = self.host + cacerts = self.ui.config('web', 'cacerts') + hostfingerprint = self.ui.config('hostfingerprints', host) + if cacerts and not hostfingerprint: + msg = _verifycert(sock.getpeercert(), host) + if msg: + raise util.Abort(_('%s certificate error: %s ' + '(use --insecure to connect ' + 'insecurely)') % (host, msg)) + self.ui.debug('%s certificate successfully verified\n' % host) + else: + if getattr(sock, 'getpeercert', False): + peercert = sock.getpeercert(True) + peerfingerprint = util.sha1(peercert).hexdigest() + nicefingerprint = ":".join([peerfingerprint[x:x + 2] + for x in xrange(0, len(peerfingerprint), 2)]) + if hostfingerprint: + if peerfingerprint.lower() != \ + hostfingerprint.replace(':', '').lower(): + raise util.Abort(_('invalid certificate for %s ' + 'with fingerprint %s') % + (host, nicefingerprint)) + self.ui.debug('%s certificate matched fingerprint %s\n' % + (host, nicefingerprint)) + else: + self.ui.warn(_('warning: %s certificate ' + 'with fingerprint %s not verified ' + '(check hostfingerprints or web.cacerts ' + 'config setting)\n') % + (host, nicefingerprint)) + else: # python 2.5 ? + if hostfingerprint: + raise util.Abort(_('no certificate for %s with ' + 'configured hostfingerprint') % host) + self.ui.warn(_('warning: %s certificate not verified ' + '(check web.cacerts config setting)\n') % + host)
--- a/mercurial/statichttprepo.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/statichttprepo.py Wed May 18 09:56:27 2011 +0200 @@ -9,7 +9,7 @@ from i18n import _ import changelog, byterange, url, error -import localrepo, manifest, util, store +import localrepo, manifest, util, scmutil, store import urllib, urllib2, errno class httprangereader(object): @@ -67,17 +67,17 @@ urlopener = url.opener(ui, authinfo) urlopener.add_handler(byterange.HTTPRangeHandler()) - def opener(base): - """return a function that opens files over http""" - p = base - def o(path, mode="r", atomictemp=None): - if 'a' in mode or 'w' in mode: + class statichttpopener(scmutil.abstractopener): + def __init__(self, base): + self.base = base + + def __call__(self, path, mode="r", atomictemp=None): + if mode not in ('r', 'rb'): raise IOError('Permission denied') - f = "/".join((p, urllib.quote(path))) + f = "/".join((self.base, urllib.quote(path))) return httprangereader(f, urlopener) - return o - return opener + return statichttpopener class statichttprepository(localrepo.localrepository): def __init__(self, ui, path): @@ -85,14 +85,15 @@ self.ui = ui self.root = path - self.path, authinfo = url.getauthinfo(path.rstrip('/') + "/.hg") + u = util.url(path.rstrip('/') + "/.hg") + self.path, authinfo = u.authinfo() opener = build_opener(ui, authinfo) self.opener = opener(self.path) # find requirements try: - requirements = self.opener("requires").read().splitlines() + requirements = self.opener.read("requires").splitlines() except IOError, inst: if inst.errno != errno.ENOENT: raise
--- a/mercurial/store.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/store.py Wed May 18 09:56:27 2011 +0200 @@ -6,7 +6,7 @@ # GNU General Public License version 2 or any later version. from i18n import _ -import osutil, util +import osutil, scmutil, util import os, stat _sha = util.sha1 @@ -14,6 +14,14 @@ # This avoids a collision between a file named foo and a dir named # foo.i or foo.d def encodedir(path): + ''' + >>> encodedir('data/foo.i') + 'data/foo.i' + >>> encodedir('data/foo.i/bla.i') + 'data/foo.i.hg/bla.i' + >>> encodedir('data/foo.i.hg/bla.i') + 'data/foo.i.hg.hg/bla.i' + ''' if not path.startswith('data/'): return path return (path @@ -22,6 +30,14 @@ .replace(".d/", ".d.hg/")) def decodedir(path): + ''' + >>> decodedir('data/foo.i') + 'data/foo.i' + >>> decodedir('data/foo.i.hg/bla.i') + 'data/foo.i/bla.i' + >>> decodedir('data/foo.i.hg.hg/bla.i') + 'data/foo.i.hg/bla.i' + ''' if not path.startswith('data/') or ".hg/" not in path: return path return (path @@ -30,10 +46,33 @@ .replace(".hg.hg/", ".hg/")) def _buildencodefun(): + ''' + >>> enc, dec = _buildencodefun() + + >>> enc('nothing/special.txt') + 'nothing/special.txt' + >>> dec('nothing/special.txt') + 'nothing/special.txt' + + >>> enc('HELLO') + '_h_e_l_l_o' + >>> dec('_h_e_l_l_o') + 'HELLO' + + >>> enc('hello:world?') + 'hello~3aworld~3f' + >>> dec('hello~3aworld~3f') + 'hello:world?' + + >>> enc('the\x07quick\xADshot') + 'the~07quick~adshot' + >>> dec('the~07quick~adshot') + 'the\\x07quick\\xadshot' + ''' e = '_' - win_reserved = [ord(x) for x in '\\:*?"<>|'] + winreserved = [ord(x) for x in '\\:*?"<>|'] cmap = dict([(chr(x), chr(x)) for x in xrange(127)]) - for x in (range(32) + range(126, 256) + win_reserved): + for x in (range(32) + range(126, 256) + winreserved): cmap[chr(x)] = "~%02x" % x for x in range(ord("A"), ord("Z")+1) + [ord(e)]: cmap[chr(x)] = e + chr(x).lower() @@ -57,26 +96,54 @@ encodefilename, decodefilename = _buildencodefun() -def _build_lower_encodefun(): - win_reserved = [ord(x) for x in '\\:*?"<>|'] +def _buildlowerencodefun(): + ''' + >>> f = _buildlowerencodefun() + >>> f('nothing/special.txt') + 'nothing/special.txt' + >>> f('HELLO') + 'hello' + >>> f('hello:world?') + 'hello~3aworld~3f' + >>> f('the\x07quick\xADshot') + 'the~07quick~adshot' + ''' + winreserved = [ord(x) for x in '\\:*?"<>|'] cmap = dict([(chr(x), chr(x)) for x in xrange(127)]) - for x in (range(32) + range(126, 256) + win_reserved): + for x in (range(32) + range(126, 256) + winreserved): cmap[chr(x)] = "~%02x" % x for x in range(ord("A"), ord("Z")+1): cmap[chr(x)] = chr(x).lower() return lambda s: "".join([cmap[c] for c in s]) -lowerencode = _build_lower_encodefun() +lowerencode = _buildlowerencodefun() -_windows_reserved_filenames = '''con prn aux nul +_winreservednames = '''con prn aux nul com1 com2 com3 com4 com5 com6 com7 com8 com9 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split() def _auxencode(path, dotencode): + ''' + Encodes filenames containing names reserved by Windows or which end in + period or space. Does not touch other single reserved characters c. + Specifically, c in '\\:*?"<>|' or ord(c) <= 31 are *not* encoded here. + Additionally encodes space or period at the beginning, if dotencode is + True. + path is assumed to be all lowercase. + + >>> _auxencode('.foo/aux.txt/txt.aux/con/prn/nul/foo.', True) + '~2efoo/au~78.txt/txt.aux/co~6e/pr~6e/nu~6c/foo~2e' + >>> _auxencode('.com1com2/lpt9.lpt4.lpt1/conprn/foo.', False) + '.com1com2/lp~749.lpt4.lpt1/conprn/foo~2e' + >>> _auxencode('foo. ', True) + 'foo.~20' + >>> _auxencode(' .foo', True) + '~20.foo' + ''' res = [] for n in path.split('/'): if n: base = n.split('.')[0] - if base and (base in _windows_reserved_filenames): + if base and (base in _winreservednames): # encode third letter ('aux' -> 'au~78') ec = "~%02x" % ord(n[2]) n = n[0:2] + ec + n[3:] @@ -88,9 +155,9 @@ res.append(n) return '/'.join(res) -MAX_PATH_LEN_IN_HGSTORE = 120 -DIR_PREFIX_LEN = 8 -_MAX_SHORTENED_DIRS_LEN = 8 * (DIR_PREFIX_LEN + 1) - 4 +_maxstorepathlen = 120 +_dirprefixlen = 8 +_maxshortdirslen = 8 * (_dirprefixlen + 1) - 4 def _hybridencode(path, auxencode): '''encodes path with a length limit @@ -106,17 +173,17 @@ Hashed encoding (not reversible): - If the default-encoded path is longer than MAX_PATH_LEN_IN_HGSTORE, a + If the default-encoded path is longer than _maxstorepathlen, a non-reversible hybrid hashing of the path is done instead. - This encoding uses up to DIR_PREFIX_LEN characters of all directory + This encoding uses up to _dirprefixlen characters of all directory levels of the lowerencoded path, but not more levels than can fit into - _MAX_SHORTENED_DIRS_LEN. + _maxshortdirslen. Then follows the filler followed by the sha digest of the full path. The filler is the beginning of the basename of the lowerencoded path (the basename is everything after the last path separator). The filler is as long as possible, filling in characters from the basename until - the encoded path has MAX_PATH_LEN_IN_HGSTORE characters (or all chars - of the basename have been taken). + the encoded path has _maxstorepathlen characters (or all chars of the + basename have been taken). The extension (e.g. '.i' or '.d') is preserved. The string 'data/' at the beginning is replaced with 'dh/', if the hashed @@ -128,7 +195,7 @@ path = encodedir(path) ndpath = path[len('data/'):] res = 'data/' + auxencode(encodefilename(ndpath)) - if len(res) > MAX_PATH_LEN_IN_HGSTORE: + if len(res) > _maxstorepathlen: digest = _sha(path).hexdigest() aep = auxencode(lowerencode(ndpath)) _root, ext = os.path.splitext(aep) @@ -136,21 +203,21 @@ basename = parts[-1] sdirs = [] for p in parts[:-1]: - d = p[:DIR_PREFIX_LEN] + d = p[:_dirprefixlen] if d[-1] in '. ': # Windows can't access dirs ending in period or space d = d[:-1] + '_' t = '/'.join(sdirs) + '/' + d - if len(t) > _MAX_SHORTENED_DIRS_LEN: + if len(t) > _maxshortdirslen: break sdirs.append(d) dirs = '/'.join(sdirs) if len(dirs) > 0: dirs += '/' res = 'dh/' + dirs + digest + ext - space_left = MAX_PATH_LEN_IN_HGSTORE - len(res) - if space_left > 0: - filler = basename[:space_left] + spaceleft = _maxstorepathlen - len(res) + if spaceleft > 0: + filler = basename[:spaceleft] res = 'dh/' + dirs + filler + digest + ext return res @@ -169,12 +236,12 @@ class basicstore(object): '''base class for local repository stores''' - def __init__(self, path, opener): + def __init__(self, path, openertype): self.path = path self.createmode = _calcmode(path) - op = opener(self.path) + op = openertype(self.path) op.createmode = self.createmode - self.opener = lambda f, *args, **kw: op(encodedir(f), *args, **kw) + self.opener = scmutil.filteropener(op, encodedir) def join(self, f): return self.path + '/' + encodedir(f) @@ -218,12 +285,12 @@ pass class encodedstore(basicstore): - def __init__(self, path, opener): + def __init__(self, path, openertype): self.path = path + '/store' self.createmode = _calcmode(self.path) - op = opener(self.path) + op = openertype(self.path) op.createmode = self.createmode - self.opener = lambda f, *args, **kw: op(encodefilename(f), *args, **kw) + self.opener = scmutil.filteropener(op, encodefilename) def datafiles(self): for a, b, size in self._walk('data', True): @@ -298,21 +365,27 @@ self._load() return iter(self.entries) +class _fncacheopener(scmutil.abstractopener): + def __init__(self, op, fnc, encode): + self.opener = op + self.fncache = fnc + self.encode = encode + + def __call__(self, path, mode='r', *args, **kw): + if mode not in ('r', 'rb') and path.startswith('data/'): + self.fncache.add(path) + return self.opener(self.encode(path), mode, *args, **kw) + class fncachestore(basicstore): - def __init__(self, path, opener, encode): + def __init__(self, path, openertype, encode): self.encode = encode self.path = path + '/store' self.createmode = _calcmode(self.path) - op = opener(self.path) + op = openertype(self.path) op.createmode = self.createmode fnc = fncache(op) self.fncache = fnc - - def fncacheopener(path, mode='r', *args, **kw): - if mode not in ('r', 'rb') and path.startswith('data/'): - fnc.add(path) - return op(self.encode(path), mode, *args, **kw) - self.opener = fncacheopener + self.opener = _fncacheopener(op, fnc, encode) def join(self, f): return self.path + '/' + self.encode(f) @@ -344,11 +417,11 @@ def write(self): self.fncache.write() -def store(requirements, path, opener): +def store(requirements, path, openertype): if 'store' in requirements: if 'fncache' in requirements: auxencode = lambda f: _auxencode(f, 'dotencode' in requirements) encode = lambda f: _hybridencode(f, auxencode) - return fncachestore(path, opener, encode) - return encodedstore(path, opener) - return basicstore(path, opener) + return fncachestore(path, openertype, encode) + return encodedstore(path, openertype) + return basicstore(path, openertype)
--- a/mercurial/subrepo.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/subrepo.py Wed May 18 09:56:27 2011 +0200 @@ -5,10 +5,10 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import errno, os, re, xml.dom.minidom, shutil, urlparse, posixpath +import errno, os, re, xml.dom.minidom, shutil, posixpath import stat, subprocess, tarfile from i18n import _ -import config, util, node, error, cmdutil +import config, scmutil, util, node, error, cmdutil, bookmarks hg = None propertycache = util.propertycache @@ -145,7 +145,7 @@ debug(s, "prompt remove") wctx.sub(s).remove() - for s, r in s2.items(): + for s, r in sorted(s2.items()): if s in s1: continue elif s not in sa: @@ -194,26 +194,16 @@ """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 hasattr(repo, '_subparent'): - source = repo._subsource - if source.startswith('/') or '://' in source: - return source + source = util.url(repo._subsource) + source.path = posixpath.normpath(source.path) + if posixpath.isabs(source.path) or source.scheme: + return str(source) parent = _abssource(repo._subparent, push, abort=False) if parent: - if '://' in parent: - if parent[-1] == '/': - parent = parent[:-1] - r = urlparse.urlparse(parent + '/' + source) - if parent.startswith('ssh://') and not r[1]: - # Python before 2.6 parses ssh:// URLs wrong - host, path = r[2][2:].split('/', 1) - r2 = '//%s/%s' % (host, posixpath.normpath(path)) - else: - r2 = posixpath.normpath(r[2]) - r = urlparse.urlunparse((r[0], r[1], r2, - r[3], r[4], r[5])) - return r - else: # plain file system path - return posixpath.normpath(os.path.join(parent, repo._subsource)) + parent = util.url(parent) + parent.path = posixpath.join(parent.path, source.path) + parent.path = posixpath.normpath(parent.path) + return str(parent) else: # recursion reached top repo if hasattr(repo, '_subtoppath'): return repo._subtoppath @@ -245,7 +235,7 @@ import hg as h hg = h - util.path_auditor(ctx._repo.root)(path) + scmutil.pathauditor(ctx._repo.root)(path) state = ctx.substate.get(path, nullstate) if state[2] not in types: raise util.Abort(_('unknown subrepo type %s') % state[2]) @@ -352,8 +342,11 @@ create = True util.makedirs(root) self._repo = hg.repository(r.ui, root, create=create) - self._repo._subparent = r - self._repo._subsource = state[0] + self._initrepo(r, state[0], create) + + def _initrepo(self, parentrepo, source, create): + self._repo._subparent = parentrepo + self._repo._subsource = source if create: fp = self._repo.opener("hgrc", "w", text=True) @@ -415,7 +408,7 @@ if r == '' and not ignoreupdate: # no state recorded return True w = self._repo[None] - if w.p1() != self._repo[r] and not ignoreupdate: + if r != w.p1().hex() and not ignoreupdate: # different version checked out return True return w.dirty() # working directory changed @@ -438,15 +431,23 @@ def _get(self, state): source, revision, kind = state - try: - self._repo.lookup(revision) - except error.RepoError: + if revision not in self._repo: self._repo._subsource = source srcurl = _abssource(self._repo) - self._repo.ui.status(_('pulling subrepo %s from %s\n') - % (subrelpath(self), srcurl)) other = hg.repository(self._repo.ui, srcurl) - self._repo.pull(other) + if len(self._repo) == 0: + self._repo.ui.status(_('cloning subrepo %s from %s\n') + % (subrelpath(self), srcurl)) + parentrepo = self._repo._subparent + shutil.rmtree(self._repo.root) + other, self._repo = hg.clone(self._repo._subparent.ui, other, + self._repo.root, update=False) + self._initrepo(parentrepo, source, create=True) + else: + self._repo.ui.status(_('pulling subrepo %s from %s\n') + % (subrelpath(self), srcurl)) + self._repo.pull(other) + bookmarks.updatefromremote(self._repo.ui, self._repo, other) def get(self, state, overwrite=False): self._get(state) @@ -742,6 +743,12 @@ current = None return current + def _gitremote(self, remote): + out = self._gitcommand(['remote', 'show', '-n', remote]) + line = out.split('\n')[1] + i = line.index('URL: ') + len('URL: ') + return line[i:] + def _githavelocally(self, revision): out, code = self._gitdir(['cat-file', '-e', revision]) return code == 0 @@ -795,11 +802,14 @@ def _fetch(self, source, revision): if self._gitmissing(): - self._ui.status(_('cloning subrepo %s\n') % self._relpath) - self._gitnodir(['clone', self._abssource(source), self._abspath]) + source = self._abssource(source) + self._ui.status(_('cloning subrepo %s from %s\n') % + (self._relpath, source)) + self._gitnodir(['clone', source, self._abspath]) if self._githavelocally(revision): return - self._ui.status(_('pulling subrepo %s\n') % self._relpath) + self._ui.status(_('pulling subrepo %s from %s\n') % + (self._relpath, self._gitremote('origin'))) # try only origin: the originally cloned repo self._gitcommand(['fetch']) if not self._githavelocally(revision): @@ -827,7 +837,7 @@ return elif self._gitstate() == revision: if overwrite: - # first reset the index to unmark new files for commit, because + # first reset the index to unmark new files for commit, because # reset --hard will otherwise throw away files added for commit, # not just unmark them. self._gitcommand(['reset', 'HEAD'])
--- a/mercurial/tags.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/tags.py Wed May 18 09:56:27 2011 +0200 @@ -12,9 +12,9 @@ from node import nullid, bin, hex, short from i18n import _ -import os.path import encoding import error +import errno def findglobaltags(ui, repo, alltags, tagtypes): '''Find global tags in repo by reading .hgtags from every head that @@ -60,15 +60,18 @@ def readlocaltags(ui, repo, alltags, tagtypes): '''Read local tags in repo. Update alltags and tagtypes.''' try: - # localtags is in the local encoding; re-encode to UTF-8 on - # input for consistency with the rest of this module. - data = repo.opener("localtags").read() - filetags = _readtags( - ui, repo, data.splitlines(), "localtags", - recode=encoding.fromlocal) - _updatetags(filetags, "local", alltags, tagtypes) - except IOError: - pass + data = repo.opener.read("localtags") + except IOError, inst: + if inst.errno != errno.ENOENT: + raise + return + + # localtags is in the local encoding; re-encode to UTF-8 on + # input for consistency with the rest of this module. + filetags = _readtags( + ui, repo, data.splitlines(), "localtags", + recode=encoding.fromlocal) + _updatetags(filetags, "local", alltags, tagtypes) def _readtags(ui, repo, lines, fn, recode=None): '''Read tag definitions from a file (or any source of lines). @@ -185,7 +188,7 @@ if len(line) == 3: fnode = bin(line[2]) cachefnode[headnode] = fnode - except (ValueError, TypeError): + except Exception: # corruption of the tags cache, just recompute it ui.warn(_('.hg/cache/tags is corrupt, rebuilding it\n')) cacheheads = []
--- a/mercurial/templatefilters.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templatefilters.py Wed May 18 09:56:27 2011 +0200 @@ -8,11 +8,11 @@ import cgi, re, os, time, urllib import encoding, node, util -def stringify(thing): - '''turn nested template iterator into string.''' - if hasattr(thing, '__iter__') and not isinstance(thing, str): - return "".join([stringify(t) for t in thing if t is not None]) - return str(thing) +def addbreaks(text): + """:addbreaks: Any text. Add an XHTML "<br />" tag before the end of + every line except the last. + """ + return text.replace('\n', '<br/>\n') agescales = [("year", 3600 * 24 * 365), ("month", 3600 * 24 * 30), @@ -23,7 +23,9 @@ ("second", 1)] def age(date): - '''turn a (timestamp, tzoff) tuple into an age string.''' + """:age: Date. Returns a human-readable date/time difference between the + given date/time and the current date/time. + """ def plural(t, c): if c == 1: @@ -34,18 +36,65 @@ now = time.time() then = date[0] + future = False if then > now: - return 'in the future' - - delta = max(1, int(now - then)) - if delta > agescales[0][1] * 2: - return util.shortdate(date) + future = True + delta = max(1, int(then - now)) + if delta > agescales[0][1] * 30: + return 'in the distant future' + else: + delta = max(1, int(now - then)) + if delta > agescales[0][1] * 2: + return util.shortdate(date) for t, s in agescales: n = delta // s if n >= 2 or s == 1: + if future: + return '%s from now' % fmt(t, n) return '%s ago' % fmt(t, n) +def basename(path): + """:basename: Any text. Treats the text as a path, and returns the last + component of the path after splitting by the path separator + (ignoring trailing separators). For example, "foo/bar/baz" becomes + "baz" and "foo/bar//" becomes "bar". + """ + return os.path.basename(path) + +def datefilter(text): + """:date: Date. Returns a date in a Unix date format, including the + timezone: "Mon Sep 04 15:13:13 2006 0700". + """ + return util.datestr(text) + +def domain(author): + """:domain: Any text. Finds the first string that looks like an email + address, and extracts just the domain component. Example: ``User + <user@example.com>`` becomes ``example.com``. + """ + f = author.find('@') + if f == -1: + return '' + author = author[f + 1:] + f = author.find('>') + if f >= 0: + author = author[:f] + return author + +def email(text): + """:email: Any text. Extracts the first string that looks like an email + address. Example: ``User <user@example.com>`` becomes + ``user@example.com``. + """ + return util.email(text) + +def escape(text): + """:escape: Any text. Replaces the special XML/XHTML characters "&", "<" + and ">" with XML entities. + """ + return cgi.escape(text, True) + para_re = None space_re = None @@ -74,40 +123,45 @@ return "".join([space_re.sub(' ', util.wrap(para, width=width)) + rest for para, rest in findparas()]) +def fill68(text): + """:fill68: Any text. Wraps the text to fit in 68 columns.""" + return fill(text, 68) + +def fill76(text): + """:fill76: Any text. Wraps the text to fit in 76 columns.""" + return fill(text, 76) + def firstline(text): - '''return the first line of text''' + """:firstline: Any text. Returns the first line of text.""" try: return text.splitlines(True)[0].rstrip('\r\n') except IndexError: return '' -def nl2br(text): - '''replace raw newlines with xhtml line breaks.''' - return text.replace('\n', '<br/>\n') +def hexfilter(text): + """:hex: Any text. Convert a binary Mercurial node identifier into + its long hexadecimal representation. + """ + return node.hex(text) -def obfuscate(text): - text = unicode(text, encoding.encoding, 'replace') - return ''.join(['&#%d;' % ord(c) for c in text]) +def hgdate(text): + """:hgdate: Date. Returns the date as a pair of numbers: "1157407993 + 25200" (Unix timestamp, timezone offset). + """ + return "%d %d" % text -def domain(author): - '''get domain of author, or empty string if none.''' - f = author.find('@') - if f == -1: - return '' - author = author[f + 1:] - f = author.find('>') - if f >= 0: - author = author[:f] - return author +def isodate(text): + """:isodate: Date. Returns the date in ISO 8601 format: "2009-08-18 13:00 + +0200". + """ + return util.datestr(text, '%Y-%m-%d %H:%M %1%2') -def person(author): - '''get name of author, or else username.''' - if not '@' in author: - return author - f = author.find('<') - if f == -1: - return util.shortuser(author) - return author[:f].rstrip() +def isodatesec(text): + """:isodatesec: Date. Returns the date in ISO 8601 format, including + seconds: "2009-08-18 13:00:13 +0200". See also the rfc3339date + filter. + """ + return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2') def indent(text, prefix): '''indent each non-empty line of text after first with prefix.''' @@ -124,38 +178,6 @@ yield '\n' return "".join(indenter()) -def permissions(flags): - if "l" in flags: - return "lrwxrwxrwx" - if "x" in flags: - return "-rwxr-xr-x" - return "-rw-r--r--" - -def xmlescape(text): - text = (text - .replace('&', '&') - .replace('<', '<') - .replace('>', '>') - .replace('"', '"') - .replace("'", ''')) # ' invalid in HTML - return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text) - -def uescape(c): - if ord(c) < 0x80: - return c - else: - return '\\u%04x' % ord(c) - -_escapes = [ - ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'), - ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'), -] - -def jsonescape(s): - for k, v in _escapes: - s = s.replace(k, v) - return ''.join(uescape(c) for c in s) - def json(obj): if obj is None or obj is False or obj is True: return {None: 'null', False: 'false', True: 'true'}[obj] @@ -180,49 +202,161 @@ else: raise TypeError('cannot encode type %s' % obj.__class__.__name__) +def _uescape(c): + if ord(c) < 0x80: + return c + else: + return '\\u%04x' % ord(c) + +_escapes = [ + ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'), + ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'), +] + +def jsonescape(s): + for k, v in _escapes: + s = s.replace(k, v) + return ''.join(_uescape(c) for c in s) + +def localdate(text): + """:localdate: Date. Converts a date to local date.""" + return (text[0], util.makedate()[1]) + +def nonempty(str): + """:nonempty: Any text. Returns '(none)' if the string is empty.""" + return str or "(none)" + +def obfuscate(text): + """:obfuscate: Any text. Returns the input text rendered as a sequence of + XML entities. + """ + text = unicode(text, encoding.encoding, 'replace') + return ''.join(['&#%d;' % ord(c) for c in text]) + +def permissions(flags): + if "l" in flags: + return "lrwxrwxrwx" + if "x" in flags: + return "-rwxr-xr-x" + return "-rw-r--r--" + +def person(author): + """:person: Any text. Returns the text before an email address.""" + if not '@' in author: + return author + f = author.find('<') + if f != -1: + return author[:f].rstrip() + f = author.find('@') + return author[:f].replace('.', ' ') + +def rfc3339date(text): + """:rfc3339date: Date. Returns a date using the Internet date format + specified in RFC 3339: "2009-08-18T13:00:13+02:00". + """ + return util.datestr(text, "%Y-%m-%dT%H:%M:%S%1:%2") + +def rfc822date(text): + """:rfc822date: Date. Returns a date using the same format used in email + headers: "Tue, 18 Aug 2009 13:00:13 +0200". + """ + return util.datestr(text, "%a, %d %b %Y %H:%M:%S %1%2") + +def short(text): + """:short: Changeset hash. Returns the short form of a changeset hash, + i.e. a 12 hexadecimal digit string. + """ + return text[:12] + +def shortdate(text): + """:shortdate: Date. Returns a date like "2006-09-18".""" + return util.shortdate(text) + +def stringescape(text): + return text.encode('string_escape') + +def stringify(thing): + """:stringify: Any type. Turns the value into text by converting values into + text and concatenating them. + """ + if hasattr(thing, '__iter__') and not isinstance(thing, str): + return "".join([stringify(t) for t in thing if t is not None]) + return str(thing) + +def strip(text): + """:strip: Any text. Strips all leading and trailing whitespace.""" + return text.strip() + def stripdir(text): - '''Treat the text as path and strip a directory level, if possible.''' + """:stripdir: Treat the text as path and strip a directory level, if + possible. For example, "foo" and "foo/bar" becomes "foo". + """ dir = os.path.dirname(text) if dir == "": return os.path.basename(text) else: return dir -def nonempty(str): - return str or "(none)" +def tabindent(text): + """:tabindent: Any text. Returns the text, with every line except the + first starting with a tab character. + """ + return indent(text, '\t') + +def urlescape(text): + """:urlescape: Any text. Escapes all "special" characters. For example, + "foo bar" becomes "foo%20bar". + """ + return urllib.quote(text) + +def userfilter(text): + """:user: Any text. Returns the user portion of an email address.""" + return util.shortuser(text) + +def xmlescape(text): + text = (text + .replace('&', '&') + .replace('<', '<') + .replace('>', '>') + .replace('"', '"') + .replace("'", ''')) # ' invalid in HTML + return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text) filters = { - "addbreaks": nl2br, - "basename": os.path.basename, - "stripdir": stripdir, + "addbreaks": addbreaks, "age": age, - "date": lambda x: util.datestr(x), + "basename": basename, + "date": datefilter, "domain": domain, - "email": util.email, - "escape": lambda x: cgi.escape(x, True), - "fill68": lambda x: fill(x, width=68), - "fill76": lambda x: fill(x, width=76), + "email": email, + "escape": escape, + "fill68": fill68, + "fill76": fill76, "firstline": firstline, - "tabindent": lambda x: indent(x, '\t'), - "hgdate": lambda x: "%d %d" % x, - "isodate": lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2'), - "isodatesec": lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2'), + "hex": hexfilter, + "hgdate": hgdate, + "isodate": isodate, + "isodatesec": isodatesec, "json": json, "jsonescape": jsonescape, - "localdate": lambda x: (x[0], util.makedate()[1]), + "localdate": localdate, "nonempty": nonempty, "obfuscate": obfuscate, "permissions": permissions, "person": person, - "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2"), - "rfc3339date": lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2"), - "hex": node.hex, - "short": lambda x: x[:12], - "shortdate": util.shortdate, + "rfc3339date": rfc3339date, + "rfc822date": rfc822date, + "short": short, + "shortdate": shortdate, + "stringescape": stringescape, "stringify": stringify, - "strip": lambda x: x.strip(), - "urlescape": lambda x: urllib.quote(x), - "user": lambda x: util.shortuser(x), - "stringescape": lambda x: x.encode('string_escape'), + "strip": strip, + "stripdir": stripdir, + "tabindent": tabindent, + "urlescape": urlescape, + "user": userfilter, "xmlescape": xmlescape, } + +# tell hggettext to extract docstrings from these functions: +i18nfunctions = filters.values()
--- a/mercurial/templatekw.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templatekw.py Wed May 18 09:56:27 2011 +0200 @@ -6,7 +6,7 @@ # GNU General Public License version 2 or any later version. from node import hex -import encoding, patch, util, error +import patch, util, error def showlist(name, values, plural=None, **args): '''expand set of values. @@ -73,8 +73,7 @@ def getfiles(repo, ctx, revcache): if 'files' not in revcache: - revcache['files'] = repo.status(ctx.parents()[0].node(), - ctx.node())[:3] + revcache['files'] = repo.status(ctx.p1().node(), ctx.node())[:3] return revcache['files'] def getlatesttags(repo, ctx, cache): @@ -143,32 +142,49 @@ def showauthor(repo, ctx, templ, **args): + """:author: String. The unmodified author of the changeset.""" return ctx.user() def showbranch(**args): + """:branch: String. The name of the branch on which the changeset was + committed. + """ return args['ctx'].branch() def showbranches(**args): + """:branches: List of strings. The name of the branch on which the + changeset was committed. Will be empty if the branch name was + default. + """ branch = args['ctx'].branch() if branch != 'default': return showlist('branch', [branch], plural='branches', **args) def showbookmarks(**args): + """:bookmarks: List of strings. Any bookmarks associated with the + changeset. + """ bookmarks = args['ctx'].bookmarks() return showlist('bookmark', bookmarks, **args) def showchildren(**args): + """:children: List of strings. The children of the changeset.""" ctx = args['ctx'] childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()] return showlist('children', childrevs, **args) def showdate(repo, ctx, templ, **args): + """:date: Date information. The date when the changeset was committed.""" return ctx.date() def showdescription(repo, ctx, templ, **args): + """:desc: String. The text of the changeset description.""" return ctx.description().strip() def showdiffstat(repo, ctx, templ, **args): + """:diffstat: String. Statistics of changes with the following format: + "modified files: +added/-removed lines" + """ files, adds, removes = 0, 0, 0 for i in patch.diffstatdata(util.iterlines(ctx.diff())): files += 1 @@ -184,10 +200,14 @@ yield templ('extra', **args) def showfileadds(**args): + """:file_adds: List of strings. Files added by this changeset.""" repo, ctx, revcache = args['repo'], args['ctx'], args['revcache'] return showlist('file_add', getfiles(repo, ctx, revcache)[1], **args) def showfilecopies(**args): + """:file_copies: List of strings. Files copied in this changeset with + their sources. + """ cache, ctx = args['cache'], args['ctx'] copies = args['revcache'].get('copies') if copies is None: @@ -207,25 +227,37 @@ # provided before calling the templater, usually with a --copies # command line switch. def showfilecopiesswitch(**args): + """:file_copies_switch: List of strings. Like "file_copies" but displayed + only if the --copied switch is set. + """ copies = args['revcache'].get('copies') or [] c = [{'name': x[0], 'source': x[1]} for x in copies] return showlist('file_copy', c, plural='file_copies', **args) def showfiledels(**args): + """:file_dels: List of strings. Files removed by this changeset.""" repo, ctx, revcache = args['repo'], args['ctx'], args['revcache'] return showlist('file_del', getfiles(repo, ctx, revcache)[2], **args) def showfilemods(**args): + """:file_mods: List of strings. Files modified by this changeset.""" repo, ctx, revcache = args['repo'], args['ctx'], args['revcache'] return showlist('file_mod', getfiles(repo, ctx, revcache)[0], **args) def showfiles(**args): + """:files: List of strings. All files modified, added, or removed by this + changeset. + """ return showlist('file', args['ctx'].files(), **args) def showlatesttag(repo, ctx, templ, cache, **args): + """:latesttag: String. Most recent global tag in the ancestors of this + changeset. + """ return getlatesttags(repo, ctx, cache)[2] def showlatesttagdistance(repo, ctx, templ, cache, **args): + """:latesttagdistance: Integer. Longest path to the latest tag.""" return getlatesttags(repo, ctx, cache)[1] def showmanifest(**args): @@ -236,12 +268,17 @@ return templ('manifest', **args) def shownode(repo, ctx, templ, **args): + """:node: String. The changeset identification hash, as a 40 hexadecimal + digit string. + """ return ctx.hex() def showrev(repo, ctx, templ, **args): + """:rev: Integer. The repository-local changeset revision number.""" return ctx.rev() def showtags(**args): + """:tags: List of strings. Any tags associated with the changeset.""" return showlist('tag', args['ctx'].tags(), **args) # keywords are callables like: @@ -276,3 +313,5 @@ 'tags': showtags, } +# tell hggettext to extract docstrings from these functions: +i18nfunctions = keywords.values()
--- a/mercurial/templater.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templater.py Wed May 18 09:56:27 2011 +0200 @@ -69,7 +69,6 @@ else: raise error.ParseError(_("syntax error"), pos) pos += 1 - data[2] = pos yield ('end', None, pos) def compiletemplate(tmpl, context): @@ -91,8 +90,8 @@ parsed.append(("string", tmpl[pos:n])) pd = [tmpl, n + 1, stop] - parsed.append(p.parse(pd)) - pos = pd[2] + parseres, pos = p.parse(pd) + parsed.append(parseres) return [compileexp(e, context) for e in parsed] @@ -312,7 +311,7 @@ '''Get the template for the given template name. Use a local cache.''' if not t in self.cache: try: - self.cache[t] = open(self.map[t][1]).read() + self.cache[t] = util.readfile(self.map[t][1]) except KeyError, inst: raise util.Abort(_('"%s" not in template map') % inst.args[0]) except IOError, inst:
--- a/mercurial/templates/coal/header.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/coal/header.tmpl Wed May 18 09:56:27 2011 +0200 @@ -4,3 +4,4 @@ <link rel="icon" href="{staticurl}hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="{staticurl}style-coal.css" type="text/css" /> +<script type="text/javascript" src="{staticurl}mercurial.js"></script>
--- a/mercurial/templates/coal/map Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/coal/map Wed May 18 09:56:27 2011 +0200 @@ -94,14 +94,12 @@ filerename = '{file|escape}@' filelogrename = ' - <tr> - <th>base:</th> - <td> - <a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}"> - {file|escape}@{node|short} - </a> - </td> - </tr>' + <span class="base"> + base + <a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}"> + {file|escape}@{node|short} + </a> + </span>' fileannotateparent = ' <tr> <td class="metatag">parent:</td> @@ -199,7 +197,7 @@ <td><a href="{url}{sessionvars%urlparameter}">{name|escape}</a></td> <td>{description}</td> <td>{contact|obfuscate}</td> - <td class="age">{lastchange|age}</td> + <td class="age">{lastchange|date}</td> <td class="indexlinks">{archives%indexarchiveentry}</td> </tr>\n' indexarchiveentry = '<a href="{url}archive/{node|short}{extension|urlescape}"> ↓{type|escape}</a>'
--- a/mercurial/templates/gitweb/bookmarks.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/bookmarks.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / bookmarks +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / bookmarks </div> <div class="page_nav">
--- a/mercurial/templates/gitweb/branches.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/branches.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / branches +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / branches </div> <div class="page_nav">
--- a/mercurial/templates/gitweb/changelog.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/changelog.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / changelog +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / changelog </div> <form action="{url}log">
--- a/mercurial/templates/gitweb/changelogentry.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/changelogentry.tmpl Wed May 18 09:56:27 2011 +0200 @@ -1,5 +1,5 @@ <div> -<a class="title" href="{url}rev/{node|short}{sessionvars%urlparameter}"><span class="age">{date|age}</span>{desc|strip|firstline|escape|nonempty}<span class="logtags"> {inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span></a> +<a class="title" href="{url}rev/{node|short}{sessionvars%urlparameter}"><span class="age">{date|date}</span>{desc|strip|firstline|escape|nonempty}<span class="logtags"> {inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span></a> </div> <div class="title_text"> <div class="log_link">
--- a/mercurial/templates/gitweb/changeset.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/changeset.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / changeset +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / changeset </div> <div class="page_nav"> @@ -32,7 +32,7 @@ <div class="title_text"> <table cellspacing="0"> <tr><td>author</td><td>{author|obfuscate}</td></tr> -<tr><td></td><td>{date|date} ({date|age})</td></tr> +<tr><td></td><td class="date age">{date|date}</td></tr> {branch%changesetbranch} <tr><td>changeset {rev}</td><td style="font-family:monospace">{node|short}</td></tr> {parent%changesetparent}
--- a/mercurial/templates/gitweb/error.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/error.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / error +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / error </div> <div class="page_nav">
--- a/mercurial/templates/gitweb/fileannotate.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/fileannotate.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / annotate +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / annotate </div> <div class="page_nav"> @@ -40,7 +40,7 @@ <td>{author|obfuscate}</td></tr> <tr> <td></td> - <td>{date|date} ({date|age})</td></tr> + <td class="date age">{date|date}</td></tr> {branch%filerevbranch} <tr> <td>changeset {rev}</td>
--- a/mercurial/templates/gitweb/filediff.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/filediff.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / diff +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / diff </div> <div class="page_nav">
--- a/mercurial/templates/gitweb/filelog.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/filelog.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / file revisions +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / file revisions </div> <div class="page_nav">
--- a/mercurial/templates/gitweb/filerevision.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/filerevision.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / file revision +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / file revision </div> <div class="page_nav"> @@ -40,7 +40,7 @@ <td>{author|obfuscate}</td></tr> <tr> <td></td> - <td>{date|date} ({date|age})</td></tr> + <td class="date age">{date|date}</td></tr> {branch%filerevbranch} <tr> <td>changeset {rev}</td>
--- a/mercurial/templates/gitweb/footer.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/footer.tmpl Wed May 18 09:56:27 2011 +0200 @@ -1,3 +1,4 @@ +<script type="text/javascript">process_dates()</script> <div class="page_footer"> <div class="page_footer_text">{repo|escape}</div> <div class="rss_logo">
--- a/mercurial/templates/gitweb/graph.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/graph.tmpl Wed May 18 09:56:27 2011 +0200 @@ -9,7 +9,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / graph +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / graph </div> <form action="{url}log"> @@ -44,7 +44,6 @@ <ul id="graphnodes"></ul> </div> -<script type="text/javascript" src="{staticurl}graph.js"></script> <script> <!-- hide script content
--- a/mercurial/templates/gitweb/header.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/header.tmpl Wed May 18 09:56:27 2011 +0200 @@ -5,4 +5,4 @@ <link rel="icon" href="{staticurl}hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow"/> <link rel="stylesheet" href="{staticurl}style-gitweb.css" type="text/css" /> - +<script type="text/javascript" src="{staticurl}mercurial.js"></script>
--- a/mercurial/templates/gitweb/help.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/help.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / help +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / help </div> <div class="page_nav">
--- a/mercurial/templates/gitweb/helptopics.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/helptopics.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / help +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / help </div> <div class="page_nav">
--- a/mercurial/templates/gitweb/index.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/index.tmpl Wed May 18 09:56:27 2011 +0200 @@ -4,7 +4,7 @@ <body> <div class="page_header"> - <a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a> + <a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a> Repositories list </div>
--- a/mercurial/templates/gitweb/manifest.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/manifest.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / files +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / files </div> <div class="page_nav">
--- a/mercurial/templates/gitweb/map Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/map Wed May 18 09:56:27 2011 +0200 @@ -162,7 +162,7 @@ tags = tags.tmpl tagentry = ' <tr class="parity{parity}"> - <td class="age"><i>{date|age}</i></td> + <td class="age"><i class="age">{date|date}</i></td> <td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>{tag|escape}</b></a></td> <td class="link"> <a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> | @@ -173,7 +173,7 @@ bookmarks = bookmarks.tmpl bookmarkentry = ' <tr class="parity{parity}"> - <td class="age"><i>{date|age}</i></td> + <td class="age"><i class="age">{date|date}</i></td> <td><a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"><b>{bookmark|escape}</b></a></td> <td class="link"> <a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> | @@ -184,7 +184,7 @@ branches = branches.tmpl branchentry = ' <tr class="parity{parity}"> - <td class="age"><i>{date|age}</i></td> + <td class="age"><i class="age">{date|date}</i></td> <td><a class="list" href="{url}shortlog/{node|short}{sessionvars%urlparameter}"><b>{node|short}</b></a></td> <td class="{status}">{branch|escape}</td> <td class="link"> @@ -228,7 +228,7 @@ bookmarktag = '<span class="bookmarktag" title="{name}">{name}</span> ' shortlogentry = ' <tr class="parity{parity}"> - <td class="age"><i>{date|age}</i></td> + <td class="age"><i class="age">{date|date}</i></td> <td><i>{author|person}</i></td> <td> <a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"> @@ -243,7 +243,7 @@ </tr>' filelogentry = ' <tr class="parity{parity}"> - <td class="age"><i>{date|age}</i></td> + <td class="age"><i class="age">{date|date}</i></td> <td> <a class="list" href="{url}rev/{node|short}{sessionvars%urlparameter}"> <b>{desc|strip|firstline|escape|nonempty}</b> @@ -262,7 +262,7 @@ </td> <td>{description}</td> <td>{contact|obfuscate}</td> - <td class="age">{lastchange|age}</td> + <td class="age">{lastchange|date}</td> <td class="indexlinks">{archives%indexarchiveentry}</td> <td><div class="rss_logo"><a href="{url}rss-log">RSS</a> <a href="{url}atom-log">Atom</a></div></td> </tr>\n'
--- a/mercurial/templates/gitweb/notfound.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/notfound.tmpl Wed May 18 09:56:27 2011 +0200 @@ -5,7 +5,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a> Not found: {repo|escape} +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a> Not found: {repo|escape} </div> <div class="page_body">
--- a/mercurial/templates/gitweb/search.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/search.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / search +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / search <form action="{url}log"> {sessionvars%hiddenformentry}
--- a/mercurial/templates/gitweb/shortlog.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/shortlog.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / shortlog +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / shortlog </div> <form action="{url}log">
--- a/mercurial/templates/gitweb/summary.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/summary.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / summary +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / summary <form action="{url}log"> {sessionvars%hiddenformentry} @@ -50,6 +50,12 @@ <tr class="light"><td colspan="3"><a class="list" href="{url}tags{sessionvars%urlparameter}">...</a></td></tr> </table> +<div><a class="title" href="{url}bookmarks{sessionvars%urlparameter}">bookmarks</a></div> +<table cellspacing="0"> +{bookmarks%bookmarkentry} +<tr class="light"><td colspan="3"><a class="list" href="{url}bookmarks{sessionvars%urlparameter}">...</a></td></tr> +</table> + <div><a class="title" href="#">branches</a></div> <table cellspacing="0"> {branches%branchentry}
--- a/mercurial/templates/gitweb/tags.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/gitweb/tags.tmpl Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ <body> <div class="page_header"> -<a href="http://mercurial.selenic.com/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / tags +<a href="{logourl}" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">{repo|escape}</a> / tags </div> <div class="page_nav">
--- a/mercurial/templates/map-cmdline.compact Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/map-cmdline.compact Wed May 18 09:56:27 2011 +0200 @@ -1,4 +1,4 @@ -changeset = '{rev}{tags}{parents} {node|short} {date|isodate} {author|user}\n {desc|firstline|strip}\n\n' +changeset = '{rev}{tags}{bookmarks}{parents} {node|short} {date|isodate} {author|user}\n {desc|firstline|strip}\n\n' changeset_quiet = '{rev}:{node|short}\n' changeset_verbose = '{rev}{tags}{parents} {node|short} {date|isodate} {author}\n {desc|strip}\n\n' start_tags = '[' @@ -7,3 +7,6 @@ start_parents = ':' parent = '{rev},' last_parent = '{rev}' +start_bookmarks = '[' +bookmark = '{bookmark},' +last_bookmark = '{bookmark}]'
--- a/mercurial/templates/monoblue/changelogentry.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/monoblue/changelogentry.tmpl Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,6 @@ <h3 class="changelog"><a class="title" href="{url}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}<span class="logtags"> {inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span></a></h3> <ul class="changelog-entry"> - <li class="age">{date|age}</li> + <li class="age">{date|date}</li> <li>by <span class="name">{author|obfuscate}</span> <span class="revdate">[{date|rfc822date}] rev {rev}</span></li> <li class="description">{desc|strip|escape|addbreaks|nonempty}</li> </ul>
--- a/mercurial/templates/monoblue/changeset.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/monoblue/changeset.tmpl Wed May 18 09:56:27 2011 +0200 @@ -38,7 +38,7 @@ <h2 class="no-link no-border">changeset</h2> <h3 class="changeset"><a href="{url}raw-rev/{node|short}">{desc|strip|escape|firstline|nonempty} <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span></a></h3> - <p class="changeset-age"><span>{date|age}</span></p> + <p class="changeset-age age">{date|date}</p> <dl class="overview"> <dt>author</dt>
--- a/mercurial/templates/monoblue/fileannotate.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/monoblue/fileannotate.tmpl Wed May 18 09:56:27 2011 +0200 @@ -40,7 +40,7 @@ <h2 class="no-link no-border">{file|escape}@{node|short} (annotated)</h2> <h3 class="changeset">{file|escape}</h3> - <p class="changeset-age"><span>{date|age}</span></p> + <p class="changeset-age age">{date|date}</p> <dl class="overview"> <dt>author</dt>
--- a/mercurial/templates/monoblue/filerevision.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/monoblue/filerevision.tmpl Wed May 18 09:56:27 2011 +0200 @@ -40,7 +40,7 @@ <h2 class="no-link no-border">{file|escape}@{node|short}</h2> <h3 class="changeset">{file|escape}</h3> - <p class="changeset-age"><span>{date|age}</span></p> + <p class="changeset-age age">{date|date}</p> <dl class="overview"> <dt>author</dt>
--- a/mercurial/templates/monoblue/footer.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/monoblue/footer.tmpl Wed May 18 09:56:27 2011 +0200 @@ -1,3 +1,4 @@ + <script type="text/javascript">process_dates()</script> <div class="page-footer"> <p>Mercurial Repository: {repo|escape}</p> <ul class="rss-logo"> @@ -8,7 +9,7 @@ </div> <div id="powered-by"> - <p><a href="http://mercurial.selenic.com/" title="Mercurial"><img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a></p> + <p><a href="{logourl}" title="Mercurial"><img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a></p> </div> <div id="corner-top-left"></div>
--- a/mercurial/templates/monoblue/graph.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/monoblue/graph.tmpl Wed May 18 09:56:27 2011 +0200 @@ -40,7 +40,6 @@ <ul id="graphnodes"></ul> </div> - <script type="text/javascript" src="{staticurl}graph.js"></script> <script> <!-- hide script content
--- a/mercurial/templates/monoblue/header.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/monoblue/header.tmpl Wed May 18 09:56:27 2011 +0200 @@ -4,3 +4,4 @@ <link rel="icon" href="{staticurl}hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow"/> <link rel="stylesheet" href="{staticurl}style-monoblue.css" type="text/css" /> + <script type="text/javascript" src="{staticurl}mercurial.js"></script>
--- a/mercurial/templates/monoblue/index.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/monoblue/index.tmpl Wed May 18 09:56:27 2011 +0200 @@ -26,7 +26,7 @@ </div> <div id="powered-by"> - <p><a href="http://mercurial.selenic.com/" title="Mercurial"><img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a></p> + <p><a href="{logourl}" title="Mercurial"><img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a></p> </div> <div id="corner-top-left"></div>
--- a/mercurial/templates/monoblue/map Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/monoblue/map Wed May 18 09:56:27 2011 +0200 @@ -31,8 +31,10 @@ </tr>' filenolink = ' <tr class="parity{parity}"> + <td><a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{file|escape}</a></td> + <td></td> <td> - <a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{file|escape}</a></td><td></td><td>file | + file | annotate | <a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> | <a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">revisions</a> @@ -139,7 +141,7 @@ tags = tags.tmpl tagentry = ' <tr class="parity{parity}"> - <td class="nowrap">{date|age}</td> + <td class="nowrap age">{date|date}</td> <td><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{tag|escape}</a></td> <td class="nowrap"> <a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> | @@ -150,7 +152,7 @@ bookmarks = bookmarks.tmpl bookmarkentry = ' <tr class="parity{parity}"> - <td class="nowrap">{date|age}</td> + <td class="nowrap date">{date|date}</td> <td><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{bookmark|escape}</a></td> <td class="nowrap"> <a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a> | @@ -161,7 +163,7 @@ branches = branches.tmpl branchentry = ' <tr class="parity{parity}"> - <td class="nowrap">{date|age}</td> + <td class="nowrap age">{date|date}</td> <td><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{node|short}</a></td> <td class="{status}">{branch|escape}</td> <td class="nowrap"> @@ -194,7 +196,7 @@ bookmarktag = '<span class="bookmarktag" title="{name}">{name}</span> ' shortlogentry = ' <tr class="parity{parity}"> - <td class="nowrap">{date|age}</td> + <td class="nowrap age">{date|date}</td> <td>{author|person}</td> <td> <a href="{url}rev/{node|short}{sessionvars%urlparameter}"> @@ -209,7 +211,7 @@ </tr>' filelogentry = ' <tr class="parity{parity}"> - <td class="nowrap">{date|age}</td> + <td class="nowrap age">{date|date}</td> <td><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a></td> <td class="nowrap"> <a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a> | <a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a> | <a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a> @@ -222,7 +224,7 @@ <td><a href="{url}{sessionvars%urlparameter}">{name|escape}</a></td> <td>{description}</td> <td>{contact|obfuscate}</td> - <td>{lastchange|age}</td> + <td class="age">{lastchange|date}</td> <td class="indexlinks">{archives%indexarchiveentry}</td> <td> <div class="rss_logo">
--- a/mercurial/templates/monoblue/summary.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/monoblue/summary.tmpl Wed May 18 09:56:27 2011 +0200 @@ -58,6 +58,14 @@ </tr> </table> + <h2><a href="{url}bookmarks{sessionvars%urlparameter}">Bookmarks</a></h2> + <table> + {bookmarks%bookmarkentry} + <tr class="light"> + <td colspan="3"><a class="list" href="{url}bookmarks{sessionvars%urlparameter}">...</a></td> + </tr> + </table> + <h2 class="no-link">Branches</h2> <table> {branches%branchentry}
--- a/mercurial/templates/paper/bookmarks.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/bookmarks.tmpl Wed May 18 09:56:27 2011 +0200 @@ -10,7 +10,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" alt="mercurial" /></a> </div> <ul>
--- a/mercurial/templates/paper/branches.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/branches.tmpl Wed May 18 09:56:27 2011 +0200 @@ -10,7 +10,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" alt="mercurial" /></a> </div> <ul>
--- a/mercurial/templates/paper/changeset.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/changeset.tmpl Wed May 18 09:56:27 2011 +0200 @@ -5,7 +5,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" alt="mercurial" /></a> </div> <ul> @@ -40,7 +40,7 @@ files, or words in the commit message</div> </form> -<div class="description">{desc|strip|escape|addbreaks|nonempty}</div> +<div class="description">{desc|strip|escape|nonempty}</div> <table id="changesetEntry"> <tr> @@ -49,7 +49,7 @@ </tr> <tr> <th class="date">date</th> - <td class="date">{date|date} ({date|age})</td></tr> + <td class="date age">{date|date}</td></tr> <tr> <th class="author">parents</th> <td class="author">{parent%changesetparent}</td>
--- a/mercurial/templates/paper/error.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/error.tmpl Wed May 18 09:56:27 2011 +0200 @@ -6,7 +6,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a> </div> <ul>
--- a/mercurial/templates/paper/fileannotate.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/fileannotate.tmpl Wed May 18 09:56:27 2011 +0200 @@ -6,7 +6,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" alt="mercurial" /></a> </div> <ul> @@ -45,7 +45,7 @@ files, or words in the commit message</div> </form> -<div class="description">{desc|strip|escape|addbreaks|nonempty}</div> +<div class="description">{desc|strip|escape|nonempty}</div> <table id="changesetEntry"> <tr> @@ -54,7 +54,7 @@ </tr> <tr> <th class="date">date</th> - <td class="date">{date|date} ({date|age})</td> + <td class="date age">{date|date}</td> </tr> <tr> <th class="author">parents</th>
--- a/mercurial/templates/paper/filediff.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/filediff.tmpl Wed May 18 09:56:27 2011 +0200 @@ -6,7 +6,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" alt="mercurial" /></a> </div> <ul> @@ -44,7 +44,7 @@ files, or words in the commit message</div> </form> -<div class="description">{desc|strip|escape|addbreaks|nonempty}</div> +<div class="description">{desc|strip|escape|nonempty}</div> <table id="changesetEntry"> <tr> @@ -53,7 +53,7 @@ </tr> <tr> <th>date</th> - <td>{date|date} ({date|age})</td> + <td class="date age">{date|date}</td> </tr> <tr> <th>parents</th>
--- a/mercurial/templates/paper/filelog.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/filelog.tmpl Wed May 18 09:56:27 2011 +0200 @@ -10,7 +10,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" alt="mercurial" /></a> </div> <ul>
--- a/mercurial/templates/paper/filelogentry.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/filelogentry.tmpl Wed May 18 09:56:27 2011 +0200 @@ -1,5 +1,5 @@ <tr class="parity{parity}"> - <td class="age">{date|age}</td> + <td class="age">{date|date}</td> <td class="author">{author|person}</td> - <td class="description"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}</td> + <td class="description"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{rename%filelogrename}</td> </tr>
--- a/mercurial/templates/paper/filerevision.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/filerevision.tmpl Wed May 18 09:56:27 2011 +0200 @@ -6,7 +6,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" alt="mercurial" /></a> </div> <ul> @@ -43,7 +43,7 @@ files, or words in the commit message</div> </form> -<div class="description">{desc|strip|escape|addbreaks|nonempty}</div> +<div class="description">{desc|strip|escape|nonempty}</div> <table id="changesetEntry"> <tr> @@ -52,7 +52,7 @@ </tr> <tr> <th class="date">date</th> - <td class="date">{date|date} ({date|age})</td> + <td class="date age">{date|date}</td> </tr> <tr> <th class="author">parents</th>
--- a/mercurial/templates/paper/footer.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/footer.tmpl Wed May 18 09:56:27 2011 +0200 @@ -1,3 +1,4 @@ +<script type="text/javascript">process_dates()</script> {motd} </body>
--- a/mercurial/templates/paper/graph.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/graph.tmpl Wed May 18 09:56:27 2011 +0200 @@ -11,7 +11,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" alt="mercurial" /></a> </div> <ul> @@ -55,7 +55,6 @@ <ul id="graphnodes"></ul> </div> -<script type="text/javascript" src="{staticurl}graph.js"></script> <script type="text/javascript"> <!-- hide script content
--- a/mercurial/templates/paper/header.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/header.tmpl Wed May 18 09:56:27 2011 +0200 @@ -4,3 +4,4 @@ <link rel="icon" href="{staticurl}hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="{staticurl}style-paper.css" type="text/css" /> +<script type="text/javascript" src="{staticurl}mercurial.js"></script>
--- a/mercurial/templates/paper/help.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/help.tmpl Wed May 18 09:56:27 2011 +0200 @@ -10,7 +10,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" alt="mercurial" /></a> </div> <ul>
--- a/mercurial/templates/paper/helptopics.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/helptopics.tmpl Wed May 18 09:56:27 2011 +0200 @@ -10,7 +10,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" alt="mercurial" /></a> </div> <ul>
--- a/mercurial/templates/paper/index.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/index.tmpl Wed May 18 09:56:27 2011 +0200 @@ -5,7 +5,7 @@ <div class="container"> <div class="menu"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a> </div> <div class="main">
--- a/mercurial/templates/paper/manifest.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/manifest.tmpl Wed May 18 09:56:27 2011 +0200 @@ -6,7 +6,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" alt="mercurial" /></a> </div> <ul>
--- a/mercurial/templates/paper/map Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/map Wed May 18 09:56:27 2011 +0200 @@ -93,14 +93,12 @@ filerename = '{file|escape}@' filelogrename = ' - <tr> - <th>base:</th> - <td> - <a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}"> - {file|escape}@{node|short} - </a> - </td> - </tr>' + <span class="base"> + base + <a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}"> + {file|escape}@{node|short} + </a> + </span>' fileannotateparent = ' <tr> <td class="metatag">parent:</td> @@ -198,7 +196,7 @@ <td><a href="{url}{sessionvars%urlparameter}">{name|escape}</a></td> <td>{description}</td> <td>{contact|obfuscate}</td> - <td class="age">{lastchange|age}</td> + <td class="age">{lastchange|date}</td> <td class="indexlinks">{archives%indexarchiveentry}</td> </tr>\n' indexarchiveentry = '<a href="{url}archive/{node|short}{extension|urlescape}"> ↓{type|escape}</a>'
--- a/mercurial/templates/paper/search.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/search.tmpl Wed May 18 09:56:27 2011 +0200 @@ -6,7 +6,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a> </div> <ul>
--- a/mercurial/templates/paper/shortlog.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/shortlog.tmpl Wed May 18 09:56:27 2011 +0200 @@ -10,7 +10,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" alt="mercurial" /></a> </div> <ul>
--- a/mercurial/templates/paper/shortlogentry.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/shortlogentry.tmpl Wed May 18 09:56:27 2011 +0200 @@ -1,5 +1,5 @@ <tr class="parity{parity}"> - <td class="age">{age(date)}</td> + <td class="age">{date|date}</td> <td class="author">{author|person}</td> <td class="description"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags % '<span class="tag">{name|escape}</span> '}{bookmarks % '<span class="tag">{name|escape}</span> '}</td> </tr>
--- a/mercurial/templates/paper/tags.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/paper/tags.tmpl Wed May 18 09:56:27 2011 +0200 @@ -10,7 +10,7 @@ <div class="container"> <div class="menu"> <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" alt="mercurial" /></a> </div> <ul>
--- a/mercurial/templates/raw/map Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/raw/map Wed May 18 09:56:27 2011 +0200 @@ -24,5 +24,7 @@ indexentry = '{url}\n' tags = '{entries%tagentry}' tagentry = '{tag} {node}\n' +bookmarks = '{entries%bookmarkentry}' +bookmarkentry = '{bookmark} {node}\n' branches = '{entries%branchentry}' branchentry = '{branch} {node} {status}\n'
--- a/mercurial/templates/spartan/changelogentry.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/spartan/changelogentry.tmpl Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,6 @@ <table class="logEntry parity{parity}"> <tr> - <th class="age">{date|age}:</th> + <th><span class="age">{date|date}</span>:</th> <th class="firstline">{desc|strip|firstline|escape|nonempty}</th> </tr> <tr>
--- a/mercurial/templates/spartan/changeset.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/spartan/changeset.tmpl Wed May 18 09:56:27 2011 +0200 @@ -31,7 +31,7 @@ </tr> <tr> <th class="date">date:</th> - <td class="date">{date|date} ({date|age})</td> + <td class="date age">{date|date}</td> </tr> <tr> <th class="files">files:</th>
--- a/mercurial/templates/spartan/fileannotate.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/spartan/fileannotate.tmpl Wed May 18 09:56:27 2011 +0200 @@ -30,7 +30,7 @@ <td>{author|obfuscate}</td></tr> <tr> <td class="metatag">date:</td> - <td>{date|date} ({date|age})</td> + <td class="date age">{date|date}</td> </tr> <tr> <td class="metatag">permissions:</td>
--- a/mercurial/templates/spartan/filelogentry.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/spartan/filelogentry.tmpl Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,6 @@ <table class="logEntry parity{parity}"> <tr> - <th class="age">{date|age}:</th> + <th><span class="age">{date|date}</span>:</th> <th class="firstline"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a></th> </tr> <tr>
--- a/mercurial/templates/spartan/filerevision.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/spartan/filerevision.tmpl Wed May 18 09:56:27 2011 +0200 @@ -30,7 +30,7 @@ <td>{author|obfuscate}</td></tr> <tr> <td class="metatag">date:</td> - <td>{date|date} ({date|age})</td></tr> + <td class="date age">{date|date}</td></tr> <tr> <td class="metatag">permissions:</td> <td>{permissions|permissions}</td></tr>
--- a/mercurial/templates/spartan/footer.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/spartan/footer.tmpl Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,7 @@ +<script type="text/javascript">process_dates()</script> {motd} <div class="logo"> -<a href="http://mercurial.selenic.com/"> +<a href="{logourl}"> <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a> </div>
--- a/mercurial/templates/spartan/graph.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/spartan/graph.tmpl Wed May 18 09:56:27 2011 +0200 @@ -36,7 +36,6 @@ <ul id="graphnodes"></ul> </div> -<script type="text/javascript" src="{staticurl}graph.js"></script> <script type="text/javascript"> <!-- hide script content
--- a/mercurial/templates/spartan/header.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/spartan/header.tmpl Wed May 18 09:56:27 2011 +0200 @@ -4,3 +4,4 @@ <link rel="icon" href="{staticurl}hgicon.png" type="image/png"> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="{staticurl}style.css" type="text/css" /> +<script type="text/javascript" src="{staticurl}mercurial.js"></script>
--- a/mercurial/templates/spartan/map Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/spartan/map Wed May 18 09:56:27 2011 +0200 @@ -168,7 +168,7 @@ <td><a href="{url}{sessionvars%urlparameter}">{name|escape}</a></td> <td>{description}</td> <td>{contact|obfuscate}</td> - <td class="age">{lastchange|age}</td> + <td class="age">{lastchange|date}</td> <td class="indexlinks"> <a href="{url}rss-log">RSS</a> <a href="{url}atom-log">Atom</a>
--- a/mercurial/templates/spartan/shortlogentry.tmpl Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/spartan/shortlogentry.tmpl Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,6 @@ <table class="slogEntry parity{parity}"> <tr> - <td class="age">{date|age}</td> + <td class="age">{date|date}</td> <td class="author">{author|person}</td> <td class="node"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a></td> </tr>
--- a/mercurial/templates/static/graph.js Wed May 18 09:31:19 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,137 +0,0 @@ -// branch_renderer.js - Rendering of branch DAGs on the client side -// -// Copyright 2008 Dirkjan Ochtman <dirkjan AT ochtman DOT nl> -// Copyright 2006 Alexander Schremmer <alex AT alexanderweb DOT de> -// -// derived from code written by Scott James Remnant <scott@ubuntu.com> -// Copyright 2005 Canonical Ltd. -// -// This software may be used and distributed according to the terms -// of the GNU General Public License, incorporated herein by reference. - -var colors = [ - [ 1.0, 0.0, 0.0 ], - [ 1.0, 1.0, 0.0 ], - [ 0.0, 1.0, 0.0 ], - [ 0.0, 1.0, 1.0 ], - [ 0.0, 0.0, 1.0 ], - [ 1.0, 0.0, 1.0 ] -]; - -function Graph() { - - this.canvas = document.getElementById('graph'); - if (navigator.userAgent.indexOf('MSIE') >= 0) this.canvas = window.G_vmlCanvasManager.initElement(this.canvas); - this.ctx = this.canvas.getContext('2d'); - this.ctx.strokeStyle = 'rgb(0, 0, 0)'; - this.ctx.fillStyle = 'rgb(0, 0, 0)'; - this.cur = [0, 0]; - this.line_width = 3; - this.bg = [0, 4]; - this.cell = [2, 0]; - this.columns = 0; - this.revlink = ''; - - this.scale = function(height) { - this.bg_height = height; - this.box_size = Math.floor(this.bg_height / 1.2); - this.cell_height = this.box_size; - } - - function colorPart(num) { - num *= 255 - num = num < 0 ? 0 : num; - num = num > 255 ? 255 : num; - var digits = Math.round(num).toString(16); - if (num < 16) { - return '0' + digits; - } else { - return digits; - } - } - - this.setColor = function(color, bg, fg) { - - // Set the colour. - // - // Picks a distinct colour based on an internal wheel; the bg - // parameter provides the value that should be assigned to the 'zero' - // colours and the fg parameter provides the multiplier that should be - // applied to the foreground colours. - - color %= colors.length; - var red = (colors[color][0] * fg) || bg; - var green = (colors[color][1] * fg) || bg; - var blue = (colors[color][2] * fg) || bg; - red = Math.round(red * 255); - green = Math.round(green * 255); - blue = Math.round(blue * 255); - var s = 'rgb(' + red + ', ' + green + ', ' + blue + ')'; - this.ctx.strokeStyle = s; - this.ctx.fillStyle = s; - return s; - - } - - this.render = function(data) { - - var backgrounds = ''; - var nodedata = ''; - - for (var i in data) { - - var parity = i % 2; - this.cell[1] += this.bg_height; - this.bg[1] += this.bg_height; - - var cur = data[i]; - var node = cur[1]; - var edges = cur[2]; - var fold = false; - - for (var j in edges) { - - line = edges[j]; - start = line[0]; - end = line[1]; - color = line[2]; - - if (end > this.columns || start > this.columns) { - this.columns += 1; - } - - if (start == this.columns && start > end) { - var fold = true; - } - - x0 = this.cell[0] + this.box_size * start + this.box_size / 2; - y0 = this.bg[1] - this.bg_height / 2; - x1 = this.cell[0] + this.box_size * end + this.box_size / 2; - y1 = this.bg[1] + this.bg_height / 2; - - this.edge(x0, y0, x1, y1, color); - - } - - // Draw the revision node in the right column - - column = node[0] - color = node[1] - - radius = this.box_size / 8; - x = this.cell[0] + this.box_size * column + this.box_size / 2; - y = this.bg[1] - this.bg_height / 2; - var add = this.vertex(x, y, color, parity, cur); - backgrounds += add[0]; - nodedata += add[1]; - - if (fold) this.columns -= 1; - - } - - document.getElementById('nodebgs').innerHTML += backgrounds; - document.getElementById('graphnodes').innerHTML += nodedata; - - } - -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/templates/static/mercurial.js Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,220 @@ +// mercurial.js - JavaScript utility functions +// +// Rendering of branch DAGs on the client side +// Display of elapsed time +// +// Copyright 2008 Dirkjan Ochtman <dirkjan AT ochtman DOT nl> +// Copyright 2006 Alexander Schremmer <alex AT alexanderweb DOT de> +// +// derived from code written by Scott James Remnant <scott@ubuntu.com> +// Copyright 2005 Canonical Ltd. +// +// This software may be used and distributed according to the terms +// of the GNU General Public License, incorporated herein by reference. + +var colors = [ + [ 1.0, 0.0, 0.0 ], + [ 1.0, 1.0, 0.0 ], + [ 0.0, 1.0, 0.0 ], + [ 0.0, 1.0, 1.0 ], + [ 0.0, 0.0, 1.0 ], + [ 1.0, 0.0, 1.0 ] +]; + +function Graph() { + + this.canvas = document.getElementById('graph'); + if (navigator.userAgent.indexOf('MSIE') >= 0) this.canvas = window.G_vmlCanvasManager.initElement(this.canvas); + this.ctx = this.canvas.getContext('2d'); + this.ctx.strokeStyle = 'rgb(0, 0, 0)'; + this.ctx.fillStyle = 'rgb(0, 0, 0)'; + this.cur = [0, 0]; + this.line_width = 3; + this.bg = [0, 4]; + this.cell = [2, 0]; + this.columns = 0; + this.revlink = ''; + + this.scale = function(height) { + this.bg_height = height; + this.box_size = Math.floor(this.bg_height / 1.2); + this.cell_height = this.box_size; + } + + function colorPart(num) { + num *= 255 + num = num < 0 ? 0 : num; + num = num > 255 ? 255 : num; + var digits = Math.round(num).toString(16); + if (num < 16) { + return '0' + digits; + } else { + return digits; + } + } + + this.setColor = function(color, bg, fg) { + + // Set the colour. + // + // Picks a distinct colour based on an internal wheel; the bg + // parameter provides the value that should be assigned to the 'zero' + // colours and the fg parameter provides the multiplier that should be + // applied to the foreground colours. + + color %= colors.length; + var red = (colors[color][0] * fg) || bg; + var green = (colors[color][1] * fg) || bg; + var blue = (colors[color][2] * fg) || bg; + red = Math.round(red * 255); + green = Math.round(green * 255); + blue = Math.round(blue * 255); + var s = 'rgb(' + red + ', ' + green + ', ' + blue + ')'; + this.ctx.strokeStyle = s; + this.ctx.fillStyle = s; + return s; + + } + + this.render = function(data) { + + var backgrounds = ''; + var nodedata = ''; + + for (var i in data) { + + var parity = i % 2; + this.cell[1] += this.bg_height; + this.bg[1] += this.bg_height; + + var cur = data[i]; + var node = cur[1]; + var edges = cur[2]; + var fold = false; + + for (var j in edges) { + + line = edges[j]; + start = line[0]; + end = line[1]; + color = line[2]; + + if (end > this.columns || start > this.columns) { + this.columns += 1; + } + + if (start == this.columns && start > end) { + var fold = true; + } + + x0 = this.cell[0] + this.box_size * start + this.box_size / 2; + y0 = this.bg[1] - this.bg_height / 2; + x1 = this.cell[0] + this.box_size * end + this.box_size / 2; + y1 = this.bg[1] + this.bg_height / 2; + + this.edge(x0, y0, x1, y1, color); + + } + + // Draw the revision node in the right column + + column = node[0] + color = node[1] + + radius = this.box_size / 8; + x = this.cell[0] + this.box_size * column + this.box_size / 2; + y = this.bg[1] - this.bg_height / 2; + var add = this.vertex(x, y, color, parity, cur); + backgrounds += add[0]; + nodedata += add[1]; + + if (fold) this.columns -= 1; + + } + + document.getElementById('nodebgs').innerHTML += backgrounds; + document.getElementById('graphnodes').innerHTML += nodedata; + + } + +} + + +process_dates = (function(document, RegExp, Math, isNaN, Date, _false, _true){ + + // derived from code from mercurial/templatefilter.py + + var scales = { + 'year': 365 * 24 * 60 * 60, + 'month': 30 * 24 * 60 * 60, + 'week': 7 * 24 * 60 * 60, + 'day': 24 * 60 * 60, + 'hour': 60 * 60, + 'minute': 60, + 'second': 1 + }; + + function format(count, string){ + var ret = count + ' ' + string; + if (count > 1){ + ret = ret + 's'; + } + return ret; + } + + function age(datestr){ + var now = new Date(); + var once = new Date(datestr); + + if (isNaN(once.getTime())){ + // parsing error + return datestr; + } + + var delta = Math.floor((now.getTime() - once.getTime()) / 1000); + + var future = _false; + if (delta < 0){ + future = _true; + delta = -delta; + if (delta > (30 * scales.year)){ + return "in the distant future"; + } + } + + if (delta > (2 * scales.year)){ + return once.getFullYear() + '-' + once.getMonth() + '-' + once.getDate(); + } + + for (unit in scales){ + var s = scales[unit]; + var n = Math.floor(delta / s); + if ((n >= 2) || (s == 1)){ + if (future){ + return format(n, unit) + ' from now'; + } else { + return format(n, unit) + ' ago'; + } + } + } + } + + return function(){ + var nodes = document.getElementsByTagName('*'); + var ageclass = new RegExp('\\bage\\b'); + var dateclass = new RegExp('\\bdate\\b'); + for (var i=0; i<nodes.length; ++i){ + var node = nodes[i]; + var classes = node.className; + if (ageclass.test(classes)){ + var agevalue = age(node.textContent); + if (dateclass.test(classes)){ + // We want both: date + (age) + node.textContent += ' ('+agevalue+')'; + } else { + node.textContent = agevalue; + } + } + } + } +})(document, RegExp, Math, isNaN, Date, false, true)
--- a/mercurial/templates/static/style-coal.css Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/static/style-coal.css Wed May 18 09:56:27 2011 +0200 @@ -173,6 +173,7 @@ .bigtable .age { width: 6em; } .bigtable .author { width: 12em; } .bigtable .description { } +.bigtable .description .base { font-size: 70%; float: right; line-height: 1.66; } .bigtable .node { width: 5em; font-family: monospace;} .bigtable .lineno { width: 2em; text-align: right;} .bigtable .lineno a { color: #999; font-size: smaller; font-family: monospace;}
--- a/mercurial/templates/static/style-paper.css Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/static/style-paper.css Wed May 18 09:56:27 2011 +0200 @@ -164,6 +164,7 @@ .bigtable .age { width: 7em; } .bigtable .author { width: 12em; } .bigtable .description { } +.bigtable .description .base { font-size: 70%; float: right; line-height: 1.66; } .bigtable .node { width: 5em; font-family: monospace;} .bigtable .permissions { width: 8em; text-align: left;} .bigtable .size { width: 5em; text-align: right; } @@ -209,6 +210,8 @@ border-left: 2px solid #999; margin: 1em 0 1em 0; padding: .3em; + white-space: pre; + font-family: monospace; } /* Graph */
--- a/mercurial/templates/template-vars.txt Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/templates/template-vars.txt Wed May 18 09:56:27 2011 +0200 @@ -29,6 +29,7 @@ entries the entries relevant to the page url base url of hgweb interface +logourl base url of logo staticurl base url for static resources
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/treediscovery.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,151 @@ +# discovery.py - protocol changeset discovery functions +# +# Copyright 2010 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +from node import nullid, short +from i18n import _ +import util, error + +def findcommonincoming(repo, remote, heads=None, force=False): + """Return a tuple (common, fetch, heads) used to identify the common + subset of nodes between repo and remote. + + "common" is a list of (at least) the heads of the common subset. + "fetch" is a list of roots of the nodes that would be incoming, to be + supplied to changegroupsubset. + "heads" is either the supplied heads, or else the remote's heads. + """ + + m = repo.changelog.nodemap + search = [] + fetch = set() + seen = set() + seenbranch = set() + base = set() + + if not heads: + heads = remote.heads() + + if repo.changelog.tip() == nullid: + base.add(nullid) + if heads != [nullid]: + return [nullid], [nullid], list(heads) + return [nullid], [], heads + + # assume we're closer to the tip than the root + # and start by examining the heads + repo.ui.status(_("searching for changes\n")) + + unknown = [] + for h in heads: + if h not in m: + unknown.append(h) + else: + base.add(h) + + if not unknown: + return list(base), [], list(heads) + + heads = unknown + req = set(unknown) + reqcnt = 0 + + # search through remote branches + # a 'branch' here is a linear segment of history, with four parts: + # head, root, first parent, second parent + # (a branch always has two parents (or none) by definition) + unknown = remote.branches(unknown) + while unknown: + r = [] + while unknown: + n = unknown.pop(0) + if n[0] in seen: + continue + + repo.ui.debug("examining %s:%s\n" + % (short(n[0]), short(n[1]))) + if n[0] == nullid: # found the end of the branch + pass + elif n in seenbranch: + repo.ui.debug("branch already found\n") + continue + elif n[1] and n[1] in m: # do we know the base? + repo.ui.debug("found incomplete branch %s:%s\n" + % (short(n[0]), short(n[1]))) + search.append(n[0:2]) # schedule branch range for scanning + seenbranch.add(n) + else: + if n[1] not in seen and n[1] not in fetch: + if n[2] in m and n[3] in m: + repo.ui.debug("found new changeset %s\n" % + short(n[1])) + fetch.add(n[1]) # earliest unknown + for p in n[2:4]: + if p in m: + base.add(p) # latest known + + for p in n[2:4]: + if p not in req and p not in m: + r.append(p) + req.add(p) + seen.add(n[0]) + + if r: + reqcnt += 1 + repo.ui.progress(_('searching'), reqcnt, unit=_('queries')) + repo.ui.debug("request %d: %s\n" % + (reqcnt, " ".join(map(short, r)))) + for p in xrange(0, len(r), 10): + for b in remote.branches(r[p:p + 10]): + repo.ui.debug("received %s:%s\n" % + (short(b[0]), short(b[1]))) + unknown.append(b) + + # do binary search on the branches we found + while search: + newsearch = [] + reqcnt += 1 + repo.ui.progress(_('searching'), reqcnt, unit=_('queries')) + for n, l in zip(search, remote.between(search)): + l.append(n[1]) + p = n[0] + f = 1 + for i in l: + repo.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i))) + if i in m: + if f <= 2: + repo.ui.debug("found new branch changeset %s\n" % + short(p)) + fetch.add(p) + base.add(i) + else: + repo.ui.debug("narrowed branch search to %s:%s\n" + % (short(p), short(i))) + newsearch.append((p, i)) + break + p, f = i, f * 2 + search = newsearch + + # sanity check our fetch list + for f in fetch: + if f in m: + raise error.RepoError(_("already have changeset ") + + short(f[:4])) + + base = list(base) + if base == [nullid]: + if force: + repo.ui.warn(_("warning: repository is unrelated\n")) + else: + raise util.Abort(_("repository is unrelated")) + + repo.ui.debug("found new changesets starting at " + + " ".join([short(f) for f in fetch]) + "\n") + + repo.ui.progress(_('searching'), None) + repo.ui.debug("%d total queries\n" % reqcnt) + + return base, list(fetch), heads
--- a/mercurial/ui.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/ui.py Wed May 18 09:56:27 2011 +0200 @@ -7,7 +7,7 @@ from i18n import _ import errno, getpass, os, socket, sys, tempfile, traceback -import config, util, error +import config, scmutil, util, error class ui(object): def __init__(self, src=None): @@ -32,7 +32,7 @@ # shared read-only environment self.environ = os.environ # we always trust global config files - for f in util.rcpath(): + for f in scmutil.rcpath(): self.readconfig(f, trust=True) def copy(self): @@ -111,7 +111,7 @@ % (n, p, self.configsource('paths', n))) p = p.replace('%%', '%') p = util.expandpath(p) - if '://' not in p and not os.path.isabs(p): + if not util.hasscheme(p) and not os.path.isabs(p): p = os.path.normpath(os.path.join(root, p)) c.set("paths", n, p) @@ -164,6 +164,26 @@ return v def configbool(self, section, name, default=False, untrusted=False): + """parse a configuration element as a boolean + + >>> u = ui(); s = 'foo' + >>> u.setconfig(s, 'true', 'yes') + >>> u.configbool(s, 'true') + True + >>> u.setconfig(s, 'false', 'no') + >>> u.configbool(s, 'false') + False + >>> u.configbool(s, 'unknown') + False + >>> u.configbool(s, 'unknown', True) + True + >>> u.setconfig(s, 'invalid', 'somevalue') + >>> u.configbool(s, 'invalid') + Traceback (most recent call last): + ... + ConfigError: foo.invalid is not a boolean ('somevalue') + """ + v = self.config(section, name, None, untrusted) if v is None: return default @@ -171,12 +191,47 @@ return v b = util.parsebool(v) if b is None: - raise error.ConfigError(_("%s.%s not a boolean ('%s')") + raise error.ConfigError(_("%s.%s is not a boolean ('%s')") % (section, name, v)) return b + def configint(self, section, name, default=None, untrusted=False): + """parse a configuration element as an integer + + >>> u = ui(); s = 'foo' + >>> u.setconfig(s, 'int1', '42') + >>> u.configint(s, 'int1') + 42 + >>> u.setconfig(s, 'int2', '-42') + >>> u.configint(s, 'int2') + -42 + >>> u.configint(s, 'unknown', 7) + 7 + >>> u.setconfig(s, 'invalid', 'somevalue') + >>> u.configint(s, 'invalid') + Traceback (most recent call last): + ... + ConfigError: foo.invalid is not an integer ('somevalue') + """ + + v = self.config(section, name, None, untrusted) + if v is None: + return default + try: + return int(v) + except ValueError: + raise error.ConfigError(_("%s.%s is not an integer ('%s')") + % (section, name, v)) + def configlist(self, section, name, default=None, untrusted=False): - """Return a list of comma/space separated strings""" + """parse a configuration element as a list of comma/space separated + strings + + >>> u = ui(); s = 'foo' + >>> u.setconfig(s, 'list1', 'this,is "a small" ,test') + >>> u.configlist(s, 'list1') + ['this', 'is', 'a small', 'test'] + """ def _parse_plain(parts, s, offset): whitespace = False @@ -273,20 +328,27 @@ cfg = self._data(untrusted) for section in cfg.sections(): for name, value in self.configitems(section, untrusted): - yield section, name, str(value).replace('\n', '\\n') + yield section, name, value def plain(self): '''is plain mode active? - Plain mode means that all configuration variables which affect the - behavior and output of Mercurial should be ignored. Additionally, the - output should be stable, reproducible and suitable for use in scripts or - applications. + Plain mode means that all configuration variables which affect + the behavior and output of Mercurial should be + ignored. Additionally, the output should be stable, + reproducible and suitable for use in scripts or applications. + + The only way to trigger plain mode is by setting either the + `HGPLAIN' or `HGPLAINEXCEPT' environment variables. - The only way to trigger plain mode is by setting the `HGPLAIN' - environment variable. + The return value can either be False, True, or a list of + features that plain mode should not apply to (e.g., i18n, + progress, etc). ''' - return 'HGPLAIN' in os.environ + if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ: + return False + exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',') + return exceptions or True def username(self): """Return default username to be used in commits. @@ -325,7 +387,7 @@ def expandpath(self, loc, default=None): """Return repository location relative to cwd or from [paths]""" - if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')): + if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')): return loc path = self.config('paths', loc) @@ -483,7 +545,7 @@ self.write(msg, ' ', default, "\n") return default try: - r = self._readline(msg + ' ') + r = self._readline(self.label(msg, 'ui.prompt') + ' ') if not r: return default return r
--- a/mercurial/url.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/url.py Wed May 18 09:56:27 2011 +0200 @@ -7,142 +7,10 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO -import __builtin__ +import urllib, urllib2, httplib, os, socket, cStringIO from i18n import _ -import keepalive, util - -def _urlunparse(scheme, netloc, path, params, query, fragment, url): - '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"''' - result = urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) - if (scheme and - result.startswith(scheme + ':') and - not result.startswith(scheme + '://') and - url.startswith(scheme + '://') - ): - result = scheme + '://' + result[len(scheme + ':'):] - return result - -def hidepassword(url): - '''hide user credential in a url string''' - if url.startswith("ssh://"): - # urllib doesn't know about ssh urls - return re.sub(r'(ssh://[^/]+):[^/]+(@.*)', r'\1:***\2', url) - scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) - netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc) - return _urlunparse(scheme, netloc, path, params, query, fragment, url) - -def removeauth(url): - '''remove all authentication information from a url string''' - scheme, netloc, path, params, query, fragment = urlparse.urlparse(url) - netloc = netloc[netloc.find('@')+1:] - return _urlunparse(scheme, netloc, path, params, query, fragment, url) - -def netlocsplit(netloc): - '''split [user[:passwd]@]host[:port] into 4-tuple.''' - - a = netloc.find('@') - if a == -1: - user, passwd = None, None - else: - userpass, netloc = netloc[:a], netloc[a + 1:] - c = userpass.find(':') - if c == -1: - user, passwd = urllib.unquote(userpass), None - else: - user = urllib.unquote(userpass[:c]) - passwd = urllib.unquote(userpass[c + 1:]) - c = netloc.find(':') - if c == -1: - host, port = netloc, None - else: - host, port = netloc[:c], netloc[c + 1:] - return host, port, user, passwd - -def netlocunsplit(host, port, user=None, passwd=None): - '''turn host, port, user, passwd into [user[:passwd]@]host[:port].''' - if port: - hostport = host + ':' + port - else: - hostport = host - if user: - quote = lambda s: urllib.quote(s, safe='') - if passwd: - userpass = quote(user) + ':' + quote(passwd) - else: - userpass = quote(user) - return userpass + '@' + hostport - return hostport - -def readauthforuri(ui, uri): - # Read configuration - config = dict() - for key, val in ui.configitems('auth'): - if '.' not in key: - ui.warn(_("ignoring invalid [auth] key '%s'\n") % key) - continue - group, setting = key.rsplit('.', 1) - gdict = config.setdefault(group, dict()) - if setting in ('username', 'cert', 'key'): - val = util.expandpath(val) - gdict[setting] = val - - # Find the best match - scheme, hostpath = uri.split('://', 1) - bestlen = 0 - bestauth = None - for group, auth in config.iteritems(): - prefix = auth.get('prefix') - if not prefix: - continue - p = prefix.split('://', 1) - if len(p) > 1: - schemes, prefix = [p[0]], p[1] - else: - schemes = (auth.get('schemes') or 'https').split() - if (prefix == '*' or hostpath.startswith(prefix)) and \ - len(prefix) > bestlen and scheme in schemes: - bestlen = len(prefix) - bestauth = group, auth - return bestauth - -_safe = ('abcdefghijklmnopqrstuvwxyz' - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' - '0123456789' '_.-/') -_safeset = None -_hex = None -def quotepath(path): - '''quote the path part of a URL - - This is similar to urllib.quote, but it also tries to avoid - quoting things twice (inspired by wget): - - >>> quotepath('abc def') - 'abc%20def' - >>> quotepath('abc%20def') - 'abc%20def' - >>> quotepath('abc%20 def') - 'abc%20%20def' - >>> quotepath('abc def%20') - 'abc%20def%20' - >>> quotepath('abc def%2') - 'abc%20def%252' - >>> quotepath('abc def%') - 'abc%20def%25' - ''' - global _safeset, _hex - if _safeset is None: - _safeset = set(_safe) - _hex = set('abcdefABCDEF0123456789') - l = list(path) - for i in xrange(len(l)): - c = l[i] - if (c == '%' and i + 2 < len(l) and - l[i + 1] in _hex and l[i + 2] in _hex): - pass - elif c not in _safeset: - l[i] = '%%%02X' % ord(c) - return ''.join(l) +import keepalive, util, sslutil +import httpconnection as httpconnectionmod class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm): def __init__(self, ui): @@ -158,7 +26,7 @@ return (user, passwd) if not user: - res = readauthforuri(self.ui, authuri) + res = httpconnectionmod.readauthforuri(self.ui, authuri) if res: group, auth = res user, passwd = auth.get('username'), auth.get('password') @@ -195,14 +63,10 @@ if not (proxyurl.startswith('http:') or proxyurl.startswith('https:')): proxyurl = 'http://' + proxyurl + '/' - snpqf = urlparse.urlsplit(proxyurl) - proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf - hpup = netlocsplit(proxynetloc) - - proxyhost, proxyport, proxyuser, proxypasswd = hpup - if not proxyuser: - proxyuser = ui.config("http_proxy", "user") - proxypasswd = ui.config("http_proxy", "passwd") + proxy = util.url(proxyurl) + if not proxy.user: + proxy.user = ui.config("http_proxy", "user") + proxy.passwd = ui.config("http_proxy", "passwd") # see if we should use a proxy for this url no_list = ["localhost", "127.0.0.1"] @@ -217,13 +81,10 @@ else: self.no_list = no_list - proxyurl = urlparse.urlunsplit(( - proxyscheme, netlocunsplit(proxyhost, proxyport, - proxyuser, proxypasswd or ''), - proxypath, proxyquery, proxyfrag)) + proxyurl = str(proxy) proxies = {'http': proxyurl, 'https': proxyurl} ui.debug('proxying through http://%s:%s\n' % - (proxyhost, proxyport)) + (proxy.host, proxy.port)) else: proxies = {} @@ -256,48 +117,10 @@ return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_) -class httpsendfile(object): - """This is a wrapper around the objects returned by python's "open". - - Its purpose is to send file-like objects via HTTP and, to do so, it - defines a __len__ attribute to feed the Content-Length header. - """ - - def __init__(self, ui, *args, **kwargs): - # We can't just "self._data = open(*args, **kwargs)" here because there - # is an "open" function defined in this module that shadows the global - # one - self.ui = ui - self._data = __builtin__.open(*args, **kwargs) - self.seek = self._data.seek - self.close = self._data.close - self.write = self._data.write - self._len = os.fstat(self._data.fileno()).st_size - self._pos = 0 - self._total = len(self) / 1024 * 2 - - def read(self, *args, **kwargs): - try: - ret = self._data.read(*args, **kwargs) - except EOFError: - self.ui.progress(_('sending'), None) - self._pos += len(ret) - # We pass double the max for total because we currently have - # to send the bundle twice in the case of a server that - # requires authentication. Since we can't know until we try - # once whether authentication will be required, just lie to - # the user and maybe the push succeeds suddenly at 50%. - self.ui.progress(_('sending'), self._pos / 1024, - unit=_('kb'), total=self._total) - return ret - - def __len__(self): - return self._len - def _gen_sendfile(orgsend): def _sendfile(self, data): # send a file - if isinstance(data, httpsendfile): + if isinstance(data, httpconnectionmod.httpsendfile): # if auth required, some data sent twice, so rewind here data.seek(0) for chunk in util.filechunkiter(data): @@ -309,23 +132,6 @@ has_https = hasattr(urllib2, 'HTTPSHandler') if has_https: try: - # avoid using deprecated/broken FakeSocket in python 2.6 - import ssl - _ssl_wrap_socket = ssl.wrap_socket - CERT_REQUIRED = ssl.CERT_REQUIRED - except ImportError: - CERT_REQUIRED = 2 - - def _ssl_wrap_socket(sock, key_file, cert_file, - cert_reqs=CERT_REQUIRED, ca_certs=None): - if ca_certs: - raise util.Abort(_( - 'certificate checking requires Python 2.6')) - - ssl = socket.ssl(sock, key_file, cert_file) - return httplib.FakeSocket(sock, ssl) - - try: _create_connection = socket.create_connection except AttributeError: _GLOBAL_DEFAULT_TIMEOUT = object() @@ -364,7 +170,7 @@ self.sock.connect((self.host, self.port)) if _generic_proxytunnel(self): # we do not support client x509 certificates - self.sock = _ssl_wrap_socket(self.sock, None, None) + self.sock = sslutil.ssl_wrap_socket(self.sock, None, None) else: keepalive.HTTPConnection.connect(self) @@ -390,13 +196,9 @@ new_tunnel = False if new_tunnel or tunnel_host == req.get_full_url(): # has proxy - urlparts = urlparse.urlparse(tunnel_host) - if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS - realhostport = urlparts[1] - if realhostport[-1] == ']' or ':' not in realhostport: - realhostport += ':443' - - h.realhostport = realhostport + u = util.url(tunnel_host) + if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS + h.realhostport = ':'.join([u.host, (u.port or '443')]) h.headers = req.headers.copy() h.headers.update(handler.parent.addheaders) return @@ -509,41 +311,6 @@ _generic_start_transaction(self, h, req) return keepalive.HTTPHandler._start_transaction(self, h, req) -def _verifycert(cert, hostname): - '''Verify that cert (in socket.getpeercert() format) matches hostname. - CRLs is not handled. - - Returns error message if any problems are found and None on success. - ''' - if not cert: - return _('no certificate received') - dnsname = hostname.lower() - def matchdnsname(certname): - return (certname == dnsname or - '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]) - - san = cert.get('subjectAltName', []) - if san: - certnames = [value.lower() for key, value in san if key == 'DNS'] - for name in certnames: - if matchdnsname(name): - return None - return _('certificate is for %s') % ', '.join(certnames) - - # subject is only checked when subjectAltName is empty - for s in cert.get('subject', []): - key, value = s[0] - if key == 'commonName': - try: - # 'subject' entries are unicode - certname = value.lower().encode('ascii') - except UnicodeEncodeError: - return _('IDN in certificate not supported') - if matchdnsname(certname): - return None - return _('certificate is for %s') % certname - return _('no commonName or subjectAltName found in certificate') - if has_https: class httpsconnection(httplib.HTTPSConnection): response_class = keepalive.HTTPResponse @@ -556,55 +323,12 @@ host = self.host if self.realhostport: # use CONNECT proxy - something = _generic_proxytunnel(self) + _generic_proxytunnel(self) host = self.realhostport.rsplit(':', 1)[0] - - cacerts = self.ui.config('web', 'cacerts') - hostfingerprint = self.ui.config('hostfingerprints', host) - - if cacerts and not hostfingerprint: - cacerts = util.expandpath(cacerts) - if not os.path.exists(cacerts): - raise util.Abort(_('could not find ' - 'web.cacerts: %s') % cacerts) - self.sock = _ssl_wrap_socket(self.sock, self.key_file, - self.cert_file, cert_reqs=CERT_REQUIRED, - ca_certs=cacerts) - msg = _verifycert(self.sock.getpeercert(), host) - if msg: - raise util.Abort(_('%s certificate error: %s ' - '(use --insecure to connect ' - 'insecurely)') % (host, msg)) - self.ui.debug('%s certificate successfully verified\n' % host) - else: - self.sock = _ssl_wrap_socket(self.sock, self.key_file, - self.cert_file) - if hasattr(self.sock, 'getpeercert'): - peercert = self.sock.getpeercert(True) - peerfingerprint = util.sha1(peercert).hexdigest() - nicefingerprint = ":".join([peerfingerprint[x:x + 2] - for x in xrange(0, len(peerfingerprint), 2)]) - if hostfingerprint: - if peerfingerprint.lower() != \ - hostfingerprint.replace(':', '').lower(): - raise util.Abort(_('invalid certificate for %s ' - 'with fingerprint %s') % - (host, nicefingerprint)) - self.ui.debug('%s certificate matched fingerprint %s\n' % - (host, nicefingerprint)) - else: - self.ui.warn(_('warning: %s certificate ' - 'with fingerprint %s not verified ' - '(check hostfingerprints or web.cacerts ' - 'config setting)\n') % - (host, nicefingerprint)) - else: # python 2.5 ? - if hostfingerprint: - raise util.Abort(_('no certificate for %s with ' - 'configured hostfingerprint') % host) - self.ui.warn(_('warning: %s certificate not verified ' - '(check web.cacerts config setting)\n') % - host) + self.sock = sslutil.ssl_wrap_socket( + self.sock, self.key_file, self.cert_file, + **sslutil.sslkwargs(self.ui, host)) + sslutil.validator(self.ui, host)(self.sock) class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler): def __init__(self, ui): @@ -618,7 +342,7 @@ return keepalive.KeepAliveHandler._start_transaction(self, h, req) def https_open(self, req): - res = readauthforuri(self.ui, req.get_full_url()) + res = httpconnectionmod.readauthforuri(self.ui, req.get_full_url()) if res: group, auth = res self.auth = auth @@ -694,31 +418,6 @@ return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed( self, auth_header, host, req, headers) -def getauthinfo(path): - scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path) - if not urlpath: - urlpath = '/' - if scheme != 'file': - # XXX: why are we quoting the path again with some smart - # heuristic here? Anyway, it cannot be done with file:// - # urls since path encoding is os/fs dependent (see - # urllib.pathname2url() for details). - urlpath = quotepath(urlpath) - host, port, user, passwd = netlocsplit(netloc) - - # urllib cannot handle URLs with embedded user or passwd - url = urlparse.urlunsplit((scheme, netlocunsplit(host, port), - urlpath, query, frag)) - if user: - netloc = host - if port: - netloc += ':' + port - # Python < 2.4.3 uses only the netloc to search for a password - authinfo = (None, (url, netloc), user, passwd or '') - else: - authinfo = None - return url, authinfo - handlerfuncs = [] def opener(ui, authinfo=None): @@ -726,9 +425,12 @@ construct an opener suitable for urllib2 authinfo will be added to the password manager ''' - handlers = [httphandler()] - if has_https: - handlers.append(httpshandler(ui)) + if ui.configbool('ui', 'usehttp2', False): + handlers = [httpconnectionmod.http2handler(ui, passwordmgr(ui))] + else: + handlers = [httphandler()] + if has_https: + handlers.append(httpshandler(ui)) handlers.append(proxyhandler(ui)) @@ -749,17 +451,13 @@ opener.addheaders.append(('Accept', 'application/mercurial-0.1')) return opener -scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://') - -def open(ui, url, data=None): - scheme = None - m = scheme_re.search(url) - if m: - scheme = m.group(1).lower() - if not scheme: - path = util.normpath(os.path.abspath(url)) - url = 'file://' + urllib.pathname2url(path) +def open(ui, url_, data=None): + u = util.url(url_) + if u.scheme: + u.scheme = u.scheme.lower() + url_, authinfo = u.authinfo() + else: + path = util.normpath(os.path.abspath(url_)) + url_ = 'file://' + urllib.pathname2url(path) authinfo = None - else: - url, authinfo = getauthinfo(url) - return opener(ui, authinfo).open(url, data) + return opener(ui, authinfo).open(url_, data)
--- a/mercurial/util.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/util.py Wed May 18 09:56:27 2011 +0200 @@ -16,8 +16,8 @@ from i18n import _ import error, osutil, encoding import errno, re, shutil, sys, tempfile, traceback -import os, stat, time, calendar, textwrap, unicodedata, signal -import imp, socket +import os, time, calendar, textwrap, unicodedata, signal +import imp, socket, urllib # Python compatibility @@ -197,7 +197,7 @@ code = 0 if code: raise Abort(_("command '%s' failed: %s") % - (cmd, explain_exit(code))) + (cmd, explainexit(code))) fp = open(outname, 'rb') r = fp.read() fp.close() @@ -206,12 +206,12 @@ try: if inname: os.unlink(inname) - except: + except OSError: pass try: if outname: os.unlink(outname) - except: + except OSError: pass filtertable = { @@ -295,57 +295,9 @@ b.reverse() return os.sep.join((['..'] * len(a)) + b) or '.' -def canonpath(root, cwd, myname, auditor=None): - """return the canonical path of myname, given cwd and root""" - if endswithsep(root): - rootsep = root - else: - rootsep = root + os.sep - name = myname - if not os.path.isabs(name): - name = os.path.join(root, cwd, name) - name = os.path.normpath(name) - if auditor is None: - auditor = path_auditor(root) - if name != rootsep and name.startswith(rootsep): - name = name[len(rootsep):] - auditor(name) - return pconvert(name) - elif name == root: - return '' - else: - # Determine whether `name' is in the hierarchy at or beneath `root', - # by iterating name=dirname(name) until that causes no change (can't - # check name == '/', because that doesn't work on windows). For each - # `name', compare dev/inode numbers. If they match, the list `rel' - # holds the reversed list of components making up the relative file - # name we want. - root_st = os.stat(root) - rel = [] - while True: - try: - name_st = os.stat(name) - except OSError: - break - if samestat(name_st, root_st): - if not rel: - # name was actually the same as root (maybe a symlink) - return '' - rel.reverse() - name = os.path.join(*rel) - auditor(name) - return pconvert(name) - dirname, basename = os.path.split(name) - rel.append(basename) - if dirname == name: - break - name = dirname - - raise Abort('%s not under root' % myname) - _hgexecutable = None -def main_is_frozen(): +def mainfrozen(): """return True if we are a frozen executable. The code supports py2exe (most common, Windows only) and tools/freeze @@ -363,15 +315,15 @@ if _hgexecutable is None: hg = os.environ.get('HG') if hg: - set_hgexecutable(hg) - elif main_is_frozen(): - set_hgexecutable(sys.executable) + _sethgexecutable(hg) + elif mainfrozen(): + _sethgexecutable(sys.executable) else: - exe = find_exe('hg') or os.path.basename(sys.argv[0]) - set_hgexecutable(exe) + exe = findexe('hg') or os.path.basename(sys.argv[0]) + _sethgexecutable(exe) return _hgexecutable -def set_hgexecutable(path): +def _sethgexecutable(path): """set location of the 'hg' executable""" global _hgexecutable _hgexecutable = path @@ -417,7 +369,7 @@ rc = 0 if rc and onerr: errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]), - explain_exit(rc)[0]) + explainexit(rc)[0]) if errprefix: errmsg = '%s: %s' % (errprefix, errmsg) try: @@ -438,6 +390,9 @@ return check +def makedir(path, notindexed): + os.mkdir(path) + def unlinkpath(f): """unlink and remove the directory if it is empty""" os.unlink(f) @@ -452,7 +407,7 @@ if os.path.islink(src): try: os.unlink(dest) - except: + except OSError: pass os.symlink(os.readlink(src), dest) else: @@ -480,7 +435,7 @@ else: if hardlink: try: - os_link(src, dst) + oslink(src, dst) except (IOError, OSError): hardlink = False shutil.copy(src, dst) @@ -490,78 +445,49 @@ return hardlink, num -class path_auditor(object): - '''ensure that a filesystem path contains no banned components. - the following properties of a path are checked: - - - ends with a directory separator - - under top-level .hg - - starts at the root of a windows drive - - contains ".." - - traverses a symlink (e.g. a/symlink_here/b) - - inside a nested repository (a callback can be used to approve - some nested repositories, e.g., subrepositories) - ''' - - def __init__(self, root, callback=None): - self.audited = set() - self.auditeddir = set() - self.root = root - self.callback = callback +_winreservednames = '''con prn aux nul + com1 com2 com3 com4 com5 com6 com7 com8 com9 + lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split() +_winreservedchars = ':*?"<>|' +def checkwinfilename(path): + '''Check that the base-relative path is a valid filename on Windows. + Returns None if the path is ok, or a UI string describing the problem. - def __call__(self, path): - if path in self.audited: - return - # AIX ignores "/" at end of path, others raise EISDIR. - if endswithsep(path): - raise Abort(_("path ends in directory separator: %s") % path) - normpath = os.path.normcase(path) - parts = splitpath(normpath) - if (os.path.splitdrive(path)[0] - or parts[0].lower() in ('.hg', '.hg.', '') - or os.pardir in parts): - raise Abort(_("path contains illegal component: %s") % path) - if '.hg' in path.lower(): - lparts = [p.lower() for p in parts] - for p in '.hg', '.hg.': - if p in lparts[1:]: - pos = lparts.index(p) - base = os.path.join(*parts[:pos]) - raise Abort(_('path %r is inside repo %r') % (path, base)) - def check(prefix): - curpath = os.path.join(self.root, prefix) - try: - st = os.lstat(curpath) - except OSError, err: - # EINVAL can be raised as invalid path syntax under win32. - # They must be ignored for patterns can be checked too. - if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL): - raise - else: - if stat.S_ISLNK(st.st_mode): - raise Abort(_('path %r traverses symbolic link %r') % - (path, prefix)) - elif (stat.S_ISDIR(st.st_mode) and - os.path.isdir(os.path.join(curpath, '.hg'))): - if not self.callback or not self.callback(curpath): - raise Abort(_('path %r is inside repo %r') % - (path, prefix)) - parts.pop() - prefixes = [] - while parts: - prefix = os.sep.join(parts) - if prefix in self.auditeddir: - break - check(prefix) - prefixes.append(prefix) - parts.pop() + >>> checkwinfilename("just/a/normal/path") + >>> checkwinfilename("foo/bar/con.xml") + "filename contains 'con', which is reserved on Windows" + >>> checkwinfilename("foo/con.xml/bar") + "filename contains 'con', which is reserved on Windows" + >>> checkwinfilename("foo/bar/xml.con") + >>> checkwinfilename("foo/bar/AUX/bla.txt") + "filename contains 'AUX', which is reserved on Windows" + >>> checkwinfilename("foo/bar/bla:.txt") + "filename contains ':', which is reserved on Windows" + >>> checkwinfilename("foo/bar/b\07la.txt") + "filename contains '\\\\x07', which is invalid on Windows" + >>> checkwinfilename("foo/bar/bla ") + "filename ends with ' ', which is not allowed on Windows" + ''' + for n in path.replace('\\', '/').split('/'): + if not n: + continue + for c in n: + if c in _winreservedchars: + return _("filename contains '%s', which is reserved " + "on Windows") % c + if ord(c) <= 31: + return _("filename contains %r, which is invalid " + "on Windows") % c + base = n.split('.')[0] + if base and base.lower() in _winreservednames: + return _("filename contains '%s', which is reserved " + "on Windows") % base + t = n[-1] + if t in '. ': + return _("filename ends with '%s', which is not allowed " + "on Windows") % t - self.audited.add(path) - # only add prefixes to the cache after checking everything: we don't - # want to add "foo/bar/baz" before checking if there's a "foo/.hg" - self.auditeddir.update(prefixes) - -def lookup_reg(key, name=None, scope=None): +def lookupreg(key, name=None, scope=None): return None def hidewindow(): @@ -573,6 +499,7 @@ pass if os.name == 'nt': + checkosfilename = checkwinfilename from windows import * else: from posix import * @@ -629,7 +556,7 @@ if s2 == s1: return False return True - except: + except OSError: return True _fspathcache = {} @@ -680,45 +607,6 @@ return ''.join(result) -def checkexec(path): - """ - Check whether the given path is on a filesystem with UNIX-like exec flags - - Requires a directory (like /foo/.hg) - """ - - # VFAT on some Linux versions can flip mode but it doesn't persist - # a FS remount. Frequently we can detect it if files are created - # with exec bit on. - - try: - EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH - fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-') - try: - os.close(fh) - m = os.stat(fn).st_mode & 0777 - new_file_has_exec = m & EXECFLAGS - os.chmod(fn, m ^ EXECFLAGS) - exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m) - finally: - os.unlink(fn) - except (IOError, OSError): - # we don't care, the user probably won't be able to commit anyway - return False - return not (new_file_has_exec or exec_flags_cannot_flip) - -def checklink(path): - """check whether the given path is on a symlink-capable filesystem""" - # mktemp is not racy because symlink creation will fail if the - # file already exists - name = tempfile.mktemp(dir=path, prefix='hg-checklink-') - try: - os.symlink(".", name) - os.unlink(name) - return True - except (OSError, AttributeError): - return False - def checknlink(testfile): '''check whether hardlink count reporting works properly''' @@ -736,7 +624,7 @@ fd = None try: try: - os_link(f1, f2) + oslink(f1, f2) except OSError: return False @@ -769,7 +657,18 @@ def gui(): '''Are we running in a GUI?''' - return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY") + if sys.platform == 'darwin': + if 'SSH_CONNECTION' in os.environ: + # handle SSH access to a box where the user is logged in + return False + elif getattr(osutil, 'isgui', None): + # check if a CoreGraphics session is available + return osutil.isgui() + else: + # pure build; use a safe default + return True + else: + return os.name == "nt" or os.environ.get("DISPLAY") def mktempcopy(name, emptyok=False, createmode=None): """Create a temporary file with the same contents from name @@ -820,38 +719,41 @@ return temp class atomictempfile(object): - """file-like object that atomically updates a file + '''writeable file object that atomically updates a file - All writes will be redirected to a temporary copy of the original - file. When rename is called, the copy is renamed to the original - name, making the changes visible. - """ + All writes will go to a temporary copy of the original file. Call + rename() when you are done writing, and atomictempfile will rename + the temporary copy to the original name, making the changes visible. + + Unlike other file-like objects, close() discards your writes by + simply deleting the temporary file. + ''' def __init__(self, name, mode='w+b', createmode=None): - self.__name = name - self._fp = None - self.temp = mktempcopy(name, emptyok=('w' in mode), - createmode=createmode) - self._fp = posixfile(self.temp, mode) + self.__name = name # permanent name + self._tempname = mktempcopy(name, emptyok=('w' in mode), + createmode=createmode) + self._fp = posixfile(self._tempname, mode) - def __getattr__(self, name): - return getattr(self._fp, name) + # delegated methods + self.write = self._fp.write + self.fileno = self._fp.fileno def rename(self): if not self._fp.closed: self._fp.close() - rename(self.temp, localpath(self.__name)) + rename(self._tempname, localpath(self.__name)) def close(self): - if not self._fp: - return if not self._fp.closed: try: - os.unlink(self.temp) - except: pass + os.unlink(self._tempname) + except OSError: + pass self._fp.close() def __del__(self): - self.close() + if hasattr(self, '_fp'): # constructor actually did something + self.close() def makedirs(name, mode=None): """recursive directory creation with parent mode inheritance""" @@ -869,97 +771,26 @@ makedirs(parent, mode) makedirs(name, mode) -class opener(object): - """Open files relative to a base directory - - This class is used to hide the details of COW semantics and - remote file access from higher level code. - """ - def __init__(self, base, audit=True): - self.base = base - if audit: - self.auditor = path_auditor(base) - else: - self.auditor = always - self.createmode = None - self._trustnlink = None - - @propertycache - def _can_symlink(self): - return checklink(self.base) - - def _fixfilemode(self, name): - if self.createmode is None: - return - os.chmod(name, self.createmode & 0666) - - def __call__(self, path, mode="r", text=False, atomictemp=False): - self.auditor(path) - f = os.path.join(self.base, path) - - if not text and "b" not in mode: - mode += "b" # for that other OS +def readfile(path): + fp = open(path, 'rb') + try: + return fp.read() + finally: + fp.close() - nlink = -1 - dirname, basename = os.path.split(f) - # If basename is empty, then the path is malformed because it points - # to a directory. Let the posixfile() call below raise IOError. - if basename and mode not in ('r', 'rb'): - if atomictemp: - if not os.path.isdir(dirname): - makedirs(dirname, self.createmode) - return atomictempfile(f, mode, self.createmode) - try: - if 'w' in mode: - unlink(f) - nlink = 0 - else: - # nlinks() may behave differently for files on Windows - # shares if the file is open. - fd = posixfile(f) - nlink = nlinks(f) - if nlink < 1: - nlink = 2 # force mktempcopy (issue1922) - fd.close() - except (OSError, IOError), e: - if e.errno != errno.ENOENT: - raise - nlink = 0 - if not os.path.isdir(dirname): - makedirs(dirname, self.createmode) - if nlink > 0: - if self._trustnlink is None: - self._trustnlink = nlink > 1 or checknlink(f) - if nlink > 1 or not self._trustnlink: - rename(mktempcopy(f), f) - fp = posixfile(f, mode) - if nlink == 0: - self._fixfilemode(f) - return fp +def writefile(path, text): + fp = open(path, 'wb') + try: + fp.write(text) + finally: + fp.close() - def symlink(self, src, dst): - self.auditor(dst) - linkname = os.path.join(self.base, dst) - try: - os.unlink(linkname) - except OSError: - pass - - dirname = os.path.dirname(linkname) - if not os.path.exists(dirname): - makedirs(dirname, self.createmode) - - if self._can_symlink: - try: - os.symlink(src, linkname) - except OSError, err: - raise OSError(err.errno, _('could not symlink to %r: %s') % - (src, err.strerror), linkname) - else: - f = self(dst, "w") - f.write(src) - f.close() - self._fixfilemode(dst) +def appendfile(path, text): + fp = open(path, 'ab') + try: + fp.write(text) + finally: + fp.close() class chunkbuffer(object): """Allow arbitrary sized chunks of data to be efficiently read from an @@ -1123,7 +954,6 @@ # fill out defaults now = makedate() defaults = {} - nowmap = {} for part in ("d", "mb", "yY", "HI", "M", "S"): # this piece is for rounding the specific end of unknowns b = bias.get(part) @@ -1204,10 +1034,17 @@ return parsedate(date, extendeddateformats, d)[0] date = date.strip() - if date[0] == "<": + + if not date: + raise Abort(_("dates cannot consist entirely of whitespace")) + elif date[0] == "<": + if not date[1:]: + raise Abort(_("invalid day spec, use '<DATE'")) when = upper(date[1:]) return lambda x: x <= when elif date[0] == ">": + if not date[1:]: + raise Abort(_("invalid day spec, use '>DATE'")) when = lower(date[1:]) return lambda x: x >= when elif date[0] == "-": @@ -1215,6 +1052,9 @@ days = int(date[1:]) except ValueError: raise Abort(_("invalid day spec: %s") % date[1:]) + if days < 0: + raise Abort(_("%s must be nonnegative (see 'hg help dates')") + % date[1:]) when = makedate()[0] - days * 3600 * 24 return lambda x: x >= when elif " to " in date: @@ -1266,86 +1106,6 @@ except (UnicodeDecodeError, UnicodeEncodeError): return _ellipsis(text, maxlength)[0] -def walkrepos(path, followsym=False, seen_dirs=None, recurse=False): - '''yield every hg repository under path, recursively.''' - def errhandler(err): - if err.filename == path: - raise err - if followsym and hasattr(os.path, 'samestat'): - def _add_dir_if_not_there(dirlst, dirname): - match = False - samestat = os.path.samestat - dirstat = os.stat(dirname) - for lstdirstat in dirlst: - if samestat(dirstat, lstdirstat): - match = True - break - if not match: - dirlst.append(dirstat) - return not match - else: - followsym = False - - if (seen_dirs is None) and followsym: - seen_dirs = [] - _add_dir_if_not_there(seen_dirs, path) - for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler): - dirs.sort() - if '.hg' in dirs: - yield root # found a repository - qroot = os.path.join(root, '.hg', 'patches') - if os.path.isdir(os.path.join(qroot, '.hg')): - yield qroot # we have a patch queue repo here - if recurse: - # avoid recursing inside the .hg directory - dirs.remove('.hg') - else: - dirs[:] = [] # don't descend further - elif followsym: - newdirs = [] - for d in dirs: - fname = os.path.join(root, d) - if _add_dir_if_not_there(seen_dirs, fname): - if os.path.islink(fname): - for hgname in walkrepos(fname, True, seen_dirs): - yield hgname - else: - newdirs.append(d) - dirs[:] = newdirs - -_rcpath = None - -def os_rcpath(): - '''return default os-specific hgrc search path''' - path = system_rcpath() - path.extend(user_rcpath()) - path = [os.path.normpath(f) for f in path] - return path - -def rcpath(): - '''return hgrc search path. if env var HGRCPATH is set, use it. - for each item in path, if directory, use files ending in .rc, - else use item. - make HGRCPATH empty to only look in .hg/hgrc of current repo. - if no HGRCPATH, use default os-specific path.''' - global _rcpath - if _rcpath is None: - if 'HGRCPATH' in os.environ: - _rcpath = [] - for p in os.environ['HGRCPATH'].split(os.pathsep): - if not p: - continue - p = expandpath(p) - if os.path.isdir(p): - for f, kind in osutil.listdir(p): - if f.endswith('.rc'): - _rcpath.append(os.path.join(p, f)) - else: - _rcpath.append(p) - else: - _rcpath = os_rcpath() - return _rcpath - def bytecount(nbytes): '''return byte count formatted as readable string, with units''' @@ -1367,26 +1127,6 @@ return format % (nbytes / float(divisor)) return units[-1][2] % nbytes -def drop_scheme(scheme, path): - sc = scheme + ':' - if path.startswith(sc): - path = path[len(sc):] - if path.startswith('//'): - if scheme == 'file': - i = path.find('/', 2) - if i == -1: - return '' - # On Windows, absolute paths are rooted at the current drive - # root. On POSIX they are rooted at the file system root. - if os.name == 'nt': - droot = os.path.splitdrive(os.getcwd())[0] + '/' - path = os.path.join(droot, path[i + 1:]) - else: - path = path[i:] - else: - path = path[2:] - return path - def uirepr(s): # Avoid double backslash in Windows path repr() return repr(s).replace('\\\\', '\\') @@ -1459,7 +1199,7 @@ to avoid things opening new shell windows like batch files, so we get either the python call or current executable. """ - if main_is_frozen(): + if mainfrozen(): return [sys.executable] return gethgcmd() @@ -1564,3 +1304,290 @@ If s is not a valid boolean, returns None. """ return _booleans.get(s.lower(), None) + +_hexdig = '0123456789ABCDEFabcdef' +_hextochr = dict((a + b, chr(int(a + b, 16))) + for a in _hexdig for b in _hexdig) + +def _urlunquote(s): + """unquote('abc%20def') -> 'abc def'.""" + res = s.split('%') + # fastpath + if len(res) == 1: + return s + s = res[0] + for item in res[1:]: + try: + s += _hextochr[item[:2]] + item[2:] + except KeyError: + s += '%' + item + except UnicodeDecodeError: + s += unichr(int(item[:2], 16)) + item[2:] + return s + +class url(object): + r"""Reliable URL parser. + + This parses URLs and provides attributes for the following + components: + + <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment> + + Missing components are set to None. The only exception is + fragment, which is set to '' if present but empty. + + If parsefragment is False, fragment is included in query. If + parsequery is False, query is included in path. If both are + False, both fragment and query are included in path. + + See http://www.ietf.org/rfc/rfc2396.txt for more information. + + Note that for backward compatibility reasons, bundle URLs do not + take host names. That means 'bundle://../' has a path of '../'. + + Examples: + + >>> url('http://www.ietf.org/rfc/rfc2396.txt') + <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'> + >>> url('ssh://[::1]:2200//home/joe/repo') + <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'> + >>> url('file:///home/joe/repo') + <url scheme: 'file', path: '/home/joe/repo'> + >>> url('bundle:foo') + <url scheme: 'bundle', path: 'foo'> + >>> url('bundle://../foo') + <url scheme: 'bundle', path: '../foo'> + >>> url(r'c:\foo\bar') + <url path: 'c:\\foo\\bar'> + + Authentication credentials: + + >>> url('ssh://joe:xyz@x/repo') + <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'> + >>> url('ssh://joe@x/repo') + <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'> + + Query strings and fragments: + + >>> url('http://host/a?b#c') + <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'> + >>> url('http://host/a?b#c', parsequery=False, parsefragment=False) + <url scheme: 'http', host: 'host', path: 'a?b#c'> + """ + + _safechars = "!~*'()+" + _safepchars = "/!~*'()+" + _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match + + def __init__(self, path, parsequery=True, parsefragment=True): + # We slowly chomp away at path until we have only the path left + self.scheme = self.user = self.passwd = self.host = None + self.port = self.path = self.query = self.fragment = None + self._localpath = True + self._hostport = '' + self._origpath = path + + # special case for Windows drive letters + if hasdriveletter(path): + self.path = path + return + + # For compatibility reasons, we can't handle bundle paths as + # normal URLS + if path.startswith('bundle:'): + self.scheme = 'bundle' + path = path[7:] + if path.startswith('//'): + path = path[2:] + self.path = path + return + + if self._matchscheme(path): + parts = path.split(':', 1) + if parts[0]: + self.scheme, path = parts + self._localpath = False + + if not path: + path = None + if self._localpath: + self.path = '' + return + else: + if parsefragment and '#' in path: + path, self.fragment = path.split('#', 1) + if not path: + path = None + if self._localpath: + self.path = path + return + + if parsequery and '?' in path: + path, self.query = path.split('?', 1) + if not path: + path = None + if not self.query: + self.query = None + + # // is required to specify a host/authority + if path and path.startswith('//'): + parts = path[2:].split('/', 1) + if len(parts) > 1: + self.host, path = parts + path = path + else: + self.host = parts[0] + path = None + if not self.host: + self.host = None + if path: + path = '/' + path + + if self.host and '@' in self.host: + self.user, self.host = self.host.rsplit('@', 1) + if ':' in self.user: + self.user, self.passwd = self.user.split(':', 1) + if not self.host: + self.host = None + + # Don't split on colons in IPv6 addresses without ports + if (self.host and ':' in self.host and + not (self.host.startswith('[') and self.host.endswith(']'))): + self._hostport = self.host + self.host, self.port = self.host.rsplit(':', 1) + if not self.host: + self.host = None + + if (self.host and self.scheme == 'file' and + self.host not in ('localhost', '127.0.0.1', '[::1]')): + raise Abort(_('file:// URLs can only refer to localhost')) + + self.path = path + + for a in ('user', 'passwd', 'host', 'port', + 'path', 'query', 'fragment'): + v = getattr(self, a) + if v is not None: + setattr(self, a, _urlunquote(v)) + + def __repr__(self): + attrs = [] + for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path', + 'query', 'fragment'): + v = getattr(self, a) + if v is not None: + attrs.append('%s: %r' % (a, v)) + return '<url %s>' % ', '.join(attrs) + + def __str__(self): + r"""Join the URL's components back into a URL string. + + Examples: + + >>> str(url('http://user:pw@host:80/?foo#bar')) + 'http://user:pw@host:80/?foo#bar' + >>> str(url('ssh://user:pw@[::1]:2200//home/joe#')) + 'ssh://user:pw@[::1]:2200//home/joe#' + >>> str(url('http://localhost:80//')) + 'http://localhost:80//' + >>> str(url('http://localhost:80/')) + 'http://localhost:80/' + >>> str(url('http://localhost:80')) + 'http://localhost:80/' + >>> str(url('bundle:foo')) + 'bundle:foo' + >>> str(url('bundle://../foo')) + 'bundle:../foo' + >>> str(url('path')) + 'path' + >>> str(url('file:///tmp/foo/bar')) + 'file:///tmp/foo/bar' + >>> print url(r'bundle:foo\bar') + bundle:foo\bar + """ + if self._localpath: + s = self.path + if self.scheme == 'bundle': + s = 'bundle:' + s + if self.fragment: + s += '#' + self.fragment + return s + + s = self.scheme + ':' + if self.user or self.passwd or self.host: + s += '//' + elif self.scheme and (not self.path or self.path.startswith('/')): + s += '//' + if self.user: + s += urllib.quote(self.user, safe=self._safechars) + if self.passwd: + s += ':' + urllib.quote(self.passwd, safe=self._safechars) + if self.user or self.passwd: + s += '@' + if self.host: + if not (self.host.startswith('[') and self.host.endswith(']')): + s += urllib.quote(self.host) + else: + s += self.host + if self.port: + s += ':' + urllib.quote(self.port) + if self.host: + s += '/' + if self.path: + s += urllib.quote(self.path, safe=self._safepchars) + if self.query: + s += '?' + urllib.quote(self.query, safe=self._safepchars) + if self.fragment is not None: + s += '#' + urllib.quote(self.fragment, safe=self._safepchars) + return s + + def authinfo(self): + user, passwd = self.user, self.passwd + try: + self.user, self.passwd = None, None + s = str(self) + finally: + self.user, self.passwd = user, passwd + if not self.user: + return (s, None) + return (s, (None, (str(self), self.host), + self.user, self.passwd or '')) + + def localpath(self): + if self.scheme == 'file' or self.scheme == 'bundle': + path = self.path or '/' + # For Windows, we need to promote hosts containing drive + # letters to paths with drive letters. + if hasdriveletter(self._hostport): + path = self._hostport + '/' + self.path + elif self.host is not None and self.path: + path = '/' + path + # We also need to handle the case of file:///C:/, which + # should return C:/, not /C:/. + elif hasdriveletter(path): + # Strip leading slash from paths with drive names + return path[1:] + return path + return self._origpath + +def hasscheme(path): + return bool(url(path).scheme) + +def hasdriveletter(path): + return path[1:2] == ':' and path[0:1].isalpha() + +def localpath(path): + return url(path, parsequery=False, parsefragment=False).localpath() + +def hidepassword(u): + '''hide user credential in a url string''' + u = url(u) + if u.passwd: + u.passwd = '***' + return str(u) + +def removeauth(u): + '''remove all authentication information from a url string''' + u = url(u) + u.user = u.passwd = None + return str(u)
--- a/mercurial/win32.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/win32.py Wed May 18 09:56:27 2011 +0200 @@ -6,18 +6,22 @@ # GNU General Public License version 2 or any later version. import encoding -import ctypes, errno, os, struct, subprocess +import ctypes, errno, os, struct, subprocess, random _kernel32 = ctypes.windll.kernel32 +_advapi32 = ctypes.windll.advapi32 +_user32 = ctypes.windll.user32 _BOOL = ctypes.c_long _WORD = ctypes.c_ushort _DWORD = ctypes.c_ulong +_UINT = ctypes.c_uint +_LONG = ctypes.c_long _LPCSTR = _LPSTR = ctypes.c_char_p _HANDLE = ctypes.c_void_p _HWND = _HANDLE -_INVALID_HANDLE_VALUE = -1 +_INVALID_HANDLE_VALUE = _HANDLE(-1).value # GetLastError _ERROR_SUCCESS = 0 @@ -56,6 +60,10 @@ _OPEN_EXISTING = 3 +# SetFileAttributes +_FILE_ATTRIBUTE_NORMAL = 0x80 +_FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x2000 + # Process Security and Access Rights _PROCESS_QUERY_INFORMATION = 0x0400 @@ -116,7 +124,82 @@ ('srWindow', _SMALL_RECT), ('dwMaximumWindowSize', _COORD)] -_STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12 +_STD_ERROR_HANDLE = _DWORD(-12).value + +# types of parameters of C functions used (required by pypy) + +_kernel32.CreateFileA.argtypes = [_LPCSTR, _DWORD, _DWORD, ctypes.c_void_p, + _DWORD, _DWORD, _HANDLE] +_kernel32.CreateFileA.restype = _HANDLE + +_kernel32.GetFileInformationByHandle.argtypes = [_HANDLE, ctypes.c_void_p] +_kernel32.GetFileInformationByHandle.restype = _BOOL + +_kernel32.CloseHandle.argtypes = [_HANDLE] +_kernel32.CloseHandle.restype = _BOOL + +_kernel32.CreateHardLinkA.argtypes = [_LPCSTR, _LPCSTR, ctypes.c_void_p] +_kernel32.CreateHardLinkA.restype = _BOOL + +_kernel32.SetFileAttributesA.argtypes = [_LPCSTR, _DWORD] +_kernel32.SetFileAttributesA.restype = _BOOL + +_kernel32.OpenProcess.argtypes = [_DWORD, _BOOL, _DWORD] +_kernel32.OpenProcess.restype = _HANDLE + +_kernel32.GetExitCodeProcess.argtypes = [_HANDLE, ctypes.c_void_p] +_kernel32.GetExitCodeProcess.restype = _BOOL + +_kernel32.GetLastError.argtypes = [] +_kernel32.GetLastError.restype = _DWORD + +_kernel32.GetModuleFileNameA.argtypes = [_HANDLE, ctypes.c_void_p, _DWORD] +_kernel32.GetModuleFileNameA.restype = _DWORD + +_kernel32.CreateProcessA.argtypes = [_LPCSTR, _LPCSTR, ctypes.c_void_p, + ctypes.c_void_p, _BOOL, _DWORD, ctypes.c_void_p, _LPCSTR, ctypes.c_void_p, + ctypes.c_void_p] +_kernel32.CreateProcessA.restype = _BOOL + +_kernel32.ExitProcess.argtypes = [_UINT] +_kernel32.ExitProcess.restype = None + +_kernel32.GetCurrentProcessId.argtypes = [] +_kernel32.GetCurrentProcessId.restype = _DWORD + +_SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD) +_kernel32.SetConsoleCtrlHandler.argtypes = [_SIGNAL_HANDLER, _BOOL] +_kernel32.SetConsoleCtrlHandler.restype = _BOOL + +_kernel32.GetStdHandle.argtypes = [_DWORD] +_kernel32.GetStdHandle.restype = _HANDLE + +_kernel32.GetConsoleScreenBufferInfo.argtypes = [_HANDLE, ctypes.c_void_p] +_kernel32.GetConsoleScreenBufferInfo.restype = _BOOL + +_advapi32.RegOpenKeyExA.argtypes = [_HANDLE, _LPCSTR, _DWORD, _DWORD, + ctypes.c_void_p] +_advapi32.RegOpenKeyExA.restype = _LONG + +_advapi32.RegQueryValueExA.argtypes = [_HANDLE, _LPCSTR, ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] +_advapi32.RegQueryValueExA.restype = _LONG + +_advapi32.RegCloseKey.argtypes = [_HANDLE] +_advapi32.RegCloseKey.restype = _LONG + +_advapi32.GetUserNameA.argtypes = [ctypes.c_void_p, ctypes.c_void_p] +_advapi32.GetUserNameA.restype = _BOOL + +_user32.GetWindowThreadProcessId.argtypes = [_HANDLE, ctypes.c_void_p] +_user32.GetWindowThreadProcessId.restype = _DWORD + +_user32.ShowWindow.argtypes = [_HANDLE, ctypes.c_int] +_user32.ShowWindow.restype = _BOOL + +_WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM) +_user32.EnumWindows.argtypes = [_WNDENUMPROC, _LPARAM] +_user32.EnumWindows.restype = _BOOL def _raiseoserror(name): err = ctypes.WinError() @@ -136,8 +219,11 @@ finally: _kernel32.CloseHandle(fh) -def os_link(src, dst): - if not _kernel32.CreateHardLinkA(dst, src, None): +def oslink(src, dst): + try: + if not _kernel32.CreateHardLinkA(dst, src, None): + _raiseoserror(src) + except AttributeError: # Wine doesn't support this function _raiseoserror(src) def nlinks(name): @@ -173,7 +259,7 @@ _kernel32.CloseHandle(h) return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER -def lookup_reg(key, valname=None, scope=None): +def lookupreg(key, valname=None, scope=None): ''' Look up a key/value name in the Windows registry. valname: value name. If unspecified, the default value for the key @@ -182,7 +268,6 @@ a sequence of scopes to look up in order. Default (CURRENT_USER, LOCAL_MACHINE). ''' - adv = ctypes.windll.advapi32 byref = ctypes.byref if scope is None: scope = (_HKEY_CURRENT_USER, _HKEY_LOCAL_MACHINE) @@ -190,14 +275,14 @@ scope = (scope,) for s in scope: kh = _HANDLE() - res = adv.RegOpenKeyExA(s, key, 0, _KEY_READ, ctypes.byref(kh)) + res = _advapi32.RegOpenKeyExA(s, key, 0, _KEY_READ, ctypes.byref(kh)) if res != _ERROR_SUCCESS: continue try: size = _DWORD(600) type = _DWORD() buf = ctypes.create_string_buffer(size.value + 1) - res = adv.RegQueryValueExA(kh.value, valname, None, + res = _advapi32.RegQueryValueExA(kh.value, valname, None, byref(type), buf, byref(size)) if res != _ERROR_SUCCESS: continue @@ -209,9 +294,9 @@ s = ctypes.string_at(byref(buf), struct.calcsize(fmt)) return struct.unpack(fmt, s)[0] finally: - adv.RegCloseKey(kh.value) + _advapi32.RegCloseKey(kh.value) -def executable_path(): +def executablepath(): '''return full path of hg.exe''' size = 600 buf = ctypes.create_string_buffer(size + 1) @@ -224,17 +309,15 @@ def getuser(): '''return name of current user''' - adv = ctypes.windll.advapi32 size = _DWORD(300) buf = ctypes.create_string_buffer(size.value + 1) - if not adv.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)): + if not _advapi32.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)): raise ctypes.WinError() return buf.value -_SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD) -_signal_handler = [] +_signalhandler = [] -def set_signal_handler(): +def setsignalhandler(): '''Register a termination handler for console events including CTRL+C. python signal handlers do not work well with socket operations. @@ -242,28 +325,25 @@ def handler(event): _kernel32.ExitProcess(1) - if _signal_handler: + if _signalhandler: return # already registered h = _SIGNAL_HANDLER(handler) - _signal_handler.append(h) # needed to prevent garbage collection + _signalhandler.append(h) # needed to prevent garbage collection if not _kernel32.SetConsoleCtrlHandler(h, True): raise ctypes.WinError() -_WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM) - def hidewindow(): - user32 = ctypes.windll.user32 def callback(hwnd, pid): wpid = _DWORD() - user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid)) + _user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid)) if pid == wpid.value: - user32.ShowWindow(hwnd, _SW_HIDE) + _user32.ShowWindow(hwnd, _SW_HIDE) return False # stop enumerating windows return True pid = _kernel32.GetCurrentProcessId() - user32.EnumWindows(_WNDENUMPROC(callback), pid) + _user32.EnumWindows(_WNDENUMPROC(callback), pid) def termwidth(): # cmd.exe does not handle CR like a unix console, the CR is @@ -316,3 +396,54 @@ raise ctypes.WinError() return pi.dwProcessId + +def unlink(f): + '''try to implement POSIX' unlink semantics on Windows''' + + # POSIX allows to unlink and rename open files. Windows has serious + # problems with doing that: + # - Calling os.unlink (or os.rename) on a file f fails if f or any + # hardlinked copy of f has been opened with Python's open(). There is no + # way such a file can be deleted or renamed on Windows (other than + # scheduling the delete or rename for the next reboot). + # - Calling os.unlink on a file that has been opened with Mercurial's + # posixfile (or comparable methods) will delay the actual deletion of + # the file for as long as the file is held open. The filename is blocked + # during that time and cannot be used for recreating a new file under + # that same name ("zombie file"). Directories containing such zombie files + # cannot be removed or moved. + # A file that has been opened with posixfile can be renamed, so we rename + # f to a random temporary name before calling os.unlink on it. This allows + # callers to recreate f immediately while having other readers do their + # implicit zombie filename blocking on a temporary name. + + for tries in xrange(10): + temp = '%s-%08x' % (f, random.randint(0, 0xffffffff)) + try: + os.rename(f, temp) # raises OSError EEXIST if temp exists + break + except OSError, e: + if e.errno != errno.EEXIST: + raise + else: + raise IOError, (errno.EEXIST, "No usable temporary filename found") + + try: + os.unlink(temp) + except OSError: + # The unlink might have failed because the READONLY attribute may heave + # been set on the original file. Rename works fine with READONLY set, + # but not os.unlink. Reset all attributes and try again. + _kernel32.SetFileAttributesA(temp, _FILE_ATTRIBUTE_NORMAL) + try: + os.unlink(temp) + except OSError: + # The unlink might have failed due to some very rude AV-Scanners. + # Leaking a tempfile is the lesser evil than aborting here and + # leaving some potentially serious inconsistencies. + pass + +def makedir(path, notindexed): + os.mkdir(path) + if notindexed: + _kernel32.SetFileAttributesA(path, _FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
--- a/mercurial/windows.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/windows.py Wed May 18 09:56:27 2011 +0200 @@ -6,8 +6,8 @@ # GNU General Public License version 2 or any later version. from i18n import _ -import osutil, error -import errno, msvcrt, os, re, sys, random, subprocess +import osutil +import errno, msvcrt, os, re, sys nulldev = 'NUL:' umask = 002 @@ -32,7 +32,8 @@ def close(self): try: self.fp.close() - except: pass + except IOError: + pass def write(self, s): try: @@ -73,50 +74,7 @@ def openhardlinks(): return not _is_win_9x() -_HKEY_LOCAL_MACHINE = 0x80000002L - -def system_rcpath(): - '''return default os-specific hgrc search path''' - rcpath = [] - filename = executable_path() - # Use mercurial.ini found in directory with hg.exe - progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini') - if os.path.isfile(progrc): - rcpath.append(progrc) - return rcpath - # Use hgrc.d found in directory with hg.exe - progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d') - if os.path.isdir(progrcd): - for f, kind in osutil.listdir(progrcd): - if f.endswith('.rc'): - rcpath.append(os.path.join(progrcd, f)) - return rcpath - # else look for a system rcpath in the registry - value = lookup_reg('SOFTWARE\\Mercurial', None, _HKEY_LOCAL_MACHINE) - if not isinstance(value, str) or not value: - return rcpath - value = value.replace('/', os.sep) - for p in value.split(os.pathsep): - if p.lower().endswith('mercurial.ini'): - rcpath.append(p) - elif os.path.isdir(p): - for f, kind in osutil.listdir(p): - if f.endswith('.rc'): - rcpath.append(os.path.join(p, f)) - return rcpath - -def user_rcpath(): - '''return os-specific hgrc search path to the user dir''' - home = os.path.expanduser('~') - path = [os.path.join(home, 'mercurial.ini'), - os.path.join(home, '.hgrc')] - userprofile = os.environ.get('USERPROFILE') - if userprofile: - path.append(os.path.join(userprofile, 'mercurial.ini')) - path.append(os.path.join(userprofile, '.hgrc')) - return path - -def parse_patch_output(output_line): +def parsepatchoutput(output_line): """parses the output produced by patch and returns the filename""" pf = output_line[14:] if pf[0] == '`': @@ -129,10 +87,16 @@ args = user and ("%s@%s" % (user, host)) or host return port and ("%s %s %s" % (args, pflag, port)) or args -def set_flags(f, l, x): +def setflags(f, l, x): pass -def set_binary(fd): +def checkexec(path): + return False + +def checklink(path): + return False + +def setbinary(fd): # When run without console, pipes may expose invalid # fileno(), usually set to -1. if hasattr(fd, 'fileno') and fd.fileno() >= 0: @@ -191,7 +155,7 @@ command += " 2> %s" % nulldev return os.popen(quotecommand(command), mode) -def explain_exit(code): +def explainexit(code): return _("exited with status %d") % code, code # if you change this stub into a real check, please try to implement the @@ -199,7 +163,7 @@ def isowner(st): return True -def find_exe(command): +def findexe(command): '''Find executable for command searching like cmd.exe does. If command is a basename then PATH is searched for command. PATH isn't searched if command is an absolute or relative path. @@ -280,59 +244,19 @@ if osutil.listdir(head): return os.rmdir(head) - except: + except (ValueError, OSError): break head, tail = os.path.split(head) def unlinkpath(f): """unlink and remove the directory if it is empty""" - os.unlink(f) + unlink(f) # try removing directories that might now be empty try: _removedirs(os.path.dirname(f)) except OSError: pass -def unlink(f): - '''try to implement POSIX' unlink semantics on Windows''' - - # POSIX allows to unlink and rename open files. Windows has serious - # problems with doing that: - # - Calling os.unlink (or os.rename) on a file f fails if f or any - # hardlinked copy of f has been opened with Python's open(). There is no - # way such a file can be deleted or renamed on Windows (other than - # scheduling the delete or rename for the next reboot). - # - Calling os.unlink on a file that has been opened with Mercurial's - # posixfile (or comparable methods) will delay the actual deletion of - # the file for as long as the file is held open. The filename is blocked - # during that time and cannot be used for recreating a new file under - # that same name ("zombie file"). Directories containing such zombie files - # cannot be removed or moved. - # A file that has been opened with posixfile can be renamed, so we rename - # f to a random temporary name before calling os.unlink on it. This allows - # callers to recreate f immediately while having other readers do their - # implicit zombie filename blocking on a temporary name. - - for tries in xrange(10): - temp = '%s-%08x' % (f, random.randint(0, 0xffffffff)) - try: - os.rename(f, temp) # raises OSError EEXIST if temp exists - break - except OSError, e: - if e.errno != errno.EEXIST: - raise - else: - raise IOError, (errno.EEXIST, "No usable temporary filename found") - - try: - os.unlink(temp) - except: - # Some very rude AV-scanners on Windows may cause this unlink to fail. - # Not aborting here just leaks the temp file, whereas aborting at this - # point may leave serious inconsistencies. Ideally, we would notify - # the user in this case here. - pass - def rename(src, dst): '''atomically rename file src to dst, replacing dst if it exists''' try:
--- a/mercurial/wireproto.py Wed May 18 09:31:19 2011 +0200 +++ b/mercurial/wireproto.py Wed May 18 09:56:27 2011 +0200 @@ -15,7 +15,9 @@ # list of nodes encoding / decoding def decodelist(l, sep=' '): - return map(bin, l.split(sep)) + if l: + return map(bin, l.split(sep)) + return [] def encodelist(l, sep=' '): return sep.join(map(hex, l)) @@ -35,7 +37,15 @@ d = self._call("heads") try: return decodelist(d[:-1]) - except: + except ValueError: + self._abort(error.ResponseError(_("unexpected response:"), d)) + + def known(self, nodes): + n = encodelist(nodes) + d = self._call("known", nodes=n) + try: + return [bool(int(f)) for f in d] + except ValueError: self._abort(error.ResponseError(_("unexpected response:"), d)) def branchmap(self): @@ -57,7 +67,7 @@ try: br = [tuple(decodelist(b)) for b in d.splitlines()] return br - except: + except ValueError: self._abort(error.ResponseError(_("unexpected response:"), d)) def between(self, pairs): @@ -68,7 +78,7 @@ d = self._call("between", pairs=n) try: r.extend(l and decodelist(l) or [] for l in d.splitlines()) - except: + except ValueError: self._abort(error.ResponseError(_("unexpected response:"), d)) return r @@ -113,13 +123,29 @@ bases=bases, heads=heads) return changegroupmod.unbundle10(self._decompress(f), 'UN') + def getbundle(self, source, heads=None, common=None): + self.requirecap('getbundle', _('look up remote changes')) + opts = {} + if heads is not None: + opts['heads'] = encodelist(heads) + if common is not None: + opts['common'] = encodelist(common) + f = self._callstream("getbundle", **opts) + return changegroupmod.unbundle10(self._decompress(f), 'UN') + def unbundle(self, cg, heads, source): '''Send cg (a readable file-like object representing the changegroup to push, typically a chunkbuffer object) to the remote server as a bundle. Return an integer indicating the result of the push (see localrepository.addchangegroup()).''' - ret, output = self._callpush("unbundle", cg, heads=encodelist(heads)) + if self.capable('unbundlehash'): + heads = encodelist(['hashed', + util.sha1(''.join(sorted(heads))).digest()]) + else: + heads = encodelist(heads) + + ret, output = self._callpush("unbundle", cg, heads=heads) if ret == "": raise error.ResponseError( _('push failed:'), output) @@ -133,6 +159,15 @@ self.ui.status(_('remote: '), l) return ret + def debugwireargs(self, one, two, three=None, four=None, five=None): + # don't pass optional arguments left at their default value + opts = {} + if three is not None: + opts['three'] = three + if four is not None: + opts['four'] = four + return self._call('debugwireargs', one=one, two=two, **opts) + # server side class streamres(object): @@ -152,6 +187,17 @@ args = proto.getargs(spec) return func(repo, proto, *args) +def options(cmd, keys, others): + opts = {} + for k in keys: + if k in others: + opts[k] = others[k] + del others[k] + if others: + sys.stderr.write("abort: %s got unexpected arguments %s\n" + % (cmd, ",".join(others))) + return opts + def between(repo, proto, pairs): pairs = [decodelist(p, '-') for p in pairs.split(" ")] r = [] @@ -176,7 +222,8 @@ return "".join(r) def capabilities(repo, proto): - caps = 'lookup changegroupsubset branchmap pushkey'.split() + caps = ('lookup changegroupsubset branchmap pushkey known getbundle ' + 'unbundlehash').split() if _allowstream(repo.ui): requiredformats = repo.requirements & repo.supportedformats # if our local revlogs are just revlogv1, add 'stream' cap @@ -186,6 +233,7 @@ else: caps.append('streamreqs=%s' % ','.join(requiredformats)) caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority)) + caps.append('httpheader=1024') return ' '.join(caps) def changegroup(repo, proto, roots): @@ -199,6 +247,18 @@ cg = repo.changegroupsubset(bases, heads, 'serve') return streamres(proto.groupchunks(cg)) +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, **opts) + +def getbundle(repo, proto, others): + opts = options('getbundle', ['heads', 'common'], others) + for k, v in opts.iteritems(): + opts[k] = decodelist(v) + cg = repo.getbundle('serve', **opts) + return streamres(proto.groupchunks(cg)) + def heads(repo, proto): h = repo.heads() return encodelist(h) + "\n" @@ -228,13 +288,16 @@ success = 0 return "%s %s\n" % (success, r) +def known(repo, proto, nodes): + return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes))) + def pushkey(repo, proto, namespace, key, old, new): # compatibility with pre-1.8 clients which were accidentally # sending raw binary nodes rather than utf-8-encoded hex if len(new) == 20 and new.encode('string-escape') != new: # looks like it could be a binary node try: - u = new.decode('utf-8') + new.decode('utf-8') new = encoding.tolocal(new) # but cleanly decodes as UTF-8 except UnicodeDecodeError: pass # binary, leave unmodified @@ -298,7 +361,9 @@ def check_heads(): heads = repo.heads() - return their_heads == ['force'] or their_heads == heads + heads_hash = util.sha1(''.join(sorted(heads))).digest() + return (their_heads == ['force'] or their_heads == heads or + their_heads == ['hashed', heads_hash]) proto.redirect() @@ -343,8 +408,11 @@ 'capabilities': (capabilities, ''), 'changegroup': (changegroup, 'roots'), 'changegroupsubset': (changegroupsubset, 'bases heads'), + 'debugwireargs': (debugwireargs, 'one two *'), + 'getbundle': (getbundle, '*'), 'heads': (heads, ''), 'hello': (hello, ''), + 'known': (known, 'nodes'), 'listkeys': (listkeys, 'namespace'), 'lookup': (lookup, 'key'), 'pushkey': (pushkey, 'namespace key old new'),
--- a/setup.py Wed May 18 09:31:19 2011 +0200 +++ b/setup.py Wed May 18 09:56:27 2011 +0200 @@ -4,7 +4,7 @@ # 'python setup.py install', or # 'python setup.py --help' for more options -import sys +import sys, platform if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'): raise SystemExit("Mercurial requires Python 2.4 or later.") @@ -36,11 +36,21 @@ raise SystemExit( "Couldn't import standard zlib (incomplete Python install).") +# The base IronPython distribution (as of 2.7.1) doesn't support bz2 +isironpython = False try: - import bz2 + isironpython = platform.python_implementation().lower().find("ironpython") != -1 except: - raise SystemExit( - "Couldn't import standard bz2 (incomplete Python install).") + pass + +if isironpython: + print "warning: IronPython detected (no bz2 support)" +else: + try: + import bz2 + except: + raise SystemExit( + "Couldn't import standard bz2 (incomplete Python install).") import os, subprocess, time import shutil @@ -98,24 +108,8 @@ try: import py2exe py2exeloaded = True - - # Help py2exe to find win32com.shell - try: - import modulefinder - import win32com - for p in win32com.__path__[1:]: # Take the path to win32comext - modulefinder.AddPackagePath("win32com", p) - pn = "win32com.shell" - __import__(pn) - m = sys.modules[pn] - for p in m.__path__[1:]: - modulefinder.AddPackagePath(pn, p) - except ImportError: - pass - except ImportError: py2exeloaded = False - pass def runcmd(cmd, env): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, @@ -317,8 +311,9 @@ 'build_py': hgbuildpy, 'install_scripts': hginstallscripts} -packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert', - 'hgext.highlight', 'hgext.zeroconf'] +packages = ['mercurial', 'mercurial.hgweb', + 'mercurial.httpclient', 'mercurial.httpclient.tests', + 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf'] pymodules = [] @@ -330,11 +325,17 @@ Extension('mercurial.parsers', ['mercurial/parsers.c']), ] +osutil_ldflags = [] + +if sys.platform == 'darwin': + osutil_ldflags += ['-framework', 'ApplicationServices'] + # disable osutil.c under windows + python 2.4 (issue1364) if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'): pymodules.append('mercurial.pure.osutil') else: - extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'])) + extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'], + extra_link_args=osutil_ldflags)) if sys.platform == 'linux2' and os.uname()[2] > '2.6': # The inotify extension is only usable with Linux 2.6 kernels. @@ -385,7 +386,7 @@ # Also parse only first digit, because 3.2.1 can't be parsed nicely if (version.startswith('Xcode') and StrictVersion(version.split()[1]) >= StrictVersion('4.0')): - os.environ['ARCHFLAGS'] = '-arch i386 -arch x86_64' + os.environ['ARCHFLAGS'] = '' setup(name='mercurial', version=setupversion,
--- a/tests/autodiff.py Wed May 18 09:31:19 2011 +0200 +++ b/tests/autodiff.py Wed May 18 09:56:27 2011 +0200 @@ -1,7 +1,7 @@ # Extension dedicated to test patch.diff() upgrade modes # # -from mercurial import cmdutil, patch, util +from mercurial import scmutil, patch, util def autodiff(ui, repo, *pats, **opts): diffopts = patch.diffopts(ui, opts) @@ -28,8 +28,8 @@ else: raise util.Abort('--git must be yes, no or auto') - node1, node2 = cmdutil.revpair(repo, []) - m = cmdutil.match(repo, pats, opts) + node1, node2 = scmutil.revpair(repo, []) + m = scmutil.match(repo, pats, opts) it = patch.diff(repo, node1, node2, match=m, opts=diffopts, losedatafn=losedatafn) for chunk in it:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/bundles/rebase.sh Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,44 @@ +#!/bin/bash +hg init rebase +cd rebase + +# @ 7: 'H' +# | +# | o 6: 'G' +# |/| +# o | 5: 'F' +# | | +# | o 4: 'E' +# |/ +# | o 3: 'D' +# | | +# | o 2: 'C' +# | | +# | o 1: 'B' +# |/ +# o 0: 'A' + +echo A > A +hg ci -Am A +echo B > B +hg ci -Am B +echo C > C +hg ci -Am C +echo D > D +hg ci -Am D +hg up -q -C 0 +echo E > E +hg ci -Am E +hg up -q -C 0 +echo F > F +hg ci -Am F +hg merge -r 4 +hg ci -m G +hg up -q -C 5 +echo H > H +hg ci -Am H + +hg bundle -a ../rebase.hg + +cd .. +rm -Rf rebase
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/bundles/remote.sh Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,32 @@ +#!/bin/bash +hg init remote +cd remote + +echo "0" >> afile +hg add afile +hg commit -m "0.0" +echo "1" >> afile +hg commit -m "0.1" +echo "2" >> afile +hg commit -m "0.2" +echo "3" >> afile +hg commit -m "0.3" +hg update -C 0 +echo "1" >> afile +hg commit -m "1.1" +echo "2" >> afile +hg commit -m "1.2" +echo "a line" > fred +echo "3" >> afile +hg add fred +hg commit -m "1.3" +hg mv afile adifferentfile +hg commit -m "1.3m" +hg update -C 3 +hg mv afile anotherfile +hg commit -m "0.3m" + +hg bundle -a ../remote.hg + +cd .. +rm -Rf remote
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/dummyssh Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +import sys +import os + +os.chdir(os.getenv('TESTTMP')) + +if sys.argv[1] != "user@dummy": + sys.exit(-1) + +os.environ["SSH_CLIENT"] = "127.0.0.1 1 2" + +log = open("dummylog", "ab") +log.write("Got arguments") +for i, arg in enumerate(sys.argv[1:]): + log.write(" %d:%s" % (i+1, arg)) +log.write("\n") +log.close() +r = os.system(sys.argv[2]) +sys.exit(bool(r))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/filterpyflakes.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +# Filter output by pyflakes to control which warnings we check + +import sys, re, os + +def makekey(message): + # "path/file:line: message" + match = re.search(r"(line \d+)", message) + line = '' + if match: + line = match.group(0) + message = re.sub(r"(line \d+)", '', message) + return re.sub(r"([^:]*):([^:]+):([^']*)('[^']*')(.*)$", + r'\3:\5:\4:\1:\2:' + line, + message) + +lines = [] +for line in sys.stdin: + # We whitelist tests + pats = [ + r"imported but unused", + r"local variable '.*' is assigned to but never used", + r"unable to detect undefined names", + ] + if not re.search('|'.join(pats), line): + continue + fn = line.split(':', 1)[0] + f = open(os.path.join(os.path.dirname(os.path.dirname(__file__)), fn)) + data = f.read() + f.close() + if 'no-check-code' in data: + continue + lines.append(line) + +for line in sorted(lines, key = makekey): + sys.stdout.write(line) +print
--- a/tests/hghave Wed May 18 09:31:19 2011 +0200 +++ b/tests/hghave Wed May 18 09:56:27 2011 +0200 @@ -172,6 +172,11 @@ finally: os.rmdir(d) +def has_pyflakes(): + return matchoutput('echo "import re" 2>&1 | pyflakes', + r"<stdin>:1: 're' imported but unused", + True) + def has_pygments(): try: import pygments @@ -210,6 +215,7 @@ "mtn": (has_mtn, "monotone client (> 0.31)"), "outer-repo": (has_outer_repo, "outer repo"), "p4": (has_p4, "Perforce server and client"), + "pyflakes": (has_pyflakes, "Pyflakes python linter"), "pygments": (has_pygments, "Pygments source highlighting library"), "ssl": (has_ssl, "python >= 2.6 ssl module and python OpenSSL"), "svn": (has_svn, "subversion client and admin tools"),
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/notcapable Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,19 @@ +# Disable the $CAP wire protocol capability. + +if test -z "$CAP" +then + echo "CAP environment variable not set." +fi + +cat > notcapable-$CAP.py << EOF +from mercurial import extensions, repo +def extsetup(): + extensions.wrapfunction(repo.repository, 'capable', wrapper) +def wrapper(orig, self, name, *args, **kwargs): + if name == '$CAP': + return False + return orig(self, name, *args, **kwargs) +EOF + +echo '[extensions]' >> $HGRCPATH +echo "notcapable-$CAP = `pwd`/notcapable-$CAP.py" >> $HGRCPATH
--- a/tests/run-tests.py Wed May 18 09:31:19 2011 +0200 +++ b/tests/run-tests.py Wed May 18 09:56:27 2011 +0200 @@ -53,16 +53,37 @@ import tempfile import time import re +import threading + +processlock = threading.Lock() closefds = os.name == 'posix' -def Popen4(cmd, bufsize=-1): - p = subprocess.Popen(cmd, shell=True, bufsize=bufsize, +def Popen4(cmd, wd, timeout): + processlock.acquire() + p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, close_fds=closefds, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + processlock.release() + p.fromchild = p.stdout p.tochild = p.stdin p.childerr = p.stderr + + p.timeout = False + if timeout: + def t(): + start = time.time() + while time.time() - start < timeout and p.returncode is None: + time.sleep(1) + p.timeout = True + if p.returncode is None: + try: + p.terminate() + except OSError: + pass + threading.Thread(target=t).start() + return p # reserved exit code to skip test (used by hghave) @@ -80,6 +101,7 @@ 'jobs': ('HGTEST_JOBS', 1), 'timeout': ('HGTEST_TIMEOUT', 180), 'port': ('HGTEST_PORT', 20059), + 'shell': ('HGTEST_SHELL', '/bin/sh'), } def parseargs(): @@ -125,6 +147,8 @@ help="retest failed tests") parser.add_option("-S", "--noskips", action="store_true", help="don't report skip tests verbosely") + parser.add_option("--shell", type="string", + help="shell to use (default: $%s or %s)" % defaults['shell']) parser.add_option("-t", "--timeout", type="int", help="kill errant tests after TIMEOUT seconds" " (default: $%s or %d)" % defaults['timeout']) @@ -141,9 +165,11 @@ "temporary installation") parser.add_option("-3", "--py3k-warnings", action="store_true", help="enable Py3k warnings on Python 2.6+") + parser.add_option('--extra-config-opt', action="append", + help='set the given config opt in the test hgrc') - for option, default in defaults.items(): - defaults[option] = int(os.environ.get(*default)) + for option, (envvar, default) in defaults.items(): + defaults[option] = type(default)(os.environ.get(envvar, default)) parser.set_defaults(**defaults) (options, args) = parser.parse_args() @@ -187,12 +213,14 @@ else: pid = None def vlog(*msg): + iolock.acquire() if pid: print pid, for m in msg: print m, print sys.stdout.flush() + iolock.release() else: vlog = lambda *msg: None @@ -211,6 +239,10 @@ sys.stderr.write( 'warning: --timeout option ignored with --debug\n') options.timeout = 0 + if options.timeout and not hasattr(subprocess.Popen, 'terminate'): + sys.stderr.write('warning: timeout is not supported on this ' + 'platform and will be ignored') + options.timeout = 0 if options.py3k_warnings: if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0): parser.error('--py3k-warnings can only be used on Python 2.6+') @@ -227,8 +259,8 @@ continue for line in f.readlines(): - line = line.strip() - if line and not line.startswith('#'): + line = line.split('#', 1)[0].strip() + if line: blacklist[line] = filename f.close() @@ -277,6 +309,7 @@ return missing, failed def showdiff(expected, output, ref, err): + print for line in difflib.unified_diff(expected, output, ref, err): sys.stdout.write(line) @@ -284,7 +317,7 @@ """Search PATH for a executable program""" for p in os.environ.get('PATH', os.defpath).split(os.pathsep): name = os.path.join(p, program) - if os.access(name, os.X_OK): + if os.name == 'nt' or os.access(name, os.X_OK): return name return None @@ -334,12 +367,14 @@ # some tests run python interpreter. they must use same # interpreter we use or bad things will happen. exedir, exename = os.path.split(sys.executable) - if exename == 'python': - path = findprogram('python') + if exename in ('python', 'python.exe'): + path = findprogram(exename) if os.path.dirname(path) == exedir: return + else: + exename = 'python' vlog('# Making python executable in test path use correct Python') - mypython = os.path.join(BINDIR, 'python') + mypython = os.path.join(BINDIR, exename) try: os.symlink(sys.executable, mypython) except AttributeError: @@ -406,6 +441,22 @@ f.write(line + '\n') f.close() + hgbat = os.path.join(BINDIR, 'hg.bat') + if os.path.isfile(hgbat): + # hg.bat expects to be put in bin/scripts while run-tests.py + # installation layout put it in bin/ directly. Fix it + f = open(hgbat, 'rb') + data = f.read() + f.close() + if '"%~dp0..\python" "%~dp0hg" %*' in data: + data = data.replace('"%~dp0..\python" "%~dp0hg" %*', + '"%~dp0python" "%~dp0hg" %*') + f = open(hgbat, 'wb') + f.write(data) + f.close() + else: + print 'WARNING: cannot fix hg.bat reference to python.exe' + if options.anycoverage: custom = os.path.join(TESTDIR, 'sitecustomize.py') target = os.path.join(PYTHONDIR, 'sitecustomize.py') @@ -439,22 +490,16 @@ os.mkdir(adir) covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit) -class Timeout(Exception): - pass - -def alarmed(signum, frame): - raise Timeout - -def pytest(test, options, replacements): +def pytest(test, wd, options, replacements): py3kswitch = options.py3k_warnings and ' -3' or '' cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test) vlog("# Running", cmd) - return run(cmd, options, replacements) + return run(cmd, wd, options, replacements) -def shtest(test, options, replacements): +def shtest(test, wd, options, replacements): cmd = '"%s"' % test vlog("# Running", cmd) - return run(cmd, options, replacements) + return run(cmd, wd, options, replacements) needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub @@ -465,7 +510,7 @@ def stringescape(s): return escapesub(escapef, s) -def tsttest(test, options, replacements): +def tsttest(test, wd, options, replacements): t = open(test) out = [] script = [] @@ -504,9 +549,9 @@ os.write(fd, l) os.close(fd) - cmd = '/bin/sh "%s"' % name + cmd = '"%s" "%s"' % (options.shell, name) vlog("# Running", cmd) - exitcode, output = run(cmd, options, replacements) + exitcode, output = run(cmd, wd, options, replacements) # do not merge output if skipped, return hghave message instead # similarly, with --debug, output is None if exitcode == SKIPPED_STATUS or output is None: @@ -585,72 +630,130 @@ return exitcode, postout wifexited = getattr(os, "WIFEXITED", lambda x: False) -def run(cmd, options, replacements): +def run(cmd, wd, options, replacements): """Run command in a sub-process, capturing the output (stdout and stderr). Return a tuple (exitcode, output). output is None in debug mode.""" # TODO: Use subprocess.Popen if we're running on Python 2.4 if options.debug: - proc = subprocess.Popen(cmd, shell=True) + proc = subprocess.Popen(cmd, shell=True, cwd=wd) ret = proc.wait() return (ret, None) - if os.name == 'nt' or sys.platform.startswith('java'): - tochild, fromchild = os.popen4(cmd) - tochild.close() - output = fromchild.read() - ret = fromchild.close() - if ret is None: - ret = 0 - else: - proc = Popen4(cmd) - def cleanup(): - os.kill(proc.pid, signal.SIGTERM) - ret = proc.wait() - if ret == 0: - ret = signal.SIGTERM << 8 - killdaemons() - return ret + proc = Popen4(cmd, wd, options.timeout) + def cleanup(): + try: + proc.terminate() + except OSError: + pass + ret = proc.wait() + if ret == 0: + ret = signal.SIGTERM << 8 + killdaemons() + return ret + + output = '' + proc.tochild.close() - try: - output = '' - proc.tochild.close() - output = proc.fromchild.read() - ret = proc.wait() - if wifexited(ret): - ret = os.WEXITSTATUS(ret) - except Timeout: - vlog('# Process %d timed out - killing it' % proc.pid) - ret = cleanup() - output += ("\n### Abort: timeout after %d seconds.\n" - % options.timeout) - except KeyboardInterrupt: - vlog('# Handling keyboard interrupt') - cleanup() - raise + try: + output = proc.fromchild.read() + except KeyboardInterrupt: + vlog('# Handling keyboard interrupt') + cleanup() + raise + + ret = proc.wait() + if wifexited(ret): + ret = os.WEXITSTATUS(ret) + + if proc.timeout: + ret = 'timeout' + + if ret: + killdaemons() for s, r in replacements: output = re.sub(s, r, output) return ret, splitnewlines(output) -def runone(options, test, skips, fails): +def runone(options, test): '''tristate output: None -> skipped True -> passed False -> failed''' + global results, resultslock, iolock + + testpath = os.path.join(TESTDIR, test) + + def result(l, e): + resultslock.acquire() + results[l].append(e) + resultslock.release() + def skip(msg): if not options.verbose: - skips.append((test, msg)) + result('s', (test, msg)) else: + iolock.acquire() print "\nSkipping %s: %s" % (testpath, msg) + iolock.release() return None - def fail(msg): - fails.append((test, msg)) + def fail(msg, ret): if not options.nodiff: + iolock.acquire() print "\nERROR: %s %s" % (testpath, msg) + iolock.release() + if (not ret and options.interactive + and os.path.exists(testpath + ".err")): + iolock.acquire() + print "Accept this change? [n] ", + answer = sys.stdin.readline().strip() + iolock.release() + if answer.lower() in "y yes".split(): + if test.endswith(".t"): + rename(testpath + ".err", testpath) + else: + rename(testpath + ".err", testpath + ".out") + return + result('f', (test, msg)) + + def success(): + result('p', test) + + def ignore(msg): + result('i', (test, msg)) + + if (os.path.basename(test).startswith("test-") and '~' not in test and + ('.' not in test or test.endswith('.py') or + test.endswith('.bat') or test.endswith('.t'))): + if not os.path.exists(test): + skip("doesn't exist") + return None + else: + return None # not a supported test, don't record + + if options.blacklist: + filename = options.blacklist.get(test) + if filename is not None: + skip("blacklisted") + return None + + if options.retest and not os.path.exists(test + ".err"): + ignore("not retesting") return None + if options.keywords: + fp = open(test) + t = fp.read().lower() + test.lower() + fp.close() + for k in options.keywords.lower().split(): + if k in t: + break + else: + ignore("doesn't match keyword") + return None + vlog("# Test", test) # create a fresh hgrc @@ -667,9 +770,14 @@ hgrc.write('[inotify]\n') hgrc.write('pidfile=%s\n' % DAEMON_PIDS) hgrc.write('appendpid=True\n') + if options.extra_config_opt: + for opt in options.extra_config_opt: + section, key = opt.split('.', 1) + assert '=' in key, ('extra config opt %s must ' + 'have an = for assignment' % opt) + hgrc.write('[%s]\n%s\n' % (section, key)) hgrc.close() - testpath = os.path.join(TESTDIR, test) ref = os.path.join(TESTDIR, test+".out") err = os.path.join(TESTDIR, test+".err") if os.path.exists(err): @@ -694,14 +802,11 @@ runner = shtest # Make a tmp subdirectory to work in - testtmp = os.environ["TESTTMP"] = os.path.join(HGTMP, test) - os.mkdir(testtmp) - os.chdir(testtmp) + testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \ + os.path.join(HGTMP, os.path.basename(test)) - if options.timeout > 0: - signal.alarm(options.timeout) - - ret, out = runner(testpath, options, [ + os.mkdir(testtmp) + ret, out = runner(testpath, testtmp, options, [ (re.escape(testtmp), '$TESTTMP'), (r':%s\b' % options.port, ':$HGPORT'), (r':%s\b' % (options.port + 1), ':$HGPORT1'), @@ -709,9 +814,6 @@ ]) vlog("# Ret was:", ret) - if options.timeout > 0: - signal.alarm(0) - mark = '.' skipped = (ret == SKIPPED_STATUS) @@ -744,33 +846,41 @@ if not missing: missing = ['irrelevant'] if failed: - fail("hghave failed checking for %s" % failed[-1]) + fail("hghave failed checking for %s" % failed[-1], ret) skipped = False else: skip(missing[-1]) + elif ret == 'timeout': + mark = 't' + fail("timed out", ret) elif out != refout: mark = '!' - if ret: - fail("output changed and returned error code %d" % ret) - else: - fail("output changed") if not options.nodiff: + iolock.acquire() if options.view: os.system("%s %s %s" % (options.view, ref, err)) else: showdiff(refout, out, ref, err) + iolock.release() + if ret: + fail("output changed and returned error code %d" % ret, ret) + else: + fail("output changed", ret) ret = 1 elif ret: mark = '!' - fail("returned error code %d" % ret) + fail("returned error code %d" % ret, ret) + else: + success() if not options.verbose: + iolock.acquire() sys.stdout.write(mark) sys.stdout.flush() + iolock.release() killdaemons() - os.chdir(TESTDIR) if not options.keep_tmpdir: shutil.rmtree(testtmp, True) if skipped: @@ -799,7 +909,7 @@ the one we expect it to be. If not, print a warning to stderr.""" expecthg = os.path.join(PYTHONDIR, 'mercurial') actualhg = _gethgpath() - if actualhg != expecthg: + if os.path.abspath(actualhg) != os.path.abspath(expecthg): sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n' ' (expected %s)\n' % (verb, actualhg, expecthg)) @@ -821,6 +931,9 @@ name = '--' + opt.replace('_', '-') if value is True: opts.append(name) + elif isinstance(value, list): + for v in value: + opts.append(name + '=' + str(v)) elif value is not None: opts.append(name + '=' + str(value)) @@ -882,6 +995,16 @@ outputcoverage(options) sys.exit(failures != 0) +results = dict(p=[], f=[], s=[], i=[]) +resultslock = threading.Lock() +iolock = threading.Lock() + +def runqueue(options, tests, results): + for test in tests: + ret = runone(options, test) + if options.first and ret is not None and not ret: + break + def runtests(options, tests): global DAEMON_PIDS, HGRCPATH DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids') @@ -892,19 +1015,6 @@ installhg(options) _checkhglib("Testing") - if options.timeout > 0: - try: - signal.signal(signal.SIGALRM, alarmed) - vlog('# Running each test with %d second timeout' % - options.timeout) - except AttributeError: - print 'WARNING: cannot run tests with timeouts' - options.timeout = 0 - - tested = 0 - failed = 0 - skipped = 0 - if options.restart: orig = list(tests) while tests: @@ -915,69 +1025,30 @@ print "running all tests" tests = orig - skips = [] - fails = [] - - for test in tests: - if options.blacklist: - filename = options.blacklist.get(test) - if filename is not None: - skips.append((test, "blacklisted (%s)" % filename)) - skipped += 1 - continue - - if options.retest and not os.path.exists(test + ".err"): - skipped += 1 - continue + runqueue(options, tests, results) - if options.keywords: - fp = open(test) - t = fp.read().lower() + test.lower() - fp.close() - for k in options.keywords.lower().split(): - if k in t: - break - else: - skipped += 1 - continue - - ret = runone(options, test, skips, fails) - if ret is None: - skipped += 1 - elif not ret: - if options.interactive: - print "Accept this change? [n] ", - answer = sys.stdin.readline().strip() - if answer.lower() in "y yes".split(): - if test.endswith(".t"): - rename(test + ".err", test) - else: - rename(test + ".err", test + ".out") - tested += 1 - fails.pop() - continue - failed += 1 - if options.first: - break - tested += 1 + failed = len(results['f']) + tested = len(results['p']) + failed + skipped = len(results['s']) + ignored = len(results['i']) if options.child: fp = os.fdopen(options.child, 'w') fp.write('%d\n%d\n%d\n' % (tested, skipped, failed)) - for s in skips: + for s in results['s']: fp.write("%s %s\n" % s) - for s in fails: + for s in results['f']: fp.write("%s %s\n" % s) fp.close() else: print - for s in skips: + for s in results['s']: print "Skipped %s: %s" % s - for s in fails: + for s in results['f']: print "Failed %s: %s" % s _checkhglib("Tested") print "# Ran %d tests, %d skipped, %d failed." % ( - tested, skipped, failed) + tested, skipped + ignored, failed) if options.anycoverage: outputcoverage(options) @@ -999,22 +1070,7 @@ args = os.listdir(".") args.sort() - tests = [] - skipped = [] - for test in args: - if (test.startswith("test-") and '~' not in test and - ('.' not in test or test.endswith('.py') or - test.endswith('.bat') or test.endswith('.t'))): - if not os.path.exists(test): - skipped.append(test) - else: - tests.append(test) - if not tests: - for test in skipped: - print 'Skipped %s: does not exist' % test - print "# Ran 0 tests, %d skipped, 0 failed." % len(skipped) - return - tests = tests + skipped + tests = args # Reset some environment variables to well-known values so that # the tests produce repeatable output.
--- a/tests/test-586.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-586.t Wed May 18 09:56:27 2011 +0200 @@ -17,6 +17,7 @@ pulling from ../a searching for changes warning: repository is unrelated + requesting all changes adding changesets adding manifests adding file changes @@ -66,6 +67,7 @@ pulling from ../repob searching for changes warning: repository is unrelated + requesting all changes adding changesets adding manifests adding file changes
--- a/tests/test-acl.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-acl.t Wed May 18 09:56:27 2011 +0200 @@ -82,45 +82,23 @@ hgrc = """ """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally 3 changesets found list of changesets: ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -159,8 +137,9 @@ pretxnchangegroup.acl = python:hgext.acl.hook """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally invalidating branch cache (tip differs) 3 changesets found list of changesets: @@ -168,37 +147,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -240,8 +196,9 @@ sources = push """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally invalidating branch cache (tip differs) 3 changesets found list of changesets: @@ -249,37 +206,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -330,8 +264,9 @@ [acl.allow] """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally invalidating branch cache (tip differs) 3 changesets found list of changesets: @@ -339,37 +274,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -418,45 +330,23 @@ foo/** = fred """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally 3 changesets found list of changesets: ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -510,45 +400,23 @@ [acl.deny] """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally 3 changesets found list of changesets: ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -599,45 +467,23 @@ foo/bar/** = fred """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally 3 changesets found list of changesets: ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -693,45 +539,23 @@ foo/Bar/** = fred """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally 3 changesets found list of changesets: ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -784,45 +608,23 @@ foo/Bar/** = fred """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally 3 changesets found list of changesets: ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -877,45 +679,23 @@ ** = barney """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally 3 changesets found list of changesets: ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -973,8 +753,9 @@ **/*.txt = wilma """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally invalidating branch cache (tip differs) 3 changesets found list of changesets: @@ -982,37 +763,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -1074,45 +832,23 @@ config = ../acl.config """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally 3 changesets found list of changesets: ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -1168,45 +904,23 @@ foo/** = betty """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally 3 changesets found list of changesets: ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -1274,45 +988,23 @@ changegroup.acl = false """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally 3 changesets found list of changesets: ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -1371,8 +1063,9 @@ ** = fred """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally invalidating branch cache (tip differs) 3 changesets found list of changesets: @@ -1380,37 +1073,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -1465,8 +1135,9 @@ foo/Bar/** = * """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally invalidating branch cache (tip differs) 3 changesets found list of changesets: @@ -1474,37 +1145,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -1562,45 +1210,23 @@ ** = @group1 """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally 3 changesets found list of changesets: ef1ea85a6374b77d6da9dcda9541f498f2d17df7 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -1656,8 +1282,9 @@ foo/Bar/** = @group1 """ pushing to ../b + query 1; heads searching for changes - common changesets up to 6675d58eff77 + all remote heads known locally invalidating branch cache (tip differs) 3 changesets found list of changesets: @@ -1665,37 +1292,14 @@ f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd 911600dab2ae7a9baff75958b84fe606851ce955 adding changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 2 changesets - bundling: 2 changesets bundling: 3 changesets - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) - bundling: 0/3 manifests (0.00%) bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 1/3 manifests (33.33%) - bundling: 2/3 manifests (66.67%) - bundling: 2/3 manifests (66.67%) bundling: 2/3 manifests (66.67%) bundling: 3/3 manifests (100.00%) bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) - bundling: foo/Bar/file.txt 0/3 files (0.00%) bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: foo/file.txt 1/3 files (33.33%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) - bundling: quux/file.py 2/3 files (66.67%) bundling: quux/file.py 2/3 files (66.67%) changesets: 1 chunks add changeset ef1ea85a6374 @@ -1747,3 +1351,570 @@ acl: "unlikelytoexist" not defined in [acl.groups] error: pretxnchangegroup.acl hook failed: group 'unlikelytoexist' is undefined abort: group 'unlikelytoexist' is undefined + + +Branch acl tests setup + + $ init_config + $ cd b + $ hg up + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg branch foobar + marked working directory as branch foobar + $ hg commit -m 'create foobar' + $ echo 'foo contents' > abc.txt + $ hg add abc.txt + $ hg commit -m 'foobar contents' + $ cd .. + $ hg --cwd a pull ../b + pulling from ../b + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 1 changes to 1 files (+1 heads) + (run 'hg heads' to see heads) + +Create additional changeset on foobar branch + + $ cd a + $ hg up -C foobar + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo 'foo contents2' > abc.txt + $ hg commit -m 'foobar contents2' + $ cd .. + + +No branch acls specified + + $ do_push astro + Pushing as user astro + hgrc = """ + [acl] + sources = push + [extensions] + """ + pushing to ../b + query 1; heads + searching for changes + all remote heads known locally + 4 changesets found + list of changesets: + ef1ea85a6374b77d6da9dcda9541f498f2d17df7 + f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd + 911600dab2ae7a9baff75958b84fe606851ce955 + e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 + adding changesets + bundling: 1 changesets + bundling: 2 changesets + bundling: 3 changesets + bundling: 4 changesets + bundling: 1/4 manifests (25.00%) + bundling: 2/4 manifests (50.00%) + bundling: 3/4 manifests (75.00%) + bundling: 4/4 manifests (100.00%) + bundling: abc.txt 0/4 files (0.00%) + bundling: foo/Bar/file.txt 1/4 files (25.00%) + bundling: foo/file.txt 2/4 files (50.00%) + bundling: quux/file.py 3/4 files (75.00%) + changesets: 1 chunks + add changeset ef1ea85a6374 + changesets: 2 chunks + add changeset f9cafe1212c8 + changesets: 3 chunks + add changeset 911600dab2ae + changesets: 4 chunks + add changeset e8fc755d4d82 + adding manifests + manifests: 1/4 chunks (25.00%) + manifests: 2/4 chunks (50.00%) + manifests: 3/4 chunks (75.00%) + manifests: 4/4 chunks (100.00%) + adding file changes + adding abc.txt revisions + files: 1/4 chunks (25.00%) + adding foo/Bar/file.txt revisions + files: 2/4 chunks (50.00%) + adding foo/file.txt revisions + files: 3/4 chunks (75.00%) + adding quux/file.py revisions + files: 4/4 chunks (100.00%) + added 4 changesets with 4 changes to 4 files (+1 heads) + calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: acl.allow.branches not enabled + acl: acl.deny.branches not enabled + acl: acl.allow not enabled + acl: acl.deny not enabled + acl: branch access granted: "ef1ea85a6374" on branch "default" + acl: allowing changeset ef1ea85a6374 + acl: branch access granted: "f9cafe1212c8" on branch "default" + acl: allowing changeset f9cafe1212c8 + acl: branch access granted: "911600dab2ae" on branch "default" + acl: allowing changeset 911600dab2ae + acl: branch access granted: "e8fc755d4d82" on branch "foobar" + acl: allowing changeset e8fc755d4d82 + updating the branch cache + checking for updated bookmarks + repository tip rolled back to revision 2 (undo push) + working directory now based on revision 2 + 2:fb35475503ef + + +Branch acl deny test + + $ echo "[acl.deny.branches]" >> $config + $ echo "foobar = *" >> $config + $ do_push astro + Pushing as user astro + hgrc = """ + [acl] + sources = push + [extensions] + [acl.deny.branches] + foobar = * + """ + pushing to ../b + query 1; heads + searching for changes + all remote heads known locally + invalidating branch cache (tip differs) + 4 changesets found + list of changesets: + ef1ea85a6374b77d6da9dcda9541f498f2d17df7 + f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd + 911600dab2ae7a9baff75958b84fe606851ce955 + e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 + adding changesets + bundling: 1 changesets + bundling: 2 changesets + bundling: 3 changesets + bundling: 4 changesets + bundling: 1/4 manifests (25.00%) + bundling: 2/4 manifests (50.00%) + bundling: 3/4 manifests (75.00%) + bundling: 4/4 manifests (100.00%) + bundling: abc.txt 0/4 files (0.00%) + bundling: foo/Bar/file.txt 1/4 files (25.00%) + bundling: foo/file.txt 2/4 files (50.00%) + bundling: quux/file.py 3/4 files (75.00%) + changesets: 1 chunks + add changeset ef1ea85a6374 + changesets: 2 chunks + add changeset f9cafe1212c8 + changesets: 3 chunks + add changeset 911600dab2ae + changesets: 4 chunks + add changeset e8fc755d4d82 + adding manifests + manifests: 1/4 chunks (25.00%) + manifests: 2/4 chunks (50.00%) + manifests: 3/4 chunks (75.00%) + manifests: 4/4 chunks (100.00%) + adding file changes + adding abc.txt revisions + files: 1/4 chunks (25.00%) + adding foo/Bar/file.txt revisions + files: 2/4 chunks (50.00%) + adding foo/file.txt revisions + files: 3/4 chunks (75.00%) + adding quux/file.py revisions + files: 4/4 chunks (100.00%) + added 4 changesets with 4 changes to 4 files (+1 heads) + calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: acl.allow.branches not enabled + acl: acl.deny.branches enabled, 1 entries for user astro + acl: acl.allow not enabled + acl: acl.deny not enabled + acl: branch access granted: "ef1ea85a6374" on branch "default" + acl: allowing changeset ef1ea85a6374 + acl: branch access granted: "f9cafe1212c8" on branch "default" + acl: allowing changeset f9cafe1212c8 + acl: branch access granted: "911600dab2ae" on branch "default" + acl: allowing changeset 911600dab2ae + error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82") + transaction abort! + rollback completed + abort: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82") + no rollback information available + 2:fb35475503ef + + +Branch acl empty allow test + + $ init_config + $ echo "[acl.allow.branches]" >> $config + $ do_push astro + Pushing as user astro + hgrc = """ + [acl] + sources = push + [extensions] + [acl.allow.branches] + """ + pushing to ../b + query 1; heads + searching for changes + all remote heads known locally + 4 changesets found + list of changesets: + ef1ea85a6374b77d6da9dcda9541f498f2d17df7 + f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd + 911600dab2ae7a9baff75958b84fe606851ce955 + e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 + adding changesets + bundling: 1 changesets + bundling: 2 changesets + bundling: 3 changesets + bundling: 4 changesets + bundling: 1/4 manifests (25.00%) + bundling: 2/4 manifests (50.00%) + bundling: 3/4 manifests (75.00%) + bundling: 4/4 manifests (100.00%) + bundling: abc.txt 0/4 files (0.00%) + bundling: foo/Bar/file.txt 1/4 files (25.00%) + bundling: foo/file.txt 2/4 files (50.00%) + bundling: quux/file.py 3/4 files (75.00%) + changesets: 1 chunks + add changeset ef1ea85a6374 + changesets: 2 chunks + add changeset f9cafe1212c8 + changesets: 3 chunks + add changeset 911600dab2ae + changesets: 4 chunks + add changeset e8fc755d4d82 + adding manifests + manifests: 1/4 chunks (25.00%) + manifests: 2/4 chunks (50.00%) + manifests: 3/4 chunks (75.00%) + manifests: 4/4 chunks (100.00%) + adding file changes + adding abc.txt revisions + files: 1/4 chunks (25.00%) + adding foo/Bar/file.txt revisions + files: 2/4 chunks (50.00%) + adding foo/file.txt revisions + files: 3/4 chunks (75.00%) + adding quux/file.py revisions + files: 4/4 chunks (100.00%) + added 4 changesets with 4 changes to 4 files (+1 heads) + calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: acl.allow.branches enabled, 0 entries for user astro + acl: acl.deny.branches not enabled + acl: acl.allow not enabled + acl: acl.deny not enabled + error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374") + transaction abort! + rollback completed + abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374") + no rollback information available + 2:fb35475503ef + + +Branch acl allow other + + $ init_config + $ echo "[acl.allow.branches]" >> $config + $ echo "* = george" >> $config + $ do_push astro + Pushing as user astro + hgrc = """ + [acl] + sources = push + [extensions] + [acl.allow.branches] + * = george + """ + pushing to ../b + query 1; heads + searching for changes + all remote heads known locally + 4 changesets found + list of changesets: + ef1ea85a6374b77d6da9dcda9541f498f2d17df7 + f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd + 911600dab2ae7a9baff75958b84fe606851ce955 + e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 + adding changesets + bundling: 1 changesets + bundling: 2 changesets + bundling: 3 changesets + bundling: 4 changesets + bundling: 1/4 manifests (25.00%) + bundling: 2/4 manifests (50.00%) + bundling: 3/4 manifests (75.00%) + bundling: 4/4 manifests (100.00%) + bundling: abc.txt 0/4 files (0.00%) + bundling: foo/Bar/file.txt 1/4 files (25.00%) + bundling: foo/file.txt 2/4 files (50.00%) + bundling: quux/file.py 3/4 files (75.00%) + changesets: 1 chunks + add changeset ef1ea85a6374 + changesets: 2 chunks + add changeset f9cafe1212c8 + changesets: 3 chunks + add changeset 911600dab2ae + changesets: 4 chunks + add changeset e8fc755d4d82 + adding manifests + manifests: 1/4 chunks (25.00%) + manifests: 2/4 chunks (50.00%) + manifests: 3/4 chunks (75.00%) + manifests: 4/4 chunks (100.00%) + adding file changes + adding abc.txt revisions + files: 1/4 chunks (25.00%) + adding foo/Bar/file.txt revisions + files: 2/4 chunks (50.00%) + adding foo/file.txt revisions + files: 3/4 chunks (75.00%) + adding quux/file.py revisions + files: 4/4 chunks (100.00%) + added 4 changesets with 4 changes to 4 files (+1 heads) + calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: acl.allow.branches enabled, 0 entries for user astro + acl: acl.deny.branches not enabled + acl: acl.allow not enabled + acl: acl.deny not enabled + error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374") + transaction abort! + rollback completed + abort: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374") + no rollback information available + 2:fb35475503ef + + $ do_push george + Pushing as user george + hgrc = """ + [acl] + sources = push + [extensions] + [acl.allow.branches] + * = george + """ + pushing to ../b + query 1; heads + searching for changes + all remote heads known locally + 4 changesets found + list of changesets: + ef1ea85a6374b77d6da9dcda9541f498f2d17df7 + f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd + 911600dab2ae7a9baff75958b84fe606851ce955 + e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 + adding changesets + bundling: 1 changesets + bundling: 2 changesets + bundling: 3 changesets + bundling: 4 changesets + bundling: 1/4 manifests (25.00%) + bundling: 2/4 manifests (50.00%) + bundling: 3/4 manifests (75.00%) + bundling: 4/4 manifests (100.00%) + bundling: abc.txt 0/4 files (0.00%) + bundling: foo/Bar/file.txt 1/4 files (25.00%) + bundling: foo/file.txt 2/4 files (50.00%) + bundling: quux/file.py 3/4 files (75.00%) + changesets: 1 chunks + add changeset ef1ea85a6374 + changesets: 2 chunks + add changeset f9cafe1212c8 + changesets: 3 chunks + add changeset 911600dab2ae + changesets: 4 chunks + add changeset e8fc755d4d82 + adding manifests + manifests: 1/4 chunks (25.00%) + manifests: 2/4 chunks (50.00%) + manifests: 3/4 chunks (75.00%) + manifests: 4/4 chunks (100.00%) + adding file changes + adding abc.txt revisions + files: 1/4 chunks (25.00%) + adding foo/Bar/file.txt revisions + files: 2/4 chunks (50.00%) + adding foo/file.txt revisions + files: 3/4 chunks (75.00%) + adding quux/file.py revisions + files: 4/4 chunks (100.00%) + added 4 changesets with 4 changes to 4 files (+1 heads) + calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: acl.allow.branches enabled, 1 entries for user george + acl: acl.deny.branches not enabled + acl: acl.allow not enabled + acl: acl.deny not enabled + acl: branch access granted: "ef1ea85a6374" on branch "default" + acl: allowing changeset ef1ea85a6374 + acl: branch access granted: "f9cafe1212c8" on branch "default" + acl: allowing changeset f9cafe1212c8 + acl: branch access granted: "911600dab2ae" on branch "default" + acl: allowing changeset 911600dab2ae + acl: branch access granted: "e8fc755d4d82" on branch "foobar" + acl: allowing changeset e8fc755d4d82 + updating the branch cache + checking for updated bookmarks + repository tip rolled back to revision 2 (undo push) + working directory now based on revision 2 + 2:fb35475503ef + + +Branch acl conflicting allow +asterisk ends up applying to all branches and allowing george to +push foobar into the remote + + $ init_config + $ echo "[acl.allow.branches]" >> $config + $ echo "foobar = astro" >> $config + $ echo "* = george" >> $config + $ do_push george + Pushing as user george + hgrc = """ + [acl] + sources = push + [extensions] + [acl.allow.branches] + foobar = astro + * = george + """ + pushing to ../b + query 1; heads + searching for changes + all remote heads known locally + invalidating branch cache (tip differs) + 4 changesets found + list of changesets: + ef1ea85a6374b77d6da9dcda9541f498f2d17df7 + f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd + 911600dab2ae7a9baff75958b84fe606851ce955 + e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 + adding changesets + bundling: 1 changesets + bundling: 2 changesets + bundling: 3 changesets + bundling: 4 changesets + bundling: 1/4 manifests (25.00%) + bundling: 2/4 manifests (50.00%) + bundling: 3/4 manifests (75.00%) + bundling: 4/4 manifests (100.00%) + bundling: abc.txt 0/4 files (0.00%) + bundling: foo/Bar/file.txt 1/4 files (25.00%) + bundling: foo/file.txt 2/4 files (50.00%) + bundling: quux/file.py 3/4 files (75.00%) + changesets: 1 chunks + add changeset ef1ea85a6374 + changesets: 2 chunks + add changeset f9cafe1212c8 + changesets: 3 chunks + add changeset 911600dab2ae + changesets: 4 chunks + add changeset e8fc755d4d82 + adding manifests + manifests: 1/4 chunks (25.00%) + manifests: 2/4 chunks (50.00%) + manifests: 3/4 chunks (75.00%) + manifests: 4/4 chunks (100.00%) + adding file changes + adding abc.txt revisions + files: 1/4 chunks (25.00%) + adding foo/Bar/file.txt revisions + files: 2/4 chunks (50.00%) + adding foo/file.txt revisions + files: 3/4 chunks (75.00%) + adding quux/file.py revisions + files: 4/4 chunks (100.00%) + added 4 changesets with 4 changes to 4 files (+1 heads) + calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: acl.allow.branches enabled, 1 entries for user george + acl: acl.deny.branches not enabled + acl: acl.allow not enabled + acl: acl.deny not enabled + acl: branch access granted: "ef1ea85a6374" on branch "default" + acl: allowing changeset ef1ea85a6374 + acl: branch access granted: "f9cafe1212c8" on branch "default" + acl: allowing changeset f9cafe1212c8 + acl: branch access granted: "911600dab2ae" on branch "default" + acl: allowing changeset 911600dab2ae + acl: branch access granted: "e8fc755d4d82" on branch "foobar" + acl: allowing changeset e8fc755d4d82 + updating the branch cache + checking for updated bookmarks + repository tip rolled back to revision 2 (undo push) + working directory now based on revision 2 + 2:fb35475503ef + +Branch acl conflicting deny + + $ init_config + $ echo "[acl.deny.branches]" >> $config + $ echo "foobar = astro" >> $config + $ echo "default = astro" >> $config + $ echo "* = george" >> $config + $ do_push george + Pushing as user george + hgrc = """ + [acl] + sources = push + [extensions] + [acl.deny.branches] + foobar = astro + default = astro + * = george + """ + pushing to ../b + query 1; heads + searching for changes + all remote heads known locally + invalidating branch cache (tip differs) + 4 changesets found + list of changesets: + ef1ea85a6374b77d6da9dcda9541f498f2d17df7 + f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd + 911600dab2ae7a9baff75958b84fe606851ce955 + e8fc755d4d8217ee5b0c2bb41558c40d43b92c01 + adding changesets + bundling: 1 changesets + bundling: 2 changesets + bundling: 3 changesets + bundling: 4 changesets + bundling: 1/4 manifests (25.00%) + bundling: 2/4 manifests (50.00%) + bundling: 3/4 manifests (75.00%) + bundling: 4/4 manifests (100.00%) + bundling: abc.txt 0/4 files (0.00%) + bundling: foo/Bar/file.txt 1/4 files (25.00%) + bundling: foo/file.txt 2/4 files (50.00%) + bundling: quux/file.py 3/4 files (75.00%) + changesets: 1 chunks + add changeset ef1ea85a6374 + changesets: 2 chunks + add changeset f9cafe1212c8 + changesets: 3 chunks + add changeset 911600dab2ae + changesets: 4 chunks + add changeset e8fc755d4d82 + adding manifests + manifests: 1/4 chunks (25.00%) + manifests: 2/4 chunks (50.00%) + manifests: 3/4 chunks (75.00%) + manifests: 4/4 chunks (100.00%) + adding file changes + adding abc.txt revisions + files: 1/4 chunks (25.00%) + adding foo/Bar/file.txt revisions + files: 2/4 chunks (50.00%) + adding foo/file.txt revisions + files: 3/4 chunks (75.00%) + adding quux/file.py revisions + files: 4/4 chunks (100.00%) + added 4 changesets with 4 changes to 4 files (+1 heads) + calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: acl.allow.branches not enabled + acl: acl.deny.branches enabled, 1 entries for user george + acl: acl.allow not enabled + acl: acl.deny not enabled + error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374") + transaction abort! + rollback completed + abort: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374") + no rollback information available + 2:fb35475503ef +
--- a/tests/test-add.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-add.t Wed May 18 09:56:27 2011 +0200 @@ -33,6 +33,41 @@ A a A b + $ echo foo > con.xml + $ hg --config ui.portablefilenames=jump add con.xml + abort: ui.portablefilenames value is invalid ('jump') + [255] + $ hg --config ui.portablefilenames=abort add con.xml + abort: filename contains 'con', which is reserved on Windows: 'con.xml' + [255] + $ hg st + A a + A b + ? con.xml + $ hg add con.xml + warning: filename contains 'con', which is reserved on Windows: 'con.xml' + $ hg st + A a + A b + A con.xml + $ echo bla > 'hello:world' + $ hg --config ui.portablefilenames=abort add + adding hello:world + abort: filename contains ':', which is reserved on Windows: 'hello:world' + [255] + $ hg st + A a + A b + A con.xml + ? hello:world + $ hg --config ui.portablefilenames=ignore add + adding hello:world + $ hg st + A a + A b + A con.xml + A hello:world + $ hg ci -m 0 --traceback should fail
--- a/tests/test-addremove.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-addremove.t Wed May 18 09:56:27 2011 +0200 @@ -10,14 +10,17 @@ foo committed changeset 0:6f7f953567a2 $ cd dir/ - $ touch ../foo_2 bar_2 + $ touch ../foo_2 bar_2 con.xml $ hg -v addremove adding dir/bar_2 + adding dir/con.xml adding foo_2 + warning: filename contains 'con', which is reserved on Windows: 'dir/con.xml' $ hg -v commit -m "add 2" dir/bar_2 + dir/con.xml foo_2 - committed changeset 1:e65414bf35c5 + committed changeset 1:6bb597da00f1 $ cd .. $ hg init sim
--- a/tests/test-alias.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-alias.t Wed May 18 09:56:27 2011 +0200 @@ -17,6 +17,7 @@ > mylog = log > lognull = log -r null > shortlog = log --template '{rev} {node|short} | {date|isodate}\n' + > positional = log --template '{\$2} {\$1} | {date|isodate}\n' > dln = lognull --debug > nousage = rollback > put = export -r 0 -o "\$FOO/%R.diff" @@ -127,6 +128,10 @@ $ hg shortlog 0 e63c23eaa88a | 1970-01-01 00:00 +0000 +positional arguments + + $ hg positional 'node|short' rev + 0 e63c23eaa88a | 1970-01-01 00:00 +0000 interaction with defaults @@ -304,13 +309,7 @@ alias for: hg root - print the root (top) of the current working directory - - Print the root directory of the current repository. - - Returns 0 on success. - - use "hg -v help rt" to show global options + use "hg help rt" to show the full help text [255] invalid global arguments for normal commands, aliases, and shell aliases
--- a/tests/test-annotate.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-annotate.t Wed May 18 09:56:27 2011 +0200 @@ -228,3 +228,8 @@ $ hg annotate --follow foo foo: foo +missing file + + $ hg ann nosuchfile + abort: nosuchfile: no such file in rev c8abddb41a00 + [255]
--- a/tests/test-archive.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-archive.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,5 @@ - $ mkdir test + $ hg init test $ cd test - $ hg init $ echo foo>foo $ hg commit -Am 1 -d '1 0' adding foo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-atomictempfile.py Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,49 @@ +import os +import glob +from mercurial.util import atomictempfile + +# basic usage +def test1_simple(): + if os.path.exists('foo'): + os.remove('foo') + file = atomictempfile('foo') + (dir, basename) = os.path.split(file._tempname) + assert not os.path.isfile('foo') + assert basename in glob.glob('.foo-*') + + file.write('argh\n') + file.rename() + + assert os.path.isfile('foo') + assert basename not in glob.glob('.foo-*') + print 'OK' + +# close() removes the temp file but does not make the write +# permanent -- essentially discards your work (WTF?!) +def test2_close(): + if os.path.exists('foo'): + os.remove('foo') + file = atomictempfile('foo') + (dir, basename) = os.path.split(file._tempname) + + file.write('yo\n') + file.close() + + assert not os.path.isfile('foo') + assert basename not in os.listdir('.') + print 'OK' + +# if a programmer screws up and passes bad args to atomictempfile, they +# get a plain ordinary TypeError, not infinite recursion +def test3_oops(): + try: + file = atomictempfile() + except TypeError: + print "OK" + else: + print "expected TypeError" + +if __name__ == '__main__': + test1_simple() + test2_close() + test3_oops()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-atomictempfile.py.out Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,3 @@ +OK +OK +OK
--- a/tests/test-audit-path.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-audit-path.t Wed May 18 09:56:27 2011 +0200 @@ -33,7 +33,7 @@ $ hg init target $ cd target - $ hg unbundle $TESTDIR/tampered.hg + $ hg unbundle $TESTDIR/bundles/tampered.hg adding changesets adding manifests adding file changes @@ -53,7 +53,7 @@ $ hg manifest -r1 foo/.hg/test $ hg update -Cr1 - abort: path 'foo/.hg/test' is inside repo 'foo' + abort: path 'foo/.hg/test' is inside nested repo 'foo' [255] attack back/test where back symlinks to ..
--- a/tests/test-bad-extension.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-bad-extension.t Wed May 18 09:56:27 2011 +0200 @@ -10,6 +10,6 @@ $ hg -q help help *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow *** failed to import extension badext2: No module named badext2 - hg help [TOPIC] + hg help [-ec] [TOPIC] show help for a given topic or a help overview
--- a/tests/test-basic.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-basic.t Wed May 18 09:56:27 2011 +0200 @@ -1,8 +1,7 @@ Create a repository: - $ mkdir t + $ hg init t $ cd t - $ hg init Make a changeset: @@ -20,6 +19,22 @@ summary: test +Verify that updating to revision 0 via commands.update() works properly + + $ cat <<EOF > update_to_rev0.py + > from mercurial import ui, hg, commands + > myui = ui.ui() + > repo = hg.repository(myui, path='.') + > commands.update(myui, repo, rev=0) + > EOF + $ hg up null + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ python ./update_to_rev0.py + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg identify -n + 0 + + Poke around at hashes: $ hg manifest --debug
--- a/tests/test-bisect.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-bisect.t Wed May 18 09:56:27 2011 +0200 @@ -377,6 +377,44 @@ date: Thu Jan 01 00:00:06 1970 +0000 summary: msg 6 + $ hg log -r "bisected(good)" + changeset: 0:b99c7b9c8e11 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: msg 0 + + changeset: 5:7874a09ea728 + user: test + date: Thu Jan 01 00:00:05 1970 +0000 + summary: msg 5 + + $ hg log -r "bisected(bad)" + changeset: 6:a3d5c6fdf0d3 + user: test + date: Thu Jan 01 00:00:06 1970 +0000 + summary: msg 6 + + $ hg log -r "bisected(skip)" + changeset: 1:5cd978ea5149 + user: test + date: Thu Jan 01 00:00:01 1970 +0000 + summary: msg 1 + + changeset: 2:db07c04beaca + user: test + date: Thu Jan 01 00:00:02 1970 +0000 + summary: msg 2 + + changeset: 3:b53bea5e2fcb + user: test + date: Thu Jan 01 00:00:03 1970 +0000 + summary: msg 3 + + changeset: 4:9b2ba8336a65 + user: test + date: Thu Jan 01 00:00:04 1970 +0000 + summary: msg 4 + $ set +e
--- a/tests/test-bisect2.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-bisect2.t Wed May 18 09:56:27 2011 +0200 @@ -416,10 +416,14 @@ summary: merge 10,13 Not all ancestors of this changeset have been checked. - To check the other ancestors, start from the common ancestor, dab8161ac8fc. - $ hg bisect -g 8 # dab8161ac8fc + Use bisect --extend to continue the bisection from + the common ancestor, dab8161ac8fc. + $ hg bisect --extend + Extending search to changeset 8:dab8161ac8fc + 2 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg bisect -g # dab8161ac8fc Testing changeset 9:3c77083deb4a (3 changesets remaining, ~1 tests) - 1 files updated, 0 files merged, 2 files removed, 0 files unresolved + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg bisect -b The first bad revision is: changeset: 9:3c77083deb4a
--- a/tests/test-bookmarks-current.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-bookmarks-current.t Wed May 18 09:56:27 2011 +0200 @@ -99,3 +99,29 @@ $ hg bookmark * Y 0:719295282060 + +set bookmark Z using -i + + $ hg bookmark -r . -i Z + $ hg bookmarks + * Y 0:719295282060 + Z 0:719295282060 + +deactivate current bookmark using -i + + $ hg bookmark -i Y + $ hg bookmarks + Y 0:719295282060 + Z 0:719295282060 + + $ hg up -q Y + $ hg bookmarks + * Y 0:719295282060 + Z 0:719295282060 + +deactivate current bookmark while renaming + + $ hg bookmark -i -m Y X + $ hg bookmarks + X 0:719295282060 + Z 0:719295282060
--- a/tests/test-bookmarks-pushpull.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-bookmarks-pushpull.t Wed May 18 09:56:27 2011 +0200 @@ -26,6 +26,7 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files + updating bookmark Y (run 'hg update' to get a working copy) $ hg bookmarks Y 0:4e3505fd9583 @@ -38,7 +39,6 @@ Z 4e3505fd95835d721066b76e75dbb8cc554d7f77 $ hg pull -B X ../a pulling from ../a - searching for changes no changes found importing bookmark X $ hg bookmark @@ -172,9 +172,22 @@ foobar 000000000000 $ hg pull -B Z http://localhost:$HGPORT/ pulling from http://localhost:$HGPORT/ - searching for changes no changes found not updating divergent bookmark X importing bookmark Z + $ hg clone http://localhost:$HGPORT/ cloned-bookmarks + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 3 files (+1 heads) + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg -R cloned-bookmarks bookmarks + X 1:9b140be10808 + Y 0:4e3505fd9583 + Z 2:0d2164f0ce0d + foo -1:000000000000 + foobar -1:000000000000 $ kill `cat ../hg.pid`
--- a/tests/test-bookmarks.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-bookmarks.t Wed May 18 09:56:27 2011 +0200 @@ -84,6 +84,10 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: 1 + $ hg log -r 'bookmark(unknown)' + abort: bookmark 'unknown' does not exist + [255] + $ hg help revsets | grep 'bookmark(' "bookmark([name])" @@ -116,13 +120,13 @@ rename nonexistent bookmark $ hg bookmark -m A B - abort: a bookmark of this name does not exist + abort: bookmark 'A' does not exist [255] rename to existent bookmark $ hg bookmark -m X Y - abort: a bookmark of the same name already exists + abort: bookmark 'Y' already exists (use -f to force) [255] force rename to existent bookmark @@ -151,7 +155,7 @@ delete nonexistent bookmark $ hg bookmark -d A - abort: a bookmark of this name does not exist + abort: bookmark 'A' does not exist [255] bookmark name with spaces should be stripped @@ -189,7 +193,7 @@ bookmark with existing name $ hg bookmark Z - abort: a bookmark of the same name already exists + abort: bookmark 'Z' already exists (use -f to force) [255] force bookmark with existing name @@ -248,13 +252,93 @@ test rollback + $ echo foo > f1 + $ hg ci -Amr + adding f1 $ hg bookmark -f Y -r 1 $ hg bookmark -f Z -r 1 $ hg rollback - repository tip rolled back to revision 1 (undo commit) - working directory now based on revision 0 + repository tip rolled back to revision 2 (undo commit) + working directory now based on revision 2 + $ hg bookmarks + X2 1:925d80f479bb + Y 2:db815d6d32e6 + * Z 2:db815d6d32e6 + x y 2:db815d6d32e6 + +test clone + $ hg bookmarks - X 0:f7b1eb17ad24 + X2 1:925d80f479bb + Y 2:db815d6d32e6 + * Z 2:db815d6d32e6 + x y 2:db815d6d32e6 + $ hg clone . cloned-bookmarks + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg -R cloned-bookmarks bookmarks + X2 1:925d80f479bb + Y 2:db815d6d32e6 + Z 2:db815d6d32e6 + x y 2:db815d6d32e6 + +test clone with pull protocol + + $ hg clone --pull . cloned-bookmarks-pull + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 3 files (+1 heads) + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg -R cloned-bookmarks-pull bookmarks X2 1:925d80f479bb - Y -1:000000000000 - * Z 0:f7b1eb17ad24 + Y 2:db815d6d32e6 + Z 2:db815d6d32e6 + x y 2:db815d6d32e6 + +test clone with a specific revision + + $ hg clone -r 925d80 . cloned-bookmarks-rev + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg -R cloned-bookmarks-rev bookmarks + X2 1:925d80f479bb + +create bundle with two heads + + $ hg clone . tobundle + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo x > tobundle/x + $ hg -R tobundle add tobundle/x + $ hg -R tobundle commit -m'x' + $ hg -R tobundle update -r -2 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo y > tobundle/y + $ hg -R tobundle branch test + marked working directory as branch test + $ hg -R tobundle add tobundle/y + $ hg -R tobundle commit -m'y' + $ hg -R tobundle bundle tobundle.hg + searching for changes + 2 changesets found + $ hg unbundle tobundle.hg + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files (+1 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg update + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg bookmarks + X2 1:925d80f479bb + Y 2:db815d6d32e6 + * Z 3:125c9a1d6df6 + x y 2:db815d6d32e6 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-branch-tag-confict.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,62 @@ +Initial setup. + + $ hg init repo + $ cd repo + $ touch thefile + $ hg ci -A -m 'Initial commit.' + adding thefile + +Create a tag. + + $ hg tag branchortag + +Create a branch with the same name as the tag. + + $ hg branch branchortag + marked working directory as branch branchortag + $ hg ci -m 'Create a branch with the same name as a tag.' + +This is what we have: + + $ hg log + changeset: 2:10519b3f489a + branch: branchortag + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: Create a branch with the same name as a tag. + + changeset: 1:2635c45ca99b + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: Added tag branchortag for changeset f57387372b5d + + changeset: 0:f57387372b5d + tag: branchortag + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: Initial commit. + +Update to the tag: + + $ hg up 'tag(branchortag)' + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg parents + changeset: 0:f57387372b5d + tag: branchortag + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: Initial commit. + +Updating to the branch: + + $ hg up 'branch(branchortag)' + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg parents + changeset: 2:10519b3f489a + branch: branchortag + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: Create a branch with the same name as a tag. +
--- a/tests/test-branches.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-branches.t Wed May 18 09:56:27 2011 +0200 @@ -230,7 +230,7 @@ default 0:19709c5a4e75 (inactive) $ hg branches -c a branch name much longer than the default justification used by branches 7:10ff5895aa57 - b 12:2da6583810df (closed) + b 12:e3d49c0575d8 (closed) c 6:589736a22561 (inactive) a 5:d8cbc61dbaa6 (inactive) default 0:19709c5a4e75 (inactive) @@ -240,7 +240,7 @@ no open branch heads found on branches b [1] $ hg heads --closed b - changeset: 12:2da6583810df + changeset: 12:e3d49c0575d8 branch: b tag: tip parent: 8:eebb944467c9 @@ -248,7 +248,7 @@ date: Thu Jan 01 00:00:09 1970 +0000 summary: close this part branch too - changeset: 11:c84627f3c15d + changeset: 11:d3f163457ebf branch: b user: test date: Thu Jan 01 00:00:09 1970 +0000 @@ -261,13 +261,13 @@ --- branch b is back in action $ hg branches -a - b 13:6ac12926b8c3 + b 13:e23b5505d1ad a branch name much longer than the default justification used by branches 7:10ff5895aa57 ---- test heads listings $ hg heads - changeset: 13:6ac12926b8c3 + changeset: 13:e23b5505d1ad branch: b tag: tip user: test @@ -325,7 +325,7 @@ branch b $ hg heads b - changeset: 13:6ac12926b8c3 + changeset: 13:e23b5505d1ad branch: b tag: tip user: test @@ -333,14 +333,14 @@ summary: reopen branch with a change $ hg heads --closed b - changeset: 13:6ac12926b8c3 + changeset: 13:e23b5505d1ad branch: b tag: tip user: test date: Thu Jan 01 00:00:09 1970 +0000 summary: reopen branch with a change - changeset: 11:c84627f3c15d + changeset: 11:d3f163457ebf branch: b user: test date: Thu Jan 01 00:00:09 1970 +0000 @@ -350,6 +350,8 @@ $ echo "[extensions]" >> $HGRCPATH $ echo "color =" >> $HGRCPATH + $ echo "[color]" >> $HGRCPATH + $ echo "mode = ansi" >> $HGRCPATH $ hg up -C c 3 files updated, 0 files merged, 2 files removed, 0 files unresolved @@ -357,7 +359,7 @@ $ hg up -C b 2 files updated, 0 files merged, 3 files removed, 0 files unresolved $ hg branches --color=always - \x1b[0;32mb\x1b[0m \x1b[0;33m 13:6ac12926b8c3\x1b[0m (esc) + \x1b[0;32mb\x1b[0m \x1b[0;33m 13:e23b5505d1ad\x1b[0m (esc) \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m \x1b[0;33m7:10ff5895aa57\x1b[0m (esc) \x1b[0;0ma\x1b[0m \x1b[0;33m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc) \x1b[0;0mdefault\x1b[0m \x1b[0;33m 0:19709c5a4e75\x1b[0m (inactive) (esc) @@ -365,9 +367,9 @@ default closed branch color: $ hg branches --color=always --closed - \x1b[0;32mb\x1b[0m \x1b[0;33m 13:6ac12926b8c3\x1b[0m (esc) + \x1b[0;32mb\x1b[0m \x1b[0;33m 13:e23b5505d1ad\x1b[0m (esc) \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m \x1b[0;33m7:10ff5895aa57\x1b[0m (esc) - \x1b[0;30;1mc\x1b[0m \x1b[0;33m 14:717d2e6fabe1\x1b[0m (closed) (esc) + \x1b[0;30;1mc\x1b[0m \x1b[0;33m 14:f894c25619d3\x1b[0m (closed) (esc) \x1b[0;0ma\x1b[0m \x1b[0;33m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc) \x1b[0;0mdefault\x1b[0m \x1b[0;33m 0:19709c5a4e75\x1b[0m (inactive) (esc) @@ -383,7 +385,7 @@ custom branch colors: $ hg branches --color=always - \x1b[0;31mb\x1b[0m \x1b[0;36m 13:6ac12926b8c3\x1b[0m (esc) + \x1b[0;31mb\x1b[0m \x1b[0;36m 13:e23b5505d1ad\x1b[0m (esc) \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m \x1b[0;36m7:10ff5895aa57\x1b[0m (esc) \x1b[0;35ma\x1b[0m \x1b[0;36m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc) \x1b[0;35mdefault\x1b[0m \x1b[0;36m 0:19709c5a4e75\x1b[0m (inactive) (esc) @@ -391,8 +393,8 @@ custom closed branch color: $ hg branches --color=always --closed - \x1b[0;31mb\x1b[0m \x1b[0;36m 13:6ac12926b8c3\x1b[0m (esc) + \x1b[0;31mb\x1b[0m \x1b[0;36m 13:e23b5505d1ad\x1b[0m (esc) \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m \x1b[0;36m7:10ff5895aa57\x1b[0m (esc) - \x1b[0;34mc\x1b[0m \x1b[0;36m 14:717d2e6fabe1\x1b[0m (closed) (esc) + \x1b[0;34mc\x1b[0m \x1b[0;36m 14:f894c25619d3\x1b[0m (closed) (esc) \x1b[0;35ma\x1b[0m \x1b[0;36m 5:d8cbc61dbaa6\x1b[0m (inactive) (esc) \x1b[0;35mdefault\x1b[0m \x1b[0;36m 0:19709c5a4e75\x1b[0m (inactive) (esc)
--- a/tests/test-bundle-r.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-bundle-r.t Wed May 18 09:56:27 2011 +0200 @@ -1,62 +1,15 @@ $ hg init test $ cd test - $ echo "0" >> afile - $ hg add afile - $ hg commit -m "0.0" - $ echo "1" >> afile - $ hg commit -m "0.1" - $ echo "2" >> afile - $ hg commit -m "0.2" - $ echo "3" >> afile - $ hg commit -m "0.3" - $ hg update -C 0 + $ hg unbundle $TESTDIR/bundles/remote.hg + adding changesets + adding manifests + adding file changes + added 9 changesets with 7 changes to 4 files (+1 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg up tip 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ echo "1" >> afile - $ hg commit -m "1.1" - created new head - $ echo "2" >> afile - $ hg commit -m "1.2" - $ echo "a line" > fred - $ echo "3" >> afile - $ hg add fred - $ hg commit -m "1.3" - $ hg mv afile adifferentfile - $ hg commit -m "1.3m" - $ hg update -C 3 - 1 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ hg mv afile anotherfile - $ hg commit -m "0.3m" - $ hg debugindex .hg/store/data/afile.i - rev offset length base linkrev nodeid p1 p2 - 0 0 3 0 0 362fef284ce2 000000000000 000000000000 - 1 3 5 1 1 125144f7e028 362fef284ce2 000000000000 - 2 8 7 2 2 4c982badb186 125144f7e028 000000000000 - 3 15 9 3 3 19b1fc555737 4c982badb186 000000000000 - $ hg debugindex .hg/store/data/adifferentfile.i - rev offset length base linkrev nodeid p1 p2 - 0 0 75 0 7 2565f3199a74 000000000000 000000000000 - $ hg debugindex .hg/store/data/anotherfile.i - rev offset length base linkrev nodeid p1 p2 - 0 0 75 0 8 2565f3199a74 000000000000 000000000000 - $ hg debugindex .hg/store/data/fred.i - rev offset length base linkrev nodeid p1 p2 - 0 0 8 0 6 12ab3bcc5ea4 000000000000 000000000000 - $ hg debugindex .hg/store/00manifest.i - rev offset length base linkrev nodeid p1 p2 - 0 0 48 0 0 43eadb1d2d06 000000000000 000000000000 - 1 48 48 1 1 8b89697eba2c 43eadb1d2d06 000000000000 - 2 96 48 2 2 626a32663c2f 8b89697eba2c 000000000000 - 3 144 48 3 3 f54c32f13478 626a32663c2f 000000000000 - 4 192 58 3 6 de68e904d169 626a32663c2f 000000000000 - 5 250 68 3 7 09bb521d218d de68e904d169 000000000000 - 6 318 54 6 8 1fde233dfb0f f54c32f13478 000000000000 - $ hg verify - checking changesets - checking manifests - crosschecking files in changesets and manifests - checking files - 4 files, 9 changesets, 7 total revisions $ cd .. + $ for i in 0 1 2 3 4 5 6 7 8; do > mkdir test-"$i" > hg --cwd test-"$i" init @@ -79,7 +32,7 @@ crosschecking files in changesets and manifests checking files 1 files, 1 changesets, 1 total revisions - 0:f9ee2f85a263 + 0:bfaf4b5cbf01 searching for changes 2 changesets found adding changesets @@ -92,7 +45,7 @@ crosschecking files in changesets and manifests checking files 1 files, 2 changesets, 2 total revisions - 1:34c2bf6b0626 + 1:21f32785131f searching for changes 3 changesets found adding changesets @@ -105,7 +58,7 @@ crosschecking files in changesets and manifests checking files 1 files, 3 changesets, 3 total revisions - 2:e38ba6f5b7e0 + 2:4ce51a113780 searching for changes 4 changesets found adding changesets @@ -118,7 +71,7 @@ crosschecking files in changesets and manifests checking files 1 files, 4 changesets, 4 total revisions - 3:eebf5a27f8ca + 3:93ee6ab32777 searching for changes 2 changesets found adding changesets @@ -131,7 +84,7 @@ crosschecking files in changesets and manifests checking files 1 files, 2 changesets, 2 total revisions - 1:095197eb4973 + 1:c70afb1ee985 searching for changes 3 changesets found adding changesets @@ -144,7 +97,7 @@ crosschecking files in changesets and manifests checking files 1 files, 3 changesets, 3 total revisions - 2:1bb50a9436a7 + 2:f03ae5a9b979 searching for changes 4 changesets found adding changesets @@ -157,7 +110,7 @@ crosschecking files in changesets and manifests checking files 2 files, 4 changesets, 5 total revisions - 3:7373c1169842 + 3:095cb14b1b4d searching for changes 5 changesets found adding changesets @@ -170,7 +123,7 @@ crosschecking files in changesets and manifests checking files 3 files, 5 changesets, 6 total revisions - 4:a6a34bfa0076 + 4:faa2e4234c7a searching for changes 5 changesets found adding changesets @@ -183,7 +136,7 @@ crosschecking files in changesets and manifests checking files 2 files, 5 changesets, 5 total revisions - 4:aa35859c02ea + 4:916f1afdef90 $ cd test-8 $ hg pull ../test-7 pulling from ../test-7 @@ -231,7 +184,8 @@ issue76 msg2163 $ hg -R test bundle --base 3 -r 3 -r 3 test-bundle-cset-3.hg - 1 changesets found + no changes found + [1] Issue1910: 'hg bundle --base $head' does not exclude $head from result @@ -247,18 +201,18 @@ revision 2 $ hg tip -q - 2:e38ba6f5b7e0 + 2:4ce51a113780 $ hg unbundle ../test-bundle-should-fail.hg adding changesets transaction abort! rollback completed - abort: 00changelog.i@eebf5a27f8ca: unknown parent! + abort: 00changelog.i@93ee6ab32777: unknown parent! [255] revision 2 $ hg tip -q - 2:e38ba6f5b7e0 + 2:4ce51a113780 $ hg unbundle ../test-bundle-all.hg adding changesets adding manifests @@ -269,7 +223,7 @@ revision 8 $ hg tip -q - 8:aa35859c02ea + 8:916f1afdef90 $ hg verify checking changesets checking manifests @@ -283,7 +237,7 @@ revision 2 $ hg tip -q - 2:e38ba6f5b7e0 + 2:4ce51a113780 $ hg unbundle ../test-bundle-branch1.hg adding changesets adding manifests @@ -294,7 +248,7 @@ revision 4 $ hg tip -q - 4:aa35859c02ea + 4:916f1afdef90 $ hg verify checking changesets checking manifests @@ -314,7 +268,7 @@ revision 6 $ hg tip -q - 6:a6a34bfa0076 + 6:faa2e4234c7a $ hg verify checking changesets checking manifests @@ -334,7 +288,7 @@ revision 4 $ hg tip -q - 4:aa35859c02ea + 4:916f1afdef90 $ hg verify checking changesets checking manifests @@ -367,7 +321,7 @@ revision 9 $ hg tip -q - 9:905597b0d5d4 + 9:03fc0b0e347c $ hg verify checking changesets checking manifests
--- a/tests/test-bundle.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-bundle.t Wed May 18 09:56:27 2011 +0200 @@ -206,7 +206,7 @@ hg -R bundle://../full.hg verify $ hg pull bundle://../full.hg - pulling from bundle://../full.hg + pulling from bundle:../full.hg requesting all changes adding changesets adding manifests @@ -310,7 +310,7 @@ Incoming full.hg in partial $ hg incoming bundle://../full.hg - comparing with bundle://../full.hg + comparing with bundle:../full.hg searching for changes changeset: 4:095197eb4973 parent: 0:f9ee2f85a263 @@ -470,6 +470,22 @@ $ cd .. +test bundle with # in the filename (issue2154): + + $ cp bundle.hg 'test#bundle.hg' + $ cd orig + $ hg incoming '../test#bundle.hg' + comparing with ../test + abort: unknown revision 'bundle.hg'! + [255] + +note that percent encoding is not handled: + + $ hg incoming ../test%23bundle.hg + abort: repository ../test%23bundle.hg not found! + [255] + $ cd .. + test for http://mercurial.selenic.com/bts/issue1144 test that verify bundle does not traceback @@ -545,32 +561,17 @@ == bundling $ hg bundle bundle.hg part --debug + query 1; heads searching for changes - common changesets up to c0025332f9ed + all remote heads known locally 2 changesets found list of changesets: d2ae7f538514cd87c17547b0de4cea71fe1af9fb 5ece8e77363e2b5269e27c66828b72da29e4341a - bundling: 0 changesets - bundling: 0 changesets - bundling: 0 changesets - bundling: 1 changesets - bundling: 1 changesets bundling: 1 changesets bundling: 2 changesets - bundling: 0/2 manifests (0.00%) - bundling: 0/2 manifests (0.00%) - bundling: 0/2 manifests (0.00%) - bundling: 1/2 manifests (50.00%) - bundling: 1/2 manifests (50.00%) bundling: 1/2 manifests (50.00%) bundling: 2/2 manifests (100.00%) bundling: b 0/2 files (0.00%) - bundling: b 0/2 files (0.00%) - bundling: b 0/2 files (0.00%) - bundling: b 0/2 files (0.00%) - bundling: b1 1/2 files (50.00%) - bundling: b1 1/2 files (50.00%) - bundling: b1 1/2 files (50.00%) bundling: b1 1/2 files (50.00%)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-casecollision.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,40 @@ +run only on case-sensitive filesystems + + $ "$TESTDIR/hghave" no-icasefs || exit 80 + +test file addition with colliding case + + $ hg init repo1 + $ cd repo1 + $ echo a > a + $ echo A > A + $ hg add a + $ hg st + A a + ? A + $ hg add --config ui.portablefilenames=abort A + abort: possible case-folding collision for A + [255] + $ hg st + A a + ? A + $ hg add A + warning: possible case-folding collision for A + $ hg st + A A + A a + $ hg forget A + $ hg st + A a + ? A + $ hg add --config ui.portablefilenames=no A + $ hg st + A A + A a + +case changing rename must not warn or abort + + $ echo c > c + $ hg ci -qAmx + $ hg mv c C + $ cd ..
--- a/tests/test-changelog-exec.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-changelog-exec.t Wed May 18 09:56:27 2011 +0200 @@ -48,6 +48,6 @@ - $ hg debugindex .hg/store/data/bar.i + $ hg debugindex bar rev offset length base linkrev nodeid p1 p2 0 0 5 0 1 b004912a8510 000000000000 000000000000
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-check-pyflakes.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,15 @@ + $ "$TESTDIR/hghave" pyflakes || exit 80 + $ cd $(dirname $TESTDIR) + $ pyflakes mercurial hgext 2>&1 | $TESTDIR/filterpyflakes.py + mercurial/hgweb/server.py:*: 'activeCount' imported but unused (glob) + mercurial/commands.py:*: 'base85' imported but unused (glob) + mercurial/commands.py:*: 'bdiff' imported but unused (glob) + mercurial/commands.py:*: 'mpatch' imported but unused (glob) + mercurial/commands.py:*: 'osutil' imported but unused (glob) + mercurial/revlog.py:*: 'short' imported but unused (glob) + hgext/inotify/linux/__init__.py:*: 'from _inotify import *' used; unable to detect undefined names (glob) + mercurial/util.py:*: 'from posix import *' used; unable to detect undefined names (glob) + mercurial/windows.py:*: 'from win32 import *' used; unable to detect undefined names (glob) + mercurial/util.py:*: 'from windows import *' used; unable to detect undefined names (glob) + +
--- a/tests/test-clone-failure.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-clone-failure.t Wed May 18 09:56:27 2011 +0200 @@ -21,9 +21,8 @@ Inaccessible destination - $ mkdir b + $ hg init b $ cd b - $ hg init $ hg clone . ../a abort: Permission denied: ../a [255] @@ -44,10 +43,7 @@ Default destination, same directory - $ mkdir q - $ cd q - $ hg init - $ cd .. + $ hg init q $ hg clone q destination directory: q abort: destination 'q' is not empty
--- a/tests/test-clone-r.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-clone-r.t Wed May 18 09:56:27 2011 +0200 @@ -37,26 +37,26 @@ $ hg mv afile anotherfile $ hg commit -m "0.3m" - $ hg debugindex -f 1 .hg/store/data/afile.i + $ hg debugindex -f 1 afile rev flag offset length size base link p1 p2 nodeid 0 0000 0 3 2 0 0 -1 -1 362fef284ce2 1 0000 3 5 4 1 1 0 -1 125144f7e028 2 0000 8 7 6 2 2 1 -1 4c982badb186 3 0000 15 9 8 3 3 2 -1 19b1fc555737 - $ hg debugindex .hg/store/data/adifferentfile.i + $ hg debugindex adifferentfile rev offset length base linkrev nodeid p1 p2 0 0 75 0 7 2565f3199a74 000000000000 000000000000 - $ hg debugindex .hg/store/data/anotherfile.i + $ hg debugindex anotherfile rev offset length base linkrev nodeid p1 p2 0 0 75 0 8 2565f3199a74 000000000000 000000000000 - $ hg debugindex .hg/store/data/fred.i + $ hg debugindex fred rev offset length base linkrev nodeid p1 p2 0 0 8 0 6 12ab3bcc5ea4 000000000000 000000000000 - $ hg debugindex .hg/store/00manifest.i + $ hg debugindex --manifest rev offset length base linkrev nodeid p1 p2 0 0 48 0 0 43eadb1d2d06 000000000000 000000000000 1 48 48 1 1 8b89697eba2c 43eadb1d2d06 000000000000
--- a/tests/test-clone.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-clone.t Wed May 18 09:56:27 2011 +0200 @@ -1,8 +1,7 @@ Prepare repo a: - $ mkdir a + $ hg init a $ cd a - $ hg init $ echo a > a $ hg add a $ hg commit -m test
--- a/tests/test-command-template.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-command-template.t Wed May 18 09:56:27 2011 +0200 @@ -77,13 +77,13 @@ 7:-1 29114dbae42b 1970-01-12 13:46 +0000 user second - 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person + 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person merge 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person new head - 4 32a18f097fcc 1970-01-17 04:53 +0000 person + 4 bbe44766e73d 1970-01-17 04:53 +0000 person new branch 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person @@ -106,13 +106,13 @@ 7:-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname> second - 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person + 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person merge 5:3 13207e5a10d9 1970-01-18 08:40 +0000 person new head - 4 32a18f097fcc 1970-01-17 04:53 +0000 person + 4 bbe44766e73d 1970-01-17 04:53 +0000 person new branch 3 10e46f2dcbf4 1970-01-16 01:06 +0000 person @@ -139,13 +139,13 @@ 7:-1,-1 29114dbae42b 1970-01-12 13:46 +0000 User Name <user@hostname> second - 6:5,4 c7b487c6c50e 1970-01-18 08:40 +0000 person + 6:5,4 d41e714fe50d 1970-01-18 08:40 +0000 person merge 5:3,-1 13207e5a10d9 1970-01-18 08:40 +0000 person new head - 4:3,-1 32a18f097fcc 1970-01-17 04:53 +0000 person + 4:3,-1 bbe44766e73d 1970-01-17 04:53 +0000 person new branch 3:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person @@ -182,9 +182,9 @@ <date>1970-01-12T13:46:40+00:00</date> <msg xml:space="preserve">second</msg> </logentry> - <logentry revision="6" node="c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f"> + <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b"> <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" /> - <parent revision="4" node="32a18f097fcccf76ef282f62f8a85b3adf8d13c4" /> + <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" /> <author email="person">person</author> <date>1970-01-18T08:40:01+00:00</date> <msg xml:space="preserve">merge</msg> @@ -195,7 +195,7 @@ <date>1970-01-18T08:40:00+00:00</date> <msg xml:space="preserve">new head</msg> </logentry> - <logentry revision="4" node="32a18f097fcccf76ef282f62f8a85b3adf8d13c4"> + <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74"> <branch>foo</branch> <author email="person">person</author> <date>1970-01-17T04:53:20+00:00</date> @@ -253,9 +253,9 @@ <path action="A">second</path> </paths> </logentry> - <logentry revision="6" node="c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f"> + <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b"> <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" /> - <parent revision="4" node="32a18f097fcccf76ef282f62f8a85b3adf8d13c4" /> + <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" /> <author email="person">person</author> <date>1970-01-18T08:40:01+00:00</date> <msg xml:space="preserve">merge</msg> @@ -271,7 +271,7 @@ <path action="A">d</path> </paths> </logentry> - <logentry revision="4" node="32a18f097fcccf76ef282f62f8a85b3adf8d13c4"> + <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74"> <branch>foo</branch> <author email="person">person</author> <date>1970-01-17T04:53:20+00:00</date> @@ -348,9 +348,9 @@ </paths> <extra key="branch">default</extra> </logentry> - <logentry revision="6" node="c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f"> + <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b"> <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" /> - <parent revision="4" node="32a18f097fcccf76ef282f62f8a85b3adf8d13c4" /> + <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" /> <author email="person">person</author> <date>1970-01-18T08:40:01+00:00</date> <msg xml:space="preserve">merge</msg> @@ -369,7 +369,7 @@ </paths> <extra key="branch">default</extra> </logentry> - <logentry revision="4" node="32a18f097fcccf76ef282f62f8a85b3adf8d13c4"> + <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74"> <branch>foo</branch> <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" /> <parent revision="-1" node="0000000000000000000000000000000000000000" /> @@ -510,7 +510,7 @@ 1970-01-18 person <person> * merge - [c7b487c6c50e] + [d41e714fe50d] * d: new head @@ -519,7 +519,7 @@ 1970-01-17 person <person> * new branch - [32a18f097fcc] <foo> + [bbe44766e73d] <foo> 1970-01-16 person <person> @@ -560,12 +560,12 @@ 1970-01-18 person <person> * merge - [c7b487c6c50e] + [d41e714fe50d] 1970-01-17 person <person> * new branch - [32a18f097fcc] <foo> + [bbe44766e73d] <foo> Keys work: @@ -886,63 +886,63 @@ files--debug: c files--debug: b files--debug: a - manifest: 8:94961b75a2da - manifest: 7:f2dbc354b94e - manifest: 6:91015e9dbdd7 - manifest: 5:4dc3def4f9b4 - manifest: 4:90ae8dda64e1 + manifest: 6:94961b75a2da + manifest: 5:f2dbc354b94e + manifest: 4:4dc3def4f9b4 + manifest: 4:4dc3def4f9b4 + manifest: 3:cb5a1327723b manifest: 3:cb5a1327723b manifest: 2:6e0e82995c35 manifest: 1:4e8d705b1e53 manifest: 0:a0c8bcbbb45c - manifest--verbose: 8:94961b75a2da - manifest--verbose: 7:f2dbc354b94e - manifest--verbose: 6:91015e9dbdd7 - manifest--verbose: 5:4dc3def4f9b4 - manifest--verbose: 4:90ae8dda64e1 + manifest--verbose: 6:94961b75a2da + manifest--verbose: 5:f2dbc354b94e + manifest--verbose: 4:4dc3def4f9b4 + manifest--verbose: 4:4dc3def4f9b4 + manifest--verbose: 3:cb5a1327723b manifest--verbose: 3:cb5a1327723b manifest--verbose: 2:6e0e82995c35 manifest--verbose: 1:4e8d705b1e53 manifest--verbose: 0:a0c8bcbbb45c - manifest--debug: 8:94961b75a2da554b4df6fb599e5bfc7d48de0c64 - manifest--debug: 7:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf - manifest--debug: 6:91015e9dbdd76a6791085d12b0a0ec7fcd22ffbf - manifest--debug: 5:4dc3def4f9b4c6e8de820f6ee74737f91e96a216 - manifest--debug: 4:90ae8dda64e1a876c792bccb9af66284f6018363 + manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64 + manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf + manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216 + manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216 + manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1 manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55 manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 node: 95c24699272ef57d062b8bccc32c878bf841784a node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453 - node: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f + node: d41e714fe50d9e4a5f11b4d595d543481b5f980b node: 13207e5a10d9fd28ec424934298e176197f2c67f - node: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4 + node: bbe44766e73d5f11ed2177f1838de10c53ef3e74 node: 10e46f2dcbf4823578cf180f33ecf0b957964c47 node: 97054abb4ab824450e9164180baf491ae0078465 node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965 node: 1e4e1b8f71e05681d422154f5421e385fec3454f node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453 - node--verbose: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f + node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f - node--verbose: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4 + node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74 node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47 node--verbose: 97054abb4ab824450e9164180baf491ae0078465 node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965 node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f node--debug: 95c24699272ef57d062b8bccc32c878bf841784a node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453 - node--debug: c7b487c6c50ef1cf464cafdc4f4f5e615fc5999f + node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f - node--debug: 32a18f097fcccf76ef282f62f8a85b3adf8d13c4 + node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74 node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47 node--debug: 97054abb4ab824450e9164180baf491ae0078465 node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965 node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f parents: parents: -1:000000000000 - parents: 5:13207e5a10d9 4:32a18f097fcc + parents: 5:13207e5a10d9 4:bbe44766e73d parents: 3:10e46f2dcbf4 parents: parents: @@ -951,7 +951,7 @@ parents: parents--verbose: parents--verbose: -1:000000000000 - parents--verbose: 5:13207e5a10d9 4:32a18f097fcc + parents--verbose: 5:13207e5a10d9 4:bbe44766e73d parents--verbose: 3:10e46f2dcbf4 parents--verbose: parents--verbose: @@ -960,7 +960,7 @@ parents--verbose: parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000 parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000 - parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:32a18f097fcccf76ef282f62f8a85b3adf8d13c4 + parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000 parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000 parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000 @@ -1115,7 +1115,7 @@ $ hg log --template '{date|age}\n' > /dev/null || exit 1 $ hg log -l1 --template '{date|age}\n' - in the future + 8 years from now $ hg log --template '{date|date}\n' Wed Jan 01 10:01:00 2020 +0000 Mon Jan 12 13:46:40 1970 +0000 @@ -1174,9 +1174,9 @@ $ hg log --template '{node|short}\n' 95c24699272e 29114dbae42b - c7b487c6c50e + d41e714fe50d 13207e5a10d9 - 32a18f097fcc + bbe44766e73d 10e46f2dcbf4 97054abb4ab8 b608e9d1a3f0 @@ -1197,9 +1197,9 @@ 8: 7: 8:95c24699272e 6: - 5: 6:c7b487c6c50e - 4: 6:c7b487c6c50e - 3: 4:32a18f097fcc 5:13207e5a10d9 + 5: 6:d41e714fe50d + 4: 6:d41e714fe50d + 3: 4:bbe44766e73d 5:13207e5a10d9 2: 3:10e46f2dcbf4 1: 2:97054abb4ab8 0: 1:b608e9d1a3f0
--- a/tests/test-commit-copy.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-commit-copy.t Wed May 18 09:56:27 2011 +0200 @@ -10,7 +10,7 @@ $ hg debugrename foo foo renamed from bar:26d3ca0dfd18e44d796b564e38dd173c9668d3a9 - $ hg debugindex .hg/store/data/bar.i + $ hg debugindex bar rev offset length base linkrev nodeid p1 p2 0 0 6 0 0 26d3ca0dfd18 000000000000 000000000000 1 6 7 1 1 d267bddd54f7 26d3ca0dfd18 000000000000
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-commit-multiple.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,129 @@ +# reproduce issue2264, issue2516 + +create test repo + $ cat <<EOF >> $HGRCPATH + > [extensions] + > transplant = + > graphlog = + > EOF + $ hg init repo + $ cd repo + $ template="{rev} {desc|firstline} [{branch}]\n" + +# we need to start out with two changesets on the default branch +# in order to avoid the cute little optimization where transplant +# pulls rather than transplants +add initial changesets + $ echo feature1 > file1 + $ hg ci -Am"feature 1" + adding file1 + $ echo feature2 >> file2 + $ hg ci -Am"feature 2" + adding file2 + +# The changes to 'bugfix' are enough to show the bug: in fact, with only +# those changes, it's a very noisy crash ("RuntimeError: nothing +# committed after transplant"). But if we modify a second file in the +# transplanted changesets, the bug is much more subtle: transplant +# silently drops the second change to 'bugfix' on the floor, and we only +# see it when we run 'hg status' after transplanting. Subtle data loss +# bugs are worse than crashes, so reproduce the subtle case here. +commit bug fixes on bug fix branch + $ hg branch fixes + marked working directory as branch fixes + $ echo fix1 > bugfix + $ echo fix1 >> file1 + $ hg ci -Am"fix 1" + adding bugfix + $ echo fix2 > bugfix + $ echo fix2 >> file1 + $ hg ci -Am"fix 2" + $ hg glog --template="$template" + @ 3 fix 2 [fixes] + | + o 2 fix 1 [fixes] + | + o 1 feature 2 [default] + | + o 0 feature 1 [default] + +transplant bug fixes onto release branch + $ hg update 0 + 1 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg branch release + marked working directory as branch release + $ hg transplant 2 3 + applying [0-9a-f]{12} (re) + [0-9a-f]{12} transplanted to [0-9a-f]{12} (re) + applying [0-9a-f]{12} (re) + [0-9a-f]{12} transplanted to [0-9a-f]{12} (re) + $ hg glog --template="$template" + @ 5 fix 2 [release] + | + o 4 fix 1 [release] + | + | o 3 fix 2 [fixes] + | | + | o 2 fix 1 [fixes] + | | + | o 1 feature 2 [default] + |/ + o 0 feature 1 [default] + + $ hg status + $ hg status --rev 0:4 + M file1 + A bugfix + $ hg status --rev 4:5 + M bugfix + M file1 + +now test that we fixed the bug for all scripts/extensions + $ cat > $TESTTMP/committwice.py <<__EOF__ + > from mercurial import ui, hg, match, node + > from time import sleep + > + > def replacebyte(fn, b): + > f = open(fn, "rb+") + > f.seek(0, 0) + > f.write(b) + > f.close() + > + > def printfiles(repo, rev): + > print "revision %s files: %s" % (rev, repo[rev].files()) + > + > repo = hg.repository(ui.ui(), '.') + > assert len(repo) == 6, \ + > "initial: len(repo): %d, expected: 6" % len(repo) + > + > replacebyte("bugfix", "u") + > sleep(2) + > try: + > print "PRE: len(repo): %d" % len(repo) + > wlock = repo.wlock() + > lock = repo.lock() + > replacebyte("file1", "x") + > repo.commit(text="x", user="test", date=(0, 0)) + > replacebyte("file1", "y") + > repo.commit(text="y", user="test", date=(0, 0)) + > print "POST: len(repo): %d" % len(repo) + > finally: + > lock.release() + > wlock.release() + > printfiles(repo, 6) + > printfiles(repo, 7) + > __EOF__ + $ $PYTHON $TESTTMP/committwice.py + PRE: len(repo): 6 + POST: len(repo): 8 + revision 6 files: ['bugfix', 'file1'] + revision 7 files: ['file1'] + +Do a size-preserving modification outside of that process + $ echo abcd > bugfix + $ hg status + M bugfix + $ hg log --template "{rev} {desc} {files}\n" -r5: + 5 fix 2 bugfix file1 + 6 x bugfix file1 + 7 y file1
--- a/tests/test-commit.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-commit.t Wed May 18 09:56:27 2011 +0200 @@ -32,7 +32,7 @@ $ hg add bar $ rm bar $ hg commit -m commit-8 - nothing changed + nothing changed (1 missing files, see 'hg status') [1] $ hg commit -m commit-8-2 bar abort: bar: file not found!
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-contrib.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,302 @@ +Set vars: + + $ CONTRIBDIR=$TESTDIR/../contrib + +Prepare repo-a: + + $ hg init repo-a + $ cd repo-a + + $ echo this is file a > a + $ hg add a + $ hg commit -m first + + $ echo adding to file a >> a + $ hg commit -m second + + $ echo adding more to file a >> a + $ hg commit -m third + + $ hg verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 1 files, 3 changesets, 3 total revisions + +Dumping revlog of file a to stdout: + + $ python $CONTRIBDIR/dumprevlog .hg/store/data/a.i + file: .hg/store/data/a.i + node: 183d2312b35066fb6b3b449b84efc370d50993d0 + linkrev: 0 + parents: 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 + length: 15 + -start- + this is file a + + -end- + node: b1047953b6e6b633c0d8197eaa5116fbdfd3095b + linkrev: 1 + parents: 183d2312b35066fb6b3b449b84efc370d50993d0 0000000000000000000000000000000000000000 + length: 32 + -start- + this is file a + adding to file a + + -end- + node: 8c4fd1f7129b8cdec6c7f58bf48fb5237a4030c1 + linkrev: 2 + parents: b1047953b6e6b633c0d8197eaa5116fbdfd3095b 0000000000000000000000000000000000000000 + length: 54 + -start- + this is file a + adding to file a + adding more to file a + + -end- + +Dump all revlogs to file repo.dump: + + $ find .hg/store -name "*.i" | sort | xargs python $CONTRIBDIR/dumprevlog > ../repo.dump + $ cd .. + +Undumping into repo-b: + + $ hg init repo-b + $ cd repo-b + $ python $CONTRIBDIR/undumprevlog < ../repo.dump + .hg/store/00changelog.i + .hg/store/00manifest.i + .hg/store/data/a.i + $ cd .. + +Rebuild fncache with clone --pull: + + $ hg clone --pull -U repo-b repo-c + requesting all changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 1 files + +Verify: + + $ hg -R repo-c verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 1 files, 3 changesets, 3 total revisions + +Compare repos: + + $ hg -R repo-c incoming repo-a + comparing with repo-a + searching for changes + no changes found + [1] + + $ hg -R repo-a incoming repo-c + comparing with repo-c + searching for changes + no changes found + [1] + + +Test shrink-revlog: + $ cd repo-a + $ hg --config extensions.shrink=$CONTRIBDIR/shrink-revlog.py shrink + shrinking $TESTTMP/repo-a/.hg/store/00manifest.i + reading revs + sorting revs + writing revs + old file size: 324 bytes ( 0.0 MiB) + new file size: 324 bytes ( 0.0 MiB) + shrinkage: 0.0% (1.0x) + note: old revlog saved in: + $TESTTMP/repo-a/.hg/store/00manifest.i.old + $TESTTMP/repo-a/.hg/store/00manifest.d.old + (You can delete those files when you are satisfied that your + repository is still sane. Running 'hg verify' is strongly recommended.) + $ hg verify + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 1 files, 3 changesets, 3 total revisions + $ cd .. + +Test simplemerge command: + + $ cp "$CONTRIBDIR/simplemerge" . + $ echo base > base + $ echo local > local + $ cat base >> local + $ cp local orig + $ cat base > other + $ echo other >> other + +changing local directly + + $ python simplemerge local base other && echo "merge succeeded" + merge succeeded + $ cat local + local + base + other + $ cp orig local + +printing to stdout + + $ python simplemerge -p local base other + local + base + other + +local: + + $ cat local + local + base + +conflicts + + $ cp base conflict-local + $ cp other conflict-other + $ echo not other >> conflict-local + $ echo end >> conflict-local + $ echo end >> conflict-other + $ python simplemerge -p conflict-local base conflict-other + base + <<<<<<< conflict-local + not other + ======= + other + >>>>>>> conflict-other + end + warning: conflicts during merge. + [1] + +--no-minimal + + $ python simplemerge -p --no-minimal conflict-local base conflict-other + base + <<<<<<< conflict-local + not other + end + ======= + other + end + >>>>>>> conflict-other + warning: conflicts during merge. + [1] + +1 label + + $ python simplemerge -p -L foo conflict-local base conflict-other + base + <<<<<<< foo + not other + ======= + other + >>>>>>> conflict-other + end + warning: conflicts during merge. + [1] + +2 labels + + $ python simplemerge -p -L foo -L bar conflict-local base conflict-other + base + <<<<<<< foo + not other + ======= + other + >>>>>>> bar + end + warning: conflicts during merge. + [1] + +too many labels + + $ python simplemerge -p -L foo -L bar -L baz conflict-local base conflict-other + abort: can only specify two labels. + [255] + +binary file + + $ python -c "f = file('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. + [1] + +binary file --text + + $ python simplemerge -a -p binary-local base other 2>&1 + warning: binary-local looks like a binary file. + \x00local (esc) + base + other + +help + + $ python simplemerge --help + simplemerge [OPTS] LOCAL BASE OTHER + + Simple three-way file merge utility with a minimal feature set. + + Apply to LOCAL the changes necessary to go from BASE to OTHER. + + By default, LOCAL is overwritten with the results of this operation. + + options: + -L --label labels to use on conflict markers + -a --text treat all files as text + -p --print print results instead of overwriting LOCAL + --no-minimal do not try to minimize conflict regions + -h --help display help and exit + -q --quiet suppress output + +wrong number of arguments + + $ python simplemerge + simplemerge: wrong number of arguments + simplemerge [OPTS] LOCAL BASE OTHER + + Simple three-way file merge utility with a minimal feature set. + + Apply to LOCAL the changes necessary to go from BASE to OTHER. + + By default, LOCAL is overwritten with the results of this operation. + + options: + -L --label labels to use on conflict markers + -a --text treat all files as text + -p --print print results instead of overwriting LOCAL + --no-minimal do not try to minimize conflict regions + -h --help display help and exit + -q --quiet suppress output + [1] + +bad option + + $ python simplemerge --foo -p local base other + simplemerge: option --foo not recognized + simplemerge [OPTS] LOCAL BASE OTHER + + Simple three-way file merge utility with a minimal feature set. + + Apply to LOCAL the changes necessary to go from BASE to OTHER. + + By default, LOCAL is overwritten with the results of this operation. + + options: + -L --label labels to use on conflict markers + -a --text treat all files as text + -p --print print results instead of overwriting LOCAL + --no-minimal do not try to minimize conflict regions + -h --help display help and exit + -q --quiet suppress output + [1]
--- a/tests/test-convert-baz Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-baz Wed May 18 09:56:27 2011 +0200 @@ -2,10 +2,6 @@ "$TESTDIR/hghave" baz || exit 80 -mkdir do_not_use_HOME_baz -cd do_not_use_HOME_baz -HOME=`pwd`; export HOME -cd .. baz my-id "mercurial <mercurial@selenic.com>" echo "[extensions]" >> $HGRCPATH
--- a/tests/test-convert-darcs.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-darcs.t Wed May 18 09:56:27 2011 +0200 @@ -4,7 +4,6 @@ $ echo "convert=" >> $HGRCPATH $ echo 'graphlog =' >> $HGRCPATH $ DARCS_EMAIL='test@example.org'; export DARCS_EMAIL - $ HOME=`pwd`/do_not_use_HOME_darcs; export HOME skip if we can't import elementtree @@ -17,7 +16,7 @@ try converting darcs1 repository - $ hg clone -q "$TESTDIR/darcs1.hg" darcs + $ hg clone -q "$TESTDIR/bundles/darcs1.hg" darcs $ hg convert -s darcs darcs/darcs1 2>&1 | grep darcs-1.0 darcs-1.0 repository format is unsupported, please upgrade
--- a/tests/test-convert-filemap.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-filemap.t Wed May 18 09:56:27 2011 +0200 @@ -14,11 +14,13 @@ $ mkdir -p dir/subdir $ echo dir/file >> dir/file $ echo dir/file2 >> dir/file2 + $ echo dir/file3 >> dir/file3 # to be corrupted in rev 0 $ echo dir/subdir/file3 >> dir/subdir/file3 $ echo dir/subdir/file4 >> dir/subdir/file4 $ hg ci -d '0 0' -qAm '0: add foo baz dir/' $ echo bar > bar $ echo quux > quux + $ echo dir/file4 >> dir/file4 # to be corrupted in rev 1 $ hg copy foo copied $ hg ci -d '1 0' -qAm '1: add bar quux; copy foo to copied' $ echo >> foo @@ -63,9 +65,9 @@ | | o | 2 "2: change foo" files: foo |/ - o 1 "1: add bar quux; copy foo to copied" files: bar copied quux + o 1 "1: add bar quux; copy foo to copied" files: bar copied dir/file4 quux | - o 0 "0: add foo baz dir/" files: baz dir/file dir/file2 dir/subdir/file3 dir/subdir/file4 foo + o 0 "0: add foo baz dir/" files: baz dir/file dir/file2 dir/file3 dir/subdir/file3 dir/subdir/file4 foo final file versions in this repo: @@ -76,6 +78,8 @@ 7711d36246cc83e61fb29cd6d4ef394c63f1ceaf 644 copied 3e20847584beff41d7cd16136b7331ab3d754be0 644 dir/file 75e6d3f8328f5f6ace6bf10b98df793416a09dca 644 dir/file2 + e96dce0bc6a217656a3a410e5e6bec2c4f42bf7c 644 dir/file3 + 6edd55f559cdce67132b12ca09e09cee08b60442 644 dir/file4 5fe139720576e18e34bcc9f79174db8897c8afe9 644 dir/subdir/file3 57a1c1511590f3de52874adfa04effe8a77d64af 644 dir/subdir/file4 9a7b52012991e4873687192c3e17e61ba3e837a3 644 foo @@ -234,7 +238,14 @@ > exclude dir/subdir > include dir/subdir/file3 > EOF - $ hg -q convert --filemap renames.fmap --datesort source renames.repo + $ rm source/.hg/store/data/dir/file3.i + $ rm source/.hg/store/data/dir/file4.i + $ hg -q convert --filemap renames.fmap --datesort source dummydest + abort: data/dir/file3.i@e96dce0bc6a2: no match found! + [255] + $ hg -q convert --filemap renames.fmap --datesort --config convert.hg.ignoreerrors=1 source renames.repo + ignoring: data/dir/file3.i@e96dce0bc6a2: no match found + ignoring: data/dir/file4.i@6edd55f559cd: no match found $ hg up -q -R renames.repo $ glog -R renames.repo @ 4 "8: change foo" files: foo2
--- a/tests/test-convert-git.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-git.t Wed May 18 09:56:27 2011 +0200 @@ -57,9 +57,11 @@ 2 t4.1 1 t4.2 0 Merge branch other + updating bookmarks $ hg up -q -R git-repo-hg $ hg -R git-repo-hg tip -v changeset: 5:c78094926be2 + bookmark: master tag: tip parent: 3:f5f5cb45432b parent: 4:4e174f80c67c @@ -217,6 +219,7 @@ sorting... converting... 0 addbinary + updating bookmarks $ cd git-repo3-hg $ hg up -C 1 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -248,8 +251,10 @@ converting... 1 addfoo 0 addfoo2 + updating bookmarks $ hg -R git-repo4-hg log -v changeset: 1:d63e967f93da + bookmark: master tag: tip user: nottest <test@example.org> date: Mon Jan 01 00:00:21 2007 +0000
--- a/tests/test-convert-hg-source.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-hg-source.t Wed May 18 09:56:27 2011 +0200 @@ -17,6 +17,7 @@ $ hg copy foo baz $ hg ci -m 'make bar and baz copies of foo' -d '2 0' created new head + $ hg bookmark premerge1 $ hg merge merging baz and foo to baz 1 files updated, 1 files merged, 0 files removed, 0 files unresolved @@ -24,6 +25,7 @@ $ hg ci -m 'merge local copy' -d '3 0' $ hg up -C 1 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg bookmark premerge2 $ hg merge 2 merging foo and baz to baz 1 files updated, 1 files merged, 0 files removed, 0 files unresolved @@ -44,12 +46,16 @@ 2 merge local copy 1 merge remote copy 0 mark baz executable + updating bookmarks $ cd new $ hg out ../orig comparing with ../orig searching for changes no changes found [1] + $ hg bookmarks + premerge1 3:973ef48a98a4 + premerge2 5:13d9b87cf8f8 $ cd .. check shamap LF and CRLF handling @@ -76,6 +82,7 @@ converting... 1 change foo again again 0 change foo again + updating bookmarks init broken repository
--- a/tests/test-convert-hg-startrev.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-hg-startrev.t Wed May 18 09:56:27 2011 +0200 @@ -1,5 +1,5 @@ - $ cat > $HGRCPATH <<EOF + $ cat >> $HGRCPATH <<EOF > [extensions] > graphlog = > convert =
--- a/tests/test-convert-mtn.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-mtn.t Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,6 @@ $ echo "[extensions]" >> $HGRCPATH $ echo "convert=" >> $HGRCPATH $ echo 'graphlog =' >> $HGRCPATH - $ HOME=`pwd`/do_not_use_HOME_mtn; export HOME Windows version of monotone home @@ -208,6 +207,21 @@ $ mtn ci -m divergentdirmove2 mtn: beginning commit on branch 'com.selenic.test' mtn: committed revision 4a736634505795f17786fffdf2c9cbf5b11df6f6 + +test large file support (> 32kB) + + $ python -c 'for x in range(10000): print x' > large-file + $ $TESTDIR/md5sum.py large-file + 5d6de8a95c3b6bf9e0ffb808ba5299c1 large-file + $ mtn add large-file + mtn: adding large-file to workspace manifest + $ mtn ci -m largefile + mtn: beginning commit on branch 'com.selenic.test' + mtn: committed revision f0a20fecd10dc4392d18fe69a03f1f4919d3387b + +test suspending (closing a branch) + + $ mtn suspend f0a20fecd10dc4392d18fe69a03f1f4919d3387b 2> /dev/null $ cd .. convert incrementally @@ -217,27 +231,30 @@ scanning source... sorting... converting... - 11 update2 "with" quotes - 10 createdir1 - 9 movedir1 - 8 movedir - 7 emptydir - 6 dropdirectory - 5 dirfilemove - 4 dirfilemove2 - 3 dirdirmove - 2 dirdirmove2 - 1 divergentdirmove - 0 divergentdirmove2 + 12 update2 "with" quotes + 11 createdir1 + 10 movedir1 + 9 movedir + 8 emptydir + 7 dropdirectory + 6 dirfilemove + 5 dirfilemove2 + 4 dirdirmove + 3 dirdirmove2 + 2 divergentdirmove + 1 divergentdirmove2 + 0 largefile $ glog() > { > hg glog --template '{rev} "{desc|firstline}" files: {files}\n' "$@" > } $ cd repo.mtn-hg $ hg up -C - 11 files updated, 0 files merged, 0 files removed, 0 files unresolved + 12 files updated, 0 files merged, 0 files removed, 0 files unresolved $ glog - @ 13 "divergentdirmove2" files: dir7-2/c dir7/c dir7/dir9/b dir7/dir9/dir8/a dir8-2/a dir9-2/b + @ 14 "largefile" files: large-file + | + o 13 "divergentdirmove2" files: dir7-2/c dir7/c dir7/dir9/b dir7/dir9/dir8/a dir8-2/a dir9-2/b | o 12 "divergentdirmove" files: dir7/c dir7/dir9/b dir7/dir9/dir8/a | @@ -280,6 +297,7 @@ dir8-2/a dir9-2/b e + large-file contents @@ -356,3 +374,15 @@ dir8-2/a dir9-2/b e + +test large file support (> 32kB) + + $ $TESTDIR/md5sum.py large-file + 5d6de8a95c3b6bf9e0ffb808ba5299c1 large-file + +check branch closing + + $ hg branches -a + $ hg branches -c + com.selenic.test 14:* (closed) (glob) +
--- a/tests/test-convert-svn-branches.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-svn-branches.t Wed May 18 09:56:27 2011 +0200 @@ -1,7 +1,7 @@ $ "$TESTDIR/hghave" svn svn-bindings || exit 80 - $ cat > $HGRCPATH <<EOF + $ cat >> $HGRCPATH <<EOF > [extensions] > convert = > graphlog = @@ -32,6 +32,21 @@ 1 move back to old 0 last change to a +Test template keywords + + $ hg -R A-hg log --template '{rev} {svnuuid}{svnpath}@{svnrev}\n' + 10 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@10 + 9 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@9 + 8 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old2@8 + 7 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@7 + 6 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@6 + 5 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@6 + 4 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@5 + 3 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@4 + 2 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@3 + 1 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@2 + 0 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@1 + Convert again $ hg convert --branchmap=branchmap --datesort svn-repo A-hg @@ -68,10 +83,10 @@ $ hg branches - newbranch 11:08fca3ff8634 - default 10:098988aa63ba - old 9:b308f345079b - old2 8:49f2336c7b8b (inactive) + newbranch 11:a6d7cc050ad1 + default 10:6e2b33404495 + old 9:93c4b0f99529 + old2 8:b52884d7bead (inactive) $ hg tags -q tip $ cd ..
--- a/tests/test-convert-svn-encoding.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-svn-encoding.t Wed May 18 09:56:27 2011 +0200 @@ -1,7 +1,7 @@ $ "$TESTDIR/hghave" svn svn-bindings || exit 80 - $ cat > $HGRCPATH <<EOF + $ cat >> $HGRCPATH <<EOF > [extensions] > convert = > graphlog = @@ -129,7 +129,7 @@ Check tags are in UTF-8 $ cat .hgtags - 221c3fdaf24df5f14c0a64c597581e2eacfb47bb branch\xc3\xa9e (esc) - 7a40952c2db29cf00d9e31df3749e98d8a4bdcbf branch\xc3\xa9 (esc) + e94e4422020e715add80525e8f0f46c9968689f1 branch\xc3\xa9e (esc) + f7e66f98380ed1e53a797c5c7a7a2616a7ab377d branch\xc3\xa9 (esc) $ cd ..
--- a/tests/test-convert-svn-move.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-svn-move.t Wed May 18 09:56:27 2011 +0200 @@ -5,7 +5,7 @@ > { > tr '\\' / > } - $ cat > $HGRCPATH <<EOF + $ cat >> $HGRCPATH <<EOF > [extensions] > convert = > graphlog =
--- a/tests/test-convert-svn-sink.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-svn-sink.t Wed May 18 09:56:27 2011 +0200 @@ -1,5 +1,5 @@ - $ "$TESTDIR/hghave" svn svn-bindings no-outer-repo || exit 80 + $ "$TESTDIR/hghave" svn no-outer-repo || exit 80 $ fixpath() > { @@ -22,7 +22,7 @@ > ) > } - $ cat > $HGRCPATH <<EOF + $ cat >> $HGRCPATH <<EOF > [extensions] > convert = > graphlog =
--- a/tests/test-convert-svn-source.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-svn-source.t Wed May 18 09:56:27 2011 +0200 @@ -5,7 +5,7 @@ > { > tr '\\' / > } - $ cat > $HGRCPATH <<EOF + $ cat >> $HGRCPATH <<EOF > [extensions] > convert = > graphlog = @@ -107,6 +107,11 @@ Committed revision 8. $ cd .. + $ hg convert -s svn "$svnurl/non-existent-path" dest + initializing destination dest repository + abort: no revision found in module /proj B/non-existent-path + [255] + ######################################## Test incremental conversion
--- a/tests/test-convert-svn-startrev.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-svn-startrev.t Wed May 18 09:56:27 2011 +0200 @@ -1,7 +1,7 @@ $ "$TESTDIR/hghave" svn svn-bindings || exit 80 - $ cat > $HGRCPATH <<EOF + $ cat >> $HGRCPATH <<EOF > [extensions] > convert = > graphlog =
--- a/tests/test-convert-svn-tags.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-svn-tags.t Wed May 18 09:56:27 2011 +0200 @@ -1,7 +1,7 @@ $ "$TESTDIR/hghave" svn svn-bindings || exit 80 - $ cat > $HGRCPATH <<EOF + $ cat >> $HGRCPATH <<EOF > [extensions] > convert = > graphlog =
--- a/tests/test-convert-tagsbranch-topology.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-tagsbranch-topology.t Wed May 18 09:56:27 2011 +0200 @@ -49,6 +49,7 @@ converting... 0 rev1 updating tags + updating bookmarks Simulate upstream updates after first conversion @@ -67,6 +68,7 @@ converting... 0 rev2 updating tags + updating bookmarks Print the log
--- a/tests/test-convert-tla.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-convert-tla.t Wed May 18 09:56:27 2011 +0200 @@ -1,9 +1,5 @@ $ "$TESTDIR/hghave" tla || exit 80 - $ mkdir do_not_use_HOME_tla - $ cd do_not_use_HOME_tla - $ HOME=`pwd`; export HOME - $ cd .. $ tla my-id "mercurial <mercurial@selenic.com>" $ echo "[extensions]" >> $HGRCPATH $ echo "convert=" >> $HGRCPATH
--- a/tests/test-copy-move-merge.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-copy-move-merge.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,5 @@ - $ mkdir t + $ hg init t $ cd t - $ hg init $ echo 1 > a $ hg ci -qAm "first"
--- a/tests/test-copy.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-copy.t Wed May 18 09:56:27 2011 +0200 @@ -4,6 +4,9 @@ $ hg commit -m "1" $ hg status $ hg copy a b + $ hg --config ui.portablefilenames=abort copy a con.xml + abort: filename contains 'con', which is reserved on Windows: 'con.xml' + [255] $ hg status A b $ hg sum @@ -49,7 +52,7 @@ this should show a revision linked to changeset 0 - $ hg debugindex .hg/store/data/a.i + $ hg debugindex a rev offset length base linkrev nodeid p1 p2 0 0 3 0 0 b789fdd96dc2 000000000000 000000000000 @@ -65,13 +68,13 @@ this should show a revision linked to changeset 1 - $ hg debugindex .hg/store/data/b.i + $ hg debugindex b rev offset length base linkrev nodeid p1 p2 0 0 65 0 1 37d9b5d994ea 000000000000 000000000000 this should show the rename information in the metadata - $ hg debugdata .hg/store/data/b.d 0 | head -3 | tail -2 + $ hg debugdata b 0 | head -3 | tail -2 copy: a copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
--- a/tests/test-copy2.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-copy2.t Wed May 18 09:56:27 2011 +0200 @@ -46,7 +46,7 @@ $ hg st -C should match - $ hg debugindex .hg/store/data/foo.i + $ hg debugindex foo rev offset length base linkrev nodeid p1 p2 0 0 5 0 0 2ed2a3912a0b 000000000000 000000000000 $ hg debugrename bar @@ -68,13 +68,13 @@ $ hg commit -m3 should show no parents for tip - $ hg debugindex .hg/store/data/bar.i + $ hg debugindex bar rev offset length base linkrev nodeid p1 p2 0 0 69 0 1 7711d36246cc 000000000000 000000000000 1 69 6 1 2 bdf70a2b8d03 7711d36246cc 000000000000 2 75 81 1 3 b2558327ea8d 000000000000 000000000000 should match - $ hg debugindex .hg/store/data/foo.i + $ hg debugindex foo rev offset length base linkrev nodeid p1 p2 0 0 5 0 0 2ed2a3912a0b 000000000000 000000000000 1 5 7 1 2 dd12c926cf16 2ed2a3912a0b 000000000000
--- a/tests/test-debugbuilddag.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-debugbuilddag.t Wed May 18 09:56:27 2011 +0200 @@ -1,12 +1,55 @@ $ echo "[extensions]" >> $HGRCPATH $ echo "graphlog=" >> $HGRCPATH -overwritten and appended files +plain - $ rm -rf repo - $ hg init repo - $ cd repo - $ hg debugbuilddag '+2:f +3:p2 @temp <f+4 @default /p2 +2' -q -oa + $ hg init + $ hg debugbuilddag '+2:f +3:p2 @temp <f+4 @default /p2 +2' \ + > --config extensions.progress= --config progress.assume-tty=1 \ + > --config progress.delay=0 --config progress.refresh=0 \ + > --config progress.width=60 2>&1 | \ + > python $TESTDIR/filtercr.py + + building [ ] 0/12 + building [ ] 0/12 + building [ ] 0/12 + building [ ] 0/12 + building [==> ] 1/12 + building [==> ] 1/12 + building [==> ] 1/12 + building [==> ] 1/12 + building [======> ] 2/12 + building [======> ] 2/12 + building [=========> ] 3/12 + building [=========> ] 3/12 + building [=============> ] 4/12 + building [=============> ] 4/12 + building [=============> ] 4/12 + building [=============> ] 4/12 + building [=============> ] 4/12 + building [=============> ] 4/12 + building [================> ] 5/12 + building [================> ] 5/12 + building [====================> ] 6/12 + building [====================> ] 6/12 + building [=======================> ] 7/12 + building [=======================> ] 7/12 + building [===========================> ] 8/12 + building [===========================> ] 8/12 + building [===========================> ] 8/12 + building [===========================> ] 8/12 + building [==============================> ] 9/12 + building [==============================> ] 9/12 + building [==================================> ] 10/12 + building [==================================> ] 10/12 + building [=====================================> ] 11/12 + building [=====================================> ] 11/12 + \r (esc) + +tags + $ cat .hg/localtags + 66f7d451a68b85ed82ff5fcc254daf50c74144bd f + bebd167eb94d257ace0e814aeb98e6972ed2970d p2 dag $ hg debugdag -t -b +2:f @@ -15,10 +58,55 @@ @default*/p2+2:tip tip $ hg id - f96e381c614c tip + 000000000000 glog $ hg glog --template '{rev}: {desc} [{branches}] @ {date}\n' - @ 11: r11 [] @ 11.00 + o 11: r11 [] @ 11.00 + | + o 10: r10 [] @ 10.00 + | + o 9: r9 [] @ 9.00 + |\ + | o 8: r8 [temp] @ 8.00 + | | + | o 7: r7 [temp] @ 7.00 + | | + | o 6: r6 [temp] @ 6.00 + | | + | o 5: r5 [temp] @ 5.00 + | | + o | 4: r4 [] @ 4.00 + | | + o | 3: r3 [] @ 3.00 + | | + o | 2: r2 [] @ 2.00 + |/ + o 1: r1 [] @ 1.00 + | + o 0: r0 [] @ 0.00 + + +overwritten files + + $ rm -r .hg + $ hg init + $ hg debugbuilddag '+2:f +3:p2 @temp <f+4 @default /p2 +2' -q -o +tags + $ cat .hg/localtags + 2a8ed67d317e370eac733dccc501b12d7b9c441a f + 4226a30965b7af58f94d0cda7e6c2c9c63e6bf90 p2 +dag + $ hg debugdag -t -b + +2:f + +3:p2 + @temp*f+3 + @default*/p2+2:tip +tip + $ hg id + 000000000000 +glog + $ hg glog --template '{rev}: {desc} [{branches}] @ {date}\n' + o 11: r11 [] @ 11.00 | o 10: r10 [] @ 10.00 | @@ -44,33 +132,7 @@ glog of $ hg glog --template '{rev}: {desc} [{branches}]\n' of - @ 11: r11 [] - | - o 10: r10 [] - | - o 9: r9 [] - |\ - | o 8: r8 [temp] - | | - | o 7: r7 [temp] - | | - | o 6: r6 [temp] - | | - | o 5: r5 [temp] - | | - o | 4: r4 [] - | | - o | 3: r3 [] - | | - o | 2: r2 [] - |/ - o 1: r1 [] - | - o 0: r0 [] - -glog af - $ hg glog --template '{rev}: {desc} [{branches}]\n' af - @ 11: r11 [] + o 11: r11 [] | o 10: r10 [] | @@ -96,30 +158,18 @@ tags $ hg tags -v - tip 11:f96e381c614c - p2 4:d9d6db981b55 local - f 1:73253def624e local + tip 11:58a51e5eb988 + p2 4:4226a30965b7 local + f 1:2a8ed67d317e local cat of - $ hg cat of + $ hg cat of --rev tip r11 -cat af - $ hg cat af - r0 - r1 - r5 - r6 - r7 - r8 - r9 - r10 - r11 - $ cd .. + new and mergeable files - $ rm -rf repo - $ hg init repo - $ cd repo + $ rm -r .hg + $ hg init $ hg debugbuilddag '+2:f +3:p2 @temp <f+4 @default /p2 +2' -q -mn dag $ hg debugdag -t -b @@ -129,10 +179,10 @@ @default*/p2+2:tip tip $ hg id - 9c5ce9b70771 tip + 000000000000 glog $ hg glog --template '{rev}: {desc} [{branches}] @ {date}\n' - @ 11: r11 [] @ 11.00 + o 11: r11 [] @ 11.00 | o 10: r10 [] @ 10.00 | @@ -158,7 +208,7 @@ glog mf $ hg glog --template '{rev}: {desc} [{branches}]\n' mf - @ 11: r11 [] + o 11: r11 [] | o 10: r10 [] | @@ -253,7 +303,7 @@ 22 23 man - $ hg manifest + $ hg manifest --rev tip mf nf0 nf1 @@ -268,7 +318,7 @@ nf8 nf9 cat mf - $ hg cat mf + $ hg cat mf --rev tip 0 r0 1 2 r1 @@ -293,29 +343,6 @@ 21 22 r11 23 - $ cd .. -command - $ rm -rf repo - $ hg init repo - $ cd repo - $ hg debugbuilddag '+2 !"touch X" +2' -q -o -dag - $ hg debugdag -t -b - +4:tip -glog - $ hg glog --template '{rev}: {desc} [{branches}]\n' - @ 3: r3 [] - | - o 2: r2 [] - | - o 1: r1 [] - | - o 0: r0 [] - -glog X - $ hg glog --template '{rev}: {desc} [{branches}]\n' X - o 2: r2 [] - - $ cd .. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-debugbundle.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,36 @@ + +Create a test repository: + + $ hg init repo + $ cd repo + $ touch a ; hg add a ; hg ci -ma + $ touch b ; hg add b ; hg ci -mb + $ touch c ; hg add c ; hg ci -mc + $ hg bundle --base 0 --rev tip bundle.hg + 2 changesets found + +Terse output: + + $ hg debugbundle bundle.hg + 0e067c57feba1a5694ca4844f05588bb1bf82342 + 991a3460af53952d10ec8a295d3d2cc2e5fa9690 + +Verbose output: + + $ hg debugbundle --all bundle.hg + format: id, p1, p2, cset, delta base, len(delta) + + changelog + 0e067c57feba1a5694ca4844f05588bb1bf82342 3903775176ed42b1458a6281db4a0ccf4d9f287a 0000000000000000000000000000000000000000 0e067c57feba1a5694ca4844f05588bb1bf82342 3903775176ed42b1458a6281db4a0ccf4d9f287a 80 + 991a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 0000000000000000000000000000000000000000 991a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 80 + + manifest + 686dbf0aeca417636fa26a9121c681eabbb15a20 8515d4bfda768e04af4c13a69a72e28c7effbea7 0000000000000000000000000000000000000000 0e067c57feba1a5694ca4844f05588bb1bf82342 8515d4bfda768e04af4c13a69a72e28c7effbea7 55 + ae25a31b30b3490a981e7b96a3238cc69583fda1 686dbf0aeca417636fa26a9121c681eabbb15a20 0000000000000000000000000000000000000000 991a3460af53952d10ec8a295d3d2cc2e5fa9690 686dbf0aeca417636fa26a9121c681eabbb15a20 55 + + b + b80de5d138758541c5f05265ad144ab9fa86d1db 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 0e067c57feba1a5694ca4844f05588bb1bf82342 0000000000000000000000000000000000000000 12 + + c + b80de5d138758541c5f05265ad144ab9fa86d1db 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 991a3460af53952d10ec8a295d3d2cc2e5fa9690 0000000000000000000000000000000000000000 12 +
--- a/tests/test-debugcomplete.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-debugcomplete.t Wed May 18 09:56:27 2011 +0200 @@ -67,6 +67,7 @@ $ hg debugcomplete debug debugancestor debugbuilddag + debugbundle debugcheckstate debugcommands debugcomplete @@ -74,19 +75,24 @@ debugdag debugdata debugdate + debugdiscovery debugfsinfo + debuggetbundle debugignore debugindex debugindexdot debuginstall + debugknown debugpushkey debugrebuildstate debugrename + debugrevlog debugrevspec debugsetparents debugstate debugsub debugwalk + debugwireargs Do not show the alias of a debug command if there are other candidates (this should hide rawcommit) @@ -199,37 +205,43 @@ addremove: similarity, include, exclude, dry-run archive: no-decode, prefix, rev, type, subrepos, include, exclude backout: merge, parent, tool, rev, include, exclude, message, logfile, date, user - bisect: reset, good, bad, skip, command, noupdate - bookmarks: force, rev, delete, rename + bisect: reset, good, bad, skip, extend, command, noupdate + bookmarks: force, rev, delete, rename, inactive branch: force, clean branches: active, closed bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure cat: output, rev, decode, include, exclude copy: after, force, include, exclude, dry-run debugancestor: - debugbuilddag: mergeable-file, appended-file, overwritten-file, new-file + debugbuilddag: mergeable-file, overwritten-file, new-file + debugbundle: all debugcheckstate: debugcommands: debugcomplete: options debugdag: tags, branches, dots, spaces - debugdata: + debugdata: changelog, manifest debugdate: extended + debugdiscovery: old, nonheads, ssh, remotecmd, insecure debugfsinfo: + debuggetbundle: head, common, type debugignore: - debugindex: format + debugindex: changelog, manifest, format debugindexdot: debuginstall: + debugknown: debugpushkey: debugrebuildstate: rev debugrename: rev + debugrevlog: changelog, manifest, dump debugrevspec: debugsetparents: - debugstate: nodates + debugstate: nodates, datesort debugsub: rev debugwalk: include, exclude - grep: print0, all, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude + debugwireargs: three, four, five, ssh, remotecmd, insecure + grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude heads: rev, topo, active, closed, style, template - help: + help: extension, command identify: rev, num, id, branch, tags, bookmarks import: strip, base, force, no-commit, exact, import-branch, message, logfile, date, user, similarity incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, style, template, ssh, remotecmd, insecure, subrepos
--- a/tests/test-diff-color.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-diff-color.t Wed May 18 09:56:27 2011 +0200 @@ -1,5 +1,7 @@ Setup + $ echo "[color]" >> $HGRCPATH + $ echo "mode = ansi" >> $HGRCPATH $ echo "[extensions]" >> $HGRCPATH $ echo "color=" >> $HGRCPATH $ hg init repo @@ -81,7 +83,7 @@ \x1b[0;36;1mold mode 100644\x1b[0m (esc) \x1b[0;36;1mnew mode 100755\x1b[0m (esc) 1 hunks, 1 lines changed - examine changes to 'a'? [Ynsfdaq?] + \x1b[0;33mexamine changes to 'a'? [Ynsfdaq?]\x1b[0m (esc) \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc) c a @@ -91,7 +93,7 @@ a a c - record this change to 'a'? [Ynsfdaq?] + \x1b[0;33mrecord this change to 'a'? [Ynsfdaq?]\x1b[0m (esc) $ echo $ echo "[extensions]" >> $HGRCPATH @@ -110,7 +112,7 @@ \x1b[0;36;1mold mode 100644\x1b[0m (esc) \x1b[0;36;1mnew mode 100755\x1b[0m (esc) 1 hunks, 1 lines changed - examine changes to 'a'? [Ynsfdaq?] + \x1b[0;33mexamine changes to 'a'? [Ynsfdaq?]\x1b[0m (esc) \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc) c a @@ -120,6 +122,6 @@ a a c - record this change to 'a'? [Ynsfdaq?] + \x1b[0;33mrecord this change to 'a'? [Ynsfdaq?]\x1b[0m (esc) $ echo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-diff-issue2761.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,23 @@ +Test issue2761 + + $ hg init + + $ touch to-be-deleted + $ hg add + adding to-be-deleted + $ hg ci -m first + $ echo a > to-be-deleted + $ hg ci -m second + $ rm to-be-deleted + $ hg diff -r 0 + +Same issue, different code path + + $ hg up -C + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ touch doesnt-exist-in-1 + $ hg add + adding doesnt-exist-in-1 + $ hg ci -m third + $ rm doesnt-exist-in-1 + $ hg diff -r 1
--- a/tests/test-dispatch.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-dispatch.t Wed May 18 09:56:27 2011 +0200 @@ -6,6 +6,11 @@ $ hg init a $ cd a + +Redundant options used to crash (issue436): + $ hg -v log -v + $ hg -v log -v x + $ echo a > a $ hg ci -Ama adding a @@ -18,20 +23,6 @@ output the current or given revision of files - Print the specified files as they were at the given revision. If no - revision is given, the parent of the working directory is used, or tip if - no revision is checked out. - - Output may be to a file, in which case the name of the file is given using - a format string. The formatting rules are the same as for the export - command, with the following additions: - - "%s" basename of file being printed - "%d" dirname of file being printed, or '.' if in repository root - "%p" root-relative path name of file being printed - - Returns 0 on success. - options: -o --output FORMAT print output to file with formatted name @@ -42,7 +33,7 @@ [+] marked option can be specified multiple times - use "hg -v help cat" to show global options + use "hg help cat" to show the full help text [255] [defaults] @@ -61,6 +52,6 @@ $ cd $dir $ hg cat - abort: There is no Mercurial repository here (.hg not found)! + abort: no repository found in '$TESTTMP' (.hg not found)! [255]
--- a/tests/test-doctest.py Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-doctest.py Wed May 18 09:56:27 2011 +0200 @@ -13,6 +13,12 @@ import mercurial.match doctest.testmod(mercurial.match) +import mercurial.store +doctest.testmod(mercurial.store) + +import mercurial.ui +doctest.testmod(mercurial.ui) + import mercurial.url doctest.testmod(mercurial.url)
--- a/tests/test-dumprevlog.t Wed May 18 09:31:19 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -Set vars: - - $ CONTRIBDIR=$TESTDIR/../contrib - -Prepare repo-a: - - $ mkdir repo-a - $ cd repo-a - $ hg init - - $ echo this is file a > a - $ hg add a - $ hg commit -m first - - $ echo adding to file a >> a - $ hg commit -m second - - $ echo adding more to file a >> a - $ hg commit -m third - - $ hg verify - checking changesets - checking manifests - crosschecking files in changesets and manifests - checking files - 1 files, 3 changesets, 3 total revisions - -Dumping revlog of file a to stdout: - - $ python $CONTRIBDIR/dumprevlog .hg/store/data/a.i - file: .hg/store/data/a.i - node: 183d2312b35066fb6b3b449b84efc370d50993d0 - linkrev: 0 - parents: 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 - length: 15 - -start- - this is file a - - -end- - node: b1047953b6e6b633c0d8197eaa5116fbdfd3095b - linkrev: 1 - parents: 183d2312b35066fb6b3b449b84efc370d50993d0 0000000000000000000000000000000000000000 - length: 32 - -start- - this is file a - adding to file a - - -end- - node: 8c4fd1f7129b8cdec6c7f58bf48fb5237a4030c1 - linkrev: 2 - parents: b1047953b6e6b633c0d8197eaa5116fbdfd3095b 0000000000000000000000000000000000000000 - length: 54 - -start- - this is file a - adding to file a - adding more to file a - - -end- - -Dump all revlogs to file repo.dump: - - $ find .hg/store -name "*.i" | sort | xargs python $CONTRIBDIR/dumprevlog > ../repo.dump - $ cd .. - -Undumping into repo-b: - - $ mkdir repo-b - $ cd repo-b - $ hg init - $ python $CONTRIBDIR/undumprevlog < ../repo.dump - .hg/store/00changelog.i - .hg/store/00manifest.i - .hg/store/data/a.i - $ cd .. - -Rebuild fncache with clone --pull: - - $ hg clone --pull -U repo-b repo-c - requesting all changes - adding changesets - adding manifests - adding file changes - added 3 changesets with 3 changes to 1 files - -Verify: - - $ hg -R repo-c verify - checking changesets - checking manifests - crosschecking files in changesets and manifests - checking files - 1 files, 3 changesets, 3 total revisions - -Compare repos: - - $ hg -R repo-c incoming repo-a - comparing with repo-a - searching for changes - no changes found - [1] - - $ hg -R repo-a incoming repo-c - comparing with repo-c - searching for changes - no changes found - [1] -
--- a/tests/test-empty-group.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-empty-group.t Wed May 18 09:56:27 2011 +0200 @@ -67,7 +67,7 @@ $ hg -R a outgoing b comparing with b searching for changes - changeset: 4:119caaef4ed1 + changeset: 4:1ec3c74fc0e0 tag: tip parent: 1:79f9e10cd04e parent: 2:8e1bb01c1a24 @@ -78,7 +78,7 @@ $ hg -R a outgoing c comparing with c searching for changes - changeset: 3:cbb48b367d1b + changeset: 3:d15a0c284984 parent: 2:8e1bb01c1a24 parent: 1:79f9e10cd04e user: test @@ -88,7 +88,7 @@ $ hg -R b outgoing c comparing with c searching for changes - changeset: 3:cbb48b367d1b + changeset: 3:d15a0c284984 tag: tip parent: 2:8e1bb01c1a24 parent: 1:79f9e10cd04e @@ -99,7 +99,7 @@ $ hg -R c outgoing b comparing with b searching for changes - changeset: 3:119caaef4ed1 + changeset: 3:1ec3c74fc0e0 tag: tip parent: 1:79f9e10cd04e parent: 2:8e1bb01c1a24
--- a/tests/test-encode.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-encode.t Wed May 18 09:56:27 2011 +0200 @@ -25,9 +25,9 @@ check contents in repo are encoded - $ hg debugdata .hg/store/data/a.gz.d 0 + $ hg debugdata a.gz 0 this is a test - $ hg debugdata .hg/store/data/not.gz.d 0 + $ hg debugdata not.gz 0 THIS IS A TEST check committed content was decoded
--- a/tests/test-encoding.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-encoding.t Wed May 18 09:56:27 2011 +0200 @@ -5,7 +5,7 @@ we need a repo with some legacy latin-1 changesets - $ hg unbundle $TESTDIR/legacy-encoding.hg + $ hg unbundle $TESTDIR/bundles/legacy-encoding.hg adding changesets adding manifests adding file changes @@ -48,7 +48,7 @@ hg log (ascii) $ hg --encoding ascii log - changeset: 5:093c6077d1c8 + changeset: 5:a52c0692f24a branch: ? tag: tip user: test @@ -85,7 +85,7 @@ hg log (latin-1) $ hg --encoding latin-1 log - changeset: 5:093c6077d1c8 + changeset: 5:a52c0692f24a branch: \xe9 (esc) tag: tip user: test @@ -122,7 +122,7 @@ hg log (utf-8) $ hg --encoding utf-8 log - changeset: 5:093c6077d1c8 + changeset: 5:a52c0692f24a branch: \xc3\xa9 (esc) tag: tip user: test @@ -159,37 +159,37 @@ hg tags (ascii) $ HGENCODING=ascii hg tags - tip 5:093c6077d1c8 + tip 5:a52c0692f24a ? 3:ca661e7520de hg tags (latin-1) $ HGENCODING=latin-1 hg tags - tip 5:093c6077d1c8 + tip 5:a52c0692f24a \xe9 3:ca661e7520de (esc) hg tags (utf-8) $ HGENCODING=utf-8 hg tags - tip 5:093c6077d1c8 + tip 5:a52c0692f24a \xc3\xa9 3:ca661e7520de (esc) hg branches (ascii) $ HGENCODING=ascii hg branches - ? 5:093c6077d1c8 + ? 5:a52c0692f24a default 4:94db611b4196 (inactive) hg branches (latin-1) $ HGENCODING=latin-1 hg branches - \xe9 5:093c6077d1c8 (esc) + \xe9 5:a52c0692f24a (esc) default 4:94db611b4196 (inactive) hg branches (utf-8) $ HGENCODING=utf-8 hg branches - \xc3\xa9 5:093c6077d1c8 (esc) + \xc3\xa9 5:a52c0692f24a (esc) default 4:94db611b4196 (inactive) $ echo '[ui]' >> .hg/hgrc $ echo 'fallbackencoding = koi8-r' >> .hg/hgrc @@ -197,7 +197,7 @@ hg log (utf-8) $ HGENCODING=utf-8 hg log - changeset: 5:093c6077d1c8 + changeset: 5:a52c0692f24a branch: \xc3\xa9 (esc) tag: tip user: test
--- a/tests/test-eol-add.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-eol-add.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,6 @@ Test adding .hgeol - $ cat > $HGRCPATH <<EOF + $ cat >> $HGRCPATH <<EOF > [diff] > git = 1 > EOF
--- a/tests/test-eol-clone.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-eol-clone.t Wed May 18 09:56:27 2011 +0200 @@ -1,9 +1,6 @@ Testing cloning with the EOL extension - $ cat > $HGRCPATH <<EOF - > [diff] - > git = True - > + $ cat >> $HGRCPATH <<EOF > [extensions] > eol = >
--- a/tests/test-eol-hook.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-eol-hook.t Wed May 18 09:56:27 2011 +0200 @@ -1,14 +1,7 @@ Test the EOL hook - $ cat > $HGRCPATH <<EOF - > [diff] - > git = True - > EOF $ hg init main $ cat > main/.hg/hgrc <<EOF - > [extensions] - > eol = - > > [hooks] > pretxnchangegroup = python:hgext.eol.hook > EOF @@ -47,10 +40,12 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files - error: pretxnchangegroup hook failed: a.txt should not have CRLF line endings + error: pretxnchangegroup hook failed: end-of-line check failed: + a.txt in a8ee6548cd86 should not have CRLF line endings transaction abort! rollback completed - abort: a.txt should not have CRLF line endings + abort: end-of-line check failed: + a.txt in a8ee6548cd86 should not have CRLF line endings [255] $ printf "first\nsecond\nthird\n" > a.txt @@ -73,10 +68,12 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files - error: pretxnchangegroup hook failed: crlf.txt should not have LF line endings + error: pretxnchangegroup hook failed: end-of-line check failed: + crlf.txt in 004ba2132725 should not have LF line endings transaction abort! rollback completed - abort: crlf.txt should not have LF line endings + abort: end-of-line check failed: + crlf.txt in 004ba2132725 should not have LF line endings [255] $ printf "first\r\nsecond\r\nthird\r\n" > crlf.txt @@ -88,3 +85,133 @@ adding manifests adding file changes added 2 changesets with 2 changes to 1 files + + $ printf "first\r\nsecond" > b.txt + $ hg add b.txt + $ hg commit -m 'CRLF b.txt' + $ hg push ../main + pushing to ../main + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + error: pretxnchangegroup hook failed: end-of-line check failed: + b.txt in fbcf9b1025f5 should not have CRLF line endings + transaction abort! + rollback completed + abort: end-of-line check failed: + b.txt in fbcf9b1025f5 should not have CRLF line endings + [255] + + $ hg up -r -2 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ printf "some\nother\nfile" > c.txt + $ hg add c.txt + $ hg commit -m "LF c.txt, b.txt doesn't exist here" + created new head + $ hg push -f ../main + pushing to ../main + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files (+1 heads) + error: pretxnchangegroup hook failed: end-of-line check failed: + b.txt in fbcf9b1025f5 should not have CRLF line endings + transaction abort! + rollback completed + abort: end-of-line check failed: + b.txt in fbcf9b1025f5 should not have CRLF line endings + [255] + +Test checkheadshook alias + + $ cat > ../main/.hg/hgrc <<EOF + > [hooks] + > pretxnchangegroup = python:hgext.eol.checkheadshook + > EOF + $ hg push -f ../main + pushing to ../main + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files (+1 heads) + error: pretxnchangegroup hook failed: end-of-line check failed: + b.txt in fbcf9b1025f5 should not have CRLF line endings + transaction abort! + rollback completed + abort: end-of-line check failed: + b.txt in fbcf9b1025f5 should not have CRLF line endings + [255] + +We can fix the head and push again + + $ hg up 6 + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ printf "first\nsecond" > b.txt + $ hg ci -m "remove CRLF from b.txt" + $ hg push -f ../main + pushing to ../main + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 2 files (+1 heads) + $ hg -R ../main rollback + repository tip rolled back to revision 5 (undo push) + working directory now based on revision -1 + +Test it still fails with checkallhook + + $ cat > ../main/.hg/hgrc <<EOF + > [hooks] + > pretxnchangegroup = python:hgext.eol.checkallhook + > EOF + $ hg push -f ../main + pushing to ../main + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 2 files (+1 heads) + error: pretxnchangegroup hook failed: end-of-line check failed: + b.txt in fbcf9b1025f5 should not have CRLF line endings + transaction abort! + rollback completed + abort: end-of-line check failed: + b.txt in fbcf9b1025f5 should not have CRLF line endings + [255] + +But we can push the clean head + + $ hg push -r7 -f ../main + pushing to ../main + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + +Test multiple files/revisions output + + $ printf "another\r\nbad\r\none" > d.txt + $ hg add d.txt + $ hg ci -m "add d.txt" + $ hg push -f ../main + pushing to ../main + searching for changes + adding changesets + adding manifests + adding file changes + added 3 changesets with 3 changes to 2 files (+1 heads) + error: pretxnchangegroup hook failed: end-of-line check failed: + d.txt in a7040e68714f should not have CRLF line endings + b.txt in fbcf9b1025f5 should not have CRLF line endings + transaction abort! + rollback completed + abort: end-of-line check failed: + d.txt in a7040e68714f should not have CRLF line endings + b.txt in fbcf9b1025f5 should not have CRLF line endings + [255]
--- a/tests/test-eol-patch.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-eol-patch.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,6 @@ Test EOL patching - $ cat > $HGRCPATH <<EOF + $ cat >> $HGRCPATH <<EOF > [diff] > git = 1 > EOF
--- a/tests/test-eol-tag.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-eol-tag.t Wed May 18 09:56:27 2011 +0200 @@ -2,10 +2,7 @@ Testing tagging with the EOL extension - $ cat > $HGRCPATH <<EOF - > [diff] - > git = True - > + $ cat >> $HGRCPATH <<EOF > [extensions] > eol = >
--- a/tests/test-eol-update.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-eol-update.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,6 @@ Test EOL update - $ cat > $HGRCPATH <<EOF + $ cat >> $HGRCPATH <<EOF > [diff] > git = 1 > EOF
--- a/tests/test-eol.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-eol.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,6 @@ Test EOL extension - $ cat > $HGRCPATH <<EOF + $ cat >> $HGRCPATH <<EOF > [diff] > git = True > EOF
--- a/tests/test-eolfilename.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-eolfilename.t Wed May 18 09:56:27 2011 +0200 @@ -36,6 +36,17 @@ o hell o + $ echo bla > quickfox + $ hg add quickfox + $ hg ci -m 2 + $ A=`printf 'quick\rfox'` + $ hg cp quickfox "$A" + abort: '\n' and '\r' disallowed in filenames: 'quick\rfox' + [255] + $ hg mv quickfox "$A" + abort: '\n' and '\r' disallowed in filenames: 'quick\rfox' + [255] + http://mercurial.selenic.com/bts/issue2036 $ cd .. @@ -46,6 +57,8 @@ $ cd bar $ echo "[extensions]" >> $HGRCPATH $ echo "color=" >> $HGRCPATH + $ echo "[color]" >> $HGRCPATH + $ echo "mode = ansi" >> $HGRCPATH $ A=`printf 'foo\nbar'` $ B=`printf 'foo\nbar.baz'` $ touch "$A"
--- a/tests/test-excessive-merge.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-excessive-merge.t Wed May 18 09:56:27 2011 +0200 @@ -63,7 +63,7 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: test - $ hg debugindex .hg/store/00changelog.i + $ hg debugindex --changelog rev offset length base linkrev nodeid p1 p2 0 0 60 0 0 5e0375449e74 000000000000 000000000000 1 60 62 1 1 96155394af80 5e0375449e74 000000000000 @@ -88,7 +88,7 @@ 79d7492df40aa0fa093ec4209be78043c181f094 644 a 79d7492df40aa0fa093ec4209be78043c181f094 644 b - $ hg debugindex .hg/store/data/a.i + $ hg debugindex a rev offset length base linkrev nodeid p1 p2 0 0 5 0 0 2ed2a3912a0b 000000000000 000000000000 1 5 6 1 1 79d7492df40a 2ed2a3912a0b 000000000000
--- a/tests/test-extdiff.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-extdiff.t Wed May 18 09:56:27 2011 +0200 @@ -57,7 +57,7 @@ Should diff cloned files directly: $ hg falabala -r 0:1 - diffing a.8a5febb7f867/a a.34eed99112ab/a + diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) [1] Test diff during merge: @@ -75,7 +75,7 @@ Should diff cloned file against wc file: $ hg falabala - diffing a.2a13a4d2da36/a $TESTTMP/a/a + diffing */extdiff.*/a.2a13a4d2da36/a */a/a (glob) [1] @@ -83,13 +83,13 @@ $ hg ci -d '2 0' -mtest3 $ hg falabala -c 1 - diffing a.8a5febb7f867/a a.34eed99112ab/a + diffing */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) [1] Check diff are made from the first parent: $ hg falabala -c 3 || echo "diff-like tools yield a non-zero exit code" - diffing a.2a13a4d2da36/a a.46c0e4daeb72/a + diffing */extdiff.*/a.2a13a4d2da36/a a.46c0e4daeb72/a (glob) diff-like tools yield a non-zero exit code Test extdiff of multiple files in tmp dir: @@ -161,14 +161,27 @@ Test extdiff with --option: $ hg extdiff -p echo -o this -c 1 - this a.8a5febb7f867/a a.34eed99112ab/a + this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) [1] $ hg falabala -o this -c 1 - diffing this a.8a5febb7f867/a a.34eed99112ab/a + diffing this */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) + [1] + +Test with revsets: + + $ hg extdif -p echo -c "rev(1)" + */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) [1] + + $ hg extdif -p echo -r "0::1" + */extdiff.*/a.8a5febb7f867/a a.34eed99112ab/a (glob) + [1] + $ cd .. +Test symlinks handling (issue1909) + $ hg init testsymlinks $ cd testsymlinks $ echo a > a @@ -181,4 +194,3 @@ diffing testsymlinks.07f494440405 testsymlinks [1] $ cd .. -
--- a/tests/test-extension.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-extension.t Wed May 18 09:56:27 2011 +0200 @@ -232,6 +232,156 @@ [+] marked option can be specified multiple times $ echo 'debugextension = !' >> $HGRCPATH +Extension module help vs command help: + + $ echo 'extdiff =' >> $HGRCPATH + $ hg help extdiff + hg extdiff [OPT]... [FILE]... + + use external program to diff repository (or selected files) + + Show differences between revisions for the specified files, using an + external program. The default program used is diff, with default options + "-Npru". + + To select a different program, use the -p/--program option. The program + will be passed the names of two directories to compare. To pass additional + options to the program, use -o/--option. These will be passed before the + names of the directories to compare. + + When two revision arguments are given, then changes are shown between + those revisions. If only one revision is specified then that revision is + compared to the working directory, and, when no revisions are specified, + the working directory files are compared to its parent. + + use "hg help -e extdiff" to show help for the extdiff extension + + options: + + -p --program CMD comparison program to run + -o --option OPT [+] pass option to comparison program + -r --rev REV [+] revision + -c --change REV change made by revision + -I --include PATTERN [+] include names matching the given patterns + -X --exclude PATTERN [+] exclude names matching the given patterns + + [+] marked option can be specified multiple times + + use "hg -v help extdiff" to show global options + + $ hg help --extension extdiff + extdiff extension - command to allow external programs to compare revisions + + The extdiff Mercurial extension allows you to use external programs to compare + revisions, or revision with working directory. The external diff programs are + called with a configurable set of options and two non-option arguments: paths + to directories containing snapshots of files to compare. + + The extdiff extension also allows you to configure new diff commands, so you + do not need to type "hg extdiff -p kdiff3" always. + + [extdiff] + # add new command that runs GNU diff(1) in 'context diff' mode + cdiff = gdiff -Nprc5 + ## or the old way: + #cmd.cdiff = gdiff + #opts.cdiff = -Nprc5 + + # add new command called vdiff, runs kdiff3 + vdiff = kdiff3 + + # add new command called meld, runs meld (no need to name twice) + meld = + + # add new command called vimdiff, runs gvimdiff with DirDiff plugin + # (see http://www.vim.org/scripts/script.php?script_id=102) Non + # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in + # your .vimrc + vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)' + + Tool arguments can include variables that are expanded at runtime: + + $parent1, $plabel1 - filename, descriptive label of first parent + $child, $clabel - filename, descriptive label of child revision + $parent2, $plabel2 - filename, descriptive label of second parent + $root - repository root + $parent is an alias for $parent1. + + The extdiff extension will look in your [diff-tools] and [merge-tools] + sections for diff tool arguments, when none are specified in [extdiff]. + + [extdiff] + kdiff3 = + + [diff-tools] + kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child + + You can use -I/-X and list of file or directory names like normal "hg diff" + command. The extdiff extension makes snapshots of only needed files, so + running the external diff program will actually be pretty fast (at least + faster than having to compare the entire tree). + + list of commands: + + extdiff use external program to diff repository (or selected files) + + use "hg -v help extdiff" to show builtin aliases and global options + + $ echo 'extdiff = !' >> $HGRCPATH + +Test help topic with same name as extension + + $ cat > multirevs.py <<EOF + > from mercurial import commands + > """multirevs extension + > Big multi-line module docstring.""" + > def multirevs(ui, repo, arg, *args, **opts): + > """multirevs command""" + > pass + > cmdtable = { + > "multirevs": (multirevs, [], 'ARG') + > } + > commands.norepo += ' multirevs' + > EOF + $ echo "multirevs = multirevs.py" >> $HGRCPATH + + $ hg help multirevs + Specifying Multiple Revisions + + When Mercurial accepts more than one revision, they may be specified + individually, or provided as a topologically continuous range, separated + by the ":" character. + + The syntax of range notation is [BEGIN]:[END], where BEGIN and END are + revision identifiers. Both BEGIN and END are optional. If BEGIN is not + specified, it defaults to revision number 0. If END is not specified, it + defaults to the tip. The range ":" thus means "all revisions". + + If BEGIN is greater than END, revisions are treated in reverse order. + + A range acts as a closed interval. This means that a range of 3:5 gives 3, + 4 and 5. Similarly, a range of 9:6 gives 9, 8, 7, and 6. + + use "hg help -c multirevs" to see help for the multirevs command + + $ hg help -c multirevs + hg multirevs ARG + + multirevs command + + use "hg -v help multirevs" to show global options + + $ hg multirevs + hg multirevs: invalid arguments + hg multirevs ARG + + multirevs command + + use "hg help multirevs" to show the full help text + [255] + + $ echo "multirevs = !" >> $HGRCPATH + Issue811: Problem loading extensions twice (by site and by user) $ debugpath=`pwd`/debugissue811.py
--- a/tests/test-extra-filelog-entry.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-extra-filelog-entry.t Wed May 18 09:56:27 2011 +0200 @@ -15,7 +15,7 @@ $ echo b > b $ hg qrefresh - $ hg debugindex .hg/store/data/b.i + $ hg debugindex b rev offset length base linkrev nodeid p1 p2 0 0 3 0 0 1e88685f5dde 000000000000 000000000000
--- a/tests/test-filebranch.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-filebranch.t Wed May 18 09:56:27 2011 +0200 @@ -75,7 +75,7 @@ main: we should have a merge here: - $ hg debugindex .hg/store/00changelog.i + $ hg debugindex --changelog rev offset length base linkrev nodeid p1 p2 0 0 73 0 0 cdca01651b96 000000000000 000000000000 1 73 68 1 1 f6718a9cb7f3 cdca01651b96 000000000000 @@ -99,7 +99,7 @@ foo: we should have a merge here: - $ hg debugindex .hg/store/data/foo.i + $ hg debugindex foo rev offset length base linkrev nodeid p1 p2 0 0 3 0 0 b8e02f643373 000000000000 000000000000 1 3 4 1 1 2ffeddde1b65 b8e02f643373 000000000000 @@ -108,21 +108,21 @@ bar: we should not have a merge here: - $ hg debugindex .hg/store/data/bar.i + $ hg debugindex bar rev offset length base linkrev nodeid p1 p2 0 0 3 0 0 b8e02f643373 000000000000 000000000000 1 3 4 1 2 33d1fb69067a b8e02f643373 000000000000 baz: we should not have a merge here: - $ hg debugindex .hg/store/data/baz.i + $ hg debugindex baz rev offset length base linkrev nodeid p1 p2 0 0 3 0 0 b8e02f643373 000000000000 000000000000 1 3 4 1 1 2ffeddde1b65 b8e02f643373 000000000000 quux: we should not have a merge here: - $ hg debugindex .hg/store/data/quux.i + $ hg debugindex quux rev offset length base linkrev nodeid p1 p2 0 0 3 0 0 b8e02f643373 000000000000 000000000000 1 3 5 1 3 6128c0f33108 b8e02f643373 000000000000
--- a/tests/test-flags.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-flags.t Wed May 18 09:56:27 2011 +0200 @@ -1,8 +1,7 @@ $ umask 027 - $ mkdir test1 + + $ hg init test1 $ cd test1 - - $ hg init $ touch a b $ hg add a b $ hg ci -m "added a b" @@ -11,10 +10,9 @@ $ hg clone test1 test3 updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ mkdir test2 + + $ hg init test2 $ cd test2 - - $ hg init $ hg pull ../test1 pulling from ../test1 requesting all changes @@ -137,13 +135,13 @@ -rwxr-x--- -rwxr-x--- - $ hg debugindex .hg/store/data/a.i + $ hg debugindex a rev offset length base linkrev nodeid p1 p2 0 0 0 0 0 b80de5d13875 000000000000 000000000000 - $ hg debugindex ../test2/.hg/store/data/a.i + $ hg debugindex -R ../test2 a rev offset length base linkrev nodeid p1 p2 0 0 0 0 0 b80de5d13875 000000000000 000000000000 - $ hg debugindex ../test1/.hg/store/data/a.i + $ hg debugindex -R ../test1 a rev offset length base linkrev nodeid p1 p2 0 0 0 0 0 b80de5d13875 000000000000 000000000000 1 0 5 1 1 7fe919cc0336 b80de5d13875 000000000000
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-getbundle.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,253 @@ + += Test the getbundle() protocol function = + +Enable graphlog extension: + + $ echo "[extensions]" >> $HGRCPATH + $ echo "graphlog=" >> $HGRCPATH + +Create a test repository: + + $ hg init repo + $ cd repo + $ hg debugbuilddag -n -m '+2 :fork +5 :p1 *fork +6 :p2 /p1 :m1 +3' > /dev/null + $ hg glog --template '{node}\n' + o 10c14a2cc935e1d8c31f9e98587dcf27fb08a6da + | + o 4801a72e5d88cb515b0c7e40fae34180f3f837f2 + | + o 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3 + | + o 8365676dbab05860ce0d9110f2af51368b961bbd + |\ + | o 5686dbbd9fc46cb806599c878d02fe1cb56b83d3 + | | + | o 13c0170174366b441dc68e8e33757232fa744458 + | | + | o 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 + | | + | o 700b7e19db54103633c4bf4a6a6b6d55f4d50c03 + | | + | o 928b5f94cdb278bb536eba552de348a4e92ef24d + | | + | o f34414c64173e0ecb61b25dc55e116dbbcc89bee + | | + | o 8931463777131cd73923e560b760061f2aa8a4bc + | | + o | 6621d79f61b23ec74cf4b69464343d9e0980ec8b + | | + o | bac16991d12ff45f9dc43c52da1946dfadb83e80 + | | + o | ff42371d57168345fdf1a3aac66a51f6a45d41d2 + | | + o | d5f6e1ea452285324836a49d7d3c2a63cfed1d31 + | | + o | 713346a995c363120712aed1aee7e04afd867638 + |/ + o 29a4d1f17bd3f0779ca0525bebb1cfb51067c738 + | + o 7704483d56b2a7b5db54dcee7c62378ac629b348 + + $ cd .. + + += Test locally = + +Get everything: + + $ hg debuggetbundle repo bundle + $ hg debugbundle bundle + 7704483d56b2a7b5db54dcee7c62378ac629b348 + 29a4d1f17bd3f0779ca0525bebb1cfb51067c738 + 713346a995c363120712aed1aee7e04afd867638 + d5f6e1ea452285324836a49d7d3c2a63cfed1d31 + ff42371d57168345fdf1a3aac66a51f6a45d41d2 + bac16991d12ff45f9dc43c52da1946dfadb83e80 + 6621d79f61b23ec74cf4b69464343d9e0980ec8b + 8931463777131cd73923e560b760061f2aa8a4bc + f34414c64173e0ecb61b25dc55e116dbbcc89bee + 928b5f94cdb278bb536eba552de348a4e92ef24d + 700b7e19db54103633c4bf4a6a6b6d55f4d50c03 + 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 + 13c0170174366b441dc68e8e33757232fa744458 + 5686dbbd9fc46cb806599c878d02fe1cb56b83d3 + 8365676dbab05860ce0d9110f2af51368b961bbd + 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3 + 4801a72e5d88cb515b0c7e40fae34180f3f837f2 + 10c14a2cc935e1d8c31f9e98587dcf27fb08a6da + +Get part of linear run: + + $ hg debuggetbundle repo bundle -H 4801a72e5d88cb515b0c7e40fae34180f3f837f2 -C 8365676dbab05860ce0d9110f2af51368b961bbd + $ hg debugbundle bundle + 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3 + 4801a72e5d88cb515b0c7e40fae34180f3f837f2 + +Get missing branch and merge: + + $ hg debuggetbundle repo bundle -H 4801a72e5d88cb515b0c7e40fae34180f3f837f2 -C 13c0170174366b441dc68e8e33757232fa744458 + $ hg debugbundle bundle + 713346a995c363120712aed1aee7e04afd867638 + d5f6e1ea452285324836a49d7d3c2a63cfed1d31 + ff42371d57168345fdf1a3aac66a51f6a45d41d2 + bac16991d12ff45f9dc43c52da1946dfadb83e80 + 6621d79f61b23ec74cf4b69464343d9e0980ec8b + 5686dbbd9fc46cb806599c878d02fe1cb56b83d3 + 8365676dbab05860ce0d9110f2af51368b961bbd + 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3 + 4801a72e5d88cb515b0c7e40fae34180f3f837f2 + +Get from only one head: + + $ hg debuggetbundle repo bundle -H 928b5f94cdb278bb536eba552de348a4e92ef24d -C 29a4d1f17bd3f0779ca0525bebb1cfb51067c738 + $ hg debugbundle bundle + 8931463777131cd73923e560b760061f2aa8a4bc + f34414c64173e0ecb61b25dc55e116dbbcc89bee + 928b5f94cdb278bb536eba552de348a4e92ef24d + +Get parts of two branches: + + $ hg debuggetbundle repo bundle -H 13c0170174366b441dc68e8e33757232fa744458 -C 700b7e19db54103633c4bf4a6a6b6d55f4d50c03 -H bac16991d12ff45f9dc43c52da1946dfadb83e80 -C d5f6e1ea452285324836a49d7d3c2a63cfed1d31 + $ hg debugbundle bundle + ff42371d57168345fdf1a3aac66a51f6a45d41d2 + bac16991d12ff45f9dc43c52da1946dfadb83e80 + 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 + 13c0170174366b441dc68e8e33757232fa744458 + +Check that we get all needed file changes: + + $ hg debugbundle bundle --all + format: id, p1, p2, cset, delta base, len(delta) + + changelog + ff42371d57168345fdf1a3aac66a51f6a45d41d2 d5f6e1ea452285324836a49d7d3c2a63cfed1d31 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 d5f6e1ea452285324836a49d7d3c2a63cfed1d31 99 + bac16991d12ff45f9dc43c52da1946dfadb83e80 ff42371d57168345fdf1a3aac66a51f6a45d41d2 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 ff42371d57168345fdf1a3aac66a51f6a45d41d2 99 + 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 700b7e19db54103633c4bf4a6a6b6d55f4d50c03 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 bac16991d12ff45f9dc43c52da1946dfadb83e80 102 + 13c0170174366b441dc68e8e33757232fa744458 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 102 + + manifest + dac7984588fc4eea7acbf39693a9c1b06f5b175d 591f732a3faf1fb903815273f3c199a514a61ccb 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 591f732a3faf1fb903815273f3c199a514a61ccb 113 + 0772616e6b48a76afb6c1458e193cbb3dae2e4ff dac7984588fc4eea7acbf39693a9c1b06f5b175d 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 dac7984588fc4eea7acbf39693a9c1b06f5b175d 113 + eb498cd9af6c44108e43041e951ce829e29f6c80 bff2f4817ced57b386caf7c4e3e36a4bc9af7e93 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 0772616e6b48a76afb6c1458e193cbb3dae2e4ff 295 + b15709c071ddd2d93188508ba156196ab4f19620 eb498cd9af6c44108e43041e951ce829e29f6c80 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 eb498cd9af6c44108e43041e951ce829e29f6c80 114 + + mf + 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 301ca08d026bb72cb4258a9d211bdf7ca0bcd810 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 301ca08d026bb72cb4258a9d211bdf7ca0bcd810 17 + c7b583de053293870e145f45bd2d61643563fd06 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 18 + 266ee3c0302a5a18f1cf96817ac79a51836179e9 edc0f6b8db80d68ae6aff2b19f7e5347ab68fa63 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 c7b583de053293870e145f45bd2d61643563fd06 149 + 698c6a36220548cd3903ca7dada27c59aa500c52 266ee3c0302a5a18f1cf96817ac79a51836179e9 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 266ee3c0302a5a18f1cf96817ac79a51836179e9 19 + + nf11 + 33fbc651630ffa7ccbebfe4eb91320a873e7291c 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 0000000000000000000000000000000000000000 16 + + nf12 + ddce0544363f037e9fb889faca058f52dc01c0a5 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 0000000000000000000000000000000000000000 16 + + nf4 + 3c1407305701051cbed9f9cb9a68bdfb5997c235 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 0000000000000000000000000000000000000000 15 + + nf5 + 0dbd89c185f53a1727c54cd1ce256482fa23968e 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 0000000000000000000000000000000000000000 15 + +Get branch and merge: + + $ hg debuggetbundle repo bundle -C 7704483d56b2a7b5db54dcee7c62378ac629b348 -H 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3 + $ hg debugbundle bundle + 29a4d1f17bd3f0779ca0525bebb1cfb51067c738 + 713346a995c363120712aed1aee7e04afd867638 + d5f6e1ea452285324836a49d7d3c2a63cfed1d31 + ff42371d57168345fdf1a3aac66a51f6a45d41d2 + bac16991d12ff45f9dc43c52da1946dfadb83e80 + 6621d79f61b23ec74cf4b69464343d9e0980ec8b + 8931463777131cd73923e560b760061f2aa8a4bc + f34414c64173e0ecb61b25dc55e116dbbcc89bee + 928b5f94cdb278bb536eba552de348a4e92ef24d + 700b7e19db54103633c4bf4a6a6b6d55f4d50c03 + 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 + 13c0170174366b441dc68e8e33757232fa744458 + 5686dbbd9fc46cb806599c878d02fe1cb56b83d3 + 8365676dbab05860ce0d9110f2af51368b961bbd + 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3 + + += Test via HTTP = + +Get everything: + + $ hg serve -R repo -p $HGPORT -d --pid-file=hg.pid -E error.log -A access.log + $ cat hg.pid >> $DAEMON_PIDS + $ hg debuggetbundle http://localhost:$HGPORT/ bundle + $ hg debugbundle bundle + 7704483d56b2a7b5db54dcee7c62378ac629b348 + 29a4d1f17bd3f0779ca0525bebb1cfb51067c738 + 713346a995c363120712aed1aee7e04afd867638 + d5f6e1ea452285324836a49d7d3c2a63cfed1d31 + ff42371d57168345fdf1a3aac66a51f6a45d41d2 + bac16991d12ff45f9dc43c52da1946dfadb83e80 + 6621d79f61b23ec74cf4b69464343d9e0980ec8b + 8931463777131cd73923e560b760061f2aa8a4bc + f34414c64173e0ecb61b25dc55e116dbbcc89bee + 928b5f94cdb278bb536eba552de348a4e92ef24d + 700b7e19db54103633c4bf4a6a6b6d55f4d50c03 + 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 + 13c0170174366b441dc68e8e33757232fa744458 + 5686dbbd9fc46cb806599c878d02fe1cb56b83d3 + 8365676dbab05860ce0d9110f2af51368b961bbd + 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3 + 4801a72e5d88cb515b0c7e40fae34180f3f837f2 + 10c14a2cc935e1d8c31f9e98587dcf27fb08a6da + +Get parts of two branches: + + $ hg debuggetbundle http://localhost:$HGPORT/ bundle -H 13c0170174366b441dc68e8e33757232fa744458 -C 700b7e19db54103633c4bf4a6a6b6d55f4d50c03 -H bac16991d12ff45f9dc43c52da1946dfadb83e80 -C d5f6e1ea452285324836a49d7d3c2a63cfed1d31 + $ hg debugbundle bundle + ff42371d57168345fdf1a3aac66a51f6a45d41d2 + bac16991d12ff45f9dc43c52da1946dfadb83e80 + 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 + 13c0170174366b441dc68e8e33757232fa744458 + +Check that we get all needed file changes: + + $ hg debugbundle bundle --all + format: id, p1, p2, cset, delta base, len(delta) + + changelog + ff42371d57168345fdf1a3aac66a51f6a45d41d2 d5f6e1ea452285324836a49d7d3c2a63cfed1d31 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 d5f6e1ea452285324836a49d7d3c2a63cfed1d31 99 + bac16991d12ff45f9dc43c52da1946dfadb83e80 ff42371d57168345fdf1a3aac66a51f6a45d41d2 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 ff42371d57168345fdf1a3aac66a51f6a45d41d2 99 + 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 700b7e19db54103633c4bf4a6a6b6d55f4d50c03 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 bac16991d12ff45f9dc43c52da1946dfadb83e80 102 + 13c0170174366b441dc68e8e33757232fa744458 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 102 + + manifest + dac7984588fc4eea7acbf39693a9c1b06f5b175d 591f732a3faf1fb903815273f3c199a514a61ccb 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 591f732a3faf1fb903815273f3c199a514a61ccb 113 + 0772616e6b48a76afb6c1458e193cbb3dae2e4ff dac7984588fc4eea7acbf39693a9c1b06f5b175d 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 dac7984588fc4eea7acbf39693a9c1b06f5b175d 113 + eb498cd9af6c44108e43041e951ce829e29f6c80 bff2f4817ced57b386caf7c4e3e36a4bc9af7e93 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 0772616e6b48a76afb6c1458e193cbb3dae2e4ff 295 + b15709c071ddd2d93188508ba156196ab4f19620 eb498cd9af6c44108e43041e951ce829e29f6c80 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 eb498cd9af6c44108e43041e951ce829e29f6c80 114 + + mf + 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 301ca08d026bb72cb4258a9d211bdf7ca0bcd810 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 301ca08d026bb72cb4258a9d211bdf7ca0bcd810 17 + c7b583de053293870e145f45bd2d61643563fd06 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 18 + 266ee3c0302a5a18f1cf96817ac79a51836179e9 edc0f6b8db80d68ae6aff2b19f7e5347ab68fa63 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 c7b583de053293870e145f45bd2d61643563fd06 149 + 698c6a36220548cd3903ca7dada27c59aa500c52 266ee3c0302a5a18f1cf96817ac79a51836179e9 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 266ee3c0302a5a18f1cf96817ac79a51836179e9 19 + + nf11 + 33fbc651630ffa7ccbebfe4eb91320a873e7291c 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 0000000000000000000000000000000000000000 16 + + nf12 + ddce0544363f037e9fb889faca058f52dc01c0a5 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 0000000000000000000000000000000000000000 16 + + nf4 + 3c1407305701051cbed9f9cb9a68bdfb5997c235 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 0000000000000000000000000000000000000000 15 + + nf5 + 0dbd89c185f53a1727c54cd1ce256482fa23968e 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 0000000000000000000000000000000000000000 15 + +Verify we hit the HTTP server: + + $ cat access.log + * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=700b7e19db54103633c4bf4a6a6b6d55f4d50c03+d5f6e1ea452285324836a49d7d3c2a63cfed1d31&heads=13c0170174366b441dc68e8e33757232fa744458+bac16991d12ff45f9dc43c52da1946dfadb83e80 (glob) + + $ cat error.log +
--- a/tests/test-globalopts.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-globalopts.t Wed May 18 09:56:27 2011 +0200 @@ -28,6 +28,7 @@ pulling from ../b searching for changes warning: repository is unrelated + requesting all changes adding changesets adding manifests adding file changes @@ -83,13 +84,13 @@ $ hg ann a/a a/a 0: a $ hg ann a/a b/b - abort: There is no Mercurial repository here (.hg not found)! + abort: no repository found in '$TESTTMP' (.hg not found)! [255] $ hg -R b ann a/a abort: a/a not under root [255] $ hg log - abort: There is no Mercurial repository here (.hg not found)! + abort: no repository found in '$TESTTMP' (.hg not found)! [255] Abbreviation of long option: @@ -334,22 +335,25 @@ config Configuration Files dates Date Formats - patterns File Name Patterns + diffs Diff Formats environment Environment Variables - revisions Specifying Single Revisions + extensions Using additional features + glossary Glossary + hgignore syntax for Mercurial ignore files + hgweb Configuring hgweb + merge-tools Merge Tools multirevs Specifying Multiple Revisions + patterns File Name Patterns + revisions Specifying Single Revisions revsets Specifying Revision Sets - diffs Diff Formats - merge-tools Merge Tools + subrepos Subrepositories templating Template Usage urls URL Paths - extensions Using additional features - subrepos Subrepositories - hgweb Configuring hgweb - glossary Glossary use "hg -v help" to show builtin aliases and global options + + $ hg --help Mercurial Distributed SCM @@ -411,19 +415,20 @@ config Configuration Files dates Date Formats - patterns File Name Patterns + diffs Diff Formats environment Environment Variables - revisions Specifying Single Revisions + extensions Using additional features + glossary Glossary + hgignore syntax for Mercurial ignore files + hgweb Configuring hgweb + merge-tools Merge Tools multirevs Specifying Multiple Revisions + patterns File Name Patterns + revisions Specifying Single Revisions revsets Specifying Revision Sets - diffs Diff Formats - merge-tools Merge Tools + subrepos Subrepositories templating Template Usage urls URL Paths - extensions Using additional features - subrepos Subrepositories - hgweb Configuring hgweb - glossary Glossary use "hg -v help" to show builtin aliases and global options
--- a/tests/test-glog.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-glog.t Wed May 18 09:56:27 2011 +0200 @@ -463,115 +463,115 @@ | | | date: Thu Jan 01 00:00:32 1970 +0000 | | | summary: (32) expand | | | - | o | changeset: 31:621d83e11f67 - | | | parent: 21:d42a756af44d - | | | parent: 30:6e11cd4b648f - | | | user: test - | | | date: Thu Jan 01 00:00:31 1970 +0000 - | | | summary: (31) expand - | | | - | o | changeset: 30:6e11cd4b648f - | |\ \ parent: 28:44ecd0b9ae99 - | | | | parent: 29:cd9bb2be7593 - | | | | user: test - | | | | date: Thu Jan 01 00:00:30 1970 +0000 - | | | | summary: (30) expand - | | | | - | | o | changeset: 29:cd9bb2be7593 - | | | | parent: 0:e6eb3150255d - | | | | user: test - | | | | date: Thu Jan 01 00:00:29 1970 +0000 - | | | | summary: (29) regular commit - | | | | - | o | | changeset: 28:44ecd0b9ae99 - | | | | parent: 1:6db2ef61d156 - | | | | parent: 26:7f25b6c2f0b9 + | o | changeset: 31:621d83e11f67 + | |\ \ parent: 21:d42a756af44d + | | | | parent: 30:6e11cd4b648f | | | | user: test - | | | | date: Thu Jan 01 00:00:28 1970 +0000 - | | | | summary: (28) merge zero known - | | | | - o | | | changeset: 27:886ed638191b - | | | | parent: 21:d42a756af44d - | | | | user: test - | | | | date: Thu Jan 01 00:00:27 1970 +0000 - | | | | summary: (27) collapse - | | | | - | o | | changeset: 26:7f25b6c2f0b9 - | | | | parent: 18:1aa84d96232a - | | | | parent: 25:91da8ed57247 - | | | | user: test - | | | | date: Thu Jan 01 00:00:26 1970 +0000 - | | | | summary: (26) merge one known; far right - | | | | - | o | | changeset: 25:91da8ed57247 - | | | | parent: 21:d42a756af44d - | | | | parent: 24:a9c19a3d96b7 - | | | | user: test - | | | | date: Thu Jan 01 00:00:25 1970 +0000 - | | | | summary: (25) merge one known; far left - | | | | - | o | | changeset: 24:a9c19a3d96b7 - | | | | parent: 0:e6eb3150255d - | | | | parent: 23:a01cddf0766d - | | | | user: test - | | | | date: Thu Jan 01 00:00:24 1970 +0000 - | | | | summary: (24) merge one known; immediate right + | | | | date: Thu Jan 01 00:00:31 1970 +0000 + | | | | summary: (31) expand | | | | - | o | | changeset: 23:a01cddf0766d - | | | | parent: 1:6db2ef61d156 - | | | | parent: 22:e0d9cccacb5d - | | | | user: test - | | | | date: Thu Jan 01 00:00:23 1970 +0000 - | | | | summary: (23) merge one known; immediate left - | | | | - | o | | changeset: 22:e0d9cccacb5d - |/ / / parent: 18:1aa84d96232a - | | | parent: 21:d42a756af44d - | | | user: test - | | | date: Thu Jan 01 00:00:22 1970 +0000 - | | | summary: (22) merge two known; one far left, one far right - | | | - o | | changeset: 21:d42a756af44d - |\ \ \ parent: 19:31ddc2c1573b - | | | | parent: 20:d30ed6450e32 - | | | | user: test - | | | | date: Thu Jan 01 00:00:21 1970 +0000 - | | | | summary: (21) expand + | | o | changeset: 30:6e11cd4b648f + | | |\ \ parent: 28:44ecd0b9ae99 + | | | | | parent: 29:cd9bb2be7593 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:30 1970 +0000 + | | | | | summary: (30) expand + | | | | | + | | | o | changeset: 29:cd9bb2be7593 + | | | | | parent: 0:e6eb3150255d + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:29 1970 +0000 + | | | | | summary: (29) regular commit + | | | | | + | | o | | changeset: 28:44ecd0b9ae99 + | | |\ \ \ parent: 1:6db2ef61d156 + | | | | | | parent: 26:7f25b6c2f0b9 + | | | | | | user: test + | | | | | | date: Thu Jan 01 00:00:28 1970 +0000 + | | | | | | summary: (28) merge zero known + | | | | | | + o | | | | | changeset: 27:886ed638191b + |/ / / / / parent: 21:d42a756af44d + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:27 1970 +0000 + | | | | | summary: (27) collapse + | | | | | + | | o---+ changeset: 26:7f25b6c2f0b9 + | | | | | parent: 18:1aa84d96232a + | | | | | parent: 25:91da8ed57247 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:26 1970 +0000 + | | | | | summary: (26) merge one known; far right + | | | | | + +---o | | changeset: 25:91da8ed57247 + | | | | | parent: 21:d42a756af44d + | | | | | parent: 24:a9c19a3d96b7 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:25 1970 +0000 + | | | | | summary: (25) merge one known; far left + | | | | | + | | o | | changeset: 24:a9c19a3d96b7 + | | |\| | parent: 0:e6eb3150255d + | | | | | parent: 23:a01cddf0766d + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:24 1970 +0000 + | | | | | summary: (24) merge one known; immediate right + | | | | | + | | o | | changeset: 23:a01cddf0766d + | |/| | | parent: 1:6db2ef61d156 + | | | | | parent: 22:e0d9cccacb5d + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:23 1970 +0000 + | | | | | summary: (23) merge one known; immediate left + | | | | | + +---o---+ changeset: 22:e0d9cccacb5d + | | | | parent: 18:1aa84d96232a + | | / / parent: 21:d42a756af44d + | | | | user: test + | | | | date: Thu Jan 01 00:00:22 1970 +0000 + | | | | summary: (22) merge two known; one far left, one far right | | | | - | o---+ changeset: 20:d30ed6450e32 - | | | parent: 0:e6eb3150255d - | / / parent: 18:1aa84d96232a - | | | user: test - | | | date: Thu Jan 01 00:00:20 1970 +0000 - | | | summary: (20) merge two known; two far right - | | | - o | | changeset: 19:31ddc2c1573b - |\ \ \ parent: 15:1dda3f72782d - | | | | parent: 17:44765d7c06e0 - | | | | user: test - | | | | date: Thu Jan 01 00:00:19 1970 +0000 - | | | | summary: (19) expand + o | | | changeset: 21:d42a756af44d + |\ \ \ \ parent: 19:31ddc2c1573b + | | | | | parent: 20:d30ed6450e32 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:21 1970 +0000 + | | | | | summary: (21) expand + | | | | | + | o---+-+ changeset: 20:d30ed6450e32 + | | | | parent: 0:e6eb3150255d + | / / / parent: 18:1aa84d96232a + | | | | user: test + | | | | date: Thu Jan 01 00:00:20 1970 +0000 + | | | | summary: (20) merge two known; two far right | | | | - +-----o changeset: 18:1aa84d96232a - | | | parent: 1:6db2ef61d156 - | | | parent: 15:1dda3f72782d - | | | user: test - | | | date: Thu Jan 01 00:00:18 1970 +0000 - | | | summary: (18) merge two known; two far left - | | | - | o | changeset: 17:44765d7c06e0 - | |\ \ parent: 12:86b91144a6e9 - | | | | parent: 16:3677d192927d - | | | | user: test - | | | | date: Thu Jan 01 00:00:17 1970 +0000 - | | | | summary: (17) expand + o | | | changeset: 19:31ddc2c1573b + |\ \ \ \ parent: 15:1dda3f72782d + | | | | | parent: 17:44765d7c06e0 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:19 1970 +0000 + | | | | | summary: (19) expand + | | | | | + +---+---o changeset: 18:1aa84d96232a + | | | | parent: 1:6db2ef61d156 + | | | | parent: 15:1dda3f72782d + | | | | user: test + | | | | date: Thu Jan 01 00:00:18 1970 +0000 + | | | | summary: (18) merge two known; two far left | | | | - | | o | changeset: 16:3677d192927d - | | | | parent: 0:e6eb3150255d - | | | | parent: 1:6db2ef61d156 - | | | | user: test - | | | | date: Thu Jan 01 00:00:16 1970 +0000 - | | | | summary: (16) merge two known; one immediate right, one near right + | o | | changeset: 17:44765d7c06e0 + | |\ \ \ parent: 12:86b91144a6e9 + | | | | | parent: 16:3677d192927d + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:17 1970 +0000 + | | | | | summary: (17) expand + | | | | | + | | o---+ changeset: 16:3677d192927d + | | | | | parent: 0:e6eb3150255d + | | |/ / parent: 1:6db2ef61d156 + | | | | user: test + | | | | date: Thu Jan 01 00:00:16 1970 +0000 + | | | | summary: (16) merge two known; one immediate right, one near right | | | | o | | | changeset: 15:1dda3f72782d |\ \ \ \ parent: 13:22d8966a97e3 @@ -580,9 +580,9 @@ | | | | | date: Thu Jan 01 00:00:15 1970 +0000 | | | | | summary: (15) expand | | | | | - | o | | | changeset: 14:8eac370358ef - | |/ / / parent: 0:e6eb3150255d - | | | | parent: 12:86b91144a6e9 + | o-----+ changeset: 14:8eac370358ef + | | | | | parent: 0:e6eb3150255d + | |/ / / parent: 12:86b91144a6e9 | | | | user: test | | | | date: Thu Jan 01 00:00:14 1970 +0000 | | | | summary: (14) merge two known; one immediate right, one far right @@ -595,72 +595,72 @@ | | | | | summary: (13) expand | | | | | +---o | | changeset: 12:86b91144a6e9 - | | / / parent: 1:6db2ef61d156 + | | |/ / parent: 1:6db2ef61d156 | | | | parent: 9:7010c0af0a35 | | | | user: test | | | | date: Thu Jan 01 00:00:12 1970 +0000 | | | | summary: (12) merge two known; one immediate right, one far left | | | | - | o | | changeset: 11:832d76e6bdf2 - | | | | parent: 6:b105a072e251 - | | | | parent: 10:74c64d036d72 - | | | | user: test - | | | | date: Thu Jan 01 00:00:11 1970 +0000 - | | | | summary: (11) expand - | | | | - | o | | changeset: 10:74c64d036d72 - | | | | parent: 0:e6eb3150255d - | | | | parent: 6:b105a072e251 - | | | | user: test - | | | | date: Thu Jan 01 00:00:10 1970 +0000 - | | | | summary: (10) merge two known; one immediate left, one near right + | o | | changeset: 11:832d76e6bdf2 + | |\ \ \ parent: 6:b105a072e251 + | | | | | parent: 10:74c64d036d72 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:11 1970 +0000 + | | | | | summary: (11) expand + | | | | | + | | o---+ changeset: 10:74c64d036d72 + | | | | | parent: 0:e6eb3150255d + | |/ / / parent: 6:b105a072e251 + | | | | user: test + | | | | date: Thu Jan 01 00:00:10 1970 +0000 + | | | | summary: (10) merge two known; one immediate left, one near right | | | | - o | | | changeset: 9:7010c0af0a35 - | | | | parent: 7:b632bb1b1224 - | | | | parent: 8:7a0b11f71937 - | | | | user: test - | | | | date: Thu Jan 01 00:00:09 1970 +0000 - | | | | summary: (9) expand - | | | | - o | | | changeset: 8:7a0b11f71937 - | | | | parent: 0:e6eb3150255d - | | | | parent: 7:b632bb1b1224 - | | | | user: test - | | | | date: Thu Jan 01 00:00:08 1970 +0000 - | | | | summary: (8) merge two known; one immediate left, one far right + o | | | changeset: 9:7010c0af0a35 + |\ \ \ \ parent: 7:b632bb1b1224 + | | | | | parent: 8:7a0b11f71937 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:09 1970 +0000 + | | | | | summary: (9) expand + | | | | | + | o-----+ changeset: 8:7a0b11f71937 + | | | | | parent: 0:e6eb3150255d + |/ / / / parent: 7:b632bb1b1224 + | | | | user: test + | | | | date: Thu Jan 01 00:00:08 1970 +0000 + | | | | summary: (8) merge two known; one immediate left, one far right | | | | - o | | | changeset: 7:b632bb1b1224 - | | | | parent: 2:3d9a33b8d1e1 - | | | | parent: 5:4409d547b708 - | | | | user: test - | | | | date: Thu Jan 01 00:00:07 1970 +0000 - | | | | summary: (7) expand + o | | | changeset: 7:b632bb1b1224 + |\ \ \ \ parent: 2:3d9a33b8d1e1 + | | | | | parent: 5:4409d547b708 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:07 1970 +0000 + | | | | | summary: (7) expand + | | | | | + +---o | | changeset: 6:b105a072e251 + | |/ / / parent: 2:3d9a33b8d1e1 + | | | | parent: 5:4409d547b708 + | | | | user: test + | | | | date: Thu Jan 01 00:00:06 1970 +0000 + | | | | summary: (6) merge two known; one immediate left, one far left | | | | - | o | | changeset: 6:b105a072e251 - |/ / / parent: 2:3d9a33b8d1e1 - | | | parent: 5:4409d547b708 - | | | user: test - | | | date: Thu Jan 01 00:00:06 1970 +0000 - | | | summary: (6) merge two known; one immediate left, one far left - | | | - o | | changeset: 5:4409d547b708 - | | | parent: 3:27eef8ed80b4 - | | | parent: 4:26a8bac39d9f - | | | user: test - | | | date: Thu Jan 01 00:00:05 1970 +0000 - | | | summary: (5) expand - | | | - o | | changeset: 4:26a8bac39d9f - | | | parent: 1:6db2ef61d156 - | | | parent: 3:27eef8ed80b4 - | | | user: test - | | | date: Thu Jan 01 00:00:04 1970 +0000 - | | | summary: (4) merge two known; one immediate left, one immediate right - | | | - o | | changeset: 3:27eef8ed80b4 - | | | user: test - | | | date: Thu Jan 01 00:00:03 1970 +0000 - | | | summary: (3) collapse + | o | | changeset: 5:4409d547b708 + | |\ \ \ parent: 3:27eef8ed80b4 + | | | | | parent: 4:26a8bac39d9f + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:05 1970 +0000 + | | | | | summary: (5) expand + | | | | | + | | o | | changeset: 4:26a8bac39d9f + | |/|/ / parent: 1:6db2ef61d156 + | | | | parent: 3:27eef8ed80b4 + | | | | user: test + | | | | date: Thu Jan 01 00:00:04 1970 +0000 + | | | | summary: (4) merge two known; one immediate left, one immediate right + | | | | + | o | | changeset: 3:27eef8ed80b4 + |/ / / user: test + | | | date: Thu Jan 01 00:00:03 1970 +0000 + | | | summary: (3) collapse | | | o | | changeset: 2:3d9a33b8d1e1 |/ / user: test @@ -678,18 +678,438 @@ summary: (0) root -Unused arguments: - $ hg glog -q foo bar - hg glog: invalid arguments - hg glog [OPTION]... [FILE] +File glog per revset: + + $ hg glog -r 'file("a")' + @ changeset: 34:fea3ac5810e0 + | tag: tip + | parent: 32:d06dffa21a31 + | user: test + | date: Thu Jan 01 00:00:34 1970 +0000 + | summary: (34) head + | + | o changeset: 33:68608f5145f9 + | | parent: 18:1aa84d96232a + | | user: test + | | date: Thu Jan 01 00:00:33 1970 +0000 + | | summary: (33) head + | | + o | changeset: 32:d06dffa21a31 + |\ \ parent: 27:886ed638191b + | | | parent: 31:621d83e11f67 + | | | user: test + | | | date: Thu Jan 01 00:00:32 1970 +0000 + | | | summary: (32) expand + | | | + | o | changeset: 31:621d83e11f67 + | |\ \ parent: 21:d42a756af44d + | | | | parent: 30:6e11cd4b648f + | | | | user: test + | | | | date: Thu Jan 01 00:00:31 1970 +0000 + | | | | summary: (31) expand + | | | | + | | o | changeset: 30:6e11cd4b648f + | | |\ \ parent: 28:44ecd0b9ae99 + | | | | | parent: 29:cd9bb2be7593 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:30 1970 +0000 + | | | | | summary: (30) expand + | | | | | + | | | o | changeset: 29:cd9bb2be7593 + | | | | | parent: 0:e6eb3150255d + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:29 1970 +0000 + | | | | | summary: (29) regular commit + | | | | | + | | o | | changeset: 28:44ecd0b9ae99 + | | |\ \ \ parent: 1:6db2ef61d156 + | | | | | | parent: 26:7f25b6c2f0b9 + | | | | | | user: test + | | | | | | date: Thu Jan 01 00:00:28 1970 +0000 + | | | | | | summary: (28) merge zero known + | | | | | | + o | | | | | changeset: 27:886ed638191b + |/ / / / / parent: 21:d42a756af44d + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:27 1970 +0000 + | | | | | summary: (27) collapse + | | | | | + | | o---+ changeset: 26:7f25b6c2f0b9 + | | | | | parent: 18:1aa84d96232a + | | | | | parent: 25:91da8ed57247 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:26 1970 +0000 + | | | | | summary: (26) merge one known; far right + | | | | | + +---o | | changeset: 25:91da8ed57247 + | | | | | parent: 21:d42a756af44d + | | | | | parent: 24:a9c19a3d96b7 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:25 1970 +0000 + | | | | | summary: (25) merge one known; far left + | | | | | + | | o | | changeset: 24:a9c19a3d96b7 + | | |\| | parent: 0:e6eb3150255d + | | | | | parent: 23:a01cddf0766d + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:24 1970 +0000 + | | | | | summary: (24) merge one known; immediate right + | | | | | + | | o | | changeset: 23:a01cddf0766d + | |/| | | parent: 1:6db2ef61d156 + | | | | | parent: 22:e0d9cccacb5d + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:23 1970 +0000 + | | | | | summary: (23) merge one known; immediate left + | | | | | + +---o---+ changeset: 22:e0d9cccacb5d + | | | | parent: 18:1aa84d96232a + | | / / parent: 21:d42a756af44d + | | | | user: test + | | | | date: Thu Jan 01 00:00:22 1970 +0000 + | | | | summary: (22) merge two known; one far left, one far right + | | | | + o | | | changeset: 21:d42a756af44d + |\ \ \ \ parent: 19:31ddc2c1573b + | | | | | parent: 20:d30ed6450e32 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:21 1970 +0000 + | | | | | summary: (21) expand + | | | | | + | o---+-+ changeset: 20:d30ed6450e32 + | | | | parent: 0:e6eb3150255d + | / / / parent: 18:1aa84d96232a + | | | | user: test + | | | | date: Thu Jan 01 00:00:20 1970 +0000 + | | | | summary: (20) merge two known; two far right + | | | | + o | | | changeset: 19:31ddc2c1573b + |\ \ \ \ parent: 15:1dda3f72782d + | | | | | parent: 17:44765d7c06e0 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:19 1970 +0000 + | | | | | summary: (19) expand + | | | | | + +---+---o changeset: 18:1aa84d96232a + | | | | parent: 1:6db2ef61d156 + | | | | parent: 15:1dda3f72782d + | | | | user: test + | | | | date: Thu Jan 01 00:00:18 1970 +0000 + | | | | summary: (18) merge two known; two far left + | | | | + | o | | changeset: 17:44765d7c06e0 + | |\ \ \ parent: 12:86b91144a6e9 + | | | | | parent: 16:3677d192927d + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:17 1970 +0000 + | | | | | summary: (17) expand + | | | | | + | | o---+ changeset: 16:3677d192927d + | | | | | parent: 0:e6eb3150255d + | | |/ / parent: 1:6db2ef61d156 + | | | | user: test + | | | | date: Thu Jan 01 00:00:16 1970 +0000 + | | | | summary: (16) merge two known; one immediate right, one near right + | | | | + o | | | changeset: 15:1dda3f72782d + |\ \ \ \ parent: 13:22d8966a97e3 + | | | | | parent: 14:8eac370358ef + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:15 1970 +0000 + | | | | | summary: (15) expand + | | | | | + | o-----+ changeset: 14:8eac370358ef + | | | | | parent: 0:e6eb3150255d + | |/ / / parent: 12:86b91144a6e9 + | | | | user: test + | | | | date: Thu Jan 01 00:00:14 1970 +0000 + | | | | summary: (14) merge two known; one immediate right, one far right + | | | | + o | | | changeset: 13:22d8966a97e3 + |\ \ \ \ parent: 9:7010c0af0a35 + | | | | | parent: 11:832d76e6bdf2 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:13 1970 +0000 + | | | | | summary: (13) expand + | | | | | + +---o | | changeset: 12:86b91144a6e9 + | | |/ / parent: 1:6db2ef61d156 + | | | | parent: 9:7010c0af0a35 + | | | | user: test + | | | | date: Thu Jan 01 00:00:12 1970 +0000 + | | | | summary: (12) merge two known; one immediate right, one far left + | | | | + | o | | changeset: 11:832d76e6bdf2 + | |\ \ \ parent: 6:b105a072e251 + | | | | | parent: 10:74c64d036d72 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:11 1970 +0000 + | | | | | summary: (11) expand + | | | | | + | | o---+ changeset: 10:74c64d036d72 + | | | | | parent: 0:e6eb3150255d + | |/ / / parent: 6:b105a072e251 + | | | | user: test + | | | | date: Thu Jan 01 00:00:10 1970 +0000 + | | | | summary: (10) merge two known; one immediate left, one near right + | | | | + o | | | changeset: 9:7010c0af0a35 + |\ \ \ \ parent: 7:b632bb1b1224 + | | | | | parent: 8:7a0b11f71937 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:09 1970 +0000 + | | | | | summary: (9) expand + | | | | | + | o-----+ changeset: 8:7a0b11f71937 + | | | | | parent: 0:e6eb3150255d + |/ / / / parent: 7:b632bb1b1224 + | | | | user: test + | | | | date: Thu Jan 01 00:00:08 1970 +0000 + | | | | summary: (8) merge two known; one immediate left, one far right + | | | | + o | | | changeset: 7:b632bb1b1224 + |\ \ \ \ parent: 2:3d9a33b8d1e1 + | | | | | parent: 5:4409d547b708 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:07 1970 +0000 + | | | | | summary: (7) expand + | | | | | + +---o | | changeset: 6:b105a072e251 + | |/ / / parent: 2:3d9a33b8d1e1 + | | | | parent: 5:4409d547b708 + | | | | user: test + | | | | date: Thu Jan 01 00:00:06 1970 +0000 + | | | | summary: (6) merge two known; one immediate left, one far left + | | | | + | o | | changeset: 5:4409d547b708 + | |\ \ \ parent: 3:27eef8ed80b4 + | | | | | parent: 4:26a8bac39d9f + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:05 1970 +0000 + | | | | | summary: (5) expand + | | | | | + | | o | | changeset: 4:26a8bac39d9f + | |/|/ / parent: 1:6db2ef61d156 + | | | | parent: 3:27eef8ed80b4 + | | | | user: test + | | | | date: Thu Jan 01 00:00:04 1970 +0000 + | | | | summary: (4) merge two known; one immediate left, one immediate right + | | | | + | o | | changeset: 3:27eef8ed80b4 + |/ / / user: test + | | | date: Thu Jan 01 00:00:03 1970 +0000 + | | | summary: (3) collapse + | | | + o | | changeset: 2:3d9a33b8d1e1 + |/ / user: test + | | date: Thu Jan 01 00:00:02 1970 +0000 + | | summary: (2) collapse + | | + o | changeset: 1:6db2ef61d156 + |/ user: test + | date: Thu Jan 01 00:00:01 1970 +0000 + | summary: (1) collapse + | + o changeset: 0:e6eb3150255d + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: (0) root - show revision history alongside an ASCII revision graph - [255] + + +File glog per revset (only merges): -Only one file is allowed: - $ hg log -G foo bar - abort: --graph option allows at most one file - [255] + $ hg log -G -r 'file("a")' -m + o changeset: 32:d06dffa21a31 + |\ parent: 27:886ed638191b + | | parent: 31:621d83e11f67 + | | user: test + | | date: Thu Jan 01 00:00:32 1970 +0000 + | | summary: (32) expand + | | + o | changeset: 31:621d83e11f67 + |\| parent: 21:d42a756af44d + | | parent: 30:6e11cd4b648f + | | user: test + | | date: Thu Jan 01 00:00:31 1970 +0000 + | | summary: (31) expand + | | + o | changeset: 30:6e11cd4b648f + |\ \ parent: 28:44ecd0b9ae99 + | | | parent: 29:cd9bb2be7593 + | | | user: test + | | | date: Thu Jan 01 00:00:30 1970 +0000 + | | | summary: (30) expand + | | | + o | | changeset: 28:44ecd0b9ae99 + |\ \ \ parent: 1:6db2ef61d156 + | | | | parent: 26:7f25b6c2f0b9 + | | | | user: test + | | | | date: Thu Jan 01 00:00:28 1970 +0000 + | | | | summary: (28) merge zero known + | | | | + o | | | changeset: 26:7f25b6c2f0b9 + |\ \ \ \ parent: 18:1aa84d96232a + | | | | | parent: 25:91da8ed57247 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:26 1970 +0000 + | | | | | summary: (26) merge one known; far right + | | | | | + | o-----+ changeset: 25:91da8ed57247 + | | | | | parent: 21:d42a756af44d + | | | | | parent: 24:a9c19a3d96b7 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:25 1970 +0000 + | | | | | summary: (25) merge one known; far left + | | | | | + | o | | | changeset: 24:a9c19a3d96b7 + | |\ \ \ \ parent: 0:e6eb3150255d + | | | | | | parent: 23:a01cddf0766d + | | | | | | user: test + | | | | | | date: Thu Jan 01 00:00:24 1970 +0000 + | | | | | | summary: (24) merge one known; immediate right + | | | | | | + | o---+ | | changeset: 23:a01cddf0766d + | | | | | | parent: 1:6db2ef61d156 + | | | | | | parent: 22:e0d9cccacb5d + | | | | | | user: test + | | | | | | date: Thu Jan 01 00:00:23 1970 +0000 + | | | | | | summary: (23) merge one known; immediate left + | | | | | | + | o-------+ changeset: 22:e0d9cccacb5d + | | | | | | parent: 18:1aa84d96232a + |/ / / / / parent: 21:d42a756af44d + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:22 1970 +0000 + | | | | | summary: (22) merge two known; one far left, one far right + | | | | | + | | | | o changeset: 21:d42a756af44d + | | | | |\ parent: 19:31ddc2c1573b + | | | | | | parent: 20:d30ed6450e32 + | | | | | | user: test + | | | | | | date: Thu Jan 01 00:00:21 1970 +0000 + | | | | | | summary: (21) expand + | | | | | | + +-+-------o changeset: 20:d30ed6450e32 + | | | | | parent: 0:e6eb3150255d + | | | | | parent: 18:1aa84d96232a + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:20 1970 +0000 + | | | | | summary: (20) merge two known; two far right + | | | | | + | | | | o changeset: 19:31ddc2c1573b + | | | | |\ parent: 15:1dda3f72782d + | | | | | | parent: 17:44765d7c06e0 + | | | | | | user: test + | | | | | | date: Thu Jan 01 00:00:19 1970 +0000 + | | | | | | summary: (19) expand + | | | | | | + o---+---+ | changeset: 18:1aa84d96232a + | | | | | parent: 1:6db2ef61d156 + / / / / / parent: 15:1dda3f72782d + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:18 1970 +0000 + | | | | | summary: (18) merge two known; two far left + | | | | | + | | | | o changeset: 17:44765d7c06e0 + | | | | |\ parent: 12:86b91144a6e9 + | | | | | | parent: 16:3677d192927d + | | | | | | user: test + | | | | | | date: Thu Jan 01 00:00:17 1970 +0000 + | | | | | | summary: (17) expand + | | | | | | + +-+-------o changeset: 16:3677d192927d + | | | | | parent: 0:e6eb3150255d + | | | | | parent: 1:6db2ef61d156 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:16 1970 +0000 + | | | | | summary: (16) merge two known; one immediate right, one near right + | | | | | + | | | o | changeset: 15:1dda3f72782d + | | | |\ \ parent: 13:22d8966a97e3 + | | | | | | parent: 14:8eac370358ef + | | | | | | user: test + | | | | | | date: Thu Jan 01 00:00:15 1970 +0000 + | | | | | | summary: (15) expand + | | | | | | + +-------o | changeset: 14:8eac370358ef + | | | | |/ parent: 0:e6eb3150255d + | | | | | parent: 12:86b91144a6e9 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:14 1970 +0000 + | | | | | summary: (14) merge two known; one immediate right, one far right + | | | | | + | | | o | changeset: 13:22d8966a97e3 + | | | |\ \ parent: 9:7010c0af0a35 + | | | | | | parent: 11:832d76e6bdf2 + | | | | | | user: test + | | | | | | date: Thu Jan 01 00:00:13 1970 +0000 + | | | | | | summary: (13) expand + | | | | | | + | +---+---o changeset: 12:86b91144a6e9 + | | | | | parent: 1:6db2ef61d156 + | | | | | parent: 9:7010c0af0a35 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:12 1970 +0000 + | | | | | summary: (12) merge two known; one immediate right, one far left + | | | | | + | | | | o changeset: 11:832d76e6bdf2 + | | | | |\ parent: 6:b105a072e251 + | | | | | | parent: 10:74c64d036d72 + | | | | | | user: test + | | | | | | date: Thu Jan 01 00:00:11 1970 +0000 + | | | | | | summary: (11) expand + | | | | | | + +---------o changeset: 10:74c64d036d72 + | | | | |/ parent: 0:e6eb3150255d + | | | | | parent: 6:b105a072e251 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:10 1970 +0000 + | | | | | summary: (10) merge two known; one immediate left, one near right + | | | | | + | | | o | changeset: 9:7010c0af0a35 + | | | |\ \ parent: 7:b632bb1b1224 + | | | | | | parent: 8:7a0b11f71937 + | | | | | | user: test + | | | | | | date: Thu Jan 01 00:00:09 1970 +0000 + | | | | | | summary: (9) expand + | | | | | | + +-------o | changeset: 8:7a0b11f71937 + | | | |/ / parent: 0:e6eb3150255d + | | | | | parent: 7:b632bb1b1224 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:08 1970 +0000 + | | | | | summary: (8) merge two known; one immediate left, one far right + | | | | | + | | | o | changeset: 7:b632bb1b1224 + | | | |\ \ parent: 2:3d9a33b8d1e1 + | | | | | | parent: 5:4409d547b708 + | | | | | | user: test + | | | | | | date: Thu Jan 01 00:00:07 1970 +0000 + | | | | | | summary: (7) expand + | | | | | | + | | | +---o changeset: 6:b105a072e251 + | | | | |/ parent: 2:3d9a33b8d1e1 + | | | | | parent: 5:4409d547b708 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:06 1970 +0000 + | | | | | summary: (6) merge two known; one immediate left, one far left + | | | | | + | | | o | changeset: 5:4409d547b708 + | | | |\ \ parent: 3:27eef8ed80b4 + | | | | | | parent: 4:26a8bac39d9f + | | | | | | user: test + | | | | | | date: Thu Jan 01 00:00:05 1970 +0000 + | | | | | | summary: (5) expand + | | | | | | + | +---o | | changeset: 4:26a8bac39d9f + | | | |/ / parent: 1:6db2ef61d156 + | | | | | parent: 3:27eef8ed80b4 + | | | | | user: test + | | | | | date: Thu Jan 01 00:00:04 1970 +0000 + | | | | | summary: (4) merge two known; one immediate left, one immediate right + | | | | | + Empty revision range - display nothing: $ hg glog -r 1..0 @@ -733,10 +1153,10 @@ | summary: more | o changeset: 1:5ac72c0599bf - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: two - + | user: test + | date: Thu Jan 01 00:00:00 1970 +0000 + | summary: two + | Issue1896: File log with explicit style $ hg glog --style=default one @@ -868,7 +1288,26 @@ | | | summary: (32) expand | | | +Point out a common and an uncommon unshown parent + + $ hg glog -r 'rev(8) or rev(9)' + o changeset: 9:7010c0af0a35 + |\ parent: 7:b632bb1b1224 + | | parent: 8:7a0b11f71937 + | | user: test + | | date: Thu Jan 01 00:00:09 1970 +0000 + | | summary: (9) expand + | | + o | changeset: 8:7a0b11f71937 + |\| parent: 0:e6eb3150255d + | | parent: 7:b632bb1b1224 + | | user: test + | | date: Thu Jan 01 00:00:08 1970 +0000 + | | summary: (8) merge two known; one immediate left, one far right + | | + File + limit + -ra:b, b < tip: + $ hg glog -l1 -r32:34 a o changeset: 34:fea3ac5810e0 | parent: 32:d06dffa21a31 @@ -877,7 +1316,33 @@ | summary: (34) head | +file(File) + limit + -ra:b, b < tip: + + $ hg glog -l1 -r32:34 -r 'file("a")' + o changeset: 34:fea3ac5810e0 + | parent: 32:d06dffa21a31 + | user: test + | date: Thu Jan 01 00:00:34 1970 +0000 + | summary: (34) head + | + +limit(file(File) and a::b), b < tip: + + $ hg glog -r 'limit(file("a") and 32::34, 1)' + o changeset: 32:d06dffa21a31 + |\ parent: 27:886ed638191b + | | parent: 31:621d83e11f67 + | | user: test + | | date: Thu Jan 01 00:00:32 1970 +0000 + | | summary: (32) expand + | | + +File + limit + -ra:b, b < tip: + + $ hg glog -r 'limit(file("a") and 34::32, 1)' + File + limit + -ra:b, b < tip, (b - a) < limit: + $ hg glog -l10 -r33:34 a o changeset: 34:fea3ac5810e0 | parent: 32:d06dffa21a31 @@ -928,3 +1393,79 @@ | | | date: Thu Jan 01 00:00:32 1970 +0000 | | | summary: (32) expand | | | + +Test log -G options + + $ hg log -G -u 'something nice' + $ hg log -G -b 'something nice' + abort: unknown revision 'something nice'! + [255] + $ hg log -G -k 'something nice' + $ hg log -G --only-branch 'something nice' + abort: unknown revision 'something nice'! + [255] + $ hg log -G --include 'some file' --exclude 'another file' + $ hg log -G --follow --template 'nodetag {rev}\n' | grep nodetag | wc -l + \s*36 (re) + $ hg log -G --removed --template 'nodetag {rev}\n' | grep nodetag | wc -l + \s*0 (re) + $ hg log -G --only-merges --template 'nodetag {rev}\n' | grep nodetag | wc -l + \s*28 (re) + $ hg log -G --no-merges --template 'nodetag {rev}\n' + o nodetag 35 + | + o nodetag 34 + |\ + | \ + | |\ + | | \ + | | |\ + | | | \ + | | | |\ + | | | | \ + | | | | |\ + +-+-+-+-----o nodetag 33 + | | | | | | + +---------o nodetag 29 + | | | | | + +-+-+---o nodetag 27 + | | | |/ + | | | o nodetag 3 + | | |/ + | | o nodetag 2 + | |/ + | o nodetag 1 + |/ + o nodetag 0 + + $ hg log -G -d 'brace ) in a date' + abort: invalid date: 'brace ) in a date' + [255] + $ hg log -G -P 32 --template '{rev}\n' + @ 36 + | + o 35 + | + o 34 + | + | o 33 + | | + $ hg log -G --follow a + abort: -G/--graph option is incompatible with --follow with file argument + [255] + +Test multiple revision specifications are correctly handled + + $ hg log -G -r 27 -r 25 -r 21 -r 34 -r 32 -r 31 --template '{rev}\n' + o 34 + | + o 32 + |\ + | o 31 + | |\ + o | | 27 + |/ / + | o 25 + |/ + o 21 + |\
--- a/tests/test-grep.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-grep.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,5 @@ - $ mkdir t + $ hg init t $ cd t - $ hg init $ echo import > port $ hg add port $ hg commit -m 0 -u spam -d '0 0' @@ -166,3 +165,11 @@ $ hg grep --all red color:3:-:red color:1:+:red + + $ hg init a + $ cd a + $ cp $TESTDIR/binfile.bin . + $ hg add binfile.bin + $ hg ci -m 'add binfile.bin' + $ hg grep "MaCam" --all + binfile.bin:0:+: Binary file matches
--- a/tests/test-hardlinks.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hardlinks.t Wed May 18 09:56:27 2011 +0200 @@ -25,9 +25,8 @@ Prepare repo r1: - $ mkdir r1 + $ hg init r1 $ cd r1 - $ hg init $ echo c1 > f1 $ hg add f1
--- a/tests/test-help.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-help.t Wed May 18 09:56:27 2011 +0200 @@ -105,19 +105,20 @@ config Configuration Files dates Date Formats - patterns File Name Patterns + diffs Diff Formats environment Environment Variables - revisions Specifying Single Revisions + extensions Using additional features + glossary Glossary + hgignore syntax for Mercurial ignore files + hgweb Configuring hgweb + merge-tools Merge Tools multirevs Specifying Multiple Revisions + patterns File Name Patterns + revisions Specifying Single Revisions revsets Specifying Revision Sets - diffs Diff Formats - merge-tools Merge Tools + subrepos Subrepositories templating Template Usage urls URL Paths - extensions Using additional features - subrepos Subrepositories - hgweb Configuring hgweb - glossary Glossary use "hg -v help" to show builtin aliases and global options @@ -178,19 +179,20 @@ config Configuration Files dates Date Formats - patterns File Name Patterns + diffs Diff Formats environment Environment Variables - revisions Specifying Single Revisions + extensions Using additional features + glossary Glossary + hgignore syntax for Mercurial ignore files + hgweb Configuring hgweb + merge-tools Merge Tools multirevs Specifying Multiple Revisions + patterns File Name Patterns + revisions Specifying Single Revisions revsets Specifying Revision Sets - diffs Diff Formats - merge-tools Merge Tools + subrepos Subrepositories templating Template Usage urls URL Paths - extensions Using additional features - subrepos Subrepositories - hgweb Configuring hgweb - glossary Glossary Test short command list with verbose option @@ -387,17 +389,6 @@ add the specified files on the next commit - Schedule files to be version controlled and added to the repository. - - The files will be added to the repository at the next commit. To undo an - add before that, see "hg forget". - - If no names are given, add all files to the repository. - - Returns 0 if all files are successfully added. - - use "hg -v help add" to show verbose help - options: -I --include PATTERN [+] include names matching the given patterns @@ -407,7 +398,7 @@ [+] marked option can be specified multiple times - use "hg -v help add" to show global options + use "hg help add" to show the full help text [255] Test ambiguous command help @@ -705,22 +696,25 @@ config Configuration Files dates Date Formats - patterns File Name Patterns + diffs Diff Formats environment Environment Variables - revisions Specifying Single Revisions + extensions Using additional features + glossary Glossary + hgignore syntax for Mercurial ignore files + hgweb Configuring hgweb + merge-tools Merge Tools multirevs Specifying Multiple Revisions + patterns File Name Patterns + revisions Specifying Single Revisions revsets Specifying Revision Sets - diffs Diff Formats - merge-tools Merge Tools + subrepos Subrepositories templating Template Usage urls URL Paths - extensions Using additional features - subrepos Subrepositories - hgweb Configuring hgweb - glossary Glossary use "hg -v help" to show builtin aliases and global options + + Test list of commands with command with no help text $ hg help helpext @@ -765,6 +759,14 @@ working directory is checked out, it is equivalent to null. If an uncommitted merge is in progress, "." is the revision of the first parent. +Test templating help + + $ hg help templating | egrep '(desc|diffstat|firstline|nonempty) ' + desc String. The text of the changeset description. + diffstat String. Statistics of changes with the following format: + firstline Any text. Returns the first line of text. + nonempty Any text. Returns '(none)' if the string is empty. + Test help hooks $ cat > helphook1.py <<EOF
--- a/tests/test-hg-parseurl.py Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hg-parseurl.py Wed May 18 09:56:27 2011 +0200 @@ -8,3 +8,6 @@ testparse('http://example.com/no/anchor/branches', branch=['foo']) testparse('http://example.com/an/anchor/branches#bar', branch=['foo']) testparse('http://example.com/an/anchor/branches-None#foo', branch=None) +testparse('http://example.com/') +testparse('http://example.com') +testparse('http://example.com#foo')
--- a/tests/test-hg-parseurl.py.out Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hg-parseurl.py.out Wed May 18 09:56:27 2011 +0200 @@ -3,3 +3,6 @@ http://example.com/no/anchor/branches, branches: (None, ['foo']) http://example.com/an/anchor/branches, branches: ('bar', ['foo']) http://example.com/an/anchor/branches-None, branches: ('foo', []) +http://example.com/, branches: (None, []) +http://example.com/, branches: (None, []) +http://example.com/, branches: ('foo', [])
--- a/tests/test-hgcia.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgcia.t Wed May 18 09:56:27 2011 +0200 @@ -7,6 +7,9 @@ > [hooks] > changegroup.cia = python:hgext.hgcia.hook > + > [web] + > baseurl = http://hgserver/ + > > [cia] > user = testuser > project = testproject @@ -43,8 +46,8 @@ <author>test</author> <version>0:e63c23eaa88a</version> <log>foo</log> - - <files><file action="add">foo</file></files> + <url>http://hgserver/$TESTTMP/cia/rev/e63c23eaa88a</url> + <files><file uri="http://hgserver/$TESTTMP/cia/file/e63c23eaa88a/foo" action="add">foo</file></files> </commit> </body> <timestamp>0</timestamp>
--- a/tests/test-hgrc.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgrc.t Wed May 18 09:56:27 2011 +0200 @@ -20,12 +20,12 @@ $ cd foobar $ cat .hg/hgrc [paths] - default = */foo%bar (glob) + default = $TESTTMP/foo%bar $ hg paths - default = */foo%bar (glob) + default = $TESTTMP/foo%bar $ hg showconfig - bundle.mainreporoot=*/foobar (glob) - paths.default=*/foo%bar (glob) + bundle.mainreporoot=$TESTTMP/foobar + paths.default=$TESTTMP/foo%bar $ cd .. issue1829: wrong indentation @@ -133,3 +133,39 @@ none: ui.verbose=False none: ui.debug=True none: ui.quiet=False + +plain mode with exceptions + + $ cat > plain.py <<EOF + > def uisetup(ui): + > ui.write('plain: %r\n' % ui.plain()) + > EOF + $ echo "[extensions]" >> $HGRCPATH + $ echo "plain=./plain.py" >> $HGRCPATH + $ HGPLAINEXCEPT=; export HGPLAINEXCEPT + $ hg showconfig --config ui.traceback=True --debug + plain: [''] + read config from: $TESTTMP/hgrc + $TESTTMP/hgrc:15: extensions.plain=./plain.py + none: ui.traceback=True + none: ui.verbose=False + none: ui.debug=True + none: ui.quiet=False + $ unset HGPLAIN + $ hg showconfig --config ui.traceback=True --debug + plain: [''] + read config from: $TESTTMP/hgrc + $TESTTMP/hgrc:15: extensions.plain=./plain.py + none: ui.traceback=True + none: ui.verbose=False + none: ui.debug=True + none: ui.quiet=False + $ HGPLAINEXCEPT=i18n; export HGPLAINEXCEPT + $ hg showconfig --config ui.traceback=True --debug + plain: ['i18n'] + read config from: $TESTTMP/hgrc + $TESTTMP/hgrc:15: extensions.plain=./plain.py + none: ui.traceback=True + none: ui.verbose=False + none: ui.debug=True + none: ui.quiet=False
--- a/tests/test-hgweb-commands.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgweb-commands.t Wed May 18 09:56:27 2011 +0200 @@ -16,6 +16,7 @@ adding foo $ hg tag 1.0 $ hg bookmark something + $ hg bookmark -r0 anotherthing $ echo another > foo $ hg branch stable marked working directory as branch stable @@ -186,6 +187,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: log</title> <link rel="alternate" type="application/atom+xml" @@ -244,19 +246,19 @@ <th class="description">description</th> </tr> <tr class="parity0"> - <td class="age">1970-01-01</td> + <td class="age">Thu Jan 01 00:00:00 1970 +0000</td> <td class="author">test</td> <td class="description"><a href="/rev/1d22e65f027e">branch</a><span class="branchhead">stable</span> <span class="tag">tip</span> <span class="tag">something</span> </td> </tr> <tr class="parity1"> - <td class="age">1970-01-01</td> + <td class="age">Thu Jan 01 00:00:00 1970 +0000</td> <td class="author">test</td> <td class="description"><a href="/rev/a4f92ed23982">Added tag 1.0 for changeset 2ef0ac749a14</a><span class="branchhead">default</span> </td> </tr> <tr class="parity0"> - <td class="age">1970-01-01</td> + <td class="age">Thu Jan 01 00:00:00 1970 +0000</td> <td class="author">test</td> - <td class="description"><a href="/rev/2ef0ac749a14">base</a><span class="tag">1.0</span> </td> + <td class="description"><a href="/rev/2ef0ac749a14">base</a><span class="tag">1.0</span> <span class="tag">anotherthing</span> </td> </tr> </table> @@ -270,6 +272,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -284,6 +287,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: 2ef0ac749a14</title> </head> @@ -317,7 +321,7 @@ <div class="main"> <h2><a href="/">test</a></h2> - <h3>changeset 0:2ef0ac749a14 <span class="tag">1.0</span> </h3> + <h3>changeset 0:2ef0ac749a14 <span class="tag">1.0</span> <span class="tag">anotherthing</span> </h3> <form class="search" action="/log"> @@ -335,7 +339,7 @@ </tr> <tr> <th class="date">date</th> - <td class="date">Thu Jan 01 00:00:00 1970 +0000 (1970-01-01)</td></tr> + <td class="date age">Thu Jan 01 00:00:00 1970 +0000</td></tr> <tr> <th class="author">parents</th> <td class="author"></td> @@ -366,6 +370,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -397,6 +402,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: searching for base</title> </head> @@ -441,9 +447,9 @@ <th class="description">description</th> </tr> <tr class="parity0"> - <td class="age">1970-01-01</td> + <td class="age">Thu Jan 01 00:00:00 1970 +0000</td> <td class="author">test</td> - <td class="description"><a href="/rev/2ef0ac749a14">base</a><span class="tag">1.0</span> </td> + <td class="description"><a href="/rev/2ef0ac749a14">base</a><span class="tag">1.0</span> <span class="tag">anotherthing</span> </td> </tr> </table> @@ -456,6 +462,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -495,6 +502,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: a4f92ed23982 foo</title> </head> @@ -549,7 +557,7 @@ </tr> <tr> <th class="date">date</th> - <td class="date">Thu Jan 01 00:00:00 1970 +0000 (1970-01-01)</td> + <td class="date age">Thu Jan 01 00:00:00 1970 +0000</td> </tr> <tr> <th class="author">parents</th> @@ -572,6 +580,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -603,6 +612,11 @@ stable 1d22e65f027e5a0609357e7d8e7508cd2ba5d2fe open default a4f92ed23982be056b9852de5dfe873eaac7f0de inactive + $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/raw-bookmarks' + 200 Script output follows + + anotherthing 2ef0ac749a14e4f57a5a822464a0902c6f7f448f + something 1d22e65f027e5a0609357e7d8e7508cd2ba5d2fe $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/summary/?style=gitweb' 200 Script output follows @@ -613,7 +627,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow"/> <link rel="stylesheet" href="/static/style-gitweb.css" type="text/css" /> - + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: Summary</title> <link rel="alternate" type="application/atom+xml" @@ -658,7 +672,7 @@ <table cellspacing="0"> <tr class="parity0"> - <td class="age"><i>1970-01-01</i></td> + <td class="age"><i class="age">Thu Jan 01 00:00:00 1970 +0000</i></td> <td><i>test</i></td> <td> <a class="list" href="/rev/1d22e65f027e?style=gitweb"> @@ -672,7 +686,7 @@ </td> </tr> <tr class="parity1"> - <td class="age"><i>1970-01-01</i></td> + <td class="age"><i class="age">Thu Jan 01 00:00:00 1970 +0000</i></td> <td><i>test</i></td> <td> <a class="list" href="/rev/a4f92ed23982?style=gitweb"> @@ -686,12 +700,12 @@ </td> </tr> <tr class="parity0"> - <td class="age"><i>1970-01-01</i></td> + <td class="age"><i class="age">Thu Jan 01 00:00:00 1970 +0000</i></td> <td><i>test</i></td> <td> <a class="list" href="/rev/2ef0ac749a14?style=gitweb"> <b>base</b> - <span class="logtags"><span class="tagtag" title="1.0">1.0</span> </span> + <span class="logtags"><span class="tagtag" title="1.0">1.0</span> <span class="bookmarktag" title="anotherthing">anotherthing</span> </span> </a> </td> <td class="link" nowrap> @@ -706,7 +720,7 @@ <table cellspacing="0"> <tr class="parity0"> - <td class="age"><i>1970-01-01</i></td> + <td class="age"><i class="age">Thu Jan 01 00:00:00 1970 +0000</i></td> <td><a class="list" href="/rev/2ef0ac749a14?style=gitweb"><b>1.0</b></a></td> <td class="link"> <a href="/rev/2ef0ac749a14?style=gitweb">changeset</a> | @@ -717,11 +731,35 @@ <tr class="light"><td colspan="3"><a class="list" href="/tags?style=gitweb">...</a></td></tr> </table> + <div><a class="title" href="/bookmarks?style=gitweb">bookmarks</a></div> + <table cellspacing="0"> + + <tr class="parity0"> + <td class="age"><i class="age">Thu Jan 01 00:00:00 1970 +0000</i></td> + <td><a class="list" href="/rev/2ef0ac749a14?style=gitweb"><b>anotherthing</b></a></td> + <td class="link"> + <a href="/rev/2ef0ac749a14?style=gitweb">changeset</a> | + <a href="/log/2ef0ac749a14?style=gitweb">changelog</a> | + <a href="/file/2ef0ac749a14?style=gitweb">files</a> + </td> + </tr> + <tr class="parity1"> + <td class="age"><i class="age">Thu Jan 01 00:00:00 1970 +0000</i></td> + <td><a class="list" href="/rev/1d22e65f027e?style=gitweb"><b>something</b></a></td> + <td class="link"> + <a href="/rev/1d22e65f027e?style=gitweb">changeset</a> | + <a href="/log/1d22e65f027e?style=gitweb">changelog</a> | + <a href="/file/1d22e65f027e?style=gitweb">files</a> + </td> + </tr> + <tr class="light"><td colspan="3"><a class="list" href="/bookmarks?style=gitweb">...</a></td></tr> + </table> + <div><a class="title" href="#">branches</a></div> <table cellspacing="0"> <tr class="parity0"> - <td class="age"><i>1970-01-01</i></td> + <td class="age"><i class="age">Thu Jan 01 00:00:00 1970 +0000</i></td> <td><a class="list" href="/shortlog/1d22e65f027e?style=gitweb"><b>1d22e65f027e</b></a></td> <td class="">stable</td> <td class="link"> @@ -731,7 +769,7 @@ </td> </tr> <tr class="parity1"> - <td class="age"><i>1970-01-01</i></td> + <td class="age"><i class="age">Thu Jan 01 00:00:00 1970 +0000</i></td> <td><a class="list" href="/shortlog/a4f92ed23982?style=gitweb"><b>a4f92ed23982</b></a></td> <td class="">default</td> <td class="link"> @@ -744,6 +782,7 @@ <td colspan="4"><a class="list" href="#">...</a></td> </tr> </table> + <script type="text/javascript">process_dates()</script> <div class="page_footer"> <div class="page_footer_text">test</div> <div class="rss_logo"> @@ -766,7 +805,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow"/> <link rel="stylesheet" href="/static/style-gitweb.css" type="text/css" /> - + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: Graph</title> <link rel="alternate" type="application/atom+xml" @@ -813,11 +852,10 @@ <ul id="graphnodes"></ul> </div> - <script type="text/javascript" src="/static/graph.js"></script> <script> <!-- hide script content - var data = [["1d22e65f027e", [0, 1], [[0, 0, 1]], "branch", "test", "1970-01-01", ["stable", true], ["tip"], ["something"]], ["a4f92ed23982", [0, 1], [[0, 0, 1]], "Added tag 1.0 for changeset 2ef0ac749a14", "test", "1970-01-01", ["default", true], [], []], ["2ef0ac749a14", [0, 1], [], "base", "test", "1970-01-01", ["default", false], ["1.0"], []]]; + var data = [["1d22e65f027e", [0, 1], [[0, 0, 1]], "branch", "test", "1970-01-01", ["stable", true], ["tip"], ["something"]], ["a4f92ed23982", [0, 1], [[0, 0, 1]], "Added tag 1.0 for changeset 2ef0ac749a14", "test", "1970-01-01", ["default", true], [], []], ["2ef0ac749a14", [0, 1], [], "base", "test", "1970-01-01", ["default", false], ["1.0"], ["anotherthing"]]]; var graph = new Graph(); graph.scale(39); @@ -895,6 +933,7 @@ | <a href="/graph/2ef0ac749a14?style=gitweb">(0)</a> <a href="/graph/2ef0ac749a14?style=gitweb">-2</a> <a href="/graph/tip?style=gitweb">tip</a> </div> + <script type="text/javascript">process_dates()</script> <div class="page_footer"> <div class="page_footer_text">test</div> <div class="rss_logo"> @@ -913,7 +952,7 @@ $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'; echo 200 Script output follows - lookup changegroupsubset branchmap pushkey unbundle=HG10GZ,HG10BZ,HG10UN + lookup changegroupsubset branchmap pushkey known getbundle unbundlehash unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 heads @@ -1082,7 +1121,7 @@ $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/graph/' \ > | grep '^var data =' - var data = [["40b4d6888e92", [0, 1], [[0, 0, 1]], "\u80fd", "test", "1970-01-01", ["stable", true], ["tip"], ["something"]], ["1d22e65f027e", [0, 1], [[0, 0, 1]], "branch", "test", "1970-01-01", ["stable", false], [], []], ["a4f92ed23982", [0, 1], [[0, 0, 1]], "Added tag 1.0 for changeset 2ef0ac749a14", "test", "1970-01-01", ["default", true], [], []], ["2ef0ac749a14", [0, 1], [], "base", "test", "1970-01-01", ["default", false], ["1.0"], []]]; + var data = [["40b4d6888e92", [0, 1], [[0, 0, 1]], "\u80fd", "test", "1970-01-01", ["stable", true], ["tip"], ["something"]], ["1d22e65f027e", [0, 1], [[0, 0, 1]], "branch", "test", "1970-01-01", ["stable", false], [], []], ["a4f92ed23982", [0, 1], [[0, 0, 1]], "Added tag 1.0 for changeset 2ef0ac749a14", "test", "1970-01-01", ["default", true], [], []], ["2ef0ac749a14", [0, 1], [], "base", "test", "1970-01-01", ["default", false], ["1.0"], ["anotherthing"]]]; ERRORS ENCOUNTERED
--- a/tests/test-hgweb-descend-empties.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgweb-descend-empties.t Wed May 18 09:56:27 2011 +0200 @@ -36,6 +36,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: 9087c84a0f5d /</title> </head> @@ -129,6 +130,7 @@ </table> </div> </div> + <script type="text/javascript">process_dates()</script> </body>
--- a/tests/test-hgweb-diffs.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgweb-diffs.t Wed May 18 09:56:27 2011 +0200 @@ -29,6 +29,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: 0cd96de13884</title> </head> @@ -80,7 +81,7 @@ </tr> <tr> <th class="date">date</th> - <td class="date">Thu Jan 01 00:00:00 1970 +0000 (1970-01-01)</td></tr> + <td class="date age">Thu Jan 01 00:00:00 1970 +0000</td></tr> <tr> <th class="author">parents</th> <td class="author"></td> @@ -111,6 +112,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -153,6 +155,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: a diff</title> </head> @@ -208,7 +211,7 @@ </tr> <tr> <th>date</th> - <td>Thu Jan 01 00:00:00 1970 +0000 (1970-01-01)</td> + <td class="date age">Thu Jan 01 00:00:00 1970 +0000</td> </tr> <tr> <th>parents</th> @@ -233,6 +236,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -256,6 +260,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: 0cd96de13884</title> </head> @@ -307,7 +312,7 @@ </tr> <tr> <th class="date">date</th> - <td class="date">Thu Jan 01 00:00:00 1970 +0000 (1970-01-01)</td></tr> + <td class="date age">Thu Jan 01 00:00:00 1970 +0000</td></tr> <tr> <th class="author">parents</th> <td class="author"></td> @@ -340,6 +345,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -384,6 +390,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: a diff</title> </head> @@ -439,7 +446,7 @@ </tr> <tr> <th>date</th> - <td>Thu Jan 01 00:00:00 1970 +0000 (1970-01-01)</td> + <td class="date age">Thu Jan 01 00:00:00 1970 +0000</td> </tr> <tr> <th>parents</th> @@ -465,6 +472,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body>
--- a/tests/test-hgweb-empty.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgweb-empty.t Wed May 18 09:56:27 2011 +0200 @@ -13,6 +13,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: log</title> <link rel="alternate" type="application/atom+xml" @@ -82,6 +83,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -96,6 +98,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: log</title> <link rel="alternate" type="application/atom+xml" @@ -165,6 +168,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -179,6 +183,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: revision graph</title> <link rel="alternate" type="application/atom+xml" @@ -236,7 +241,6 @@ <ul id="graphnodes"></ul> </div> - <script type="text/javascript" src="/static/graph.js"></script> <script type="text/javascript"> <!-- hide script content @@ -320,6 +324,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -334,6 +339,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: 000000000000 /</title> </head> @@ -391,6 +397,7 @@ </table> </div> </div> + <script type="text/javascript">process_dates()</script> </body>
--- a/tests/test-hgweb-filelog.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgweb-filelog.t Wed May 18 09:56:27 2011 +0200 @@ -117,6 +117,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: a history</title> <link rel="alternate" type="application/atom+xml" @@ -178,12 +179,12 @@ <th class="description">description</th> </tr> <tr class="parity0"> - <td class="age">1970-01-01</td> + <td class="age">Thu Jan 01 00:00:00 1970 +0000</td> <td class="author">test</td> <td class="description"><a href="/rev/01de2d66a28d">second a</a></td> </tr> <tr class="parity1"> - <td class="age">1970-01-01</td> + <td class="age">Thu Jan 01 00:00:00 1970 +0000</td> <td class="author">test</td> <td class="description"><a href="/rev/5ed941583260">first a</a></td> </tr> @@ -199,6 +200,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -216,6 +218,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: a history</title> <link rel="alternate" type="application/atom+xml" @@ -277,12 +280,12 @@ <th class="description">description</th> </tr> <tr class="parity0"> - <td class="age">1970-01-01</td> + <td class="age">Thu Jan 01 00:00:00 1970 +0000</td> <td class="author">test</td> <td class="description"><a href="/rev/01de2d66a28d">second a</a></td> </tr> <tr class="parity1"> - <td class="age">1970-01-01</td> + <td class="age">Thu Jan 01 00:00:00 1970 +0000</td> <td class="author">test</td> <td class="description"><a href="/rev/5ed941583260">first a</a></td> </tr> @@ -298,6 +301,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -315,6 +319,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: a history</title> <link rel="alternate" type="application/atom+xml" @@ -376,7 +381,7 @@ <th class="description">description</th> </tr> <tr class="parity0"> - <td class="age">1970-01-01</td> + <td class="age">Thu Jan 01 00:00:00 1970 +0000</td> <td class="author">test</td> <td class="description"><a href="/rev/5ed941583260">first a</a></td> </tr> @@ -392,6 +397,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -409,6 +415,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: a history</title> <link rel="alternate" type="application/atom+xml" @@ -470,7 +477,7 @@ <th class="description">description</th> </tr> <tr class="parity0"> - <td class="age">1970-01-01</td> + <td class="age">Thu Jan 01 00:00:00 1970 +0000</td> <td class="author">test</td> <td class="description"><a href="/rev/5ed941583260">first a</a></td> </tr> @@ -486,6 +493,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -503,6 +511,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: error</title> </head> @@ -547,6 +556,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -565,6 +575,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png"> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: c history</title> <link rel="alternate" type="application/atom+xml" @@ -593,7 +604,7 @@ <table class="logEntry parity0"> <tr> - <th class="age">1970-01-01:</th> + <th><span class="age">Thu Jan 01 00:00:00 1970 +0000</span>:</th> <th class="firstline"><a href="/rev/b7682196df1c?style=spartan">change c</a></th> </tr> <tr> @@ -618,7 +629,7 @@ <table class="logEntry parity1"> <tr> - <th class="age">1970-01-01:</th> + <th><span class="age">Thu Jan 01 00:00:00 1970 +0000</span>:</th> <th class="firstline"><a href="/rev/1a6696706df2?style=spartan">mv b</a></th> </tr> <tr> @@ -651,6 +662,7 @@ + <script type="text/javascript">process_dates()</script> <div class="logo"> <a href="http://mercurial.selenic.com/">
--- a/tests/test-hgweb-no-path-info.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgweb-no-path-info.t Wed May 18 09:56:27 2011 +0200 @@ -2,9 +2,8 @@ no longer passed with the request. Instead, SCRIPT_NAME and PATH_INFO should be used from d74fc8dec2b4 onward to route the request. - $ mkdir repo + $ hg init repo $ cd repo - $ hg init $ echo foo > bar $ hg add bar $ hg commit -m "test"
--- a/tests/test-hgweb-no-request-uri.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgweb-no-request-uri.t Wed May 18 09:56:27 2011 +0200 @@ -2,9 +2,8 @@ no longer passed with the request. Instead, SCRIPT_NAME and PATH_INFO should be used from d74fc8dec2b4 onward to route the request. - $ mkdir repo + $ hg init repo $ cd repo - $ hg init $ echo foo > bar $ hg add bar $ hg commit -m "test"
--- a/tests/test-hgweb-non-interactive.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgweb-non-interactive.t Wed May 18 09:56:27 2011 +0200 @@ -1,9 +1,8 @@ Tests if hgweb can run without touching sys.stdin, as is required by the WSGI standard and strictly implemented by mod_wsgi. - $ mkdir repo + $ hg init repo $ cd repo - $ hg init $ echo foo > bar $ hg add bar $ hg commit -m "test"
--- a/tests/test-hgweb-raw.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgweb-raw.t Wed May 18 09:56:27 2011 +0200 @@ -10,6 +10,7 @@ > care about things like that. > ENDSOME $ hg add 'sub/some "text".txt' + warning: filename contains '"', which is reserved on Windows: 'sub/some "text".txt' $ hg commit -d "1 0" -m "Just some text" $ hg serve -p $HGPORT -A access.log -E error.log -d --pid-file=hg.pid
--- a/tests/test-hgweb-removed.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgweb-removed.t Wed May 18 09:56:27 2011 +0200 @@ -24,6 +24,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: c78f6c5cbea9</title> </head> @@ -75,7 +76,7 @@ </tr> <tr> <th class="date">date</th> - <td class="date">Thu Jan 01 00:00:00 1970 +0000 (1970-01-01)</td></tr> + <td class="date age">Thu Jan 01 00:00:00 1970 +0000</td></tr> <tr> <th class="author">parents</th> <td class="author"><a href="/rev/cb9a9f314b8b">cb9a9f314b8b</a> </td> @@ -102,6 +103,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -119,6 +121,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: a diff</title> </head> @@ -174,7 +177,7 @@ </tr> <tr> <th>date</th> - <td>Thu Jan 01 00:00:00 1970 +0000 (1970-01-01)</td> + <td class="date age">Thu Jan 01 00:00:00 1970 +0000</td> </tr> <tr> <th>parents</th> @@ -199,6 +202,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body>
--- a/tests/test-hgweb.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgweb.t Wed May 18 09:56:27 2011 +0200 @@ -47,6 +47,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: error</title> </head> @@ -91,6 +92,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -133,6 +135,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: error</title> </head> @@ -177,6 +180,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -201,6 +205,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>test: 2ef0ac749a14 /</title> </head> @@ -279,6 +284,7 @@ </table> </div> </div> + <script type="text/javascript">process_dates()</script> </body>
--- a/tests/test-hgwebdir.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hgwebdir.t Wed May 18 09:56:27 2011 +0200 @@ -143,6 +143,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>Mercurial repositories index</title> </head> @@ -169,7 +170,7 @@ <td><a href="/t/a/?style=paper">t/a</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -177,7 +178,7 @@ <td><a href="/b/?style=paper">b</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -185,7 +186,7 @@ <td><a href="/coll/a/?style=paper">coll/a</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -193,7 +194,7 @@ <td><a href="/coll/a/.hg/patches/?style=paper">coll/a/.hg/patches</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -201,7 +202,7 @@ <td><a href="/coll/b/?style=paper">coll/b</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -209,7 +210,7 @@ <td><a href="/coll/c/?style=paper">coll/c</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -217,7 +218,7 @@ <td><a href="/rcoll/a/?style=paper">rcoll/a</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -225,7 +226,7 @@ <td><a href="/rcoll/a/.hg/patches/?style=paper">rcoll/a/.hg/patches</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -233,7 +234,7 @@ <td><a href="/rcoll/b/?style=paper">rcoll/b</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -241,7 +242,7 @@ <td><a href="/rcoll/b/d/?style=paper">rcoll/b/d</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -249,7 +250,7 @@ <td><a href="/rcoll/c/?style=paper">rcoll/c</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -257,7 +258,7 @@ <td><a href="/star/webdir/a/?style=paper">star/webdir/a</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -265,7 +266,7 @@ <td><a href="/star/webdir/a/.hg/patches/?style=paper">star/webdir/a/.hg/patches</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -273,7 +274,7 @@ <td><a href="/star/webdir/b/?style=paper">star/webdir/b</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -281,7 +282,7 @@ <td><a href="/star/webdir/c/?style=paper">star/webdir/c</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -289,7 +290,7 @@ <td><a href="/starstar/webdir/a/?style=paper">starstar/webdir/a</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -297,7 +298,7 @@ <td><a href="/starstar/webdir/a/.hg/patches/?style=paper">starstar/webdir/a/.hg/patches</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -305,7 +306,7 @@ <td><a href="/starstar/webdir/b/?style=paper">starstar/webdir/b</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -313,7 +314,7 @@ <td><a href="/starstar/webdir/b/d/?style=paper">starstar/webdir/b/d</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -321,7 +322,7 @@ <td><a href="/starstar/webdir/c/?style=paper">starstar/webdir/c</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -329,7 +330,7 @@ <td><a href="/astar/?style=paper">astar</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> @@ -337,13 +338,14 @@ <td><a href="/astar/.hg/patches/?style=paper">astar/.hg/patches</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> </table> </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -370,6 +372,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>Mercurial repositories index</title> </head> @@ -396,13 +399,14 @@ <td><a href="/t/a/?style=paper">a</a></td> <td>unknown</td> <td>Foo Bar <foo.bar@example.com></td> - <td class="age">* ago</td> (glob) + <td class="age">*</td> (glob) <td class="indexlinks"></td> </tr> </table> </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -560,6 +564,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <title>Mercurial repositories index</title> </head> @@ -585,6 +590,7 @@ </table> </div> </div> + <script type="text/javascript">process_dates()</script> </body>
--- a/tests/test-highlight.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-highlight.t Wed May 18 09:56:27 2011 +0200 @@ -65,6 +65,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <link rel="stylesheet" href="/highlightcss" type="text/css" /> <title>test: 853dcd4de2a6 primes.py</title> @@ -120,7 +121,7 @@ </tr> <tr> <th class="date">date</th> - <td class="date">Thu Jan 01 00:00:00 1970 +0000 (1970-01-01)</td> + <td class="date age">Thu Jan 01 00:00:00 1970 +0000</td> </tr> <tr> <th class="author">parents</th> @@ -173,6 +174,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body> @@ -191,6 +193,7 @@ <link rel="icon" href="/static/hgicon.png" type="image/png" /> <meta name="robots" content="index, nofollow" /> <link rel="stylesheet" href="/static/style-paper.css" type="text/css" /> + <script type="text/javascript" src="/static/mercurial.js"></script> <link rel="stylesheet" href="/highlightcss" type="text/css" /> <title>test: primes.py annotate</title> @@ -248,7 +251,7 @@ </tr> <tr> <th class="date">date</th> - <td class="date">Thu Jan 01 00:00:00 1970 +0000 (1970-01-01)</td> + <td class="date age">Thu Jan 01 00:00:00 1970 +0000</td> </tr> <tr> <th class="author">parents</th> @@ -497,6 +500,7 @@ </div> </div> + <script type="text/javascript">process_dates()</script> </body>
--- a/tests/test-hook.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-hook.t Wed May 18 09:56:27 2011 +0200 @@ -168,6 +168,61 @@ update hook: HG_ERROR=0 HG_PARENT1=539e4b31b6dc 2 files updated, 0 files merged, 0 files removed, 0 files unresolved +pushkey hook + + $ echo 'pushkey = python "$TESTDIR"/printenv.py pushkey' >> .hg/hgrc + $ cd ../b + $ hg bookmark -r null foo + $ hg push -B foo ../a + pushing to ../a + searching for changes + no changes found + exporting bookmark foo + pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1 + $ cd ../a + +listkeys hook + + $ echo 'listkeys = python "$TESTDIR"/printenv.py listkeys' >> .hg/hgrc + $ hg bookmark -r null bar + $ cd ../b + $ hg pull -B bar ../a + pulling from ../a + listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'} + no changes found + listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'} + importing bookmark bar + $ cd ../a + +test that prepushkey can prevent incoming keys + + $ echo 'prepushkey = python "$TESTDIR"/printenv.py prepushkey.forbid 1' >> .hg/hgrc + $ cd ../b + $ hg bookmark -r null baz + $ hg push -B baz ../a + pushing to ../a + searching for changes + no changes found + listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'} + listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'} + exporting bookmark baz + prepushkey.forbid hook: HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 + abort: prepushkey hook exited with status 1 + [255] + $ cd ../a + +test that prelistkeys can prevent listing keys + + $ echo 'prelistkeys = python "$TESTDIR"/printenv.py prelistkeys.forbid 1' >> .hg/hgrc + $ hg bookmark -r null quux + $ cd ../b + $ hg pull -B quux ../a + pulling from ../a + prelistkeys.forbid hook: HG_NAMESPACE=bookmarks + abort: prelistkeys hook exited with status 1 + [255] + $ cd ../a + prechangegroup hook can prevent incoming changes $ cd ../b
--- a/tests/test-http-branchmap.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-http-branchmap.t Wed May 18 09:56:27 2011 +0200 @@ -30,7 +30,7 @@ $ echo bar >> b/foo $ hg -R b ci -m bar $ hg --encoding utf-8 -R b push - pushing to http://localhost:$HGPORT1 + pushing to http://localhost:$HGPORT1/ searching for changes remote: adding changesets remote: adding manifests
--- a/tests/test-http-clone-r.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-http-clone-r.t Wed May 18 09:56:27 2011 +0200 @@ -1,81 +1,15 @@ +creating 'remote $ hg init remote $ cd remote - -creating 'remote - - $ cat >>afile <<EOF - > 0 - > EOF - $ hg add afile - $ hg commit -m "0.0" - $ cat >>afile <<EOF - > 1 - > EOF - $ hg commit -m "0.1" - $ cat >>afile <<EOF - > 2 - > EOF - $ hg commit -m "0.2" - $ cat >>afile <<EOF - > 3 - > EOF - $ hg commit -m "0.3" - $ hg update -C 0 + $ hg unbundle $TESTDIR/bundles/remote.hg + adding changesets + adding manifests + adding file changes + added 9 changesets with 7 changes to 4 files (+1 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg up tip 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cat >>afile <<EOF - > 1 - > EOF - $ hg commit -m "1.1" - created new head - $ cat >>afile <<EOF - > 2 - > EOF - $ hg commit -m "1.2" - $ cat >fred <<EOF - > a line - > EOF - $ cat >>afile <<EOF - > 3 - > EOF - $ hg add fred - $ hg commit -m "1.3" - $ hg mv afile adifferentfile - $ hg commit -m "1.3m" - $ hg update -C 3 - 1 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ hg mv afile anotherfile - $ hg commit -m "0.3m" - $ hg debugindex .hg/store/data/afile.i - rev offset length base linkrev nodeid p1 p2 - 0 0 3 0 0 362fef284ce2 000000000000 000000000000 - 1 3 5 1 1 125144f7e028 362fef284ce2 000000000000 - 2 8 7 2 2 4c982badb186 125144f7e028 000000000000 - 3 15 9 3 3 19b1fc555737 4c982badb186 000000000000 - $ hg debugindex .hg/store/data/adifferentfile.i - rev offset length base linkrev nodeid p1 p2 - 0 0 75 0 7 2565f3199a74 000000000000 000000000000 - $ hg debugindex .hg/store/data/anotherfile.i - rev offset length base linkrev nodeid p1 p2 - 0 0 75 0 8 2565f3199a74 000000000000 000000000000 - $ hg debugindex .hg/store/data/fred.i - rev offset length base linkrev nodeid p1 p2 - 0 0 8 0 6 12ab3bcc5ea4 000000000000 000000000000 - $ hg debugindex .hg/store/00manifest.i - rev offset length base linkrev nodeid p1 p2 - 0 0 48 0 0 43eadb1d2d06 000000000000 000000000000 - 1 48 48 1 1 8b89697eba2c 43eadb1d2d06 000000000000 - 2 96 48 2 2 626a32663c2f 8b89697eba2c 000000000000 - 3 144 48 3 3 f54c32f13478 626a32663c2f 000000000000 - 4 192 58 3 6 de68e904d169 626a32663c2f 000000000000 - 5 250 68 3 7 09bb521d218d de68e904d169 000000000000 - 6 318 54 6 8 1fde233dfb0f f54c32f13478 000000000000 - $ hg verify - checking changesets - checking manifests - crosschecking files in changesets and manifests - checking files - 4 files, 9 changesets, 7 total revisions Starting server @@ -214,7 +148,7 @@ adding changesets adding manifests adding file changes - added 1 changesets with 0 changes to 1 files (+1 heads) + added 1 changesets with 0 changes to 0 files (+1 heads) (run 'hg heads' to see heads, 'hg merge' to merge) $ hg verify checking changesets @@ -238,7 +172,7 @@ adding changesets adding manifests adding file changes - added 2 changesets with 0 changes to 1 files (+1 heads) + added 2 changesets with 0 changes to 0 files (+1 heads) (run 'hg heads' to see heads, 'hg merge' to merge) $ hg verify checking changesets
--- a/tests/test-http-proxy.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-http-proxy.t Wed May 18 09:56:27 2011 +0200 @@ -98,27 +98,23 @@ updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cat proxy.log - * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?pairs=0000000000000000000000000000000000000000-0000000000000000000000000000000000000000&cmd=between HTTP/1.1" - - (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=changegroup&roots=0000000000000000000000000000000000000000 HTTP/1.1" - - (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob) * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob) - * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys&namespace=bookmarks HTTP/1.1" - - (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=heads HTTP/1.1" - - (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629 (glob) + * - - [*] "GET http://localhost:$HGPORT/?cmd=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob)
--- a/tests/test-http.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-http.t Wed May 18 09:56:27 2011 +0200 @@ -70,6 +70,24 @@ adding bar $ cd .. +incoming via HTTP + + $ hg clone http://localhost:$HGPORT1/ --rev 0 partial + adding changesets + adding manifests + adding file changes + added 1 changesets with 4 changes to 4 files + updating to branch default + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd partial + $ touch LOCAL + $ hg ci -qAm LOCAL + $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n' + comparing with http://localhost:$HGPORT1/ + searching for changes + 2 + $ cd .. + pull $ cd copy-pull
--- a/tests/test-https.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-https.t Wed May 18 09:56:27 2011 +0200 @@ -8,7 +8,7 @@ Can be dumped with: openssl x509 -in pub.pem -text - $ cat << EOT > priv.pem + $ cat << EOT > priv.pem > -----BEGIN PRIVATE KEY----- > MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApjCWeYGrIa/Vo7LH > aRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8 @@ -21,7 +21,7 @@ > -----END PRIVATE KEY----- > EOT - $ cat << EOT > pub.pem + $ cat << EOT > pub.pem > -----BEGIN CERTIFICATE----- > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw @@ -37,7 +37,7 @@ $ cat priv.pem pub.pem >> server.pem $ PRIV=`pwd`/server.pem - $ cat << EOT > pub-other.pem + $ cat << EOT > pub-other.pem > -----BEGIN CERTIFICATE----- > MIIBqzCCAVWgAwIBAgIJALwZS731c/ORMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw @@ -53,7 +53,7 @@ pub.pem patched with other notBefore / notAfter: - $ cat << EOT > pub-not-yet.pem + $ cat << EOT > pub-not-yet.pem > -----BEGIN CERTIFICATE----- > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTM1MDYwNTIwMzAxNFoXDTM1MDYw @@ -67,7 +67,7 @@ > EOT $ cat priv.pem pub-not-yet.pem > server-not-yet.pem - $ cat << EOT > pub-expired.pem + $ cat << EOT > pub-expired.pem > -----BEGIN CERTIFICATE----- > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEwMTAxNDIwMzAxNFoXDTEwMTAx @@ -118,9 +118,9 @@ adding manifests adding file changes added 1 changesets with 4 changes to 4 files - warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting) updating to branch default 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting) $ hg verify -R copy-pull checking changesets checking manifests @@ -198,7 +198,7 @@ Test server cert which isn't valid yet - $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem + $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem $ cat hg1.pid >> $DAEMON_PIDS $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/ abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob) @@ -206,7 +206,7 @@ Test server cert which no longer is valid - $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem + $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem $ cat hg2.pid >> $DAEMON_PIDS $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/ abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
--- a/tests/test-i18n.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-i18n.t Wed May 18 09:56:27 2011 +0200 @@ -8,17 +8,17 @@ using the "replace" error handler: $ LANGUAGE=pt_BR hg tip - abortado: N?o h? um reposit?rio do Mercurial aqui (.hg n?o encontrado)! + abortado: no repository found in '$TESTTMP' (.hg not found)! [255] Using a more accomodating encoding: $ HGENCODING=UTF-8 LANGUAGE=pt_BR hg tip - abortado: N\xc3\xa3o h\xc3\xa1 um reposit\xc3\xb3rio do Mercurial aqui (.hg n\xc3\xa3o encontrado)! (esc) + abortado: no repository found in '$TESTTMP' (.hg not found)! [255] Different encoding: $ HGENCODING=Latin-1 LANGUAGE=pt_BR hg tip - abortado: N\xe3o h\xe1 um reposit\xf3rio do Mercurial aqui (.hg n\xe3o encontrado)! (esc) + abortado: no repository found in '$TESTTMP' (.hg not found)! [255]
--- a/tests/test-identify.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-identify.t Wed May 18 09:56:27 2011 +0200 @@ -65,26 +65,43 @@ remote with rev number? $ hg id -n http://localhost:$HGPORT1/ - abort: can't query remote revision number, branch, tags, or bookmarks + abort: can't query remote revision number, branch, or tags [255] remote with tags? $ hg id -t http://localhost:$HGPORT1/ - abort: can't query remote revision number, branch, tags, or bookmarks + abort: can't query remote revision number, branch, or tags [255] remote with branch? $ hg id -b http://localhost:$HGPORT1/ - abort: can't query remote revision number, branch, tags, or bookmarks + abort: can't query remote revision number, branch, or tags [255] -remote with bookmarks? +test bookmark support - $ hg id -B http://localhost:$HGPORT1/ - abort: can't query remote revision number, branch, tags, or bookmarks - [255] + $ hg bookmark Y + $ hg bookmark Z + $ hg bookmarks + Y 0:cb9a9f314b8b + * Z 0:cb9a9f314b8b + $ hg id + cb9a9f314b8b+ tip Y/Z + $ hg id --bookmarks + Y Z + +test remote identify with bookmarks + + $ hg id http://localhost:$HGPORT1/ + cb9a9f314b8b Y/Z + $ hg id --bookmarks http://localhost:$HGPORT1/ + Y Z + $ hg id -r . http://localhost:$HGPORT1/ + cb9a9f314b8b Y/Z + $ hg id --bookmarks -r . http://localhost:$HGPORT1/ + Y Z Make sure we do not obscure unknown requires file entries (issue2649)
--- a/tests/test-impexp-branch.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-impexp-branch.t Wed May 18 09:56:27 2011 +0200 @@ -11,6 +11,7 @@ > sys.exit(0) > sys.exit(1) > EOF + $ hg init a $ cd a $ echo "Rev 1" >rev @@ -20,11 +21,17 @@ marked working directory as branch abranch $ echo "Rev 2" >rev $ hg commit -m "With branch." - $ if hg export 0 | python ../findbranch.py; then + + $ hg export 0 > ../r0.patch + $ hg export 1 > ../r1.patch + $ cd .. + + $ if python findbranch.py < r0.patch; then > echo "Export of default branch revision has Branch header" 1>&2 > exit 1 > fi - $ if hg export 1 | python ../findbranch.py; then + + $ if python findbranch.py < r1.patch; then > : # Do nothing > else > echo "Export of branch revision is missing Branch header" 1>&2 @@ -33,18 +40,17 @@ Make sure import still works with branch information in patches. - $ cd .. $ hg init b $ cd b - $ hg -R ../a export 0 | hg import - - applying patch from stdin - $ hg -R ../a export 1 | hg import - - applying patch from stdin + $ hg import ../r0.patch + applying ../r0.patch + $ hg import ../r1.patch + applying ../r1.patch $ cd .. - $ rm -rf b - $ hg init b - $ cd b - $ hg -R ../a export 0 | hg import --exact - - applying patch from stdin - $ hg -R ../a export 1 | hg import --exact - - applying patch from stdin + + $ hg init c + $ cd c + $ hg import --exact ../r0.patch + applying ../r0.patch + $ hg import --exact ../r1.patch + applying ../r1.patch
--- a/tests/test-import.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-import.t Wed May 18 09:56:27 2011 +0200 @@ -11,6 +11,12 @@ $ hg --cwd a ci -u someone -d '1 0' -m'second change' +generate patches for the test + + $ hg --cwd a export tip > exported-tip.patch + $ hg --cwd a diff -r0:1 > diffed-tip.patch + + import exported patch $ hg clone -r0 a b @@ -20,19 +26,18 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd a export tip > tip.patch - $ hg --cwd b import ../tip.patch - applying ../tip.patch + $ hg --cwd b import ../exported-tip.patch + applying ../exported-tip.patch -message should be same +message and committer should be same - $ hg --cwd b tip | grep 'second change' + $ hg --cwd b tip + changeset: 1:1d4bd90af0e4 + tag: tip + user: someone + date: Thu Jan 01 00:00:01 1970 +0000 summary: second change - -committer should be same - - $ hg --cwd b tip | grep someone - user: someone + $ rm -r b @@ -50,9 +55,8 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd a export tip > tip.patch - $ hg --config ui.patch='python ../dummypatch.py' --cwd b import ../tip.patch - applying ../tip.patch + $ hg --config ui.patch='python ../dummypatch.py' --cwd b import ../exported-tip.patch + applying ../exported-tip.patch $ cat b/a line2 $ rm -r b @@ -67,9 +71,8 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd a diff -r0:1 > tip.patch - $ hg --cwd b import ../tip.patch - applying ../tip.patch + $ hg --cwd b import ../diffed-tip.patch + applying ../diffed-tip.patch abort: empty commit message [255] $ rm -r b @@ -84,9 +87,8 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd a diff -r0:1 > tip.patch - $ hg --cwd b import -mpatch ../tip.patch - applying ../tip.patch + $ hg --cwd b import -mpatch ../diffed-tip.patch + applying ../diffed-tip.patch $ rm -r b @@ -99,9 +101,8 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd a diff -r0:1 > tip.patch - $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../tip.patch - applying ../tip.patch + $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch + applying ../diffed-tip.patch $ hg -R b tip -pv changeset: 1:ca68f19f3a40 tag: tip @@ -131,9 +132,8 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd a diff -r0:1 > tip.patch - $ hg --cwd b import --no-commit ../tip.patch - applying ../tip.patch + $ hg --cwd b import --no-commit ../diffed-tip.patch + applying ../diffed-tip.patch $ hg --cwd b diff --nodates diff -r 80971e65b431 a --- a/a @@ -153,8 +153,7 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd a diff -r0:1 > tip.patch - $ sed 's/1,1/foo/' < tip.patch > broken.patch + $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch $ hg --cwd b import -mpatch ../broken.patch applying ../broken.patch abort: bad hunk #1 @@ -174,10 +173,9 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd a export tip > dir/tip.patch $ cd dir - $ hg -R b import tip.patch - applying tip.patch + $ hg -R b import ../exported-tip.patch + applying ../exported-tip.patch $ cd .. $ rm -r dir @@ -191,7 +189,7 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd a export tip | hg --cwd b import - + $ hg --cwd b import - < exported-tip.patch applying patch from stdin $ rm -r b @@ -218,7 +216,7 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd a export tip | hg --cwd b import -m 'override' - + $ hg --cwd b import -m 'override' - < exported-tip.patch applying patch from stdin $ hg --cwd b tip | grep override summary: override @@ -227,7 +225,8 @@ $ cat > mkmsg.py <<EOF > import email.Message, sys > msg = email.Message.Message() - > msg.set_payload('email commit message\n' + open('tip.patch', 'rb').read()) + > patch = open(sys.argv[1], 'rb').read() + > msg.set_payload('email commit message\n' + patch) > msg['Subject'] = 'email patch' > msg['From'] = 'email patcher' > sys.stdout.write(msg.as_string()) @@ -243,8 +242,7 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd a diff -r0:1 > tip.patch - $ python mkmsg.py > msg.patch + $ python mkmsg.py diffed-tip.patch > msg.patch $ hg --cwd b import ../msg.patch applying ../msg.patch $ hg --cwd b tip | grep email @@ -306,8 +304,7 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd a export tip > tip.patch - $ python mkmsg.py | hg --cwd b import - + $ python mkmsg.py exported-tip.patch | hg --cwd b import - applying patch from stdin $ hg --cwd b tip | grep second summary: second change @@ -320,7 +317,8 @@ $ cat > mkmsg2.py <<EOF > import email.Message, sys > msg = email.Message.Message() - > msg.set_payload('email patch\n\nnext line\n---\n' + open('tip.patch').read()) + > patch = open(sys.argv[1], 'rb').read() + > msg.set_payload('email patch\n\nnext line\n---\n' + patch) > msg['Subject'] = '[PATCH] email patch' > msg['From'] = 'email patcher' > sys.stdout.write(msg.as_string()) @@ -336,8 +334,7 @@ added 1 changesets with 2 changes to 2 files updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg --cwd a diff -r0:1 > tip.patch - $ python mkmsg2.py | hg --cwd b import - + $ python mkmsg2.py diffed-tip.patch | hg --cwd b import - applying patch from stdin $ hg --cwd b tip --template '{desc}\n' email patch @@ -386,22 +383,23 @@ updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg --cwd a export tip > tmp - $ sed -e 's/d1\/d2\///' < tmp > tip.patch + $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch $ dir=`pwd` $ cd b/d1/d2 2>&1 > /dev/null - $ hg import ../../../tip.patch - applying ../../../tip.patch + $ hg import ../../../subdir-tip.patch + applying ../../../subdir-tip.patch $ cd "$dir" message should be 'subdir change' - - $ hg --cwd b tip | grep 'subdir change' - summary: subdir change - committer should be 'someoneelse' - $ hg --cwd b tip | grep someoneelse + $ hg --cwd b tip + changeset: 1:3577f5aea227 + tag: tip user: someoneelse + date: Thu Jan 01 00:00:01 1970 +0000 + summary: subdir change + should be empty @@ -422,7 +420,7 @@ $ echo line0 >> a $ echo line3 >> a $ hg ci -m change a - $ hg export tip > tip.patch + $ hg export tip > fuzzy-tip.patch $ hg up -C 0 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo line1 > a @@ -431,8 +429,8 @@ $ echo line0 >> a $ hg ci -m brancha created new head - $ hg import --no-commit -v tip.patch - applying tip.patch + $ hg import --no-commit -v fuzzy-tip.patch + applying fuzzy-tip.patch patching file a Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines). $ hg revert -a @@ -447,8 +445,8 @@ test fuzziness with eol=auto - $ hg --config patch.eol=auto import --no-commit -v tip.patch - applying tip.patch + $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch + applying fuzzy-tip.patch patching file a Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines). $ cd ..
--- a/tests/test-incoming-outgoing.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-incoming-outgoing.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,5 @@ - $ mkdir test + $ hg init test $ cd test - $ hg init $ for i in 0 1 2 3 4 5 6 7 8; do > echo $i >> foo > hg commit -A -m $i
--- a/tests/test-init.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-init.t Wed May 18 09:56:27 2011 +0200 @@ -1,26 +1,5 @@ This test tries to exercise the ssh functionality with a dummy script - $ cat <<EOF > dummyssh - > import sys - > import os - > - > os.chdir(os.path.dirname(sys.argv[0])) - > if sys.argv[1] != "user@dummy": - > sys.exit(-1) - > - > if not os.path.exists("dummyssh"): - > sys.exit(-1) - > - > log = open("dummylog", "ab") - > log.write("Got arguments") - > for i, arg in enumerate(sys.argv[1:]): - > log.write(" %d:%s" % (i+1, arg)) - > log.write("\n") - > log.close() - > r = os.system(sys.argv[2]) - > sys.exit(bool(r)) - > EOF - $ checknewrepo() > { > name=$1 @@ -80,7 +59,7 @@ init+push to remote2 - $ hg init -e "python ./dummyssh" ssh://user@dummy/remote2 + $ hg init -e "python $TESTDIR/dummyssh" ssh://user@dummy/remote2 $ hg incoming -R remote2 local comparing with local changeset: 0:08b9e9f63b32 @@ -90,7 +69,7 @@ summary: init - $ hg push -R local -e "python ./dummyssh" ssh://user@dummy/remote2 + $ hg push -R local -e "python $TESTDIR/dummyssh" ssh://user@dummy/remote2 pushing to ssh://user@dummy/remote2 searching for changes remote: adding changesets @@ -100,7 +79,7 @@ clone to remote1 - $ hg clone -e "python ./dummyssh" local ssh://user@dummy/remote1 + $ hg clone -e "python $TESTDIR/dummyssh" local ssh://user@dummy/remote1 searching for changes remote: adding changesets remote: adding manifests @@ -109,14 +88,14 @@ init to existing repo - $ hg init -e "python ./dummyssh" ssh://user@dummy/remote1 + $ hg init -e "python $TESTDIR/dummyssh" ssh://user@dummy/remote1 abort: repository remote1 already exists! abort: could not create remote repo! [255] clone to existing repo - $ hg clone -e "python ./dummyssh" local ssh://user@dummy/remote1 + $ hg clone -e "python $TESTDIR/dummyssh" local ssh://user@dummy/remote1 abort: repository remote1 already exists! abort: could not create remote repo! [255] @@ -199,3 +178,17 @@ store fncache dotencode + +clone bookmarks + + $ hg -R local bookmark test + $ hg -R local bookmarks + * test 0:08b9e9f63b32 + $ hg clone -e "python $TESTDIR/dummyssh" local ssh://user@dummy/remote-bookmarks + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + $ hg -R remote-bookmarks bookmarks + test 0:08b9e9f63b32
--- a/tests/test-inotify-dirty-dirstate.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-inotify-dirty-dirstate.t Wed May 18 09:56:27 2011 +0200 @@ -7,8 +7,7 @@ issue1810: inotify and fetch - $ mkdir test; cd test - $ hg init + $ hg init test; cd test $ hg inserve -d --pid-file=../hg.pid $ cat ../hg.pid >> "$DAEMON_PIDS" $ echo foo > foo
--- a/tests/test-install.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-install.t Wed May 18 09:56:27 2011 +0200 @@ -3,7 +3,6 @@ Checking encoding (ascii)... Checking installed modules (*/mercurial)... (glob) Checking templates... - Checking patch... Checking commit editor... Checking username... No problems detected @@ -13,7 +12,6 @@ Checking encoding (ascii)... Checking installed modules (*/mercurial)... (glob) Checking templates... - Checking patch... Checking commit editor... Checking username... no username supplied (see "hg help config")
--- a/tests/test-issue1306.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-issue1306.t Wed May 18 09:56:27 2011 +0200 @@ -18,14 +18,14 @@ adding c $ hg log - changeset: 2:1630aed6ed2b + changeset: 2:ae3d9c30ec50 branch: br tag: tip user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: c - changeset: 1:234f53e6c5ff + changeset: 1:3f7f930ca414 branch: br user: test date: Thu Jan 01 00:00:00 1970 +0000 @@ -50,7 +50,7 @@ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R local1 parents - changeset: 2:1630aed6ed2b + changeset: 2:ae3d9c30ec50 branch: br tag: tip user: test @@ -86,7 +86,7 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R local3 parents - changeset: 1:234f53e6c5ff + changeset: 1:3f7f930ca414 branch: br user: test date: Thu Jan 01 00:00:00 1970 +0000
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-issue1877.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,45 @@ +http://mercurial.selenic.com/bts/issue1877 + + $ hg init a + $ cd a + $ echo a > a + $ hg add a + $ hg ci -m 'a' + $ echo b > a + $ hg ci -m'b' + $ hg up 0 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg book main + $ hg book + * main 0:cb9a9f314b8b + $ echo c > c + $ hg add c + $ hg ci -m'c' + created new head + $ hg book + * main 2:d36c0562f908 + $ hg heads + changeset: 2:d36c0562f908 + bookmark: main + tag: tip + parent: 0:cb9a9f314b8b + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: c + + changeset: 1:1e6c11564562 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: b + + $ hg up 1e6c11564562 + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg merge main + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg book + main 2:d36c0562f908 + $ hg ci -m'merge' + $ hg book + main 2:d36c0562f908 +
--- a/tests/test-issue322.t Wed May 18 09:31:19 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -http://mercurial.selenic.com/bts/issue322 - -File replaced with directory: - - $ hg init a - $ cd a - $ echo a > a - $ hg commit -Ama - adding a - $ rm a - $ mkdir a - $ echo a > a/a - -Should fail - would corrupt dirstate: - - $ hg add a/a - abort: file 'a' in dirstate clashes with 'a/a' - [255] - - $ cd .. - -Directory replaced with file: - - $ hg init c - $ cd c - $ mkdir a - $ echo a > a/a - $ hg commit -Ama - adding a/a - - $ rm -r a - $ echo a > a - -Should fail - would corrupt dirstate: - - $ hg add a - abort: directory 'a' already in dirstate - [255] - - $ cd .. - -Directory replaced with file: - - $ hg init d - $ cd d - $ mkdir b - $ mkdir b/c - $ echo a > b/c/d - $ hg commit -Ama - adding b/c/d - $ rm -r b - $ echo a > b - -Should fail - would corrupt dirstate: - - $ hg add b - abort: directory 'b' already in dirstate - [255] -
--- a/tests/test-issue433.t Wed May 18 09:31:19 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -http://mercurial.selenic.com/bts/issue433 - - $ hg init - $ echo a > a - $ hg commit -Ama - adding a - - $ hg parents -r 0 doesnotexist - abort: 'doesnotexist' not found in manifest! - [255]
--- a/tests/test-issue436.t Wed May 18 09:31:19 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -http://mercurial.selenic.com/bts/issue436 - - $ hg init - $ hg -v log -v - $ hg -v log -v x -
--- a/tests/test-issue522.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-issue522.t Wed May 18 09:56:27 2011 +0200 @@ -48,7 +48,7 @@ $ hg manifest --debug | grep foo c6fc755d7e68f49f880599da29f15add41f42f5a 644 foo - $ hg debugindex .hg/store/data/foo.i + $ hg debugindex foo rev offset length base linkrev nodeid p1 p2 0 0 5 0 0 2ed2a3912a0b 000000000000 000000000000 1 5 9 1 1 6f4310b00b9a 2ed2a3912a0b 000000000000
--- a/tests/test-issue660.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-issue660.t Wed May 18 09:56:27 2011 +0200 @@ -1,4 +1,5 @@ -http://mercurial.selenic.com/bts/issue660 +http://mercurial.selenic.com/bts/issue660 and: +http://mercurial.selenic.com/bts/issue322 $ hg init $ echo a > a
--- a/tests/test-keyword.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-keyword.t Wed May 18 09:56:27 2011 +0200 @@ -78,7 +78,7 @@ hg ci -m addsym -u mercurial hg bundle --base null ../test-keyword.hg - $ hg pull -u "$TESTDIR"/test-keyword.hg + $ hg pull -u "$TESTDIR"/bundles/test-keyword.hg pulling from *test-keyword.hg (glob) requesting all changes adding changesets @@ -209,7 +209,7 @@ To: Test changeset a2392c293916 in $TESTTMP/Test - details: *cmd=changeset;node=a2392c293916 (glob) + details: $TESTTMP/Test?cmd=changeset;node=a2392c293916 description: addsym @@ -982,7 +982,7 @@ $ HGMERGE=internal:local hg resolve -a $ hg commit -m localresolve $ cat m - $Id: m 41efa6d38e9b Thu, 01 Jan 1970 00:00:00 +0000 test $ + $Id: m 800511b3a22d Thu, 01 Jan 1970 00:00:00 +0000 test $ bar Test restricted mode with transplant -b @@ -1000,19 +1000,19 @@ 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -y transplant -b foo tip applying 4aa30d025d50 - 4aa30d025d50 transplanted to 5a4da427c162 + 4aa30d025d50 transplanted to e00abbf63521 Expansion in changeset but not in file $ hg tip -p - changeset: 11:5a4da427c162 + changeset: 11:e00abbf63521 tag: tip - parent: 9:41efa6d38e9b + parent: 9:800511b3a22d user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: 9foobranch - diff -r 41efa6d38e9b -r 5a4da427c162 a + diff -r 800511b3a22d -r e00abbf63521 a --- a/a Thu Jan 01 00:00:00 1970 +0000 +++ b/a Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +1,4 @@ @@ -1023,7 +1023,7 @@ $ head -n 2 a foobranch - expand $Id: a 5a4da427c162 Thu, 01 Jan 1970 00:00:00 +0000 test $ + expand $Id: a e00abbf63521 Thu, 01 Jan 1970 00:00:00 +0000 test $ Turn off expansion
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-known.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,37 @@ + += Test the known() protocol function = + +Create a test repository: + + $ hg init repo + $ cd repo + $ touch a ; hg add a ; hg ci -ma + $ touch b ; hg add b ; hg ci -mb + $ touch c ; hg add c ; hg ci -mc + $ hg log --template '{node}\n' + 991a3460af53952d10ec8a295d3d2cc2e5fa9690 + 0e067c57feba1a5694ca4844f05588bb1bf82342 + 3903775176ed42b1458a6281db4a0ccf4d9f287a + $ cd .. + +Test locally: + + $ hg debugknown repo 991a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 3903775176ed42b1458a6281db4a0ccf4d9f287a + 111 + $ hg debugknown repo 000a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 0003775176ed42b1458a6281db4a0ccf4d9f287a + 010 + $ hg debugknown repo + + +Test via HTTP: + + $ hg serve -R repo -p $HGPORT -d --pid-file=hg.pid -E error.log -A access.log + $ cat hg.pid >> $DAEMON_PIDS + $ hg debugknown http://localhost:$HGPORT/ 991a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 3903775176ed42b1458a6281db4a0ccf4d9f287a + 111 + $ hg debugknown http://localhost:$HGPORT/ 000a3460af53952d10ec8a295d3d2cc2e5fa9690 0e067c57feba1a5694ca4844f05588bb1bf82342 0003775176ed42b1458a6281db4a0ccf4d9f287a + 010 + $ hg debugknown http://localhost:$HGPORT/ + + $ cat error.log +
--- a/tests/test-locate.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-locate.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,5 @@ - $ mkdir t + $ hg init t $ cd t - $ hg init $ echo 0 > a $ echo 0 > b $ echo 0 > t.h
--- a/tests/test-log.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-log.t Wed May 18 09:56:27 2011 +0200 @@ -512,12 +512,33 @@ date: Thu Jan 01 00:00:01 1970 +0000 summary: r1 +log -d " " (whitespaces only) + $ hg log -d " " + abort: dates cannot consist entirely of whitespace + [255] log -d -1 $ hg log -d -1 +log -d ">" + + $ hg log -d ">" + abort: invalid day spec, use '>DATE' + [255] + +log -d "<" + + $ hg log -d "<" + abort: invalid day spec, use '<DATE' + [255] + +Negative ranges + $ hg log -d "--2" + abort: -2 must be nonnegative (see 'hg help dates') + [255] + log -p -l2 --color=always
--- a/tests/test-manifest.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-manifest.t Wed May 18 09:56:27 2011 +0200 @@ -10,7 +10,7 @@ # hg ci -Amb -d'1 0' $ hg init - $ hg -q pull "$TESTDIR/test-manifest.hg" + $ hg -q pull "$TESTDIR/bundles/test-manifest.hg" The next call is expected to return nothing:
--- a/tests/test-merge-commit.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-merge-commit.t Wed May 18 09:56:27 2011 +0200 @@ -34,7 +34,7 @@ $ hg ci -m '3: merge with local rename' - $ hg debugindex .hg/store/data/bar.i + $ hg debugindex bar rev offset length base linkrev nodeid p1 p2 0 0 77 0 2 d35118874825 000000000000 000000000000 1 77 76 0 3 5345f5ab8abd 000000000000 d35118874825 @@ -42,7 +42,7 @@ $ hg debugrename bar bar renamed from foo:9e25c27b87571a1edee5ae4dddee5687746cc8e2 - $ hg debugindex .hg/store/data/foo.i + $ hg debugindex foo rev offset length base linkrev nodeid p1 p2 0 0 7 0 0 690b295714ae 000000000000 000000000000 1 7 13 1 1 9e25c27b8757 690b295714ae 000000000000 @@ -87,7 +87,7 @@ $ hg ci -m '5: merge' - $ hg debugindex .hg/store/data/bar.i + $ hg debugindex bar rev offset length base linkrev nodeid p1 p2 0 0 77 0 2 d35118874825 000000000000 000000000000 1 77 76 0 3 5345f5ab8abd 000000000000 d35118874825 @@ -121,7 +121,7 @@ $ hg ci -m '3: merge with remote rename' - $ hg debugindex .hg/store/data/bar.i + $ hg debugindex bar rev offset length base linkrev nodeid p1 p2 0 0 77 0 2 d35118874825 000000000000 000000000000 1 77 76 0 3 5345f5ab8abd 000000000000 d35118874825 @@ -129,7 +129,7 @@ $ hg debugrename bar bar renamed from foo:9e25c27b87571a1edee5ae4dddee5687746cc8e2 - $ hg debugindex .hg/store/data/foo.i + $ hg debugindex foo rev offset length base linkrev nodeid p1 p2 0 0 7 0 0 690b295714ae 000000000000 000000000000 1 7 13 1 1 9e25c27b8757 690b295714ae 000000000000 @@ -174,7 +174,7 @@ $ hg ci -m '5: merge' - $ hg debugindex .hg/store/data/bar.i + $ hg debugindex bar rev offset length base linkrev nodeid p1 p2 0 0 77 0 2 d35118874825 000000000000 000000000000 1 77 76 0 3 5345f5ab8abd 000000000000 d35118874825
--- a/tests/test-merge-symlinks.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-merge-symlinks.t Wed May 18 09:56:27 2011 +0200 @@ -30,7 +30,7 @@ $ hg init t $ cd t - $ hg -q pull "$TESTDIR/test-merge-symlinks.hg" + $ hg -q pull "$TESTDIR/bundles/test-merge-symlinks.hg" $ hg up -C 3 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-merge1.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-merge1.t Wed May 18 09:56:27 2011 +0200 @@ -12,9 +12,8 @@ > EOF $ HGMERGE="python ../merge"; export HGMERGE - $ mkdir t + $ hg init t $ cd t - $ hg init $ echo This is file a1 > a $ hg add a $ hg commit -m "commit #0" @@ -49,9 +48,8 @@ M b $ cd ..; rm -r t - $ mkdir t + $ hg init t $ cd t - $ hg init $ echo This is file a1 > a $ hg add a $ hg commit -m "commit #0" @@ -86,9 +84,8 @@ M b $ cd ..; rm -r t - $ mkdir t + $ hg init t $ cd t - $ hg init $ echo This is file a1 > a $ hg add a $ hg commit -m "commit #0" @@ -129,9 +126,8 @@ M b $ cd ..; rm -r t - $ mkdir t + $ hg init t $ cd t - $ hg init $ echo This is file a1 > a $ hg add a $ hg commit -m "commit #0"
--- a/tests/test-merge2.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-merge2.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,5 @@ - $ mkdir t + $ hg init t $ cd t - $ hg init $ echo This is file a1 > a $ hg add a $ hg commit -m "commit #0" @@ -34,9 +33,8 @@ created new head $ cd ..; rm -r t - $ mkdir t + $ hg init t $ cd t - $ hg init $ echo This is file a1 > a $ hg add a $ hg commit -m "commit #0"
--- a/tests/test-merge6.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-merge6.t Wed May 18 09:56:27 2011 +0200 @@ -4,9 +4,8 @@ > EOF $ HGMERGE="python ../merge"; export HGMERGE - $ mkdir A1 + $ hg init A1 $ cd A1 - $ hg init $ echo This is file foo1 > foo $ echo This is file bar1 > bar $ hg add foo bar
--- a/tests/test-merge7.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-merge7.t Wed May 18 09:56:27 2011 +0200 @@ -104,7 +104,7 @@ >>>>>>> other three - $ hg debugindex .hg/store/data/test.txt.i + $ hg debugindex test.txt rev offset length base linkrev nodeid p1 p2 0 0 7 0 0 01365c4cca56 000000000000 000000000000 1 7 9 1 1 7b013192566a 01365c4cca56 000000000000
--- a/tests/test-mq-guards.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-mq-guards.t Wed May 18 09:56:27 2011 +0200 @@ -309,7 +309,7 @@ qseries again, but with color - $ hg --config extensions.color= qseries -v --color=always + $ hg --config extensions.color= --config color.mode=ansi qseries -v --color=always 0 G \x1b[0;30;1mnew.patch\x1b[0m (esc) 1 G \x1b[0;30;1mb.patch\x1b[0m (esc) 2 A \x1b[0;34;1;4mc.patch\x1b[0m (esc) @@ -432,5 +432,5 @@ hg qseries -m with color - $ hg --config extensions.color= qseries -m --color=always + $ hg --config extensions.color= --config color.mode=ansi qseries -m --color=always \x1b[0;31;1mb.patch\x1b[0m (esc)
--- a/tests/test-mq-missingfiles.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-mq-missingfiles.t Wed May 18 09:56:27 2011 +0200 @@ -101,8 +101,8 @@ applying changeb unable to find 'b' for patching 1 out of 1 hunks FAILED -- saving rejects to file b.rej + b: No such file or directory patch failed, unable to continue (try -v) - b: No such file or directory patch failed, rejects left in working dir errors during apply, please fix and refresh changeb [2]
--- a/tests/test-mq-qdelete.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-mq-qdelete.t Wed May 18 09:56:27 2011 +0200 @@ -8,77 +8,77 @@ $ hg ci -Ambase -d '1 0' adding base - $ hg qnew -d '1 0' a - $ hg qnew -d '1 0' b - $ hg qnew -d '1 0' c + $ hg qnew -d '1 0' pa + $ hg qnew -d '1 0' pb + $ hg qnew -d '1 0' pc $ hg qdel abort: qdelete requires at least one revision or patch name [255] - $ hg qdel c - abort: cannot delete applied patch c + $ hg qdel pc + abort: cannot delete applied patch pc [255] $ hg qpop - popping c - now at: b + popping pc + now at: pb Delete the same patch twice in one command (issue2427) - $ hg qdel c c + $ hg qdel pc pc $ hg qseries - a - b + pa + pb $ ls .hg/patches - a - b + pa + pb series status $ hg qpop - popping b - now at: a + popping pb + now at: pa $ hg qdel -k 1 $ ls .hg/patches - a - b + pa + pb series status - $ hg qdel -r a - patch a finalized without changeset message + $ hg qdel -r pa + patch pa finalized without changeset message $ hg qapplied $ hg log --template '{rev} {desc}\n' - 1 [mq]: a + 1 [mq]: pa 0 base - $ hg qnew d - $ hg qnew e - $ hg qnew f + $ hg qnew pd + $ hg qnew pe + $ hg qnew pf - $ hg qdel -r e + $ hg qdel -r pe abort: cannot delete revision 3 above applied patches [255] - $ hg qdel -r qbase:e - patch d finalized without changeset message - patch e finalized without changeset message + $ hg qdel -r qbase:pe + patch pd finalized without changeset message + patch pe finalized without changeset message $ hg qapplied - f + pf $ hg log --template '{rev} {desc}\n' - 4 [mq]: f - 3 [mq]: e - 2 [mq]: d - 1 [mq]: a + 4 [mq]: pf + 3 [mq]: pe + 2 [mq]: pd + 1 [mq]: pa 0 base $ cd .. @@ -97,53 +97,53 @@ $ hg qfinish -a no patches applied - $ hg qnew -d '1 0' a - $ hg qnew -d '1 0' b - $ hg qnew c # XXX fails to apply by /usr/bin/patch if we put a date + $ hg qnew -d '1 0' pa + $ hg qnew -d '1 0' pb + $ hg qnew pc # XXX fails to apply by /usr/bin/patch if we put a date $ hg qfinish 0 abort: revision 0 is not managed [255] - $ hg qfinish b + $ hg qfinish pb abort: cannot delete revision 2 above applied patches [255] $ hg qpop - popping c - now at: b + popping pc + now at: pb - $ hg qfinish -a c - abort: unknown revision 'c'! + $ hg qfinish -a pc + abort: unknown revision 'pc'! [255] $ hg qpush - applying c - patch c is empty - now at: c + applying pc + patch pc is empty + now at: pc - $ hg qfinish qbase:b - patch a finalized without changeset message - patch b finalized without changeset message + $ hg qfinish qbase:pb + patch pa finalized without changeset message + patch pb finalized without changeset message $ hg qapplied - c + pc $ hg log --template '{rev} {desc}\n' - 3 imported patch c - 2 [mq]: b - 1 [mq]: a + 3 imported patch pc + 2 [mq]: pb + 1 [mq]: pa 0 base - $ hg qfinish -a c - patch c finalized without changeset message + $ hg qfinish -a pc + patch pc finalized without changeset message $ hg qapplied $ hg log --template '{rev} {desc}\n' - 3 imported patch c - 2 [mq]: b - 1 [mq]: a + 3 imported patch pc + 2 [mq]: pb + 1 [mq]: pa 0 base $ ls .hg/patches @@ -162,3 +162,33 @@ adding 3.diff to series file $ hg qfinish -a no patches applied + + +resilience to inconsistency: qfinish -a with applied patches not in series + + $ hg qser + 3.diff + $ hg qapplied + $ hg qpush + applying 3.diff + patch 3.diff is empty + now at: 3.diff + $ echo next >> base + $ hg qrefresh -d '1 0' + $ echo > .hg/patches/series # remove 3.diff from series to confuse mq + $ hg qfinish -a + revision 47dfa8501675 refers to unknown patches: 3.diff + +more complex state 'both known and unknown patches + + $ echo hip >> base + $ hg qnew -f -d '1 0' -m 4 4.diff + $ echo hop >> base + $ hg qnew -f -d '1 0' -m 5 5.diff + $ echo > .hg/patches/series # remove 4.diff and 5.diff from series to confuse mq + $ echo hup >> base + $ hg qnew -f -d '1 0' -m 6 6.diff + $ hg qfinish -a + revision 2b1c98802260 refers to unknown patches: 5.diff + revision 33a6861311c0 refers to unknown patches: 4.diff +
--- a/tests/test-mq-qdiff.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-mq-qdiff.t Wed May 18 09:56:27 2011 +0200 @@ -163,4 +163,13 @@ $ hg qdiff --stat lines | 7 +++++-- 1 files changed, 5 insertions(+), 2 deletions(-) + $ hg qrefresh +qdiff when file deleted (but not removed) in working dir: + + $ hg qnew deleted-file + $ echo a > newfile + $ hg add newfile + $ hg qrefresh + $ rm newfile + $ hg qdiff
--- a/tests/test-mq-qnew.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-mq-qnew.t Wed May 18 09:56:27 2011 +0200 @@ -106,10 +106,10 @@ abort: "guards" cannot be used as the name of a patch abort: "." cannot be used as the name of a patch abort: ".." cannot be used as the name of a patch - abort: ".hgignore" cannot be used as the name of a patch - abort: ".mqfoo" cannot be used as the name of a patch - abort: "foo#bar" cannot be used as the name of a patch - abort: "foo:bar" cannot be used as the name of a patch + abort: patch name cannot begin with ".hg" + abort: patch name cannot begin with ".mq" + abort: "#" cannot be used in the name of a patch + abort: ":" cannot be used in the name of a patch % qnew with name containing slash abort: path ends in directory separator: foo/ abort: "foo" already exists as a directory @@ -173,10 +173,10 @@ abort: "guards" cannot be used as the name of a patch abort: "." cannot be used as the name of a patch abort: ".." cannot be used as the name of a patch - abort: ".hgignore" cannot be used as the name of a patch - abort: ".mqfoo" cannot be used as the name of a patch - abort: "foo#bar" cannot be used as the name of a patch - abort: "foo:bar" cannot be used as the name of a patch + abort: patch name cannot begin with ".hg" + abort: patch name cannot begin with ".mq" + abort: "#" cannot be used in the name of a patch + abort: ":" cannot be used in the name of a patch % qnew with name containing slash abort: path ends in directory separator: foo/ abort: "foo" already exists as a directory
--- a/tests/test-mq-qpush-exact.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-mq-qpush-exact.t Wed May 18 09:56:27 2011 +0200 @@ -163,8 +163,12 @@ $ hg update 1 -q $ echo c0 >> f0 $ hg qpush -e - abort: local changes found, refresh first - [255] + applying p0 + now at: p0 + $ cat f0 + c0 + $ hg qpop -aq + patch queue now empty $ hg qpush -ef applying p0 now at: p0 @@ -178,8 +182,13 @@ $ hg update 1 -q $ echo c0 >> f0 $ hg qpush -e p1 - abort: local changes found, refresh first - [255] + applying p0 + applying p1 + now at: p1 + $ cat f0 + c0 + $ hg qpop -aq + patch queue now empty $ hg qpush -e p1 -f applying p0 applying p1 @@ -197,7 +206,7 @@ $ echo cp0-bad >> fp0 $ hg add fp0 $ hg qpush -e - abort: local changes found, refresh first + abort: local changes found [255] $ hg qpush -ef applying p0 @@ -223,7 +232,7 @@ $ echo cp1-bad >> fp1 $ hg add fp1 $ hg qpush -e p1 - abort: local changes found, refresh first + abort: local changes found [255] $ hg qpush -e p1 -f applying p0
--- a/tests/test-mq-strip.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-mq-strip.t Wed May 18 09:56:27 2011 +0200 @@ -410,7 +410,7 @@ abort: local changes found [255] $ hg strip tip --keep - saved backup bundle to * (glob) + saved backup bundle to $TESTTMP/test/.hg/strip-backup/*-backup.hg (glob) $ hg log --graph @ changeset: 0:9ab35a2d17cb tag: tip
--- a/tests/test-mq.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-mq.t Wed May 18 09:56:27 2011 +0200 @@ -177,7 +177,7 @@ status --mq with color (issue2096) - $ hg status --mq --config extensions.color= --color=always + $ hg status --mq --config extensions.color= --config color.mode=ansi --color=always \x1b[0;32;1mA .hgignore\x1b[0m (esc) \x1b[0;32;1mA A\x1b[0m (esc) \x1b[0;32;1mA B\x1b[0m (esc) @@ -843,6 +843,14 @@ 1 foo qbase 2 bar qtip tip +mq revset + + $ hg log -r 'mq()' --template '{rev}\n' + 1 + 2 + $ hg help revsets | grep -i mq + "mq()" + Changesets managed by MQ. bad node in status @@ -1251,6 +1259,64 @@ now at: changea $ cd .. +test qpop with local changes, issue2780 + + $ hg init forcepop + $ cd forcepop + $ echo 1 > 1 + $ hg ci -Am 1 + adding 1 + $ hg qnew foo + $ echo 2 > 2 + $ hg add + adding 2 + +unrelated changes + + $ hg qpop + popping foo + patch queue now empty + +related changes + + $ hg forget 2 + $ rm 2 + $ hg qpush + applying foo + patch foo is empty + now at: foo + $ echo 2 >> 1 + $ hg qrefresh + $ echo 2 >> 1 + $ hg qpop + abort: local changes found, refresh first + [255] + $ hg st + M 1 + +related changes with force + $ hg qpop --force + popping foo + patch queue now empty + $ hg st + +related renamed source without change + $ hg qpush + applying foo + now at: foo + $ echo 1 > 1 + $ hg mv 1 2 + $ hg qref --git + $ hg qpop + popping foo + patch queue now empty + $ echo 3 > 1 + $ hg st + M 1 + $ hg qpush + abort: local changes found + [255] + $ cd .. test qpush with --force, issue1087 @@ -1268,21 +1334,14 @@ $ echo world >> hello.txt -qpush should fail, local changes +apply, should not discard changes with empty patch $ hg qpush - abort: local changes found, refresh first - [255] - - -apply force, should not discard changes with empty patch - - $ hg qpush -f applying empty patch empty is empty now at: empty $ hg diff --config diff.nodates=True - diff -r bf5fc3f07a0a hello.txt + diff -r d58265112590 hello.txt --- a/hello.txt +++ b/hello.txt @@ -1,1 +1,2 @@ @@ -1296,7 +1355,7 @@ hello +world $ hg log -l1 -p - changeset: 1:bf5fc3f07a0a + changeset: 1:d58265112590 tag: empty tag: qbase tag: qtip @@ -1317,7 +1376,7 @@ qpush should fail, local changes $ hg qpush - abort: local changes found, refresh first + abort: local changes found [255]
--- a/tests/test-nested-repo.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-nested-repo.t Wed May 18 09:56:27 2011 +0200 @@ -11,16 +11,16 @@ Should fail: $ hg st b/x - abort: path 'b/x' is inside repo 'b' + abort: path 'b/x' is inside nested repo 'b' [255] $ hg add b/x - abort: path 'b/x' is inside repo 'b' + abort: path 'b/x' is inside nested repo 'b' [255] Should fail: $ hg add b b/x - abort: path 'b/x' is inside repo 'b' + abort: path 'b/x' is inside nested repo 'b' [255] $ hg st @@ -34,7 +34,7 @@ Should fail: $ hg mv a b - abort: path 'b/a' is inside repo 'b' + abort: path 'b/a' is inside nested repo 'b' [255] $ hg st
--- a/tests/test-newbranch.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-newbranch.t Wed May 18 09:56:27 2011 +0200 @@ -19,7 +19,8 @@ Branch shadowing: $ hg branch default - abort: a branch of the same name already exists (use 'hg update' to switch to it) + abort: a branch of the same name already exists + (use 'hg update' to switch to it) [255] $ hg branch -f default @@ -31,7 +32,7 @@ There should be only one default branch head $ hg heads . - changeset: 3:9d567d0b51f9 + changeset: 3:1c28f494dae6 tag: tip user: test date: Thu Jan 01 00:00:00 1970 +0000 @@ -54,34 +55,34 @@ $ hg ci -m "merge" $ hg log - changeset: 5:dc140083783b + changeset: 5:530046499edf branch: foo tag: tip - parent: 4:98d14f698afe - parent: 3:9d567d0b51f9 + parent: 4:adf1a74a7f7b + parent: 3:1c28f494dae6 user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: merge - changeset: 4:98d14f698afe + changeset: 4:adf1a74a7f7b branch: foo - parent: 1:0079f24813e2 + parent: 1:6c0e42da283a user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: modify a branch - changeset: 3:9d567d0b51f9 + changeset: 3:1c28f494dae6 user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: clear branch name - changeset: 2:ed2bbf4e0102 + changeset: 2:c21617b13b22 branch: bar user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: change branch name - changeset: 1:0079f24813e2 + changeset: 1:6c0e42da283a branch: foo user: test date: Thu Jan 01 00:00:00 1970 +0000 @@ -93,9 +94,9 @@ summary: initial $ hg branches - foo 5:dc140083783b - default 3:9d567d0b51f9 (inactive) - bar 2:ed2bbf4e0102 (inactive) + foo 5:530046499edf + default 3:1c28f494dae6 (inactive) + bar 2:c21617b13b22 (inactive) $ hg branches -q foo @@ -111,10 +112,10 @@ $ cp $branchcache .hg/bc-invalid $ hg log -r foo - changeset: 4:98d14f698afe + changeset: 4:adf1a74a7f7b branch: foo tag: tip - parent: 1:0079f24813e2 + parent: 1:6c0e42da283a user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: modify a branch @@ -123,12 +124,12 @@ $ hg --debug log -r foo invalidating branch cache (tip differs) - changeset: 4:98d14f698afeaff8cb612dcf215ce95e639effc3 + changeset: 4:adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 branch: foo tag: tip - parent: 1:0079f24813e2b73a891577c243684c5066347bc8 + parent: 1:6c0e42da283a56b5edc5b4fadb491365ec7f5fa8 parent: -1:0000000000000000000000000000000000000000 - manifest: 4:d01b250baaa05909152f7ae07d7a649deea0df9a + manifest: 1:8c342a37dfba0b3d3ce073562a00d8a813c54ffe user: test date: Thu Jan 01 00:00:00 1970 +0000 files: a @@ -141,13 +142,13 @@ $ echo corrupted > $branchcache $ hg log -qr foo - 4:98d14f698afe + 4:adf1a74a7f7b $ cat $branchcache - 98d14f698afeaff8cb612dcf215ce95e639effc3 4 - 9d567d0b51f9e2068b054e1948e1a927f99b5874 default - 98d14f698afeaff8cb612dcf215ce95e639effc3 foo - ed2bbf4e01029020711be82ca905283e883f0e11 bar + adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 4 + 1c28f494dae69a2f8fc815059d257eccf3fcfe75 default + adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 foo + c21617b13b220988e7a2e26290fbe4325ffa7139 bar Push should update the branch cache: @@ -166,22 +167,22 @@ $ hg push -qf ../target $ cat ../target/$branchcache - 98d14f698afeaff8cb612dcf215ce95e639effc3 4 - 9d567d0b51f9e2068b054e1948e1a927f99b5874 default - 98d14f698afeaff8cb612dcf215ce95e639effc3 foo - ed2bbf4e01029020711be82ca905283e883f0e11 bar + adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 4 + 1c28f494dae69a2f8fc815059d257eccf3fcfe75 default + adf1a74a7f7b4cd193d12992f5d0d6a004ed21d6 foo + c21617b13b220988e7a2e26290fbe4325ffa7139 bar Update with no arguments: tipmost revision of the current branch: $ hg up -q -C 0 $ hg up -q $ hg id - 9d567d0b51f9 + 1c28f494dae6 $ hg up -q 1 $ hg up -q $ hg id - 98d14f698afe (foo) tip + adf1a74a7f7b (foo) tip $ hg branch foobar marked working directory as branch foobar @@ -210,11 +211,11 @@ foo $ hg commit -m'Merge ff into foo' $ hg parents - changeset: 6:917eb54e1b4b + changeset: 6:185ffbfefa30 branch: foo tag: tip - parent: 4:98d14f698afe - parent: 5:6683a60370cb + parent: 4:adf1a74a7f7b + parent: 5:1a3c27dc5e11 user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: Merge ff into foo
--- a/tests/test-no-symlinks.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-no-symlinks.t Wed May 18 09:56:27 2011 +0200 @@ -16,7 +16,7 @@ $ hg init t $ cd t - $ hg pull -q "$TESTDIR/test-no-symlinks.hg" + $ hg pull -q "$TESTDIR/bundles/test-no-symlinks.hg" $ hg update 4 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cat a.lnk && echo
--- a/tests/test-notify.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-notify.t Wed May 18 09:56:27 2011 +0200 @@ -298,11 +298,11 @@ Date: * (glob) Subject: merge From: test@test.com - X-Hg-Notification: changeset 22c88b85aa27 + X-Hg-Notification: changeset 6a0cf76b2701 Message-Id: <*> (glob) To: baz@test.com, foo@bar - changeset 22c88b85aa27 in b + changeset 6a0cf76b2701 in b description: merge (run 'hg update' to get a working copy) @@ -330,11 +330,11 @@ Date: * (glob) Subject: \xc3\xa0... (esc) From: test@test.com - X-Hg-Notification: changeset 4a47f01c1356 + X-Hg-Notification: changeset 7ea05ad269dc Message-Id: <*> (glob) To: baz@test.com, foo@bar - changeset 4a47f01c1356 in b + changeset 7ea05ad269dc in b description: \xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4 (esc) diffstat: @@ -343,7 +343,7 @@ diffs (7 lines): - diff -r 22c88b85aa27 -r 4a47f01c1356 a + diff -r 6a0cf76b2701 -r 7ea05ad269dc a --- a/a Thu Jan 01 00:00:03 1970 +0000 +++ b/a Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +1,3 @@
--- a/tests/test-parentrevspec.t Wed May 18 09:31:19 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ - - $ commit() - > { - > msg=$1 - > p1=$2 - > p2=$3 - > - > if [ "$p1" ]; then - > hg up -qC $p1 - > fi - > - > if [ "$p2" ]; then - > HGMERGE=true hg merge -q $p2 - > fi - > - > echo >> foo - > - > hg commit -qAm "$msg" - > } - $ hg init repo - $ cd repo - $ echo '[extensions]' > .hg/hgrc - $ echo 'parentrevspec =' >> .hg/hgrc - $ commit '0: add foo' - $ commit '1: change foo 1' - $ commit '2: change foo 2a' - $ commit '3: change foo 3a' - $ commit '4: change foo 2b' 1 - $ commit '5: merge' 3 4 - $ commit '6: change foo again' - $ hg log --template '{rev}:{node|short} {parents}\n' - 6:755d1e0d79e9 - 5:9ce2ce29723a 3:a3e00c7dbf11 4:bb4475edb621 - 4:bb4475edb621 1:5d953a1917d1 - 3:a3e00c7dbf11 - 2:befc7d89d081 - 1:5d953a1917d1 - 0:837088b6e1d9 - $ echo - - $ lookup() - > { - > for rev in "$@"; do - > printf "$rev: " - > hg id -nr $rev - > done - > true - > } - $ tipnode=`hg id -ir tip` - -should work with tag/branch/node/rev - - $ for r in tip default $tipnode 6; do - > lookup "$r^" - > done - tip^: 5 - default^: 5 - 755d1e0d79e9^: 5 - 6^: 5 - $ echo - - -some random lookups - - $ lookup "6^^" "6^^^" "6^^^^" "6^^^^^" "6^^^^^^" "6^1" "6^2" "6^^2" "6^1^2" "6^^3" - 6^^: 3 - 6^^^: 2 - 6^^^^: 1 - 6^^^^^: 0 - 6^^^^^^: -1 - 6^1: 5 - 6^2: hg: parse error at 1: syntax error - 6^^2: 4 - 6^1^2: 4 - 6^^3: hg: parse error at 1: syntax error - $ lookup "6~" "6~1" "6~2" "6~3" "6~4" "6~5" "6~42" "6~1^2" "6~1^2~2" - 6~: hg: parse error at 1: syntax error - 6~1: 5 - 6~2: 3 - 6~3: 2 - 6~4: 1 - 6~5: 0 - 6~42: -1 - 6~1^2: 4 - 6~1^2~2: 0 - $ echo - - -with a tag "6^" pointing to rev 1 - - $ hg tag -l -r 1 "6^" - $ lookup "6^" "6^1" "6~1" "6^^" - 6^: 1 - 6^1: 5 - 6~1: 5 - 6^^: 3 - $ echo - - -with a tag "foo^bar" pointing to rev 2 - - $ hg tag -l -r 2 "foo^bar" - $ lookup "foo^bar" "foo^bar^" - foo^bar: 2 - foo^bar^: hg: parse error at 3: syntax error
--- a/tests/test-parse-date.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-parse-date.t Wed May 18 09:56:27 2011 +0200 @@ -96,52 +96,32 @@ Test date formats with '>' or '<' accompanied by space characters $ hg log -d '>' --template '{date|date}\n' - Sun Jan 15 13:30:00 2006 +0500 - Sun Jan 15 13:30:00 2006 -0800 - Sat Jul 15 13:30:00 2006 +0500 - Sat Jul 15 13:30:00 2006 -0700 - Sun Jun 11 00:26:40 2006 -0400 - Sat Apr 15 13:30:00 2006 +0200 - Sat Apr 15 13:30:00 2006 +0000 - Wed Feb 01 13:00:30 2006 -0500 - Wed Feb 01 13:00:30 2006 +0000 + abort: invalid day spec, use '>DATE' + [255] $ hg log -d '<' hg log -d '>' --template '{date|date}\n' + abort: invalid day spec, use '>DATE' + [255] $ hg log -d ' >' --template '{date|date}\n' - Sun Jan 15 13:30:00 2006 +0500 - Sun Jan 15 13:30:00 2006 -0800 - Sat Jul 15 13:30:00 2006 +0500 - Sat Jul 15 13:30:00 2006 -0700 - Sun Jun 11 00:26:40 2006 -0400 - Sat Apr 15 13:30:00 2006 +0200 - Sat Apr 15 13:30:00 2006 +0000 - Wed Feb 01 13:00:30 2006 -0500 - Wed Feb 01 13:00:30 2006 +0000 + abort: invalid day spec, use '>DATE' + [255] $ hg log -d ' <' --template '{date|date}\n' + abort: invalid day spec, use '<DATE' + [255] $ hg log -d '> ' --template '{date|date}\n' - Sun Jan 15 13:30:00 2006 +0500 - Sun Jan 15 13:30:00 2006 -0800 - Sat Jul 15 13:30:00 2006 +0500 - Sat Jul 15 13:30:00 2006 -0700 - Sun Jun 11 00:26:40 2006 -0400 - Sat Apr 15 13:30:00 2006 +0200 - Sat Apr 15 13:30:00 2006 +0000 - Wed Feb 01 13:00:30 2006 -0500 - Wed Feb 01 13:00:30 2006 +0000 + abort: invalid day spec, use '>DATE' + [255] $ hg log -d '< ' --template '{date|date}\n' + abort: invalid day spec, use '<DATE' + [255] $ hg log -d ' > ' --template '{date|date}\n' - Sun Jan 15 13:30:00 2006 +0500 - Sun Jan 15 13:30:00 2006 -0800 - Sat Jul 15 13:30:00 2006 +0500 - Sat Jul 15 13:30:00 2006 -0700 - Sun Jun 11 00:26:40 2006 -0400 - Sat Apr 15 13:30:00 2006 +0200 - Sat Apr 15 13:30:00 2006 +0000 - Wed Feb 01 13:00:30 2006 -0500 - Wed Feb 01 13:00:30 2006 +0000 + abort: invalid day spec, use '>DATE' + [255] $ hg log -d ' < ' --template '{date|date}\n' + abort: invalid day spec, use '<DATE' + [255] $ hg log -d '>02/01' --template '{date|date}\n' $ hg log -d '<02/01' --template '{date|date}\n'
--- a/tests/test-parseindex.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-parseindex.t Wed May 18 09:56:27 2011 +0200 @@ -26,7 +26,7 @@ summary: change foo $ cat >> test.py << EOF - > from mercurial import changelog, util + > from mercurial import changelog, scmutil > from mercurial.node import * > > class singlebyteread(object): @@ -42,7 +42,7 @@ > return getattr(self.real, key) > > def opener(*args): - > o = util.opener(*args) + > o = scmutil.opener(*args) > def wrapper(*a): > f = o(*a) > return singlebyteread(f)
--- a/tests/test-patchbomb.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-patchbomb.t Wed May 18 09:56:27 2011 +0200 @@ -158,16 +158,30 @@ $ hg email -m test.mbox -f quux -t foo -c bar -s test 0:tip \ > --config extensions.progress= --config progress.assume-tty=1 \ - > --config progress.delay=0 --config progress.refresh=0 + > --config progress.delay=0 --config progress.refresh=0 \ + > --config progress.width=60 2>&1 | \ + > python $TESTDIR/filtercr.py This patch series consists of 2 patches. Write the introductory message for the patch series. - \rwriting [ ] 0/3\rwriting [ ] 0/3\r \r\r \r\rwriting [====================> ] 1/3\rwriting [====================> ] 1/3\r \r\r \r\rwriting [==========================================> ] 2/3\rwriting [==========================================> ] 2/3\r \r (esc) + + writing [ ] 0/3 + writing [ ] 0/3 + + + writing [==============> ] 1/3 + writing [==============> ] 1/3 + + + writing [=============================> ] 2/3 + writing [=============================> ] 2/3 + \r (esc) Writing [PATCH 0 of 2] test ... Writing [PATCH 1 of 2] a ... Writing [PATCH 2 of 2] b ... + $ cd ..
--- a/tests/test-paths.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-paths.t Wed May 18 09:56:27 2011 +0200 @@ -25,3 +25,34 @@ $ SOMETHING=/foo hg paths dupe = $TESTTMP/b expand = /foo/bar + $ hg paths -q + dupe + expand + $ hg paths dupe + $TESTTMP/b + $ hg paths -q dupe + $ hg paths unknown + not found! + [1] + $ hg paths -q unknown + [1] + $ cd .. + +'file:' disables [paths] entries for clone destination + + $ cat >> $HGRCPATH <<EOF + > [paths] + > gpath1 = http://hg.example.com + > EOF + + $ hg clone a gpath1 + abort: cannot create new http repository + [255] + + $ hg clone a file:gpath1 + updating to branch default + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd gpath1 + $ hg -q id + 000000000000 +
--- a/tests/test-pull-branch.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-pull-branch.t Wed May 18 09:56:27 2011 +0200 @@ -134,3 +134,77 @@ not updating, since new heads added (run 'hg heads' to see heads, 'hg merge' to merge) +Make changes on new branch on tt + + $ hg branch branchC + marked working directory as branch branchC + $ echo b1 > bar + $ hg ci -Am "commit on branchC on tt" + adding bar + +Make changes on default branch on t + + $ cd ../t + $ hg up -C default + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo a1 > bar + $ hg ci -Am "commit on default on t" + adding bar + +Pull branchC from tt + + $ hg pull ../tt + pulling from ../tt + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + (run 'hg heads' to see heads) + +Make changes on default and branchC on tt + + $ cd ../tt + $ hg pull ../t + pulling from ../t + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files (+1 heads) + (run 'hg heads' to see heads) + $ hg up -C default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo a1 > bar1 + $ hg ci -Am "commit on default on tt" + adding bar1 + $ hg up branchC + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo a1 > bar2 + $ hg ci -Am "commit on branchC on tt" + adding bar2 + +Make changes on default and branchC on t + + $ cd ../t + $ hg up default + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo a1 > bar3 + $ hg ci -Am "commit on default on t" + adding bar3 + $ hg up branchC + 2 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo a1 > bar4 + $ hg ci -Am "commit on branchC on tt" + adding bar4 + +Pull from tt + + $ hg pull ../tt + pulling from ../tt + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 2 files (+2 heads) + (run 'hg heads .' to see heads, 'hg merge' to merge)
--- a/tests/test-pull-permission.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-pull-permission.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,5 @@ - $ mkdir a + $ hg init a $ cd a - $ hg init $ echo foo > b $ hg add b $ hg ci -m "b"
--- a/tests/test-pull-r.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-pull-r.t Wed May 18 09:56:27 2011 +0200 @@ -27,6 +27,36 @@ summary: add foo $ cd .. + +don't show "(+1 heads)" message when pulling closed head + + $ hg clone -q repo repo2 + $ hg clone -q repo2 repo3 + $ cd repo2 + $ hg up -q 0 + $ echo hello >> foo + $ hg ci -mx1 + created new head + $ hg ci -mx2 --close-branch + $ cd ../repo3 + $ hg heads -q --closed + 2:effea6de0384 + 1:ed1b79f46b9a + $ hg pull + pulling from $TESTTMP/repo2 + searching for changes + adding changesets + adding manifests + adding file changes + added 2 changesets with 1 changes to 1 files + (run 'hg update' to get a working copy) + $ hg heads -q --closed + 4:00cfe9073916 + 2:effea6de0384 + 1:ed1b79f46b9a + + $ cd .. + $ hg init copy $ cd copy
--- a/tests/test-pull.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-pull.t Wed May 18 09:56:27 2011 +0200 @@ -1,8 +1,7 @@ - $ mkdir test + $ hg init test $ cd test $ echo foo>foo - $ hg init $ hg addremove adding foo $ hg commit -m 1 @@ -68,7 +67,11 @@ Test 'file:' uri handling: $ hg pull -q file://../test-doesnt-exist - abort: repository /test-doesnt-exist not found! + abort: file:// URLs can only refer to localhost + [255] + + $ hg pull -q file://../test + abort: file:// URLs can only refer to localhost [255] $ hg pull -q file:../test @@ -78,4 +81,8 @@ $ URL=`python -c "import os; print 'file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"` $ hg pull -q "$URL" + abort: file:// URLs can only refer to localhost + [255] + $ URL=`python -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"` + $ hg pull -q "$URL"
--- a/tests/test-push-cgi.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-push-cgi.t Wed May 18 09:56:27 2011 +0200 @@ -59,7 +59,7 @@ adding file changes added 0 changesets with 0 changes to 1 files -successful push +successful push, list of heads $ QUERY_STRING="cmd=unbundle&heads=f7b1eb17ad24730a1651fccd46c43826d1bbc2ac"; export QUERY_STRING $ python hgweb.cgi <bundle.hg >page3 2>&1 @@ -72,3 +72,17 @@ adding manifests adding file changes added 0 changesets with 0 changes to 1 files + +successful push, SHA1 hash of heads (unbundlehash capability) + + $ QUERY_STRING="cmd=unbundle&heads=686173686564 5a785a5f9e0d433b88ed862b206b011b0c3a9d13"; export QUERY_STRING + $ python hgweb.cgi <bundle.hg >page4 2>&1 + $ cat page4 + Status: 200 Script output follows\r (esc) + Content-Type: application/mercurial-0.1\r (esc) + \r (esc) + 1 + adding changesets + adding manifests + adding file changes + added 0 changesets with 0 changes to 1 files
--- a/tests/test-push-http.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-push-http.t Wed May 18 09:56:27 2011 +0200 @@ -66,6 +66,40 @@ repository tip rolled back to revision 0 (undo serve) working directory now based on revision 0 +expect success, server lacks the httpheader capability + + $ CAP=httpheader + $ . "$TESTDIR/notcapable" + $ req + pushing to http://localhost:$HGPORT/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_URL=remote:http:*: (glob) + % serve errors + $ hg rollback + repository tip rolled back to revision 0 (undo serve) + working directory now based on revision 0 + +expect success, server lacks the unbundlehash capability + + $ CAP=unbundlehash + $ . "$TESTDIR/notcapable" + $ req + pushing to http://localhost:$HGPORT/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_URL=remote:http:*: (glob) + % serve errors + $ hg rollback + repository tip rolled back to revision 0 (undo serve) + working directory now based on revision 0 + expect authorization error: all users denied $ echo '[web]' > .hg/hgrc
--- a/tests/test-push-r.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-push-r.t Wed May 18 09:56:27 2011 +0200 @@ -1,95 +1,13 @@ $ hg init test $ cd test - - $ cat >>afile <<EOF - > 0 - > EOF - $ hg add afile - $ hg commit -m "0.0" - - $ cat >>afile <<EOF - > 1 - > EOF - $ hg commit -m "0.1" - - $ cat >>afile <<EOF - > 2 - > EOF - $ hg commit -m "0.2" - - $ cat >>afile <<EOF - > 3 - > EOF - $ hg commit -m "0.3" - - $ hg update -C 0 + $ hg unbundle $TESTDIR/bundles/remote.hg + adding changesets + adding manifests + adding file changes + added 9 changesets with 7 changes to 4 files (+1 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg up tip 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - - $ cat >>afile <<EOF - > 1 - > EOF - $ hg commit -m "1.1" - created new head - - $ cat >>afile <<EOF - > 2 - > EOF - $ hg commit -m "1.2" - - $ cat >fred <<EOF - > a line - > EOF - $ cat >>afile <<EOF - > 3 - > EOF - $ hg add fred - $ hg commit -m "1.3" - - $ hg mv afile adifferentfile - $ hg commit -m "1.3m" - - $ hg update -C 3 - 1 files updated, 0 files merged, 2 files removed, 0 files unresolved - - $ hg mv afile anotherfile - $ hg commit -m "0.3m" - - $ hg debugindex .hg/store/data/afile.i - rev offset length base linkrev nodeid p1 p2 - 0 0 3 0 0 362fef284ce2 000000000000 000000000000 - 1 3 5 1 1 125144f7e028 362fef284ce2 000000000000 - 2 8 7 2 2 4c982badb186 125144f7e028 000000000000 - 3 15 9 3 3 19b1fc555737 4c982badb186 000000000000 - - $ hg debugindex .hg/store/data/adifferentfile.i - rev offset length base linkrev nodeid p1 p2 - 0 0 75 0 7 2565f3199a74 000000000000 000000000000 - - $ hg debugindex .hg/store/data/anotherfile.i - rev offset length base linkrev nodeid p1 p2 - 0 0 75 0 8 2565f3199a74 000000000000 000000000000 - - $ hg debugindex .hg/store/data/fred.i - rev offset length base linkrev nodeid p1 p2 - 0 0 8 0 6 12ab3bcc5ea4 000000000000 000000000000 - - $ hg debugindex .hg/store/00manifest.i - rev offset length base linkrev nodeid p1 p2 - 0 0 48 0 0 43eadb1d2d06 000000000000 000000000000 - 1 48 48 1 1 8b89697eba2c 43eadb1d2d06 000000000000 - 2 96 48 2 2 626a32663c2f 8b89697eba2c 000000000000 - 3 144 48 3 3 f54c32f13478 626a32663c2f 000000000000 - 4 192 58 3 6 de68e904d169 626a32663c2f 000000000000 - 5 250 68 3 7 09bb521d218d de68e904d169 000000000000 - 6 318 54 6 8 1fde233dfb0f f54c32f13478 000000000000 - - $ hg verify - checking changesets - checking manifests - crosschecking files in changesets and manifests - checking files - 4 files, 9 changesets, 7 total revisions - $ cd .. $ for i in 0 1 2 3 4 5 6 7 8; do
--- a/tests/test-push-warn.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-push-warn.t Wed May 18 09:56:27 2011 +0200 @@ -1,9 +1,8 @@ $ echo "[extensions]" >> $HGRCPATH $ echo "graphlog=" >> $HGRCPATH - $ mkdir a + $ hg init a $ cd a - $ hg init $ echo foo > t1 $ hg add t1 $ hg commit -m "1" @@ -32,15 +31,12 @@ $ hg push --debug ../a pushing to ../a + query 1; heads searching for changes - examining 1c9246a22a0a:d8d565842d04 - found incomplete branch 1c9246a22a0a:d8d565842d04 - searching: 1 queries - narrowing 1:1 d8d565842d04 - found new branch changeset 1c9246a22a0a - found new changesets starting at 1c9246a22a0a - 1 total queries - common changesets up to d8d565842d04 + taking quick initial sample + searching: 2 queries + query 2; still undecided: 2, sample size is: 2 + 2 total queries new remote heads on branch 'default' new remote head 1e108cc5548c abort: push creates new remote heads on branch 'default'!
--- a/tests/test-qrecord.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-qrecord.t Wed May 18 09:56:27 2011 +0200 @@ -2,36 +2,101 @@ $ echo "[ui]" >> $HGRCPATH $ echo "interactive=true" >> $HGRCPATH + +help record (no record) + + $ hg help record + record extension - commands to interactively select changes for commit/qrefresh + + use "hg help extensions" for information on enabling extensions + +help qrecord (no record) + + $ hg help qrecord + 'qrecord' is provided by the following extension: + + record commands to interactively select changes for commit/qrefresh + + use "hg help extensions" for information on enabling extensions + $ echo "[extensions]" >> $HGRCPATH $ echo "record=" >> $HGRCPATH +help record (record) + + $ hg help record + hg record [OPTION]... [FILE]... + + interactively select changes to commit + + If a list of files is omitted, all changes reported by "hg status" will be + candidates for recording. + + See "hg help dates" for a list of formats valid for -d/--date. + + You will be prompted for whether to record changes to each modified file, + and for files with multiple changes, for each change to use. For each + query, the following responses are possible: + + y - record this change + n - skip this change + + s - skip remaining changes to this file + f - record remaining changes to this file + + d - done, skip remaining changes and files + a - record all changes to all remaining files + q - quit, recording no changes + + ? - display help + + This command is not available when committing a merge. + + options: + + -A --addremove mark new/missing files as added/removed before + committing + --close-branch mark a branch as closed, hiding it from the branch + list + -I --include PATTERN [+] include names matching the given patterns + -X --exclude PATTERN [+] exclude names matching the given patterns + -m --message TEXT use text as commit message + -l --logfile FILE read commit message from file + -d --date DATE record the specified date as commit date + -u --user USER record the specified user as committer + + [+] marked option can be specified multiple times + + use "hg -v help record" to show global options + help (no mq, so no qrecord) $ hg help qrecord - hg: unknown command 'qrecord' - Mercurial Distributed SCM + hg qrecord [OPTION]... PATCH [FILE]... - basic commands: + interactively record a new patch + + See "hg help qnew" & "hg help record" for more information and usage. - add add the specified files on the next commit - annotate show changeset information by line for each file - clone make a copy of an existing repository - commit commit the specified files or all outstanding changes - diff diff repository (or selected files) - export dump the header and diffs for one or more changesets - forget forget the specified files on the next commit - init create a new repository in the given directory - log show revision history of entire repository or files - merge merge working directory with another revision - pull pull changes from the specified source - push push changes to the specified destination - remove remove the specified files on the next commit - serve start stand-alone webserver - status show changed files in the working directory - summary summarize working directory state - update update working directory (or switch revisions) + use "hg -v help qrecord" to show global options + + $ hg init a + +qrecord (mq not present) + + $ hg -R a qrecord + hg qrecord: invalid arguments + hg qrecord [OPTION]... PATCH [FILE]... - use "hg help" for the full list of commands or "hg -v" for details + interactively record a new patch + + use "hg help qrecord" to show the full help text + [255] + +qrecord patch (mq not present) + + $ hg -R a qrecord patch + abort: 'mq' extension not loaded [255] help (mq present) @@ -61,7 +126,6 @@ use "hg -v help qrecord" to show global options - $ hg init a $ cd a Base commit
--- a/tests/test-rebase-cache.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-rebase-cache.t Wed May 18 09:56:27 2011 +0200 @@ -85,9 +85,9 @@ o 0: 'A' $ hg branches - branch3 8:05b64c4ca2d8 - branch2 6:b410fbec727a - branch1 2:9d931918fcf7 (inactive) + branch3 8:4666b71e8e32 + branch2 6:5097051d331d + branch1 2:0a03079c47fd (inactive) default 0:1994f17a630e (inactive) $ hg theads @@ -101,9 +101,9 @@ saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob) $ hg branches - branch3 8:c1d4b9719987 - branch2 4:1be2b203ae5e - branch1 2:9d931918fcf7 + branch3 8:466cdfb14b62 + branch2 4:e4fdb121d036 + branch1 2:0a03079c47fd default 0:1994f17a630e (inactive) $ hg theads @@ -162,9 +162,9 @@ saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob) $ hg branches - branch2 8:e1e80ed73210 - branch3 7:75fd7b643dce - branch1 2:9d931918fcf7 (inactive) + branch2 8:6b4bdc1b5ac0 + branch3 7:653b9feb4616 + branch1 2:0a03079c47fd (inactive) default 0:1994f17a630e (inactive) $ hg theads @@ -226,8 +226,8 @@ saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob) $ hg branches - branch2 7:e1e80ed73210 - branch1 2:9d931918fcf7 (inactive) + branch2 7:6b4bdc1b5ac0 + branch1 2:0a03079c47fd (inactive) default 0:1994f17a630e (inactive) $ hg theads
--- a/tests/test-rebase-collapse.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-rebase-collapse.t Wed May 18 09:56:27 2011 +0200 @@ -11,45 +11,14 @@ $ hg init a $ cd a - - $ echo A > A - $ hg ci -Am A - adding A - $ echo B > B - $ hg ci -Am B - adding B - $ echo C > C - $ hg ci -Am C - adding C - $ echo D > D - $ hg ci -Am D - adding D - - $ hg up -q -C 0 - - $ echo E > E - $ hg ci -Am E - adding E - created new head - - $ hg up -q -C 0 - - $ echo F > F - $ hg ci -Am F - adding F - created new head - - $ hg merge -r 4 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - $ hg ci -m G - - $ hg up -q -C 5 - - $ echo H > H - $ hg ci -Am H - adding H - created new head + $ hg unbundle $TESTDIR/bundles/rebase.hg + adding changesets + adding manifests + adding file changes + added 8 changesets with 7 changes to 7 files (+2 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg up tip + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg tglog @ 7: 'H' @@ -137,6 +106,40 @@ $ cd .. +Rebasing G onto H with custom message: + + $ hg clone -q -u . a a3 + $ cd a3 + + $ hg rebase --base 6 -m 'custom message' + abort: message can only be specified with collapse + [255] + + $ hg rebase --base 6 --collapse -m 'custom message' + saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob) + + $ hg tglog + @ 6: 'custom message' + | + o 5: 'H' + | + o 4: 'F' + | + | o 3: 'D' + | | + | o 2: 'C' + | | + | o 1: 'B' + |/ + o 0: 'A' + + $ hg manifest + A + E + F + H + + $ cd .. Create repo b:
--- a/tests/test-rebase-detach.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-rebase-detach.t Wed May 18 09:56:27 2011 +0200 @@ -10,41 +10,32 @@ $ hg init a $ cd a - - $ echo A > A - $ hg ci -Am A - adding A - - $ echo B > B - $ hg ci -Am B - adding B - - $ echo C > C - $ hg ci -Am C - adding C - - $ echo D > D - $ hg ci -Am D - adding D - - $ hg up -q -C 0 - - $ echo E > E - $ hg ci -Am E - adding E - created new head + $ hg unbundle $TESTDIR/bundles/rebase.hg + adding changesets + adding manifests + adding file changes + added 8 changesets with 7 changes to 7 files (+2 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg up tip + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd .. -Rebasing D onto E detaching from C: +Rebasing D onto H detaching from C: $ hg clone -q -u . a a1 $ cd a1 $ hg tglog - @ 4: 'E' + @ 7: 'H' | + | o 6: 'G' + |/| + o | 5: 'F' + | | + | o 4: 'E' + |/ | o 3: 'D' | | | o 2: 'C' @@ -53,14 +44,20 @@ |/ o 0: 'A' - $ hg rebase --detach -s 3 -d 4 + $ hg rebase --detach -s 3 -d 7 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 4: 'D' + @ 7: 'D' + | + o 6: 'H' | - o 3: 'E' - | + | o 5: 'G' + |/| + o | 4: 'F' + | | + | o 3: 'E' + |/ | o 2: 'C' | | | o 1: 'B' @@ -70,19 +67,26 @@ $ hg manifest A D - E + F + H $ cd .. -Rebasing C onto E detaching from B: +Rebasing C onto H detaching from B: $ hg clone -q -u . a a2 $ cd a2 $ hg tglog - @ 4: 'E' + @ 7: 'H' | + | o 6: 'G' + |/| + o | 5: 'F' + | | + | o 4: 'E' + |/ | o 3: 'D' | | | o 2: 'C' @@ -91,16 +95,22 @@ |/ o 0: 'A' - $ hg rebase --detach -s 2 -d 4 + $ hg rebase --detach -s 2 -d 7 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 4: 'D' + @ 7: 'D' + | + o 6: 'C' + | + o 5: 'H' | - o 3: 'C' - | - o 2: 'E' - | + | o 4: 'G' + |/| + o | 3: 'F' + | | + | o 2: 'E' + |/ | o 1: 'B' |/ o 0: 'A' @@ -109,19 +119,26 @@ A C D - E + F + H $ cd .. -Rebasing B onto E using detach (same as not using it): +Rebasing B onto H using detach (same as not using it): $ hg clone -q -u . a a3 $ cd a3 $ hg tglog - @ 4: 'E' + @ 7: 'H' | + | o 6: 'G' + |/| + o | 5: 'F' + | | + | o 4: 'E' + |/ | o 3: 'D' | | | o 2: 'C' @@ -130,18 +147,24 @@ |/ o 0: 'A' - $ hg rebase --detach -s 1 -d 4 + $ hg rebase --detach -s 1 -d 7 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 4: 'D' + @ 7: 'D' | - o 3: 'C' + o 6: 'C' + | + o 5: 'B' | - o 2: 'B' + o 4: 'H' | - o 1: 'E' - | + | o 3: 'G' + |/| + o | 2: 'F' + | | + | o 1: 'E' + |/ o 0: 'A' $ hg manifest @@ -149,19 +172,26 @@ B C D - E + F + H $ cd .. -Rebasing C onto E detaching from B and collapsing: +Rebasing C onto H detaching from B and collapsing: $ hg clone -q -u . a a4 $ cd a4 $ hg tglog - @ 4: 'E' + @ 7: 'H' | + | o 6: 'G' + |/| + o | 5: 'F' + | | + | o 4: 'E' + |/ | o 3: 'D' | | | o 2: 'C' @@ -170,15 +200,21 @@ |/ o 0: 'A' - $ hg rebase --detach --collapse -s 2 -d 4 + $ hg rebase --detach --collapse -s 2 -d 7 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 3: 'Collapsed revision + @ 6: 'Collapsed revision | * C | * D' - o 2: 'E' + o 5: 'H' | + | o 4: 'G' + |/| + o | 3: 'F' + | | + | o 2: 'E' + |/ | o 1: 'B' |/ o 0: 'A' @@ -187,7 +223,8 @@ A C D - E + F + H $ cd .. @@ -204,10 +241,16 @@ created new head $ hg tglog - @ 5: 'extra branch' + @ 8: 'extra branch' - o 4: 'E' + o 7: 'H' | + | o 6: 'G' + |/| + o | 5: 'F' + | | + | o 4: 'E' + |/ | o 3: 'D' | | | o 2: 'C' @@ -220,15 +263,21 @@ saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 5: 'D' + @ 8: 'D' | - o 4: 'C' + o 7: 'C' + | + o 6: 'B' | - o 3: 'B' + o 5: 'extra branch' + + o 4: 'H' | - o 2: 'extra branch' - - o 1: 'E' - | + | o 3: 'G' + |/| + o | 2: 'F' + | | + | o 1: 'E' + |/ o 0: 'A'
--- a/tests/test-rebase-keep-branch.t Wed May 18 09:31:19 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ - $ cat >> $HGRCPATH <<EOF - > [extensions] - > graphlog= - > rebase= - > - > [alias] - > tglog = log -G --template "{rev}: '{desc}' {branches}\n" - > EOF - - - $ hg init a - $ cd a - - $ echo c1 > c1 - $ hg ci -Am c1 - adding c1 - - $ echo c2 > c2 - $ hg ci -Am c2 - adding c2 - - $ echo l1 > l1 - $ hg ci -Am l1 - adding l1 - - $ echo l2 > l2 - $ hg ci -Am l2 - adding l2 - - $ hg up -q -C 1 - - $ hg branch 'notdefault' - marked working directory as branch notdefault - - $ echo r1 > r1 - $ hg ci -Am r1 - adding r1 - - $ hg tglog - @ 4: 'r1' notdefault - | - | o 3: 'l2' - | | - | o 2: 'l1' - |/ - o 1: 'c2' - | - o 0: 'c1' - - -Rebase a branch while preserving the branch name: - - $ hg up -q -C 3 - - $ hg rebase -b 4 -d 3 --keepbranches - saved backup bundle to $TESTTMP/a/.hg/strip-backup/*-backup.hg (glob) - - $ hg tglog - @ 4: 'r1' notdefault - | - o 3: 'l2' - | - o 2: 'l1' - | - o 1: 'c2' - | - o 0: 'c1' - - $ hg branch - notdefault -
--- a/tests/test-rebase-mq.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-rebase-mq.t Wed May 18 09:56:27 2011 +0200 @@ -235,3 +235,73 @@ -mq1 +mq2 + +Rebase with guards + + $ hg init foo + $ cd foo + $ echo a > a + $ hg ci -Am a + adding a + +Create mq repo with guarded patches foo and bar: + + $ hg qinit + $ hg qnew foo + $ hg qguard foo +baz + $ echo foo > foo + $ hg qref + $ hg qpop + popping foo + patch queue now empty + + $ hg qnew bar + $ hg qguard bar +baz + $ echo bar > bar + $ hg qref + + $ hg qguard -l + bar: +baz + foo: +baz + + $ hg tglog + @ 1:* '[mq]: bar' tags: bar qbase qtip tip (glob) + | + o 0:* 'a' tags: qparent (glob) + +Create new head to rebase bar onto: + + $ hg up -C 0 + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo b > b + $ hg add b + $ hg ci -m b + created new head + $ hg up -C 1 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo a >> a + $ hg qref + + $ hg tglog + @ 2:* '[mq]: bar' tags: bar qbase qtip tip (glob) + | + | o 1:* 'b' tags: (glob) + |/ + o 0:* 'a' tags: qparent (glob) + + +Rebase bar: + + $ hg -q rebase -d 1 + + $ hg qguard -l + foo: +baz + bar: +baz + + $ hg tglog + @ 2:* '[mq]: bar' tags: bar qbase qtip tip (glob) + | + o 1:* 'b' tags: qparent (glob) + | + o 0:* 'a' tags: (glob) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rebase-named-branches.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,107 @@ + $ cat >> $HGRCPATH <<EOF + > [extensions] + > graphlog= + > rebase= + > + > [alias] + > tglog = log -G --template "{rev}: '{desc}' {branches}\n" + > EOF + + $ hg init a + $ cd a + $ hg unbundle $TESTDIR/bundles/rebase.hg + adding changesets + adding manifests + adding file changes + added 8 changesets with 7 changes to 7 files (+2 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg up tip + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd .. + + +Rebasing descendant onto ancestor across different named branches + + $ hg clone -q -u . a a1 + + $ cd a1 + + $ hg branch dev + marked working directory as branch dev + + $ echo x > x + + $ hg add x + + $ hg ci -m 'extra named branch' + + $ hg tglog + @ 8: 'extra named branch' dev + | + o 7: 'H' + | + | o 6: 'G' + |/| + o | 5: 'F' + | | + | o 4: 'E' + |/ + | o 3: 'D' + | | + | o 2: 'C' + | | + | o 1: 'B' + |/ + o 0: 'A' + + + + $ hg rebase -s 1 -d 8 --keepbranches + saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob) + + $ hg tglog + @ 8: 'D' + | + o 7: 'C' + | + o 6: 'B' + | + o 5: 'extra named branch' dev + | + o 4: 'H' + | + | o 3: 'G' + |/| + o | 2: 'F' + | | + | o 1: 'E' + |/ + o 0: 'A' + + $ hg rebase -s 4 -d 5 + abort: source is ancestor of destination + [255] + + $ hg rebase -s 5 -d 4 + saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob) + + $ hg tglog + @ 8: 'D' + | + o 7: 'C' + | + o 6: 'B' + | + o 5: 'extra named branch' + | + o 4: 'H' + | + | o 3: 'G' + |/| + o | 2: 'F' + | | + | o 1: 'E' + |/ + o 0: 'A' + + $ cd ..
--- a/tests/test-rebase-parameters.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-rebase-parameters.t Wed May 18 09:56:27 2011 +0200 @@ -10,60 +10,37 @@ $ hg init a $ cd a - - $ echo c1 > c1 - $ hg ci -Am c1 - adding c1 - - $ echo c2 > c2 - $ hg ci -Am c2 - adding c2 - - $ echo c3 > c3 - $ hg ci -Am c3 - adding c3 - - $ hg up -q -C 1 + $ hg unbundle $TESTDIR/bundles/rebase.hg + adding changesets + adding manifests + adding file changes + added 8 changesets with 7 changes to 7 files (+2 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg up tip + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ echo l1 > l1 - $ hg ci -Am l1 - adding l1 - created new head - - $ echo l2 > l2 - $ hg ci -Am l2 - adding l2 - - $ echo l3 > l3 - $ hg ci -Am l3 - adding l3 - - $ hg up -q -C 2 - - $ echo r1 > r1 - $ hg ci -Am r1 - adding r1 - - $ echo r2 > r2 - $ hg ci -Am r2 - adding r2 + $ echo I > I + $ hg ci -AmI + adding I $ hg tglog - @ 7: 'r2' + @ 8: 'I' | - o 6: 'r1' + o 7: 'H' | - | o 5: 'l3' - | | - | o 4: 'l2' + | o 6: 'G' + |/| + o | 5: 'F' | | - | o 3: 'l1' - | | - o | 2: 'c3' + | o 4: 'E' |/ - o 1: 'c2' - | - o 0: 'c1' + | o 3: 'D' + | | + | o 2: 'C' + | | + | o 1: 'B' + |/ + o 0: 'A' $ cd .. @@ -73,6 +50,10 @@ $ hg clone -q -u . a a1 $ cd a1 + $ hg rebase -s 8 -d 7 + abort: source is descendant of destination + [255] + $ hg rebase --continue --abort abort: cannot use both abort and continue [255] @@ -93,7 +74,7 @@ nothing to rebase [1] - $ hg up -q 6 + $ hg up -q 7 $ hg rebase nothing to rebase @@ -102,29 +83,31 @@ These work: -Rebase with no arguments (from 3 onto 7): +Rebase with no arguments (from 3 onto 8): - $ hg up -q -C 5 + $ hg up -q -C 3 $ hg rebase saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 7: 'l3' + @ 8: 'D' | - o 6: 'l2' + o 7: 'C' | - o 5: 'l1' + o 6: 'B' | - o 4: 'r2' + o 5: 'I' | - o 3: 'r1' - | - o 2: 'c3' + o 4: 'H' | - o 1: 'c2' - | - o 0: 'c1' + | o 3: 'G' + |/| + o | 2: 'F' + | | + | o 1: 'E' + |/ + o 0: 'A' Try to rollback after a rebase (fail): @@ -135,198 +118,272 @@ $ cd .. -Rebase with base == '.' => same as no arguments (from 3 onto 7): +Rebase with base == '.' => same as no arguments (from 3 onto 8): - $ hg clone -q -u 5 a a2 + $ hg clone -q -u 3 a a2 $ cd a2 $ hg rebase --base . saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 7: 'l3' + @ 8: 'D' | - o 6: 'l2' + o 7: 'C' | - o 5: 'l1' + o 6: 'B' | - o 4: 'r2' + o 5: 'I' | - o 3: 'r1' - | - o 2: 'c3' + o 4: 'H' | - o 1: 'c2' - | - o 0: 'c1' + | o 3: 'G' + |/| + o | 2: 'F' + | | + | o 1: 'E' + |/ + o 0: 'A' $ cd .. -Rebase with dest == `hg branch` => same as no arguments (from 3 onto 7): +Rebase with dest == `hg branch` => same as no arguments (from 3 onto 8): - $ hg clone -q -u 5 a a3 + $ hg clone -q -u 3 a a3 $ cd a3 $ hg rebase --dest `hg branch` saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 7: 'l3' + @ 8: 'D' | - o 6: 'l2' + o 7: 'C' | - o 5: 'l1' + o 6: 'B' | - o 4: 'r2' + o 5: 'I' | - o 3: 'r1' - | - o 2: 'c3' + o 4: 'H' | - o 1: 'c2' - | - o 0: 'c1' + | o 3: 'G' + |/| + o | 2: 'F' + | | + | o 1: 'E' + |/ + o 0: 'A' $ cd .. -Specify only source (from 4 onto 7): +Specify only source (from 2 onto 8): $ hg clone -q -u . a a4 $ cd a4 - $ hg rebase --source 4 + $ hg rebase --source 2 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 7: 'l3' + @ 8: 'D' | - o 6: 'l2' + o 7: 'C' |\ - | o 5: 'r2' + | o 6: 'I' | | - | o 4: 'r1' + | o 5: 'H' | | - o | 3: 'l1' - | | - | o 2: 'c3' + | | o 4: 'G' + | |/| + | o | 3: 'F' + | | | + | | o 2: 'E' + | |/ + o | 1: 'B' |/ - o 1: 'c2' - | - o 0: 'c1' + o 0: 'A' $ cd .. Specify only dest (from 3 onto 6): - $ hg clone -q -u 5 a a5 + $ hg clone -q -u 3 a a5 $ cd a5 $ hg rebase --dest 6 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 7: 'l3' + @ 8: 'D' | - o 6: 'l2' + o 7: 'C' + | + o 6: 'B' | - o 5: 'l1' - | - | o 4: 'r2' + | o 5: 'I' + | | + | o 4: 'H' + | | + o | 3: 'G' + |\| + | o 2: 'F' + | | + o | 1: 'E' |/ - o 3: 'r1' - | - o 2: 'c3' - | - o 1: 'c2' - | - o 0: 'c1' + o 0: 'A' $ cd .. -Specify only base (from 3 onto 7): +Specify only base (from 1 onto 8): $ hg clone -q -u . a a6 $ cd a6 - $ hg rebase --base 5 + $ hg rebase --base 3 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 7: 'l3' + @ 8: 'D' | - o 6: 'l2' + o 7: 'C' | - o 5: 'l1' + o 6: 'B' | - o 4: 'r2' + o 5: 'I' + | + o 4: 'H' | - o 3: 'r1' - | - o 2: 'c3' + | o 3: 'G' + |/| + o | 2: 'F' + | | + | o 1: 'E' + |/ + o 0: 'A' + + $ cd .. + + +Specify source and dest (from 2 onto 7): + + $ hg clone -q -u . a a7 + $ cd a7 + + $ hg rebase --detach --source 2 --dest 7 + saved backup bundle to $TESTTMP/a7/.hg/strip-backup/*-backup.hg (glob) + + $ hg tglog + @ 8: 'D' | - o 1: 'c2' + o 7: 'C' + | + | o 6: 'I' + |/ + o 5: 'H' | - o 0: 'c1' + | o 4: 'G' + |/| + o | 3: 'F' + | | + | o 2: 'E' + |/ + | o 1: 'B' + |/ + o 0: 'A' $ cd .. -Specify source and dest (from 4 onto 6): - - $ hg clone -q -u . a a7 - $ cd a7 - - $ hg rebase --source 4 --dest 6 - saved backup bundle to $TESTTMP/a7/.hg/strip-backup/*-backup.hg (glob) - - $ hg tglog - @ 7: 'l3' - | - o 6: 'l2' - |\ - | | o 5: 'r2' - | |/ - | o 4: 'r1' - | | - o | 3: 'l1' - | | - | o 2: 'c3' - |/ - o 1: 'c2' - | - o 0: 'c1' - - $ cd .. - - -Specify base and dest (from 3 onto 6): +Specify base and dest (from 1 onto 7): $ hg clone -q -u . a a8 $ cd a8 - $ hg rebase --base 4 --dest 6 + $ hg rebase --base 3 --dest 7 saved backup bundle to $TESTTMP/a8/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 7: 'l3' + @ 8: 'D' | - o 6: 'l2' + o 7: 'C' | - o 5: 'l1' + o 6: 'B' | - | o 4: 'r2' + | o 5: 'I' |/ - o 3: 'r1' - | - o 2: 'c3' + o 4: 'H' | - o 1: 'c2' - | - o 0: 'c1' + | o 3: 'G' + |/| + o | 2: 'F' + | | + | o 1: 'E' + |/ + o 0: 'A' $ cd .. +Test --tool parameter: + + $ hg init b + $ cd b + + $ echo c1 > c1 + $ hg ci -Am c1 + adding c1 + + $ echo c2 > c2 + $ hg ci -Am c2 + adding c2 + + $ hg up -q 0 + $ echo c2b > c2 + $ hg ci -Am c2b + adding c2 + created new head + + $ cd .. + + $ hg clone -q -u . b b1 + $ cd b1 + + $ hg rebase -s 2 -d 1 --tool internal:local + saved backup bundle to $TESTTMP/b1/.hg/strip-backup/*-backup.hg (glob) + + $ hg cat c2 + c2 + + $ cd .. + + + $ hg clone -q -u . b b2 + $ cd b2 + + $ hg rebase -s 2 -d 1 --tool internal:other + saved backup bundle to $TESTTMP/b2/.hg/strip-backup/*-backup.hg (glob) + + $ hg cat c2 + c2b + + $ cd .. + + + $ hg clone -q -u . b b3 + $ cd b3 + + $ hg rebase -s 2 -d 1 --tool internal:fail + abort: unresolved conflicts (see hg resolve, then hg rebase --continue) + [255] + + $ hg resolve -l + U c2 + + $ hg resolve -m c2 + $ hg rebase -c --tool internal:fail + tool option will be ignored + saved backup bundle to $TESTTMP/b3/.hg/strip-backup/*-backup.hg (glob) +
--- a/tests/test-rebase-scenario-global.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-rebase-scenario-global.t Wed May 18 09:56:27 2011 +0200 @@ -10,122 +10,111 @@ $ hg init a $ cd a - - $ echo A > A - $ hg ci -Am A - adding A - - $ echo B > B - $ hg ci -Am B - adding B - - $ hg up -q -C 0 - - $ echo C > C - $ hg ci -Am C - adding C - created new head - - $ hg up -q -C 0 - - $ echo D > D - $ hg ci -Am D - adding D - created new head - - $ hg merge -r 2 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - (branch merge, don't forget to commit) - - $ hg ci -m E - - $ hg up -q -C 3 - - $ echo F > F - $ hg ci -Am F - adding F - created new head - + $ hg unbundle $TESTDIR/bundles/rebase.hg + adding changesets + adding manifests + adding file changes + added 8 changesets with 7 changes to 7 files (+2 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg up tip + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd .. Rebasing -B onto F - simple rebase: +D onto H - simple rebase: $ hg clone -q -u . a a1 $ cd a1 $ hg tglog - @ 5: 'F' + @ 7: 'H' | + | o 6: 'G' + |/| + o | 5: 'F' + | | | o 4: 'E' - |/| - o | 3: 'D' + |/ + | o 3: 'D' | | | o 2: 'C' - |/ + | | | o 1: 'B' |/ o 0: 'A' - $ hg rebase -s 1 -d 5 + + $ hg rebase -s 3 -d 7 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 5: 'B' - | - o 4: 'F' - | - | o 3: 'E' - |/| - o | 2: 'D' + @ 7: 'D' + |\ + | o 6: 'H' | | - | o 1: 'C' + | | o 5: 'G' + | |/| + | o | 4: 'F' + | | | + | | o 3: 'E' + | |/ + o | 2: 'C' + | | + o | 1: 'B' |/ o 0: 'A' $ cd .. -B onto D - intermediate point: +D onto F - intermediate point: $ hg clone -q -u . a a2 $ cd a2 - $ hg rebase -s 1 -d 3 + $ hg rebase -s 3 -d 5 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 5: 'B' - | - | o 4: 'F' - |/ - | o 3: 'E' - |/| - o | 2: 'D' + @ 7: 'D' + |\ + | | o 6: 'H' + | |/ + | | o 5: 'G' + | |/| + | o | 4: 'F' + | | | + | | o 3: 'E' + | |/ + o | 2: 'C' | | - | o 1: 'C' + o | 1: 'B' |/ o 0: 'A' $ cd .. -C onto F - skip of E: +E onto H - skip of G: $ hg clone -q -u . a a3 $ cd a3 - $ hg rebase -s 2 -d 5 + $ hg rebase -s 4 -d 7 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 4: 'C' + @ 6: 'E' + | + o 5: 'H' | - o 3: 'F' + o 4: 'F' | - o 2: 'D' - | + | o 3: 'D' + | | + | o 2: 'C' + | | | o 1: 'B' |/ o 0: 'A' @@ -133,21 +122,25 @@ $ cd .. -D onto C - rebase of a branching point (skip E): +F onto E - rebase of a branching point (skip G): $ hg clone -q -u . a a4 $ cd a4 - $ hg rebase -s 3 -d 2 + $ hg rebase -s 5 -d 4 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 4: 'F' + @ 6: 'H' + | + o 5: 'F' | - o 3: 'D' + o 4: 'E' | - o 2: 'C' - | + | o 3: 'D' + | | + | o 2: 'C' + | | | o 1: 'B' |/ o 0: 'A' @@ -155,23 +148,27 @@ $ cd .. -E onto F - merged revision having a parent in ancestors of target: +G onto H - merged revision having a parent in ancestors of target: $ hg clone -q -u . a a5 $ cd a5 - $ hg rebase -s 4 -d 5 + $ hg rebase -s 6 -d 7 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 5: 'E' + @ 7: 'G' |\ - | o 4: 'F' + | o 6: 'H' | | + | o 5: 'F' + | | + o | 4: 'E' + |/ | o 3: 'D' | | - o | 2: 'C' - |/ + | o 2: 'C' + | | | o 1: 'B' |/ o 0: 'A' @@ -179,22 +176,26 @@ $ cd .. -D onto B - E maintains C as parent: +F onto B - G maintains E as parent: $ hg clone -q -u . a a6 $ cd a6 - $ hg rebase -s 3 -d 1 + $ hg rebase -s 5 -d 1 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/*-backup.hg (glob) $ hg tglog - @ 5: 'F' + @ 7: 'H' | - | o 4: 'E' + | o 6: 'G' |/| - o | 3: 'D' + o | 5: 'F' + | | + | o 4: 'E' | | - | o 2: 'C' + | | o 3: 'D' + | | | + +---o 2: 'C' | | o | 1: 'B' |/ @@ -205,45 +206,45 @@ These will fail (using --source): -E onto D - rebase onto an ancestor: +G onto F - rebase onto an ancestor: $ hg clone -q -u . a a7 $ cd a7 - $ hg rebase -s 4 -d 3 + $ hg rebase -s 6 -d 5 abort: source is descendant of destination [255] -D onto E - rebase onto a descendant: +F onto G - rebase onto a descendant: - $ hg rebase -s 3 -d 4 + $ hg rebase -s 5 -d 6 abort: source is ancestor of destination [255] -E onto B - merge revision with both parents not in ancestors of target: +G onto B - merge revision with both parents not in ancestors of target: - $ hg rebase -s 4 -d 1 - abort: cannot use revision 4 as base, result would have 3 parents + $ hg rebase -s 6 -d 1 + abort: cannot use revision 6 as base, result would have 3 parents [255] These will abort gracefully (using --base): -E onto E - rebase onto same changeset: +G onto G - rebase onto same changeset: - $ hg rebase -b 4 -d 4 + $ hg rebase -b 6 -d 6 nothing to rebase [1] -E onto D - rebase onto an ancestor: +G onto F - rebase onto an ancestor: - $ hg rebase -b 4 -d 3 + $ hg rebase -b 6 -d 5 nothing to rebase [1] -D onto E - rebase onto a descendant: +F onto G - rebase onto a descendant: - $ hg rebase -b 3 -d 4 + $ hg rebase -b 5 -d 6 nothing to rebase [1]
--- a/tests/test-relink.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-relink.t Wed May 18 09:56:27 2011 +0200 @@ -20,23 +20,29 @@ $ hg init repo $ cd repo - $ echo '[ui]' > .hg/hgrc - $ echo 'username= A. Foo <a.foo@bar.com>' >> .hg/hgrc $ echo a > a $ echo b > b $ hg ci -Am addfile adding a adding b - $ echo a >> a - $ echo a >> b + $ cat $TESTDIR/binfile.bin >> a + $ cat $TESTDIR/binfile.bin >> b $ hg ci -Am changefiles +make another commit to create files larger than 1 KB to test +formatting of final byte count + + $ cat $TESTDIR/binfile.bin >> a + $ cat $TESTDIR/binfile.bin >> b + $ hg ci -m anotherchange + don't sit forever trying to double-lock the source repo $ hg relink . relinking $TESTTMP/repo/.hg/store to $TESTTMP/repo/.hg/store there is nothing to relink + Test files are read in binary mode $ python -c "file('.hg/store/data/dummy.i', 'wb').write('a\r\nb\n')" @@ -53,8 +59,6 @@ updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd clone - $ echo '[ui]' >> .hg/hgrc - $ echo 'username= A. Baz <a.baz@bar.com>' >> .hg/hgrc $ hg pull -q $ echo b >> b $ hg ci -m changeb @@ -81,7 +85,7 @@ pruned down to 2 probably relinkable files relinking: data/a.i 1/2 files (50.00%) not linkable: data/dummy.i - relinked 1 files (136 bytes reclaimed) + relinked 1 files (1.37 KB reclaimed) $ cd ..
--- a/tests/test-rename-dir-merge.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-rename-dir-merge.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,5 @@ - $ mkdir t + $ hg init t $ cd t - $ hg init $ mkdir a $ echo foo > a/a @@ -118,9 +117,8 @@ Second scenario with two repos: $ cd .. - $ mkdir r1 + $ hg init r1 $ cd r1 - $ hg init $ mkdir a $ echo foo > a/f $ hg add a
--- a/tests/test-rename-merge1.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-rename-merge1.t Wed May 18 09:56:27 2011 +0200 @@ -67,7 +67,7 @@ $ hg ci -m "merge" - $ hg debugindex .hg/store/data/b.i + $ hg debugindex b rev offset length base linkrev nodeid p1 p2 0 0 67 0 1 57eacc201a7f 000000000000 000000000000 1 67 72 1 3 4727ba907962 000000000000 57eacc201a7f @@ -131,27 +131,27 @@ $ hg init repo2089 $ cd repo2089 - $ echo 0 > A - $ hg -q ci -Am 0 + $ echo c0 > f1 + $ hg ci -Aqm0 - $ hg -q up -C null - $ echo 1 > A - $ hg -q ci -Am 1 + $ hg up null -q + $ echo c1 > f1 + $ hg ci -Aqm1 - $ hg -q up -C 0 + $ hg up 0 -q $ hg merge 1 -q --tool internal:local - $ echo 2 > A - $ hg -q ci -m 2 + $ echo c2 > f1 + $ hg ci -qm2 - $ hg -q up -C 1 - $ hg mv A a - $ hg -q ci -Am 3 + $ hg up 1 -q + $ hg mv f1 f2 + $ hg ci -Aqm3 - $ hg -q up -C 2 + $ hg up 2 -q $ hg merge 3 - merging A and a to a + merging f1 and f2 to f2 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ cat a - 2 + $ cat f2 + c2
--- a/tests/test-rename-merge2.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-rename-merge2.t Wed May 18 09:56:27 2011 +0200 @@ -17,9 +17,8 @@ $ tm() > { - > mkdir t + > hg init t > cd t - > hg init > echo "[merge]" >> .hg/hgrc > echo "followcopies = 1" >> .hg/hgrc >
--- a/tests/test-rename.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-rename.t Wed May 18 09:56:27 2011 +0200 @@ -11,6 +11,9 @@ rename a single file $ hg rename d1/d11/a1 d2/c + $ hg --config ui.portablefilenames=abort rename d1/a d1/con.xml + abort: filename contains 'con', which is reserved on Windows: 'd1/con.xml' + [255] $ hg sum parent: 0:9b4b6e7b2c26 tip 1
--- a/tests/test-repair-strip Wed May 18 09:31:19 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -#!/bin/sh - -echo "[extensions]" >> $HGRCPATH -echo "mq=">> $HGRCPATH - -teststrip() { - hg -q up -C $1 - echo % before update $1, strip $2 - hg parents - chmod -$3 $4 - hg strip $2 2>&1 | sed 's/\(bundle\).*/\1/' | sed 's/Permission denied.*\.hg\/store\/\(.*\)/Permission denied \.hg\/store\/\1/' - echo % after update $1, strip $2 - chmod +$3 $4 - hg verify - echo % journal contents - if [ -f .hg/store/journal ]; then - sed -e 's/\.i[^\n]*/\.i/' .hg/store/journal - else - echo "(no journal)" - fi - ls .hg/store/journal >/dev/null 2>&1 && hg recover - ls .hg/strip-backup/* >/dev/null 2>&1 && hg unbundle -q .hg/strip-backup/* - rm -rf .hg/strip-backup -} - -hg init test -cd test - -echo a > a -hg -q ci -m "a" -A - -echo b > b -hg -q ci -m "b" -A - -echo b2 >> b -hg -q ci -m "b2" -A - -echo c > c -hg -q ci -m "c" -A - -teststrip 0 2 w .hg/store/data/b.i -teststrip 0 2 r .hg/store/data/b.i -teststrip 0 2 w .hg/store/00manifest.i -
--- a/tests/test-repair-strip.out Wed May 18 09:31:19 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -% before update 0, strip 2 -changeset: 0:cb9a9f314b8b -user: test -date: Thu Jan 01 00:00:00 1970 +0000 -summary: a - -saved backup bundle -transaction abort! -failed to truncate data/b.i -rollback failed - please run hg recover -strip failed, full bundle -abort: Permission denied .hg/store/data/b.i -% after update 0, strip 2 -abandoned transaction found - run hg recover -checking changesets -checking manifests -crosschecking files in changesets and manifests -checking files - b@?: rev 1 points to nonexistent changeset 2 - (expected 1) - b@?: 736c29771fba not in manifests -warning: orphan revlog 'data/c.i' -2 files, 2 changesets, 3 total revisions -2 warnings encountered! -2 integrity errors encountered! -% journal contents -00changelog.i -00manifest.i -data/b.i -data/c.i -rolling back interrupted transaction -checking changesets -checking manifests -crosschecking files in changesets and manifests -checking files -2 files, 2 changesets, 2 total revisions -% before update 0, strip 2 -changeset: 0:cb9a9f314b8b -user: test -date: Thu Jan 01 00:00:00 1970 +0000 -summary: a - -abort: Permission denied .hg/store/data/b.i -% after update 0, strip 2 -checking changesets -checking manifests -crosschecking files in changesets and manifests -checking files -3 files, 4 changesets, 4 total revisions -% journal contents -(no journal) -% before update 0, strip 2 -changeset: 0:cb9a9f314b8b -user: test -date: Thu Jan 01 00:00:00 1970 +0000 -summary: a - -saved backup bundle -transaction abort! -failed to truncate 00manifest.i -rollback failed - please run hg recover -strip failed, full bundle -abort: Permission denied .hg/store/00manifest.i -% after update 0, strip 2 -abandoned transaction found - run hg recover -checking changesets -checking manifests - manifest@?: rev 2 points to nonexistent changeset 2 - manifest@?: 3362547cdf64 not in changesets - manifest@?: rev 3 points to nonexistent changeset 3 - manifest@?: 265a85892ecb not in changesets -crosschecking files in changesets and manifests - c@3: in manifest but not in changeset -checking files - b@?: rev 1 points to nonexistent changeset 2 - (expected 1) - c@?: rev 0 points to nonexistent changeset 3 -3 files, 2 changesets, 4 total revisions -1 warnings encountered! -7 integrity errors encountered! -(first damaged changeset appears to be 3) -% journal contents -00changelog.i -00manifest.i -data/b.i -data/c.i -rolling back interrupted transaction -checking changesets -checking manifests -crosschecking files in changesets and manifests -checking files -2 files, 2 changesets, 2 total revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-repair-strip.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,130 @@ + + $ echo "[extensions]" >> $HGRCPATH + $ echo "mq=">> $HGRCPATH + + $ teststrip() { + > hg -q up -C $1 + > echo % before update $1, strip $2 + > hg parents + > chmod -$3 $4 + > hg strip $2 2>&1 | sed 's/\(bundle\).*/\1/' | sed 's/Permission denied.*\.hg\/store\/\(.*\)/Permission denied \.hg\/store\/\1/' + > echo % after update $1, strip $2 + > chmod +$3 $4 + > hg verify + > echo % journal contents + > if [ -f .hg/store/journal ]; then + > sed -e 's/\.i[^\n]*/\.i/' .hg/store/journal + > else + > echo "(no journal)" + > fi + > ls .hg/store/journal >/dev/null 2>&1 && hg recover + > ls .hg/strip-backup/* >/dev/null 2>&1 && hg unbundle -q .hg/strip-backup/* + > rm -rf .hg/strip-backup + > } + + $ hg init test + $ cd test + $ echo a > a + $ hg -q ci -m "a" -A + $ echo b > b + $ hg -q ci -m "b" -A + $ echo b2 >> b + $ hg -q ci -m "b2" -A + $ echo c > c + $ hg -q ci -m "c" -A + $ teststrip 0 2 w .hg/store/data/b.i + % before update 0, strip 2 + changeset: 0:cb9a9f314b8b + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: a + + saved backup bundle + transaction abort! + failed to truncate data/b.i + rollback failed - please run hg recover + strip failed, full bundle + abort: Permission denied .hg/store/data/b.i + % after update 0, strip 2 + abandoned transaction found - run hg recover + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + b@?: rev 1 points to nonexistent changeset 2 + (expected 1) + b@?: 736c29771fba not in manifests + warning: orphan revlog 'data/c.i' + 2 files, 2 changesets, 3 total revisions + 2 warnings encountered! + 2 integrity errors encountered! + % journal contents + 00changelog.i + 00manifest.i + data/b.i + data/c.i + rolling back interrupted transaction + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 2 files, 2 changesets, 2 total revisions + $ teststrip 0 2 r .hg/store/data/b.i + % before update 0, strip 2 + changeset: 0:cb9a9f314b8b + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: a + + abort: Permission denied .hg/store/data/b.i + % after update 0, strip 2 + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 3 files, 4 changesets, 4 total revisions + % journal contents + (no journal) + $ teststrip 0 2 w .hg/store/00manifest.i + % before update 0, strip 2 + changeset: 0:cb9a9f314b8b + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: a + + saved backup bundle + transaction abort! + failed to truncate 00manifest.i + rollback failed - please run hg recover + strip failed, full bundle + abort: Permission denied .hg/store/00manifest.i + % after update 0, strip 2 + abandoned transaction found - run hg recover + checking changesets + checking manifests + manifest@?: rev 2 points to nonexistent changeset 2 + manifest@?: 3362547cdf64 not in changesets + manifest@?: rev 3 points to nonexistent changeset 3 + manifest@?: 265a85892ecb not in changesets + crosschecking files in changesets and manifests + c@3: in manifest but not in changeset + checking files + b@?: rev 1 points to nonexistent changeset 2 + (expected 1) + c@?: rev 0 points to nonexistent changeset 3 + 3 files, 2 changesets, 4 total revisions + 1 warnings encountered! + 7 integrity errors encountered! + (first damaged changeset appears to be 3) + % journal contents + 00changelog.i + 00manifest.i + data/b.i + data/c.i + rolling back interrupted transaction + checking changesets + checking manifests + crosschecking files in changesets and manifests + checking files + 2 files, 2 changesets, 2 total revisions +
--- a/tests/test-requires.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-requires.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,5 @@ - $ mkdir t + $ hg init t $ cd t - $ hg init $ echo a > a $ hg add a $ hg commit -m test
--- a/tests/test-revlog-packentry.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-revlog-packentry.t Wed May 18 09:56:27 2011 +0200 @@ -15,7 +15,7 @@ adding foo created new head - $ hg debugindex .hg/store/data/foo.i + $ hg debugindex foo rev offset length base linkrev nodeid p1 p2 0 0 0 0 0 b80de5d13875 000000000000 000000000000 1 0 24 0 1 0376abec49b8 000000000000 000000000000
--- a/tests/test-revset-outgoing.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-revset-outgoing.t Wed May 18 09:56:27 2011 +0200 @@ -39,7 +39,7 @@ $ cd b $ cat .hg/hgrc [paths] - default = */a#stable (glob) + default = $TESTTMP/a#stable $ echo red >> a $ hg ci -qm3 @@ -60,7 +60,7 @@ $ hg tout - comparing with */a (glob) + comparing with $TESTTMP/a searching for changes 2:1d4099801a4e: '3' stable @@ -79,11 +79,11 @@ $ cat .hg/hgrc [paths] - default = */a#stable (glob) + default = $TESTTMP/a#stable green = ../a#default $ hg tout green - comparing with */a (glob) + comparing with $TESTTMP/a searching for changes 3:f0461977a3db: '4'
--- a/tests/test-revset.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-revset.t Wed May 18 09:56:27 2011 +0200 @@ -2,7 +2,7 @@ $ export HGENCODING $ try() { - > hg debugrevspec --debug $@ + > hg debugrevspec --debug "$@" > } $ log() { @@ -297,6 +297,12 @@ 6 $ log 'tag(tip)' 9 + $ log 'tag(unknown)' + abort: tag 'unknown' does not exist + [255] + $ log 'branch(unknown)' + abort: unknown revision 'unknown'! + [255] $ log 'user(bob)' 2 @@ -356,3 +362,84 @@ 9 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(é)))' 4 + +issue2654: report a parse error if the revset was not completely parsed + + $ log '1 OR 2' + hg: parse error at 2: invalid token + [255] + +or operator should preserve ordering: + $ log 'reverse(2::4) or tip' + 4 + 2 + 9 + +parentrevspec + + $ log 'merge()^0' + 6 + $ log 'merge()^' + 5 + $ log 'merge()^1' + 5 + $ log 'merge()^2' + 4 + $ log 'merge()^^' + 3 + $ log 'merge()^1^' + 3 + $ log 'merge()^^^' + 1 + + $ log 'merge()~0' + 6 + $ log 'merge()~1' + 5 + $ log 'merge()~2' + 3 + $ log 'merge()~2^1' + 1 + $ log 'merge()~3' + 1 + + $ log '(-3:tip)^' + 4 + 6 + 8 + + $ log 'tip^foo' + hg: parse error: ^ expects a number 0, 1, or 2 + [255] + +aliases: + + $ echo '[revsetalias]' >> .hg/hgrc + $ echo 'm = merge()' >> .hg/hgrc + $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc + $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc + + $ try m + ('symbol', 'm') + ('func', ('symbol', 'merge'), None) + 6 + $ try 'd(2:5)' + ('func', ('symbol', 'd'), ('range', ('symbol', '2'), ('symbol', '5'))) + ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('range', ('symbol', '2'), ('symbol', '5')), ('symbol', 'date')))) + 4 + 5 + 3 + 2 + $ try 'rs(2 or 3, date)' + ('func', ('symbol', 'rs'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date'))) + ('func', ('symbol', 'reverse'), ('func', ('symbol', 'sort'), ('list', ('or', ('symbol', '2'), ('symbol', '3')), ('symbol', 'date')))) + 3 + 2 + +issue2549 - correct optimizations + + $ log 'limit(1 or 2 or 3, 2) and not 2' + 1 + $ log 'max(1 or 2) and not 2' + $ log 'min(1 or 2) and not 1' + $ log 'last(1 or 2, 1) and not 2'
--- a/tests/test-rollback.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-rollback.t Wed May 18 09:56:27 2011 +0200 @@ -1,7 +1,6 @@ - $ mkdir t + $ hg init t $ cd t - $ hg init $ echo a > a $ hg add a $ hg commit -m "test"
--- a/tests/test-schemes.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-schemes.t Wed May 18 09:56:27 2011 +0200 @@ -25,10 +25,12 @@ $ hg incoming --debug parts://localhost using http://localhost:$HGPORT/ - sending between command - comparing with parts://localhost + sending capabilities command + comparing with parts://localhost/ + query 1; heads sending heads command searching for changes + all remote heads known locally no changes found [1]
--- a/tests/test-serve Wed May 18 09:31:19 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -#!/bin/sh - -hgserve() -{ - hg serve -a localhost -d --pid-file=hg.pid -E errors.log -v $@ \ - | sed -e "s/:$HGPORT1\\([^0-9]\\)/:HGPORT1\1/g" \ - -e "s/:$HGPORT2\\([^0-9]\\)/:HGPORT2\1/g" \ - -e 's/http:\/\/[^/]*\//http:\/\/localhost\//' - cat hg.pid >> "$DAEMON_PIDS" - echo % errors - cat errors.log - sleep 1 - if [ "$KILLQUIETLY" = "Y" ]; then - kill `cat hg.pid` 2>/dev/null - else - kill `cat hg.pid` - fi - sleep 1 -} - -hg init test -cd test - -echo '[web]' > .hg/hgrc -echo 'accesslog = access.log' >> .hg/hgrc -echo "port = $HGPORT1" >> .hg/hgrc - -echo % Without -v -hg serve -a localhost -p $HGPORT -d --pid-file=hg.pid -E errors.log -cat hg.pid >> "$DAEMON_PIDS" -if [ -f access.log ]; then - echo 'access log created - .hg/hgrc respected' -fi -echo % errors -cat errors.log - -echo % With -v -hgserve - -echo % With -v and -p HGPORT2 -hgserve -p "$HGPORT2" - -echo '% With -v and -p daytime (should fail because low port)' -KILLQUIETLY=Y -hgserve -p daytime -KILLQUIETLY=N - -echo % With --prefix foo -hgserve --prefix foo - -echo % With --prefix /foo -hgserve --prefix /foo - -echo % With --prefix foo/ -hgserve --prefix foo/ - -echo % With --prefix /foo/ -hgserve --prefix /foo/
--- a/tests/test-serve.out Wed May 18 09:31:19 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -% Without -v -access log created - .hg/hgrc respected -% errors -% With -v -listening at http://localhost/ (bound to 127.0.0.1:HGPORT1) -% errors -% With -v and -p HGPORT2 -listening at http://localhost/ (bound to 127.0.0.1:HGPORT2) -% errors -% With -v and -p daytime (should fail because low port) -abort: cannot start server at 'localhost:13': Permission denied -abort: child process failed to start -% errors -% With --prefix foo -listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1) -% errors -% With --prefix /foo -listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1) -% errors -% With --prefix foo/ -listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1) -% errors -% With --prefix /foo/ -listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1) -% errors
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-serve.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,82 @@ + + $ hgserve() + > { + > hg serve -a localhost -d --pid-file=hg.pid -E errors.log -v $@ \ + > | sed -e "s/:$HGPORT1\\([^0-9]\\)/:HGPORT1\1/g" \ + > -e "s/:$HGPORT2\\([^0-9]\\)/:HGPORT2\1/g" \ + > -e 's/http:\/\/[^/]*\//http:\/\/localhost\//' + > cat hg.pid >> "$DAEMON_PIDS" + > echo % errors + > cat errors.log + > sleep 1 + > if [ "$KILLQUIETLY" = "Y" ]; then + > kill `cat hg.pid` 2>/dev/null + > else + > kill `cat hg.pid` + > fi + > sleep 1 + > } + + $ hg init test + $ cd test + $ echo '[web]' > .hg/hgrc + $ echo 'accesslog = access.log' >> .hg/hgrc + $ echo "port = $HGPORT1" >> .hg/hgrc + +Without -v + + $ hg serve -a localhost -p $HGPORT -d --pid-file=hg.pid -E errors.log + $ cat hg.pid >> "$DAEMON_PIDS" + $ if [ -f access.log ]; then + $ echo 'access log created - .hg/hgrc respected' + access log created - .hg/hgrc respected + $ fi + +errors + + $ cat errors.log + +With -v + + $ hgserve + listening at http://localhost/ (bound to 127.0.0.1:HGPORT1) + % errors + +With -v and -p HGPORT2 + + $ hgserve -p "$HGPORT2" + listening at http://localhost/ (bound to 127.0.0.1:HGPORT2) + % errors + +With -v and -p daytime (should fail because low port) + + $ KILLQUIETLY=Y + $ hgserve -p daytime + abort: cannot start server at 'localhost:13': Permission denied + abort: child process failed to start + % errors + $ KILLQUIETLY=N + +With --prefix foo + + $ hgserve --prefix foo + listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1) + % errors + +With --prefix /foo + + $ hgserve --prefix /foo + listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1) + % errors + +With --prefix foo/ + + $ hgserve --prefix foo/ + listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1) + % errors + +With --prefix /foo/ + + $ hgserve --prefix /foo/ + listening at http://localhost/foo/ (bound to 127.0.0.1:HGPORT1) + % errors
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-setdiscovery.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,271 @@ + +Function to test discovery between two repos in both directions, using both the local shortcut +(which is currently not activated by default) and the full remotable protocol: + + $ testdesc() { # revs_a, revs_b, dagdesc + > if [ -e foo ]; then rm -rf foo; fi + > hg init foo + > cd foo + > hg debugbuilddag "$3" + > hg clone . a $1 --quiet + > hg clone . b $2 --quiet + > echo + > echo "% -- a -> b tree" + > hg -R a debugdiscovery b --verbose --old + > echo + > echo "% -- a -> b set" + > hg -R a debugdiscovery b --verbose --debug + > echo + > echo "% -- b -> a tree" + > hg -R b debugdiscovery a --verbose --old + > echo + > echo "% -- b -> a set" + > hg -R b debugdiscovery a --verbose --debug + > cd .. + > } + + +Small superset: + + $ testdesc '-ra1 -ra2' '-rb1 -rb2 -rb3' ' + > +2:f +1:a1:b1 + > <f +4 :a2 + > +5 :b2 + > <f +3 :b3' + + % -- a -> b tree + comparing with b + searching for changes + unpruned common: b5714e113bc0 66f7d451a68b 01241442b3c2 + common heads: b5714e113bc0 01241442b3c2 + local is subset + + % -- a -> b set + comparing with b + query 1; heads + searching for changes + taking initial sample + searching: 2 queries + query 2; still undecided: 4, sample size is: 4 + 2 total queries + common heads: b5714e113bc0 01241442b3c2 + local is subset + + % -- b -> a tree + comparing with a + searching for changes + unpruned common: b5714e113bc0 01241442b3c2 + common heads: b5714e113bc0 01241442b3c2 + remote is subset + + % -- b -> a set + comparing with a + query 1; heads + searching for changes + all remote heads known locally + common heads: b5714e113bc0 01241442b3c2 + remote is subset + + +Many new: + + $ testdesc '-ra1 -ra2' '-rb' ' + > +2:f +3:a1 +3:b + > <f +30 :a2' + + % -- a -> b tree + comparing with b + searching for changes + unpruned common: bebd167eb94d + common heads: bebd167eb94d + + % -- a -> b set + comparing with b + query 1; heads + searching for changes + taking quick initial sample + searching: 2 queries + query 2; still undecided: 35, sample size is: 35 + 2 total queries + common heads: bebd167eb94d + + % -- b -> a tree + comparing with a + searching for changes + unpruned common: bebd167eb94d 66f7d451a68b + common heads: bebd167eb94d + + % -- b -> a set + comparing with a + query 1; heads + searching for changes + taking initial sample + searching: 2 queries + query 2; still undecided: 3, sample size is: 3 + 2 total queries + common heads: bebd167eb94d + + +Both sides many new with stub: + + $ testdesc '-ra1 -ra2' '-rb' ' + > +2:f +2:a1 +30 :b + > <f +30 :a2' + + % -- a -> b tree + comparing with b + searching for changes + unpruned common: 2dc09a01254d + common heads: 2dc09a01254d + + % -- a -> b set + comparing with b + query 1; heads + searching for changes + taking quick initial sample + searching: 2 queries + query 2; still undecided: 34, sample size is: 34 + 2 total queries + common heads: 2dc09a01254d + + % -- b -> a tree + comparing with a + searching for changes + unpruned common: 66f7d451a68b 2dc09a01254d + common heads: 2dc09a01254d + + % -- b -> a set + comparing with a + query 1; heads + searching for changes + taking initial sample + searching: 2 queries + query 2; still undecided: 30, sample size is: 30 + 2 total queries + common heads: 2dc09a01254d + + +Both many new: + + $ testdesc '-ra' '-rb' ' + > +2:f +30 :b + > <f +30 :a' + + % -- a -> b tree + comparing with b + searching for changes + unpruned common: 66f7d451a68b + common heads: 66f7d451a68b + + % -- a -> b set + comparing with b + query 1; heads + searching for changes + taking quick initial sample + searching: 2 queries + query 2; still undecided: 32, sample size is: 32 + 2 total queries + common heads: 66f7d451a68b + + % -- b -> a tree + comparing with a + searching for changes + unpruned common: 66f7d451a68b + common heads: 66f7d451a68b + + % -- b -> a set + comparing with a + query 1; heads + searching for changes + taking quick initial sample + searching: 2 queries + query 2; still undecided: 32, sample size is: 32 + 2 total queries + common heads: 66f7d451a68b + + +Both many new skewed: + + $ testdesc '-ra' '-rb' ' + > +2:f +30 :b + > <f +50 :a' + + % -- a -> b tree + comparing with b + searching for changes + unpruned common: 66f7d451a68b + common heads: 66f7d451a68b + + % -- a -> b set + comparing with b + query 1; heads + searching for changes + taking quick initial sample + searching: 2 queries + query 2; still undecided: 52, sample size is: 52 + 2 total queries + common heads: 66f7d451a68b + + % -- b -> a tree + comparing with a + searching for changes + unpruned common: 66f7d451a68b + common heads: 66f7d451a68b + + % -- b -> a set + comparing with a + query 1; heads + searching for changes + taking quick initial sample + searching: 2 queries + query 2; still undecided: 32, sample size is: 32 + 2 total queries + common heads: 66f7d451a68b + + +Both many new on top of long history: + + $ testdesc '-ra' '-rb' ' + > +1000:f +30 :b + > <f +50 :a' + + % -- a -> b tree + comparing with b + searching for changes + unpruned common: 7ead0cba2838 + common heads: 7ead0cba2838 + + % -- a -> b set + comparing with b + query 1; heads + searching for changes + taking quick initial sample + searching: 2 queries + query 2; still undecided: 1050, sample size is: 11 + sampling from both directions + searching: 3 queries + query 3; still undecided: 31, sample size is: 31 + 3 total queries + common heads: 7ead0cba2838 + + % -- b -> a tree + comparing with a + searching for changes + unpruned common: 7ead0cba2838 + common heads: 7ead0cba2838 + + % -- b -> a set + comparing with a + query 1; heads + searching for changes + taking quick initial sample + searching: 2 queries + query 2; still undecided: 1030, sample size is: 11 + sampling from both directions + searching: 3 queries + query 3; still undecided: 16, sample size is: 16 + 3 total queries + common heads: 7ead0cba2838 + + +
--- a/tests/test-simple-update.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-simple-update.t Wed May 18 09:56:27 2011 +0200 @@ -1,7 +1,6 @@ - $ mkdir test + $ hg init test $ cd test $ echo foo>foo - $ hg init $ hg addremove adding foo $ hg commit -m "1" @@ -50,3 +49,8 @@ $ hg manifest --debug 6f4310b00b9a147241b071a60c28a650827fb03d 644 foo +update to rev 0 with a date + + $ hg upd -d foo 0 + abort: you can't specify a revision and a date + [255]
--- a/tests/test-simplemerge-cmd.t Wed May 18 09:31:19 2011 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,172 +0,0 @@ - - $ cp "$TESTDIR"/../contrib/simplemerge . - $ echo base > base - $ echo local > local - $ cat base >> local - $ cp local orig - $ cat base > other - $ echo other >> other - -changing local directly - - $ python simplemerge local base other && echo "merge succeeded" - merge succeeded - $ cat local - local - base - other - $ cp orig local - -printing to stdout - - $ python simplemerge -p local base other - local - base - other - -local: - - $ cat local - local - base - -conflicts - - $ cp base conflict-local - $ cp other conflict-other - $ echo not other >> conflict-local - $ echo end >> conflict-local - $ echo end >> conflict-other - $ python simplemerge -p conflict-local base conflict-other - base - <<<<<<< conflict-local - not other - ======= - other - >>>>>>> conflict-other - end - warning: conflicts during merge. - [1] - ---no-minimal - - $ python simplemerge -p --no-minimal conflict-local base conflict-other - base - <<<<<<< conflict-local - not other - end - ======= - other - end - >>>>>>> conflict-other - warning: conflicts during merge. - [1] - -1 label - - $ python simplemerge -p -L foo conflict-local base conflict-other - base - <<<<<<< foo - not other - ======= - other - >>>>>>> conflict-other - end - warning: conflicts during merge. - [1] - -2 labels - - $ python simplemerge -p -L foo -L bar conflict-local base conflict-other - base - <<<<<<< foo - not other - ======= - other - >>>>>>> bar - end - warning: conflicts during merge. - [1] - -too many labels - - $ python simplemerge -p -L foo -L bar -L baz conflict-local base conflict-other - abort: can only specify two labels. - [255] - -binary file - - $ python -c "f = file('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. - [1] - -binary file --text - - $ python simplemerge -a -p binary-local base other 2>&1 - warning: binary-local looks like a binary file. - \x00local (esc) - base - other - -help - - $ python simplemerge --help - simplemerge [OPTS] LOCAL BASE OTHER - - Simple three-way file merge utility with a minimal feature set. - - Apply to LOCAL the changes necessary to go from BASE to OTHER. - - By default, LOCAL is overwritten with the results of this operation. - - options: - -L --label labels to use on conflict markers - -a --text treat all files as text - -p --print print results instead of overwriting LOCAL - --no-minimal do not try to minimize conflict regions - -h --help display help and exit - -q --quiet suppress output - -wrong number of arguments - - $ python simplemerge - simplemerge: wrong number of arguments - simplemerge [OPTS] LOCAL BASE OTHER - - Simple three-way file merge utility with a minimal feature set. - - Apply to LOCAL the changes necessary to go from BASE to OTHER. - - By default, LOCAL is overwritten with the results of this operation. - - options: - -L --label labels to use on conflict markers - -a --text treat all files as text - -p --print print results instead of overwriting LOCAL - --no-minimal do not try to minimize conflict regions - -h --help display help and exit - -q --quiet suppress output - [1] - -bad option - - $ python simplemerge --foo -p local base other - simplemerge: option --foo not recognized - simplemerge [OPTS] LOCAL BASE OTHER - - Simple three-way file merge utility with a minimal feature set. - - Apply to LOCAL the changes necessary to go from BASE to OTHER. - - By default, LOCAL is overwritten with the results of this operation. - - options: - -L --label labels to use on conflict markers - -a --text treat all files as text - -p --print print results instead of overwriting LOCAL - --no-minimal do not try to minimize conflict regions - -h --help display help and exit - -q --quiet suppress output - [1]
--- a/tests/test-ssh-clone-r.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-ssh-clone-r.t Wed May 18 09:56:27 2011 +0200 @@ -1,110 +1,23 @@ This test tries to exercise the ssh functionality with a dummy script - $ cat <<EOF > dummyssh - > import sys - > import os - > - > os.chdir(os.path.dirname(sys.argv[0])) - > if sys.argv[1] != "user@dummy": - > sys.exit(-1) - > - > if not os.path.exists("dummyssh"): - > sys.exit(-1) - > - > os.environ["SSH_CLIENT"] = "127.0.0.1 1 2" - > - > log = open("dummylog", "ab") - > log.write("Got arguments") - > for i, arg in enumerate(sys.argv[1:]): - > log.write(" %d:%s" % (i+1, arg)) - > log.write("\n") - > log.close() - > r = os.system(sys.argv[2]) - > sys.exit(bool(r)) - > EOF +creating 'remote' repo + $ hg init remote $ cd remote - -creating 'remote - - $ cat >>afile <<EOF - > 0 - > EOF - $ hg add afile - $ hg commit -m "0.0" - $ cat >>afile <<EOF - > 1 - > EOF - $ hg commit -m "0.1" - $ cat >>afile <<EOF - > 2 - > EOF - $ hg commit -m "0.2" - $ cat >>afile <<EOF - > 3 - > EOF - $ hg commit -m "0.3" - $ hg update -C 0 + $ hg unbundle $TESTDIR/bundles/remote.hg + adding changesets + adding manifests + adding file changes + added 9 changesets with 7 changes to 4 files (+1 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg up tip 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ cat >>afile <<EOF - > 1 - > EOF - $ hg commit -m "1.1" - created new head - $ cat >>afile <<EOF - > 2 - > EOF - $ hg commit -m "1.2" - $ cat >fred <<EOF - > a line - > EOF - $ cat >>afile <<EOF - > 3 - > EOF - $ hg add fred - $ hg commit -m "1.3" - $ hg mv afile adifferentfile - $ hg commit -m "1.3m" - $ hg update -C 3 - 1 files updated, 0 files merged, 2 files removed, 0 files unresolved - $ hg mv afile anotherfile - $ hg commit -m "0.3m" - $ hg debugindex .hg/store/data/afile.i - rev offset length base linkrev nodeid p1 p2 - 0 0 3 0 0 362fef284ce2 000000000000 000000000000 - 1 3 5 1 1 125144f7e028 362fef284ce2 000000000000 - 2 8 7 2 2 4c982badb186 125144f7e028 000000000000 - 3 15 9 3 3 19b1fc555737 4c982badb186 000000000000 - $ hg debugindex .hg/store/data/adifferentfile.i - rev offset length base linkrev nodeid p1 p2 - 0 0 75 0 7 2565f3199a74 000000000000 000000000000 - $ hg debugindex .hg/store/data/anotherfile.i - rev offset length base linkrev nodeid p1 p2 - 0 0 75 0 8 2565f3199a74 000000000000 000000000000 - $ hg debugindex .hg/store/data/fred.i - rev offset length base linkrev nodeid p1 p2 - 0 0 8 0 6 12ab3bcc5ea4 000000000000 000000000000 - $ hg debugindex .hg/store/00manifest.i - rev offset length base linkrev nodeid p1 p2 - 0 0 48 0 0 43eadb1d2d06 000000000000 000000000000 - 1 48 48 1 1 8b89697eba2c 43eadb1d2d06 000000000000 - 2 96 48 2 2 626a32663c2f 8b89697eba2c 000000000000 - 3 144 48 3 3 f54c32f13478 626a32663c2f 000000000000 - 4 192 58 3 6 de68e904d169 626a32663c2f 000000000000 - 5 250 68 3 7 09bb521d218d de68e904d169 000000000000 - 6 318 54 6 8 1fde233dfb0f f54c32f13478 000000000000 - $ hg verify - checking changesets - checking manifests - crosschecking files in changesets and manifests - checking files - 4 files, 9 changesets, 7 total revisions $ cd .. clone remote via stream $ for i in 0 1 2 3 4 5 6 7 8; do - > hg clone -e "python ./dummyssh" --uncompressed -r "$i" ssh://user@dummy/remote test-"$i" + > hg clone -e "python $TESTDIR/dummyssh" --uncompressed -r "$i" ssh://user@dummy/remote test-"$i" > if cd test-"$i"; then > hg verify > cd .. @@ -226,13 +139,13 @@ 4 files, 9 changesets, 7 total revisions $ cd .. $ cd test-1 - $ hg pull -e "python ../dummyssh" -r 4 ssh://user@dummy/remote + $ hg pull -e "python $TESTDIR/dummyssh" -r 4 ssh://user@dummy/remote pulling from ssh://user@dummy/remote searching for changes adding changesets adding manifests adding file changes - added 1 changesets with 0 changes to 1 files (+1 heads) + added 1 changesets with 0 changes to 0 files (+1 heads) (run 'hg heads' to see heads, 'hg merge' to merge) $ hg verify checking changesets @@ -240,7 +153,7 @@ crosschecking files in changesets and manifests checking files 1 files, 3 changesets, 2 total revisions - $ hg pull -e "python ../dummyssh" ssh://user@dummy/remote + $ hg pull -e "python $TESTDIR/dummyssh" ssh://user@dummy/remote pulling from ssh://user@dummy/remote searching for changes adding changesets @@ -250,13 +163,13 @@ (run 'hg update' to get a working copy) $ cd .. $ cd test-2 - $ hg pull -e "python ../dummyssh" -r 5 ssh://user@dummy/remote + $ hg pull -e "python $TESTDIR/dummyssh" -r 5 ssh://user@dummy/remote pulling from ssh://user@dummy/remote searching for changes adding changesets adding manifests adding file changes - added 2 changesets with 0 changes to 1 files (+1 heads) + added 2 changesets with 0 changes to 0 files (+1 heads) (run 'hg heads' to see heads, 'hg merge' to merge) $ hg verify checking changesets @@ -264,7 +177,7 @@ crosschecking files in changesets and manifests checking files 1 files, 5 changesets, 3 total revisions - $ hg pull -e "python ../dummyssh" ssh://user@dummy/remote + $ hg pull -e "python $TESTDIR/dummyssh" ssh://user@dummy/remote pulling from ssh://user@dummy/remote searching for changes adding changesets
--- a/tests/test-ssh.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-ssh.t Wed May 18 09:56:27 2011 +0200 @@ -2,41 +2,14 @@ This test tries to exercise the ssh functionality with a dummy script - $ cat <<EOF > dummyssh - > import sys - > import os - > - > os.chdir(os.path.dirname(sys.argv[0])) - > if sys.argv[1] != "user@dummy": - > sys.exit(-1) - > - > if not os.path.exists("dummyssh"): - > sys.exit(-1) - > - > os.environ["SSH_CLIENT"] = "127.0.0.1 1 2" - > - > log = open("dummylog", "ab") - > log.write("Got arguments") - > for i, arg in enumerate(sys.argv[1:]): - > log.write(" %d:%s" % (i+1, arg)) - > log.write("\n") - > log.close() - > r = os.system(sys.argv[2]) - > sys.exit(bool(r)) - > EOF - $ cat <<EOF > badhook - > import sys - > sys.stdout.write("KABOOM\n") - > EOF - -creating 'remote +creating 'remote' repo $ hg init remote $ cd remote $ echo this > foo $ echo this > fooO $ hg ci -A -m "init" foo fooO - $ echo <<EOF > .hg/hgrc + $ cat <<EOF > .hg/hgrc > [server] > uncompressed = True > @@ -47,21 +20,21 @@ repo not found error - $ hg clone -e "python ./dummyssh" ssh://user@dummy/nonexistent local + $ hg clone -e "python $TESTDIR/dummyssh" ssh://user@dummy/nonexistent local remote: abort: There is no Mercurial repository here (.hg not found)! abort: no suitable response from remote hg! [255] non-existent absolute path - $ hg clone -e "python ./dummyssh" ssh://user@dummy//`pwd`/nonexistent local + $ hg clone -e "python $TESTDIR/dummyssh" ssh://user@dummy//`pwd`/nonexistent local remote: abort: There is no Mercurial repository here (.hg not found)! abort: no suitable response from remote hg! [255] clone remote via stream - $ hg clone -e "python ./dummyssh" --uncompressed ssh://user@dummy/remote local-stream + $ hg clone -e "python $TESTDIR/dummyssh" --uncompressed ssh://user@dummy/remote local-stream streaming all changes 4 files to transfer, 392 bytes of data transferred 392 bytes in * seconds (*/sec) (glob) @@ -78,7 +51,7 @@ clone remote via pull - $ hg clone -e "python ./dummyssh" ssh://user@dummy/remote local + $ hg clone -e "python $TESTDIR/dummyssh" ssh://user@dummy/remote local requesting all changes adding changesets adding manifests @@ -103,7 +76,7 @@ $ hg paths default = ssh://user@dummy/remote - $ hg pull -e "python ../dummyssh" + $ hg pull -e "python $TESTDIR/dummyssh" pulling from ssh://user@dummy/remote searching for changes no changes found @@ -117,7 +90,7 @@ $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc $ echo "[ui]" >> .hg/hgrc - $ echo "ssh = python ../dummyssh" >> .hg/hgrc + $ echo "ssh = python $TESTDIR/dummyssh" >> .hg/hgrc find outgoing @@ -133,7 +106,7 @@ find incoming on the remote side - $ hg incoming -R ../remote -e "python ../dummyssh" ssh://user@dummy/local + $ hg incoming -R ../remote -e "python $TESTDIR/dummyssh" ssh://user@dummy/local comparing with ssh://user@dummy/local searching for changes changeset: 1:a28a9d1a809c @@ -145,7 +118,7 @@ find incoming on the remote side (using absolute path) - $ hg incoming -R ../remote -e "python ../dummyssh" "ssh://user@dummy/`pwd`" + $ hg incoming -R ../remote -e "python $TESTDIR/dummyssh" "ssh://user@dummy/`pwd`" comparing with ssh://user@dummy/$TESTTMP/local searching for changes changeset: 1:a28a9d1a809c @@ -190,7 +163,7 @@ test pushkeys and bookmarks $ cd ../local - $ hg debugpushkey --config ui.ssh="python ../dummyssh" ssh://user@dummy/remote namespaces + $ hg debugpushkey --config ui.ssh="python $TESTDIR/dummyssh" ssh://user@dummy/remote namespaces bookmarks namespaces $ hg book foo -r 0 @@ -203,7 +176,7 @@ searching for changes no changes found exporting bookmark foo - $ hg debugpushkey --config ui.ssh="python ../dummyssh" ssh://user@dummy/remote bookmarks + $ hg debugpushkey --config ui.ssh="python $TESTDIR/dummyssh" ssh://user@dummy/remote bookmarks foo 1160648e36cec0054048a7edc4110c6f84fde594 $ hg book -f foo $ hg push --traceback @@ -219,7 +192,6 @@ $ hg book -f -r 0 foo $ hg pull -B foo pulling from ssh://user@dummy/remote - searching for changes no changes found updating bookmark foo importing bookmark foo @@ -232,8 +204,13 @@ a bad, evil hook that prints to stdout + $ cat <<EOF > $TESTTMP/badhook + > import sys + > sys.stdout.write("KABOOM\n") + > EOF + $ echo '[hooks]' >> ../remote/.hg/hgrc - $ echo 'changegroup.stdout = python ../badhook' >> ../remote/.hg/hgrc + $ echo "changegroup.stdout = python $TESTTMP/badhook" >> ../remote/.hg/hgrc $ echo r > r $ hg ci -A -m z r @@ -263,10 +240,28 @@ summary: z +clone bookmarks + + $ hg -R ../remote bookmark test + $ hg -R ../remote bookmarks + * test 2:6c0482d977a3 + $ hg clone -e "python $TESTDIR/dummyssh" ssh://user@dummy/remote local-bookmarks + requesting all changes + adding changesets + adding manifests + adding file changes + added 4 changesets with 5 changes to 4 files (+1 heads) + updating to branch default + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg -R local-bookmarks bookmarks + test 2:6c0482d977a3 + passwords in ssh urls are not supported +(we use a glob here because different Python versions give different +results here) $ hg push ssh://user:erroneouspwd@dummy/remote - pushing to ssh://user:***@dummy/remote + pushing to ssh://user:*@dummy/remote (glob) abort: password in URL not supported! [255] @@ -281,6 +276,7 @@ Got arguments 1:user@dummy 2:hg -R local serve --stdio Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio Got arguments 1:user@dummy 2:hg -R remote serve --stdio + changegroup-in-remote hook: HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_URL=remote:ssh:127.0.0.1 Got arguments 1:user@dummy 2:hg -R remote serve --stdio Got arguments 1:user@dummy 2:hg -R remote serve --stdio Got arguments 1:user@dummy 2:hg -R remote serve --stdio @@ -290,3 +286,5 @@ Got arguments 1:user@dummy 2:hg -R remote serve --stdio Got arguments 1:user@dummy 2:hg -R remote serve --stdio Got arguments 1:user@dummy 2:hg -R remote serve --stdio + changegroup-in-remote hook: HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_URL=remote:ssh:127.0.0.1 + Got arguments 1:user@dummy 2:hg -R remote serve --stdio
--- a/tests/test-static-http.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-static-http.t Wed May 18 09:56:27 2011 +0200 @@ -22,9 +22,8 @@ > EOF $ python dumb.py 2>/dev/null & $ echo $! >> $DAEMON_PIDS - $ mkdir remote + $ hg init remote $ cd remote - $ hg init $ echo foo > bar $ echo c2 > '.dotfile with spaces' $ hg add @@ -118,7 +117,7 @@ adding file changes added 1 changesets with 3 changes to 3 files updating to branch default - pulling subrepo sub from static-http://localhost:$HGPORT/sub + cloning subrepo sub from static-http://localhost:$HGPORT/sub requesting all changes adding changesets adding manifests
--- a/tests/test-status-color.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-status-color.t Wed May 18 09:56:27 2011 +0200 @@ -2,6 +2,8 @@ $ echo "color=" >> $HGRCPATH $ echo "[color]" >> $HGRCPATH $ echo "mode=ansi" >> $HGRCPATH +Terminfo codes compatibility fix + $ echo "color.none=0" >> $HGRCPATH $ hg init repo1 $ cd repo1 @@ -163,6 +165,19 @@ \x1b[0;0mC .hgignore\x1b[0m (esc) \x1b[0;0mC modified\x1b[0m (esc) +hg status -A (with terminfo color): + + $ TERM=xterm hg status --config color.mode=terminfo --color=always -A + \x1b[30m\x1b[32m\x1b[1mA added\x1b[30m (esc) + \x1b[30m\x1b[32m\x1b[1mA copied\x1b[30m (esc) + \x1b[30m\x1b[30m modified\x1b[30m (esc) + \x1b[30m\x1b[31m\x1b[1mR removed\x1b[30m (esc) + \x1b[30m\x1b[36m\x1b[1m\x1b[4m! deleted\x1b[30m (esc) + \x1b[30m\x1b[35m\x1b[1m\x1b[4m? unknown\x1b[30m (esc) + \x1b[30m\x1b[30m\x1b[1mI ignored\x1b[30m (esc) + \x1b[30m\x1b[30mC .hgignore\x1b[30m (esc) + \x1b[30m\x1b[30mC modified\x1b[30m (esc) + $ echo "^ignoreddir$" > .hgignore $ mkdir ignoreddir
--- a/tests/test-strip-cross.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-strip-cross.t Wed May 18 09:56:27 2011 +0200 @@ -32,60 +32,62 @@ $ commit '012' 'manifest-file' $ cd .. $ hg clone -q -U -r -1 -r -2 -r -3 -r -4 -r -6 orig crossed - $ for i in crossed/.hg/store/00manifest.i crossed/.hg/store/data/*.i; do - > echo $i - > hg debugindex $i - > echo - > done - crossed/.hg/store/00manifest.i + $ cd crossed + $ hg debugindex --manifest rev offset length base linkrev nodeid p1 p2 0 0 112 0 0 6f105cbb914d 000000000000 000000000000 1 112 56 1 3 1b55917b3699 000000000000 000000000000 2 168 123 1 1 8f3d04e263e5 000000000000 000000000000 3 291 122 1 2 f0ef8726ac4f 000000000000 000000000000 4 413 87 4 4 0b76e38b4070 000000000000 000000000000 - - crossed/.hg/store/data/012.i + + $ for i in 012 021 102 120 201 210 manifest-file; do + > echo $i + > hg debugindex $i + > echo + > done + 012 rev offset length base linkrev nodeid p1 p2 0 0 3 0 0 b8e02f643373 000000000000 000000000000 1 3 3 1 1 5d9299349fc0 000000000000 000000000000 2 6 3 2 2 2661d26c6496 000000000000 000000000000 - crossed/.hg/store/data/021.i + 021 rev offset length base linkrev nodeid p1 p2 0 0 3 0 0 b8e02f643373 000000000000 000000000000 1 3 3 1 2 5d9299349fc0 000000000000 000000000000 2 6 3 2 1 2661d26c6496 000000000000 000000000000 - crossed/.hg/store/data/102.i + 102 rev offset length base linkrev nodeid p1 p2 0 0 3 0 1 b8e02f643373 000000000000 000000000000 1 3 3 1 0 5d9299349fc0 000000000000 000000000000 2 6 3 2 2 2661d26c6496 000000000000 000000000000 - crossed/.hg/store/data/120.i + 120 rev offset length base linkrev nodeid p1 p2 0 0 3 0 1 b8e02f643373 000000000000 000000000000 1 3 3 1 2 5d9299349fc0 000000000000 000000000000 2 6 3 2 0 2661d26c6496 000000000000 000000000000 - crossed/.hg/store/data/201.i + 201 rev offset length base linkrev nodeid p1 p2 0 0 3 0 2 b8e02f643373 000000000000 000000000000 1 3 3 1 0 5d9299349fc0 000000000000 000000000000 2 6 3 2 1 2661d26c6496 000000000000 000000000000 - crossed/.hg/store/data/210.i + 210 rev offset length base linkrev nodeid p1 p2 0 0 3 0 2 b8e02f643373 000000000000 000000000000 1 3 3 1 1 5d9299349fc0 000000000000 000000000000 2 6 3 2 0 2661d26c6496 000000000000 000000000000 - crossed/.hg/store/data/manifest-file.i + manifest-file rev offset length base linkrev nodeid p1 p2 0 0 3 0 3 b8e02f643373 000000000000 000000000000 1 3 3 1 4 5d9299349fc0 000000000000 000000000000 + $ cd .. $ for i in 0 1 2 3 4; do > hg clone -q -U --pull crossed $i > echo "% Trying to strip revision $i"
--- a/tests/test-subrepo-deep-nested-change.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-subrepo-deep-nested-change.t Wed May 18 09:56:27 2011 +0200 @@ -27,12 +27,7 @@ $ echo "sub1 = ../sub1" > main/.hgsub $ hg clone sub1 main/sub1 updating to branch default - pulling subrepo sub2 from $TESTTMP/sub2 - requesting all changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files + cloning subrepo sub2 from $TESTTMP/sub2 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg add -R main adding main/.hgsub @@ -55,18 +50,8 @@ $ hg clone main cloned updating to branch default - pulling subrepo sub1 from $TESTTMP/sub1 - requesting all changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 3 changes to 3 files - pulling subrepo sub1/sub2 from $TESTTMP/sub2 - requesting all changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files + cloning subrepo sub1 from $TESTTMP/sub1 + cloning subrepo sub1/sub2 from $TESTTMP/sub2 3 files updated, 0 files merged, 0 files removed, 0 files unresolved Checking cloned repo ids
--- a/tests/test-subrepo-git.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-subrepo-git.t Wed May 18 09:56:27 2011 +0200 @@ -73,7 +73,7 @@ $ cd t $ hg clone . ../tc updating to branch default - cloning subrepo s + cloning subrepo s from $TESTTMP/gitroot 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd ../tc $ hg debugsub @@ -96,7 +96,7 @@ $ cd ../t $ hg clone . ../ta updating to branch default - cloning subrepo s + cloning subrepo s from $TESTTMP/gitroot 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd ../ta @@ -115,7 +115,7 @@ $ cd ../t $ hg clone . ../tb updating to branch default - cloning subrepo s + cloning subrepo s from $TESTTMP/gitroot 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd ../tb/s @@ -155,7 +155,7 @@ added 1 changesets with 1 changes to 1 files (+1 heads) (run 'hg heads' to see heads, 'hg merge' to merge) $ hg merge 2>/dev/null - pulling subrepo s + pulling subrepo s from $TESTTMP/gitroot 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ cat s/f @@ -199,7 +199,7 @@ $ cd ../t $ hg clone . ../td updating to branch default - cloning subrepo s + cloning subrepo s from $TESTTMP/gitroot checking out detached HEAD in subrepo s check out a git branch if you intend to make changes 3 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -232,7 +232,7 @@ $ cd ../tb $ hg pull -q $ hg update 2>/dev/null - pulling subrepo s + pulling subrepo s from $TESTTMP/gitroot 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg debugsub path s @@ -262,7 +262,7 @@ $ cd ../tc $ hg pull -q $ hg archive --subrepos -r 5 ../archive 2>/dev/null - pulling subrepo s + pulling subrepo s from $TESTTMP/gitroot $ cd ../archive $ cat s/f f @@ -282,7 +282,7 @@ $ hg clone ../t inner updating to branch default - cloning subrepo s + cloning subrepo s from $TESTTMP/gitroot 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo inner = inner > .hgsub $ hg add .hgsub @@ -311,7 +311,7 @@ $ mkdir d $ hg clone t d/t updating to branch default - cloning subrepo s + cloning subrepo s from $TESTTMP/gitroot 3 files updated, 0 files merged, 0 files removed, 0 files unresolved Don't crash if the subrepo is missing @@ -329,7 +329,7 @@ abort: subrepo s is missing [255] $ hg update -C - cloning subrepo s + cloning subrepo s from $TESTTMP/gitroot 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg sum | grep commit commit: (clean)
--- a/tests/test-subrepo-paths.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-subrepo-paths.t Wed May 18 09:56:27 2011 +0200 @@ -21,6 +21,15 @@ source C:\libs\foo-lib\ revision +test cumulative remapping, the $HGRCPATH file is loaded first + + $ echo '[subpaths]' >> $HGRCPATH + $ echo 'libfoo = libbar' >> $HGRCPATH + $ hg debugsub + path sub + source C:\libs\bar-lib\ + revision + test bad subpaths pattern $ cat > .hg/hgrc <<EOF
--- a/tests/test-subrepo-recursion.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-subrepo-recursion.t Wed May 18 09:56:27 2011 +0200 @@ -313,18 +313,8 @@ $ cd .. $ hg clone repo repo2 updating to branch default - pulling subrepo foo from $TESTTMP/repo/foo - requesting all changes - adding changesets - adding manifests - adding file changes - added 4 changesets with 7 changes to 3 files - pulling subrepo foo/bar from $TESTTMP/repo/foo/bar - requesting all changes - adding changesets - adding manifests - adding file changes - added 3 changesets with 3 changes to 1 files + cloning subrepo foo from $TESTTMP/repo/foo + cloning subrepo foo/bar from $TESTTMP/repo/foo/bar 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd repo2 $ hg outgoing -S
--- a/tests/test-subrepo-relative-path.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-subrepo-relative-path.t Wed May 18 09:56:27 2011 +0200 @@ -44,7 +44,7 @@ adding file changes added 1 changesets with 3 changes to 3 files updating to branch default - pulling subrepo sub from http://localhost:$HGPORT/sub + cloning subrepo sub from http://localhost:$HGPORT/sub requesting all changes adding changesets adding manifests @@ -67,3 +67,37 @@ revision 863c1745b441bd97a8c4a096e87793073f4fb215 $ "$TESTDIR/killdaemons.py" + +subrepo paths with ssh urls + + $ cp $TESTDIR/dummyssh $BINDIR/ssh + + $ hg clone ssh://user@dummy/cloned sshclone + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 3 changes to 3 files + updating to branch default + cloning subrepo sub from ssh://user@dummy/sub + requesting all changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 1 changes to 1 files + 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + + $ hg -R sshclone push ssh://user@dummy/$TESTTMP/cloned + pushing to ssh://user@dummy/$TESTTMP/cloned + pushing subrepo sub to ssh://user@dummy/$TESTTMP/sub + searching for changes + no changes found + searching for changes + no changes found + + $ cat dummylog + Got arguments 1:user@dummy 2:hg -R cloned serve --stdio + Got arguments 1:user@dummy 2:hg -R sub serve --stdio + Got arguments 1:user@dummy 2:hg -R $TESTTMP/cloned serve --stdio + Got arguments 1:user@dummy 2:hg -R $TESTTMP/sub serve --stdio + $ rm $BINDIR/ssh
--- a/tests/test-subrepo-svn.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-subrepo-svn.t Wed May 18 09:56:27 2011 +0200 @@ -225,6 +225,14 @@ $ cd .. $ hg clone t tc | fix_path updating to branch default + A tc/s/alpha + U tc/s + + Fetching external item into 'tc/s/externals' + A tc/s/externals/other + Checked out external at revision 1. + + Checked out revision 3. A tc/subdir/s/alpha U tc/subdir/s @@ -233,14 +241,6 @@ Checked out external at revision 1. Checked out revision 2. - A tc/s/alpha - U tc/s - - Fetching external item into 'tc/s/externals' - A tc/s/externals/other - Checked out external at revision 1. - - Checked out revision 3. 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd tc
--- a/tests/test-subrepo.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-subrepo.t Wed May 18 09:56:27 2011 +0200 @@ -239,24 +239,9 @@ $ cd .. $ hg clone t tc updating to branch default - pulling subrepo s from $TESTTMP/sub/t/s - requesting all changes - adding changesets - adding manifests - adding file changes - added 4 changesets with 5 changes to 3 files - pulling subrepo s/ss from $TESTTMP/sub/t/s/ss - requesting all changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files - pulling subrepo t from $TESTTMP/sub/t/t - requesting all changes - adding changesets - adding manifests - adding file changes - added 4 changesets with 4 changes to 1 files (+1 heads) + cloning subrepo s from $TESTTMP/sub/t/s + cloning subrepo s/ss from $TESTTMP/sub/t/s/ss + cloning subrepo t from $TESTTMP/sub/t/t 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd tc $ hg debugsub @@ -563,12 +548,7 @@ committing subrepository s $ hg clone repo repo2 updating to branch default - pulling subrepo s from $TESTTMP/sub/repo/s - requesting all changes - adding changesets - adding manifests - adding file changes - added 1 changesets with 1 changes to 1 files + cloning subrepo s from $TESTTMP/sub/repo/s 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -q -R repo2 pull -u $ echo 1 > repo2/s/a @@ -639,12 +619,7 @@ adding manifests adding file changes added 1 changesets with 2 changes to 2 files - pulling subrepo sub/repo from issue1852a/sub/repo - requesting all changes - adding changesets - adding manifests - adding file changes - added 2 changesets with 2 changes to 1 files + cloning subrepo sub/repo from issue1852a/sub/repo 2 files updated, 0 files merged, 0 files removed, 0 files unresolved Try to push from the other side
--- a/tests/test-symlink-os-yes-fs-no.py Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-symlink-os-yes-fs-no.py Wed May 18 09:56:27 2011 +0200 @@ -1,7 +1,8 @@ import os, sys, time -from mercurial import hg, ui, commands +from mercurial import hg, ui, commands, util TESTDIR = os.environ["TESTDIR"] +BUNDLEPATH = os.path.join(TESTDIR, 'bundles', 'test-no-symlinks.hg') # only makes sense to test on os which supports symlinks if not hasattr(os, "symlink"): @@ -9,7 +10,7 @@ # clone with symlink support u = ui.ui() -hg.clone(u, os.path.join(TESTDIR, 'test-no-symlinks.hg'), 'test0') +hg.clone(u, BUNDLEPATH, 'test0') repo = hg.repository(u, 'test0') @@ -28,7 +29,7 @@ for f in 'test0/a.lnk', 'test0/d/b.lnk': os.unlink(f) fp = open(f, 'wb') - fp.write(open(f[:-4]).read()) + fp.write(util.readfile(f[:-4])) fp.close() # reload repository @@ -38,4 +39,4 @@ # try cloning a repo which contains symlinks u = ui.ui() -hg.clone(u, os.path.join(TESTDIR, 'test-no-symlinks.hg'), 'test1') +hg.clone(u, BUNDLEPATH, 'test1')
--- a/tests/test-tag.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-tag.t Wed May 18 09:56:27 2011 +0200 @@ -292,7 +292,7 @@ [255] $ hg tag --rev 1 --local t3 $ hg tags -v - tip 2:8a8f787d0d5c + tip 2:2a156e8887cc t3 1:c3adabd1a5f4 local $ cd ..
--- a/tests/test-transplant.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-transplant.t Wed May 18 09:56:27 2011 +0200 @@ -69,6 +69,18 @@ "transplanted([set])" Transplanted changesets in set, or all transplanted changesets. +test tranplanted keyword + + $ hg log --template '{rev} {transplanted}\n' + 7 a53251cdf717679d1907b289f991534be05c997a + 6 722f4667af767100cb15b6a79324bf8abbfe1ef4 + 5 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21 + 4 + 3 + 2 + 1 + 0 + $ hg clone ../t ../prune updating to branch default 4 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -331,6 +343,40 @@ [255] $ cd .. +test environment passed to filter + + $ hg init filter-environment + $ cd filter-environment + $ cat <<'EOF' >test-filter-environment + > #!/bin/sh + > echo "Transplant by $HGUSER" >> $1 + > echo "Transplant from rev $HGREVISION" >> $1 + > EOF + $ chmod +x test-filter-environment + $ hg transplant -s ../t --filter ./test-filter-environment 0 + filtering * (glob) + applying 17ab29e464c6 + 17ab29e464c6 transplanted to 5190e68026a0 + + $ hg log --template '{rev} {parents} {desc}\n' + 0 r1 + Transplant by test + Transplant from rev 17ab29e464c6ca53e329470efe2a9918ac617a6f + $ cd .. + +test transplant with filter handles invalid changelog + + $ hg init filter-invalid-log + $ cd filter-invalid-log + $ cat <<'EOF' >test-filter-invalid-log + > #!/bin/sh + > echo "" > $1 + > EOF + $ chmod +x test-filter-invalid-log + $ hg transplant -s ../t --filter ./test-filter-invalid-log 0 + filtering * (glob) + abort: filter corrupted changeset (no user or date) + [255] test with a win32ext like setup (differing EOLs)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-treediscovery.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,281 @@ +Tests discovery against servers without getbundle support: + + $ CAP=getbundle + $ . "$TESTDIR/notcapable" + $ cat >> $HGRCPATH <<EOF + > [ui] + > logtemplate="{rev} {node|short}: {desc} {branches}\n" + > [extensions] + > graphlog= + > EOF + +Setup HTTP server control: + + $ remote=http://localhost:$HGPORT/ + $ export remote + $ start() { + > echo '[web]' > $1/.hg/hgrc + > echo 'push_ssl = false' >> $1/.hg/hgrc + > echo 'allow_push = *' >> $1/.hg/hgrc + > hg serve -R $1 -p $HGPORT -d --pid-file=hg.pid -E errors.log + > cat hg.pid >> $DAEMON_PIDS + > } + $ stop() { + > "$TESTDIR/killdaemons.py" + > } + +Both are empty: + + $ hg init empty1 + $ hg init empty2 + $ start empty2 + $ hg incoming -R empty1 $remote + comparing with http://localhost:$HGPORT/ + no changes found + [1] + $ hg outgoing -R empty1 $remote + comparing with http://localhost:$HGPORT/ + no changes found + [1] + $ hg pull -R empty1 $remote + pulling from http://localhost:$HGPORT/ + no changes found + $ hg push -R empty1 $remote + pushing to http://localhost:$HGPORT/ + no changes found + $ stop + +Base repo: + + $ hg init main + $ cd main + $ hg debugbuilddag -mo '+2:tbase @name1 +3:thead1 <tbase @name2 +4:thead2 @both /thead1 +2:tmaintip' + $ hg glog + o 11 a19bfa7e7328: r11 both + | + o 10 8b6bad1512e1: r10 both + | + o 9 025829e08038: r9 both + |\ + | o 8 d8f638ac69e9: r8 name2 + | | + | o 7 b6b4d315a2ac: r7 name2 + | | + | o 6 6c6f5d5f3c11: r6 name2 + | | + | o 5 70314b29987d: r5 name2 + | | + o | 4 e71dbbc70e03: r4 name1 + | | + o | 3 2c8d5d5ec612: r3 name1 + | | + o | 2 a7892891da29: r2 name1 + |/ + o 1 0019a3b924fd: r1 + | + o 0 d57206cc072a: r0 + + $ cd .. + $ start main + +Full clone: + + $ hg clone main full + updating to branch default + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd full + $ hg incoming $remote + comparing with http://localhost:$HGPORT/ + searching for changes + no changes found + [1] + $ hg outgoing $remote + comparing with http://localhost:$HGPORT/ + searching for changes + no changes found + [1] + $ hg pull $remote + pulling from http://localhost:$HGPORT/ + searching for changes + no changes found + $ hg push $remote + pushing to http://localhost:$HGPORT/ + searching for changes + no changes found + $ cd .. + +Local is empty: + + $ cd empty1 + $ hg incoming $remote + comparing with http://localhost:$HGPORT/ + 0 d57206cc072a: r0 + 1 0019a3b924fd: r1 + 2 a7892891da29: r2 name1 + 3 2c8d5d5ec612: r3 name1 + 4 e71dbbc70e03: r4 name1 + 5 70314b29987d: r5 name2 + 6 6c6f5d5f3c11: r6 name2 + 7 b6b4d315a2ac: r7 name2 + 8 d8f638ac69e9: r8 name2 + 9 025829e08038: r9 both + 10 8b6bad1512e1: r10 both + 11 a19bfa7e7328: r11 both + $ hg outgoing $remote + comparing with http://localhost:$HGPORT/ + no changes found + [1] + $ hg push $remote + pushing to http://localhost:$HGPORT/ + no changes found + $ hg pull $remote + pulling from http://localhost:$HGPORT/ + requesting all changes + adding changesets + adding manifests + adding file changes + added 12 changesets with 24 changes to 2 files + (run 'hg update' to get a working copy) + $ hg incoming $remote + comparing with http://localhost:$HGPORT/ + searching for changes + no changes found + [1] + $ cd .. + +Local is subset: + + $ hg clone main subset --rev name2 ; cd subset + adding changesets + adding manifests + adding file changes + added 6 changesets with 12 changes to 2 files + updating to branch name2 + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg incoming $remote + comparing with http://localhost:$HGPORT/ + searching for changes + 6 a7892891da29: r2 name1 + 7 2c8d5d5ec612: r3 name1 + 8 e71dbbc70e03: r4 name1 + 9 025829e08038: r9 both + 10 8b6bad1512e1: r10 both + 11 a19bfa7e7328: r11 both + $ hg outgoing $remote + comparing with http://localhost:$HGPORT/ + searching for changes + no changes found + [1] + $ hg push $remote + pushing to http://localhost:$HGPORT/ + searching for changes + no changes found + $ hg pull $remote + pulling from http://localhost:$HGPORT/ + searching for changes + adding changesets + adding manifests + adding file changes + added 6 changesets with 12 changes to 2 files + (run 'hg update' to get a working copy) + $ hg incoming $remote + comparing with http://localhost:$HGPORT/ + searching for changes + no changes found + [1] + $ cd .. + +Remote is empty: + + $ stop ; start empty2 + $ cd main + $ hg incoming $remote + comparing with http://localhost:$HGPORT/ + searching for changes + no changes found + [1] + $ hg outgoing $remote + comparing with http://localhost:$HGPORT/ + searching for changes + 0 d57206cc072a: r0 + 1 0019a3b924fd: r1 + 2 a7892891da29: r2 name1 + 3 2c8d5d5ec612: r3 name1 + 4 e71dbbc70e03: r4 name1 + 5 70314b29987d: r5 name2 + 6 6c6f5d5f3c11: r6 name2 + 7 b6b4d315a2ac: r7 name2 + 8 d8f638ac69e9: r8 name2 + 9 025829e08038: r9 both + 10 8b6bad1512e1: r10 both + 11 a19bfa7e7328: r11 both + $ hg pull $remote + pulling from http://localhost:$HGPORT/ + searching for changes + no changes found + $ hg push $remote + pushing to http://localhost:$HGPORT/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 12 changesets with 24 changes to 2 files + $ hg outgoing $remote + comparing with http://localhost:$HGPORT/ + searching for changes + no changes found + [1] + $ cd .. + +Local is superset: + + $ stop + $ hg clone main subset2 --rev name2 + adding changesets + adding manifests + adding file changes + added 6 changesets with 12 changes to 2 files + updating to branch name2 + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ start subset2 + $ cd main + $ hg incoming $remote + comparing with http://localhost:$HGPORT/ + searching for changes + no changes found + [1] + $ hg outgoing $remote + comparing with http://localhost:$HGPORT/ + searching for changes + 2 a7892891da29: r2 name1 + 3 2c8d5d5ec612: r3 name1 + 4 e71dbbc70e03: r4 name1 + 9 025829e08038: r9 both + 10 8b6bad1512e1: r10 both + 11 a19bfa7e7328: r11 both + $ hg pull $remote + pulling from http://localhost:$HGPORT/ + searching for changes + no changes found + $ hg push $remote + pushing to http://localhost:$HGPORT/ + searching for changes + abort: push creates new remote branches: both, name1! + (use 'hg push --new-branch' to create new remote branches) + [255] + $ hg push $remote --new-branch + pushing to http://localhost:$HGPORT/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 6 changesets with 12 changes to 2 files + $ hg outgoing $remote + comparing with http://localhost:$HGPORT/ + searching for changes + no changes found + [1] + $ cd .. + + $ stop +
--- a/tests/test-ui-config.py Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-ui-config.py Wed May 18 09:56:27 2011 +0200 @@ -5,6 +5,10 @@ 'values.string=string value', 'values.bool1=true', 'values.bool2=false', + 'values.boolinvalid=foo', + 'values.int1=42', + 'values.int2=-42', + 'values.intinvalid=foo', 'lists.list1=foo', 'lists.list2=foo bar baz', 'lists.list3=alice, bob', @@ -23,7 +27,7 @@ 'lists.list16="longer quotation" with "no ending quotation', 'lists.list17=this is \\" "not a quotation mark"', 'lists.list18=\n \n\nding\ndong', -]) + ]) print repr(testui.configitems('values')) print repr(testui.configitems('lists')) @@ -43,6 +47,9 @@ print repr(testui.configbool('values', 'unknown')) print repr(testui.configbool('values', 'unknown', True)) print "---" +print repr(testui.configint('values', 'int1')) +print repr(testui.configint('values', 'int2')) +print "---" print repr(testui.configlist('lists', 'list1')) print repr(testui.configlist('lists', 'list2')) print repr(testui.configlist('lists', 'list3')) @@ -79,3 +86,13 @@ # values that aren't strings should work testui.setconfig('hook', 'commit', function) print function == testui.config('hook', 'commit') + +# invalid values +try: + testui.configbool('values', 'boolinvalid') +except error.ConfigError: + print 'boolinvalid' +try: + testui.configint('values', 'intinvalid') +except error.ConfigError: + print 'intinvalid'
--- a/tests/test-ui-config.py.out Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-ui-config.py.out Wed May 18 09:56:27 2011 +0200 @@ -1,4 +1,4 @@ -[('string', 'string value'), ('bool1', 'true'), ('bool2', 'false')] +[('string', 'string value'), ('bool1', 'true'), ('bool2', 'false'), ('boolinvalid', 'foo'), ('int1', '42'), ('int2', '-42'), ('intinvalid', 'foo')] [('list1', 'foo'), ('list2', 'foo bar baz'), ('list3', 'alice, bob'), ('list4', 'foo bar baz alice, bob'), ('list5', 'abc d"ef"g "hij def"'), ('list6', '"hello world", "how are you?"'), ('list7', 'Do"Not"Separate'), ('list8', '"Do"Separate'), ('list9', '"Do\\"NotSeparate"'), ('list10', 'string "with extraneous" quotation mark"'), ('list11', 'x, y'), ('list12', '"x", "y"'), ('list13', '""" key = "x", "y" """'), ('list14', ',,,, '), ('list15', '" just with starting quotation'), ('list16', '"longer quotation" with "no ending quotation'), ('list17', 'this is \\" "not a quotation mark"'), ('list18', '\n \n\nding\ndong')] --- 'string value' @@ -6,13 +6,16 @@ 'false' None --- -values.string not a boolean ('string value') +values.string is not a boolean ('string value') True False False False True --- +42 +-42 +--- ['foo'] ['foo', 'bar', 'baz'] ['alice', 'bob'] @@ -42,3 +45,5 @@ ['foo', 'bar'] None True +boolinvalid +intinvalid
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-unbundlehash.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,31 @@ + +Test wire protocol unbundle with hashed heads (capability: unbundlehash) + +Create a remote repository. + + $ hg init remote + $ hg serve -R remote --config web.push_ssl=False --config web.allow_push=* -p $HGPORT -d --pid-file=hg1.pid -E error.log -A access.log + $ cat hg1.pid >> $DAEMON_PIDS + +Clone the repository and push a change. + + $ hg clone http://localhost:$HGPORT/ local + no changes found + updating to branch default + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ touch local/README + $ hg ci -R local -A -m hoge + adding README + $ hg push -R local + pushing to http://localhost:$HGPORT/ + searching for changes + remote: adding changesets + remote: adding manifests + remote: adding file changes + remote: added 1 changesets with 1 changes to 1 files + +Ensure hashed heads format is used. +The hash here is always the same since the remote repository only has the null head. + + $ cat access.log | grep unbundle + * - - [*] "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=686173686564+6768033e216468247bd031a0a2d9876d79818f8f (glob)
--- a/tests/test-unrelated-pull.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-unrelated-pull.t Wed May 18 09:56:27 2011 +0200 @@ -1,14 +1,12 @@ - $ mkdir a + $ hg init a $ cd a - $ hg init $ echo 123 > a $ hg add a $ hg commit -m "a" -u a $ cd .. - $ mkdir b + $ hg init b $ cd b - $ hg init $ echo 321 > b $ hg add b $ hg commit -m "b" -u b @@ -23,6 +21,7 @@ pulling from ../a searching for changes warning: repository is unrelated + requesting all changes adding changesets adding manifests adding file changes
--- a/tests/test-up-local-change.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-up-local-change.t Wed May 18 09:56:27 2011 +0200 @@ -1,8 +1,7 @@ $ HGMERGE=true; export HGMERGE - $ mkdir r1 + $ hg init r1 $ cd r1 - $ hg init $ echo a > a $ hg addremove adding a
--- a/tests/test-url.py Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-url.py Wed May 18 09:56:27 2011 +0200 @@ -7,7 +7,7 @@ def cert(cn): return dict(subject=((('commonName', cn),),)) -from mercurial.url import _verifycert +from mercurial.sslutil import _verifycert # Test non-wildcard certificates check(_verifycert(cert('example.com'), 'example.com'), @@ -49,6 +49,169 @@ check(_verifycert(None, 'example.com'), 'no certificate received') +import doctest + +def test_url(): + """ + >>> from mercurial.util import url + + This tests for edge cases in url.URL's parsing algorithm. Most of + these aren't useful for documentation purposes, so they aren't + part of the class's doc tests. + + Query strings and fragments: + + >>> url('http://host/a?b#c') + <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'> + >>> url('http://host/a?') + <url scheme: 'http', host: 'host', path: 'a'> + >>> url('http://host/a#b#c') + <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'> + >>> url('http://host/a#b?c') + <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'> + >>> url('http://host/?a#b') + <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'> + >>> url('http://host/?a#b', parsequery=False) + <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'> + >>> url('http://host/?a#b', parsefragment=False) + <url scheme: 'http', host: 'host', path: '', query: 'a#b'> + >>> url('http://host/?a#b', parsequery=False, parsefragment=False) + <url scheme: 'http', host: 'host', path: '?a#b'> + + IPv6 addresses: + + >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one') + <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB', + query: 'objectClass?one'> + >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one') + <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]', + port: '80', path: 'c=GB', query: 'objectClass?one'> + + Missing scheme, host, etc.: + + >>> url('://192.0.2.16:80/') + <url path: '://192.0.2.16:80/'> + >>> url('http://mercurial.selenic.com') + <url scheme: 'http', host: 'mercurial.selenic.com'> + >>> url('/foo') + <url path: '/foo'> + >>> url('bundle:/foo') + <url scheme: 'bundle', path: '/foo'> + >>> url('a?b#c') + <url path: 'a?b', fragment: 'c'> + >>> url('http://x.com?arg=/foo') + <url scheme: 'http', host: 'x.com', query: 'arg=/foo'> + >>> url('http://joe:xxx@/foo') + <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'> + + Just a scheme and a path: + + >>> url('mailto:John.Doe@example.com') + <url scheme: 'mailto', path: 'John.Doe@example.com'> + >>> url('a:b:c:d') + <url path: 'a:b:c:d'> + >>> url('aa:bb:cc:dd') + <url scheme: 'aa', path: 'bb:cc:dd'> + + SSH examples: + + >>> url('ssh://joe@host//home/joe') + <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'> + >>> url('ssh://joe:xxx@host/src') + <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'> + >>> url('ssh://joe:xxx@host') + <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'> + >>> url('ssh://joe@host') + <url scheme: 'ssh', user: 'joe', host: 'host'> + >>> url('ssh://host') + <url scheme: 'ssh', host: 'host'> + >>> url('ssh://') + <url scheme: 'ssh'> + >>> url('ssh:') + <url scheme: 'ssh'> + + Non-numeric port: + + >>> url('http://example.com:dd') + <url scheme: 'http', host: 'example.com', port: 'dd'> + >>> url('ssh://joe:xxx@host:ssh/foo') + <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh', + path: 'foo'> + + Bad authentication credentials: + + >>> url('http://joe@joeville:123@4:@host/a?b#c') + <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:', + host: 'host', path: 'a', query: 'b', fragment: 'c'> + >>> url('http://!*#?/@!*#?/:@host/a?b#c') + <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'> + >>> url('http://!*#?@!*#?:@host/a?b#c') + <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'> + >>> url('http://!*@:!*@@host/a?b#c') + <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host', + path: 'a', query: 'b', fragment: 'c'> + + File paths: + + >>> url('a/b/c/d.g.f') + <url path: 'a/b/c/d.g.f'> + >>> url('/x///z/y/') + <url path: '/x///z/y/'> + >>> url('/foo:bar') + <url path: '/foo:bar'> + >>> url('\\\\foo:bar') + <url path: '\\\\foo:bar'> + >>> url('./foo:bar') + <url path: './foo:bar'> + + Non-localhost file URL: + + >>> u = url('file://mercurial.selenic.com/foo') + Traceback (most recent call last): + File "<stdin>", line 1, in ? + Abort: file:// URLs can only refer to localhost + + Empty URL: + + >>> u = url('') + >>> u + <url path: ''> + >>> str(u) + '' + + Empty path with query string: + + >>> str(url('http://foo/?bar')) + 'http://foo/?bar' + + Invalid path: + + >>> u = url('http://foo/bar') + >>> u.path = 'bar' + >>> str(u) + 'http://foo/bar' + + >>> u = url('file:/foo/bar/baz') + >>> u + <url scheme: 'file', path: '/foo/bar/baz'> + >>> str(u) + 'file:///foo/bar/baz' + + >>> u = url('file:///foo/bar/baz') + >>> u + <url scheme: 'file', path: '/foo/bar/baz'> + >>> str(u) + 'file:///foo/bar/baz' + + >>> u = url('file:foo/bar/baz') + >>> u + <url scheme: 'file', path: 'foo/bar/baz'> + >>> str(u) + 'file:foo/bar/baz' + """ + +doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) + # Unicode (IDN) certname isn't supported check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'), 'IDN in certificate not supported')
--- a/tests/test-walk.t Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-walk.t Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,5 @@ - $ mkdir t + $ hg init t $ cd t - $ hg init $ mkdir -p beans $ for b in kidney navy turtle borlotti black pinto; do > echo $b > beans/$b @@ -29,6 +28,7 @@ adding mammals/Procyonidae/coatimundi adding mammals/Procyonidae/raccoon adding mammals/skunk + warning: filename contains ':', which is reserved on Windows: 'glob:glob' $ hg commit -m "commit #0" $ hg debugwalk @@ -159,7 +159,7 @@ f mammals/Procyonidae/raccoon Procyonidae/raccoon f mammals/skunk skunk $ hg debugwalk .hg - abort: path 'mammals/.hg' is inside repo 'mammals' + abort: path 'mammals/.hg' is inside nested repo 'mammals' [255] $ hg debugwalk ../.hg abort: path contains illegal component: .hg @@ -203,7 +203,7 @@ abort: path contains illegal component: .hg/data [255] $ hg debugwalk beans/.hg - abort: path 'beans/.hg' is inside repo 'beans' + abort: path 'beans/.hg' is inside nested repo 'beans' [255] Test absolute paths: @@ -295,6 +295,18 @@ $ hg debugwalk ignored/file f ignored/file ignored/file exact +Test listfile and listfile0 + + $ python -c "file('../listfile0', 'wb').write('fenugreek\0new\0')" + $ hg debugwalk -I 'listfile0:../listfile0' + f fenugreek fenugreek + f new new + $ python -c "file('../listfile', 'wb').write('fenugreek\nnew\r\nmammals/skunk\n')" + $ hg debugwalk -I 'listfile:../listfile' + f fenugreek fenugreek + f mammals/skunk mammals/skunk + f new new + $ cd .. $ hg debugwalk -R t t/mammals/skunk f mammals/skunk t/mammals/skunk exact
--- a/tests/test-walkrepo.py Wed May 18 09:31:19 2011 +0200 +++ b/tests/test-walkrepo.py Wed May 18 09:56:27 2011 +0200 @@ -1,6 +1,6 @@ import os from mercurial import hg, ui -from mercurial.util import walkrepos +from mercurial.scmutil import walkrepos from os import mkdir, chdir from os.path import join as pjoin
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-wireproto.t Wed May 18 09:56:27 2011 +0200 @@ -0,0 +1,112 @@ + +Test wire protocol argument passing + +Setup repo: + + $ hg init repo + +Local: + + $ hg debugwireargs repo eins zwei --three drei --four vier + eins zwei drei vier None + $ hg debugwireargs repo eins zwei --four vier + eins zwei None vier None + $ hg debugwireargs repo eins zwei + eins zwei None None None + $ hg debugwireargs repo eins zwei --five fuenf + eins zwei None None fuenf + +HTTP: + + $ hg serve -R repo -p $HGPORT -d --pid-file=hg1.pid -E error.log -A access.log + $ cat hg1.pid >> $DAEMON_PIDS + + $ hg debugwireargs http://localhost:$HGPORT/ un deux trois quatre + un deux trois quatre None + $ hg debugwireargs http://localhost:$HGPORT/ \ un deux trois\ qu\ \ atre + un deux trois qu atre None + $ hg debugwireargs http://localhost:$HGPORT/ eins zwei --four vier + eins zwei None vier None + $ hg debugwireargs http://localhost:$HGPORT/ eins zwei + eins zwei None None None + $ hg debugwireargs http://localhost:$HGPORT/ eins zwei --five fuenf + eins zwei None None None + $ hg debugwireargs http://localhost:$HGPORT/ un deux trois onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + un deux trois onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx None + $ cat error.log + $ cat access.log + * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=quatre&one=un&three=trois&two=deux (glob) + * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=quatre&one=un&three=trois&two=deux (glob) + * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=qu++atre&one=+un&three=trois+&two=deux (glob) + * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=qu++atre&one=+un&three=trois+&two=deux (glob) + * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=vier&one=eins&two=zwei (glob) + * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=vier&one=eins&two=zwei (glob) + * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei (glob) + * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei (glob) + * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei (glob) + * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei (glob) + * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&one x-hgarg-2:=un&three=trois&two=deux (glob) + * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&one x-hgarg-2:=un&three=trois&two=deux (glob) + +HTTP without the httpheader capability: + + $ HGRCPATH="`pwd`/repo/.hgrc" + $ CAP=httpheader + $ . "$TESTDIR/notcapable" + + $ hg serve -R repo -p $HGPORT2 -d --pid-file=hg2.pid -E error2.log -A access2.log + $ cat hg2.pid >> $DAEMON_PIDS + + $ hg debugwireargs http://localhost:$HGPORT2/ un deux trois quatre + un deux trois quatre None + $ hg debugwireargs http://localhost:$HGPORT2/ eins zwei --four vier + eins zwei None vier None + $ hg debugwireargs http://localhost:$HGPORT2/ eins zwei + eins zwei None None None + $ hg debugwireargs http://localhost:$HGPORT2/ eins zwei --five fuenf + eins zwei None None None + $ cat error2.log + $ cat access2.log + * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob) + * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob) + +SSH (try to exercise the ssh functionality with a dummy script): + + $ cat <<EOF > dummyssh + > import sys + > import os + > os.chdir(os.path.dirname(sys.argv[0])) + > if sys.argv[1] != "user@dummy": + > sys.exit(-1) + > if not os.path.exists("dummyssh"): + > sys.exit(-1) + > os.environ["SSH_CLIENT"] = "127.0.0.1 1 2" + > r = os.system(sys.argv[2]) + > sys.exit(bool(r)) + > EOF + + $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo uno due tre quattro + uno due tre quattro None + $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo eins zwei --four vier + eins zwei None vier None + $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo eins zwei + eins zwei None None None + $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo eins zwei --five fuenf + eins zwei None None None +
--- a/tests/tinyproxy.py Wed May 18 09:31:19 2011 +0200 +++ b/tests/tinyproxy.py Wed May 18 09:56:27 2011 +0200 @@ -30,6 +30,12 @@ else: self.__base_handle() + def log_request(self, code='-', size='-'): + xheaders = [h for h in self.headers.items() if h[0].startswith('x-')] + self.log_message('"%s" %s %s%s', + self.requestline, str(code), str(size), + ''.join([' %s:%s' % h for h in sorted(xheaders)])) + def _connect_to(self, netloc, soc): i = netloc.find(':') if i >= 0: