Mercurial > hg
changeset 15273:384082750f2c stable 2.0-rc
merge default into stable for 2.0 code freeze
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Sat, 15 Oct 2011 14:30:50 -0500 |
parents | fccd350acf79 (current diff) 2889d4574726 (diff) |
children | a06575962c9e |
files | |
diffstat | 210 files changed, 8844 insertions(+), 1785 deletions(-) [+] |
line wrap: on
line diff
--- a/contrib/check-code.py Sun Oct 02 16:41:07 2011 -0500 +++ b/contrib/check-code.py Sat Oct 15 14:30:50 2011 -0500 @@ -148,7 +148,7 @@ (r'(?<!def)\s+(any|all|format)\(', "any/all/format not available in Python 2.4"), (r'(?<!def)\s+(callable)\(', - "callable not available in Python 3, use hasattr(f, '__call__')"), + "callable not available in Python 3, use getattr(f, '__call__', None)"), (r'if\s.*\selse', "if ... else form not available in Python 2.4"), (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist), "gratuitous whitespace after Python keyword"), @@ -168,6 +168,8 @@ "comparison with singleton, use 'is' or 'is not' instead"), (r'^\s*(while|if) [01]:', "use True/False for constant Boolean expression"), + (r'(?<!def)\s+hasattr', + 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'), (r'opener\([^)]*\).read\(', "use opener.read() instead"), (r'opener\([^)]*\).write\(',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/debugcmdserver.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# +# Dumps output generated by Mercurial's command server in a formatted style to a +# given file or stderr if '-' is specified. Output is also written in its raw +# format to stdout. +# +# $ ./hg serve --cmds pipe | ./contrib/debugcmdserver.py - +# o, 52 -> 'capabilities: getencoding runcommand\nencoding: UTF-8' + +import sys, struct + +if len(sys.argv) != 2: + print 'usage: debugcmdserver.py FILE' + sys.exit(1) + +outputfmt = '>cI' +outputfmtsize = struct.calcsize(outputfmt) + +if sys.argv[1] == '-': + log = sys.stderr +else: + log = open(sys.argv[1], 'a') + +def read(size): + data = sys.stdin.read(size) + if not data: + raise EOFError() + sys.stdout.write(data) + sys.stdout.flush() + return data + +try: + while True: + header = read(outputfmtsize) + channel, length = struct.unpack(outputfmt, header) + log.write('%s, %-4d' % (channel, length)) + if channel in 'IL': + log.write(' -> waiting for input\n') + else: + data = read(length) + log.write(' -> %r\n' % data) + log.flush() +except EOFError: + pass +finally: + if log != sys.stderr: + log.close()
--- a/contrib/setup3k.py Sun Oct 02 16:41:07 2011 -0500 +++ b/contrib/setup3k.py Sat Oct 15 14:30:50 2011 -0500 @@ -8,7 +8,7 @@ from lib2to3.refactor import get_fixers_from_package as getfixers import sys -if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'): +if getattr(sys, 'version_info', (0, 0, 0)) < (2, 4, 0, 'final'): raise SystemExit("Mercurial requires Python 2.4 or later.") if sys.version_info[0] >= 3: @@ -236,7 +236,7 @@ try: build_ext.build_extension(self, ext) except CCompilerError: - if not hasattr(ext, 'optional') or not ext.optional: + if getattr(ext, 'optional', False): raise log.warn("Failed to build optional extension '%s' (skipping)", ext.name)
--- a/contrib/win32/hgwebdir_wsgi.py Sun Oct 02 16:41:07 2011 -0500 +++ b/contrib/win32/hgwebdir_wsgi.py Sat Oct 15 14:30:50 2011 -0500 @@ -50,7 +50,7 @@ #sys.path.insert(0, r'c:\path\to\python\lib') # Enable tracing. Run 'python -m win32traceutil' to debug -if hasattr(sys, 'isapidllhandle'): +if getattr(sys, 'isapidllhandle', None) is not None: import win32traceutil # To serve pages in local charset instead of UTF-8, remove the two lines below
--- a/contrib/zsh_completion Sun Oct 02 16:41:07 2011 -0500 +++ b/contrib/zsh_completion Sat Oct 15 14:30:50 2011 -0500 @@ -165,6 +165,7 @@ _hg_labels() { _hg_tags "$@" _hg_bookmarks "$@" + _hg_branches "$@" } _hg_tags() { @@ -191,6 +192,17 @@ (( $#bookmarks )) && _describe -t bookmarks 'bookmarks' bookmarks } +_hg_branches() { + typeset -a branches + local branch + + _hg_cmd branches | while read branch + do + branches+=(${branch/ # [0-9]#:*}) + done + (( $#branches )) && _describe -t branches 'branches' branches +} + # likely merge candidates _hg_mergerevs() { typeset -a heads @@ -617,6 +629,7 @@ '(--only-merges -m)'{-m,--only-merges}'[show only merges]' \ '(--patch -p)'{-p,--patch}'[show patch]' \ '(--prune -P)'{-P+,--prune}'[do not display revision or any of its ancestors]:revision:_hg_labels' \ + '(--branch -b)'{-b+,--branch}'[show changesets within the given named branch]:branch:_hg_branches' \ '*:files:_hg_files' }
--- a/doc/gendoc.py Sun Oct 02 16:41:07 2011 -0500 +++ b/doc/gendoc.py Sat Oct 15 14:30:50 2011 -0500 @@ -9,6 +9,7 @@ from mercurial.i18n import _ from mercurial.help import helptable from mercurial import extensions +from mercurial import util def get_desc(docstr): if not docstr: @@ -95,7 +96,7 @@ ui.write(".. _%s:\n" % name) ui.write("\n") section(ui, sec) - if hasattr(doc, '__call__'): + if util.safehasattr(doc, '__call__'): doc = doc() ui.write(doc) ui.write("\n")
--- a/hgext/acl.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/acl.py Sat Oct 15 14:30:50 2011 -0500 @@ -216,6 +216,8 @@ if user is None: user = getpass.getuser() + ui.debug('acl: checking access for user "%s"\n' % user) + cfg = ui.config('acl', 'config') if cfg: ui.readconfig(cfg, sections = ['acl.groups', 'acl.allow.branches', @@ -242,9 +244,9 @@ for f in ctx.files(): if deny and deny(f): - ui.debug('acl: user %s denied on %s\n' % (user, f)) - raise util.Abort(_('acl: access denied for changeset %s') % ctx) + raise util.Abort(_('acl: user "%s" denied on "%s"' + ' (changeset "%s")') % (user, f, ctx)) if allow and not allow(f): - ui.debug('acl: user %s not allowed on %s\n' % (user, f)) - raise util.Abort(_('acl: access denied for changeset %s') % ctx) - ui.debug('acl: allowing changeset %s\n' % ctx) + raise util.Abort(_('acl: user "%s" not allowed on "%s"' + ' (changeset "%s")') % (user, f, ctx)) + ui.debug('acl: path access granted: "%s"\n' % ctx)
--- a/hgext/color.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/color.py Sat Oct 15 14:30:50 2011 -0500 @@ -68,6 +68,9 @@ branches.current = green branches.inactive = none + tags.normal = green + tags.local = black bold + 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 @@ -257,7 +260,9 @@ 'status.ignored': 'black bold', 'status.modified': 'blue bold', 'status.removed': 'red bold', - 'status.unknown': 'magenta bold underline'} + 'status.unknown': 'magenta bold underline', + 'tags.normal': 'green', + 'tags.local': 'black bold'} def _effect_str(effect):
--- a/hgext/convert/cvsps.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/convert/cvsps.py Sat Oct 15 14:30:50 2011 -0500 @@ -11,6 +11,7 @@ from mercurial import util from mercurial.i18n import _ from mercurial import hook +from mercurial import util class logentry(object): '''Class logentry has the following attributes: @@ -362,8 +363,14 @@ elif state == 8: # store commit log message if re_31.match(line): - state = 5 - store = True + cpeek = peek + if cpeek.endswith('\n'): + cpeek = cpeek[:-1] + if re_50.match(cpeek): + state = 5 + store = True + else: + e.comment.append(line) elif re_32.match(line): state = 0 store = True @@ -513,8 +520,8 @@ e.comment == c.comment and e.author == c.author and e.branch == c.branch and - (not hasattr(e, 'branchpoints') or - not hasattr (c, 'branchpoints') or + (not util.safehasattr(e, 'branchpoints') or + not util.safehasattr (c, 'branchpoints') or e.branchpoints == c.branchpoints) and ((c.date[0] + c.date[1]) <= (e.date[0] + e.date[1]) <=
--- a/hgext/convert/filemap.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/convert/filemap.py Sat Oct 15 14:30:50 2011 -0500 @@ -375,3 +375,6 @@ def lookuprev(self, rev): return self.base.lookuprev(rev) + + def getbookmarks(self): + return self.base.getbookmarks()
--- a/hgext/convert/git.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/convert/git.py Sat Oct 15 14:30:50 2011 -0500 @@ -16,7 +16,7 @@ # Windows does not support GIT_DIR= construct while other systems # cannot remove environment variable. Just assume none have # both issues. - if hasattr(os, 'unsetenv'): + if util.safehasattr(os, 'unsetenv'): def gitopen(self, s, noerr=False): prevgitdir = os.environ.get('GIT_DIR') os.environ['GIT_DIR'] = self.path
--- a/hgext/convert/hg.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/convert/hg.py Sat Oct 15 14:30:50 2011 -0500 @@ -70,10 +70,10 @@ self.wlock.release() def revmapfile(self): - return os.path.join(self.path, ".hg", "shamap") + return self.repo.join("shamap") def authorfile(self): - return os.path.join(self.path, ".hg", "authormap") + return self.repo.join("authormap") def getheads(self): h = self.repo.changelog.heads() @@ -178,7 +178,7 @@ closed = 'close' in commit.extra if not closed and not man.cmp(m1node, man.revision(mnode)): self.ui.status(_("filtering out empty revision\n")) - self.repo.rollback() + self.repo.rollback(force=True) return parent return p2 @@ -364,8 +364,7 @@ def converted(self, rev, destrev): if self.convertfp is None: - self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'), - 'a') + self.convertfp = open(self.repo.join('shamap'), 'a') self.convertfp.write('%s %s\n' % (destrev, rev)) self.convertfp.flush()
--- a/hgext/convert/subversion.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/convert/subversion.py Sat Oct 15 14:30:50 2011 -0500 @@ -501,11 +501,11 @@ and not p[2].startswith(badroot + '/')] # Tell tag renamings from tag creations - remainings = [] + renamings = [] for source, sourcerev, dest in pendings: tagname = dest.split('/')[-1] if source.startswith(srctagspath): - remainings.append([source, sourcerev, tagname]) + renamings.append([source, sourcerev, tagname]) continue if tagname in tags: # Keep the latest tag value @@ -521,7 +521,7 @@ # but were really created in the tag # directory. pass - pendings = remainings + pendings = renamings tagspath = srctagspath finally: stream.close()
--- a/hgext/convert/transport.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/convert/transport.py Sat Oct 15 14:30:50 2011 -0500 @@ -54,7 +54,7 @@ if p: providers.append(p) else: - if hasattr(svn.client, 'get_windows_simple_provider'): + if util.safehasattr(svn.client, 'get_windows_simple_provider'): providers.append(svn.client.get_windows_simple_provider(pool)) return svn.core.svn_auth_open(providers, pool) @@ -73,7 +73,7 @@ self.password = '' # Only Subversion 1.4 has reparent() - if ra is None or not hasattr(svn.ra, 'reparent'): + if ra is None or not util.safehasattr(svn.ra, 'reparent'): self.client = svn.client.create_context(self.pool) ab = _create_auth_baton(self.pool) if False:
--- a/hgext/eol.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/eol.py Sat Oct 15 14:30:50 2011 -0500 @@ -52,9 +52,10 @@ The rules will first apply when files are touched in the working copy, e.g. by updating to null and back to tip to touch all files. -The extension uses an optional ``[eol]`` section in your hgrc file -(not the ``.hgeol`` file) for settings that control the overall -behavior. There are two settings: +The extension uses an optional ``[eol]`` section read from both the +normal Mercurial configuration files and the ``.hgeol`` file, with the +latter overriding the former. You can use that section to control the +overall behavior. There are three settings: - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or ``CRLF`` to override the default interpretation of ``native`` for @@ -67,6 +68,10 @@ Such files are normally not touched under the assumption that they have mixed EOLs on purpose. +- ``eol.fix-trailing-newline`` (default False) can be set to True to + ensure that converted files end with a EOL character (either ``\\n`` + or ``\\r\\n`` as per the configured patterns). + The extension provides ``cleverencode:`` and ``cleverdecode:`` filters like the deprecated win32text extension does. This means that you can disable win32text and enable eol and your filters will still work. You @@ -106,6 +111,8 @@ return s if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s): return s + if ui.configbool('eol', 'fix-trailing-newline', False) and s and s[-1] != '\n': + s = s + '\n' return eolre.sub('\n', s) def tocrlf(s, params, ui, **kwargs): @@ -114,6 +121,8 @@ return s if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s): return s + if ui.configbool('eol', 'fix-trailing-newline', False) and s and s[-1] != '\n': + s = s + '\n' return eolre.sub('\r\n', s) def isbinary(s, params): @@ -158,7 +167,7 @@ # about inconsistent newlines. self.match = match.match(root, '', [], include, exclude) - def setfilters(self, ui): + def copytoui(self, ui): for pattern, style in self.cfg.items('patterns'): key = style.upper() try: @@ -167,6 +176,9 @@ except KeyError: ui.warn(_("ignoring unknown EOL style '%s' from %s\n") % (style, self.cfg.source('patterns', pattern))) + # eol.only-consistent can be specified in ~/.hgrc or .hgeol + for k, v in self.cfg.items('eol'): + ui.setconfig('eol', k, v) def checkrev(self, repo, ctx, files): failed = [] @@ -273,7 +285,7 @@ eol = parseeol(self.ui, self, nodes) if eol is None: return None - eol.setfilters(self.ui) + eol.copytoui(self.ui) return eol.match def _hgcleardirstate(self):
--- a/hgext/inotify/__init__.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/inotify/__init__.py Sat Oct 15 14:30:50 2011 -0500 @@ -11,6 +11,7 @@ # todo: socket permissions from mercurial.i18n import _ +from mercurial import util import server from client import client, QueryFailed @@ -31,7 +32,7 @@ ui.write((' %s/\n') % path) def reposetup(ui, repo): - if not hasattr(repo, 'dirstate'): + if not util.safehasattr(repo, 'dirstate'): return class inotifydirstate(repo.dirstate.__class__):
--- a/hgext/keyword.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/keyword.py Sat Oct 15 14:30:50 2011 -0500 @@ -249,10 +249,14 @@ kwcmd = self.restrict and lookup # kwexpand/kwshrink if self.restrict or expand and lookup: mf = ctx.manifest() - lctx = ctx - re_kw = (self.restrict or rekw) and self.rekw or self.rekwexp - msg = (expand and _('overwriting %s expanding keywords\n') - or _('overwriting %s shrinking keywords\n')) + if self.restrict or rekw: + re_kw = self.rekw + else: + re_kw = self.rekwexp + if expand: + msg = _('overwriting %s expanding keywords\n') + else: + msg = _('overwriting %s shrinking keywords\n') for f in candidates: if self.restrict: data = self.repo.file(f).read(mf[f]) @@ -262,18 +266,17 @@ continue if expand: if lookup: - lctx = self.linkctx(f, mf[f]) - data, found = self.substitute(data, f, lctx, re_kw.subn) + ctx = self.linkctx(f, mf[f]) + data, found = self.substitute(data, f, ctx, re_kw.subn) elif self.restrict: found = re_kw.search(data) else: data, found = _shrinktext(data, re_kw.subn) if found: self.ui.note(msg % f) - fpath = self.repo.wjoin(f) - mode = os.lstat(fpath).st_mode - self.repo.wwrite(f, data, ctx.flags(f)) - os.chmod(fpath, mode) + fp = self.repo.wopener(f, "wb", atomictemp=True) + fp.write(data) + fp.close() if kwcmd: self.repo.dirstate.normal(f) elif self.record: @@ -296,7 +299,9 @@ def wread(self, fname, data): '''If in restricted mode returns data read from wdir with keyword substitutions removed.''' - return self.restrict and self.shrink(fname, data) or data + if self.restrict: + return self.shrink(fname, data) + return data class kwfilelog(filelog.filelog): ''' @@ -325,11 +330,11 @@ text = self.kwt.shrink(self.path, text) return super(kwfilelog, self).cmp(node, text) -def _status(ui, repo, kwt, *pats, **opts): +def _status(ui, repo, wctx, kwt, *pats, **opts): '''Bails out if [keyword] configuration is not active. Returns status of working directory.''' if kwt: - return repo.status(match=scmutil.match(repo[None], pats, opts), clean=True, + return repo.status(match=scmutil.match(wctx, pats, opts), clean=True, unknown=opts.get('unknown') or opts.get('all')) if ui.configitems('keyword'): raise util.Abort(_('[keyword] patterns cannot match')) @@ -343,7 +348,7 @@ kwt = kwtools['templater'] wlock = repo.wlock() try: - status = _status(ui, repo, kwt, *pats, **opts) + status = _status(ui, repo, wctx, kwt, *pats, **opts) modified, added, removed, deleted, unknown, ignored, clean = status if modified or added or removed or deleted: raise util.Abort(_('outstanding uncommitted changes')) @@ -415,7 +420,10 @@ ui.setconfig('keywordmaps', k, v) else: ui.status(_('\n\tconfiguration using current keyword template maps\n')) - kwmaps = dict(uikwmaps) or _defaultkwmaps(ui) + if uikwmaps: + kwmaps = dict(uikwmaps) + else: + kwmaps = _defaultkwmaps(ui) uisetup(ui) reposetup(ui, repo) @@ -478,13 +486,13 @@ i = ignored (not tracked) ''' kwt = kwtools['templater'] - status = _status(ui, repo, kwt, *pats, **opts) + wctx = repo[None] + status = _status(ui, repo, wctx, kwt, *pats, **opts) cwd = pats and repo.getcwd() or '' modified, added, removed, deleted, unknown, ignored, clean = status files = [] if not opts.get('unknown') or opts.get('all'): files = sorted(modified + added + clean) - wctx = repo[None] kwfiles = kwt.iskwfile(files, wctx) kwdeleted = kwt.iskwfile(deleted, wctx) kwunknown = kwt.iskwfile(unknown, wctx) @@ -582,12 +590,12 @@ kwt.restrict = restrict return n - def rollback(self, dryrun=False): + def rollback(self, dryrun=False, force=False): wlock = self.wlock() try: if not dryrun: changed = self['.'].files() - ret = super(kwrepo, self).rollback(dryrun) + ret = super(kwrepo, self).rollback(dryrun, force) if not dryrun: ctx = self['.'] modified, added = _preselect(self[None].status(), changed)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/CONTRIBUTORS Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,4 @@ +Greg Ward, author of the original bfiles extension +Na'Tosha Bard of Unity Technologies +Fog Creek Software +Special thanks to the University of Toronto and the UCOSP program
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/__init__.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,94 @@ +# Copyright 2009-2010 Gregory P. Ward +# Copyright 2009-2010 Intelerad Medical Systems Incorporated +# Copyright 2010-2011 Fog Creek Software +# Copyright 2010-2011 Unity Technologies +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''track large binary files + +Large binary files tend to be not very compressible, not very +diffable, and not at all mergeable. Such files are not handled +efficiently by Mercurial's storage format (revlog), which is based on +compressed binary deltas; storing large binary files as regular +Mercurial files wastes bandwidth and disk space and increases +Mercurial's memory usage. The largefiles extension addresses these +problems by adding a centralized client-server layer on top of +Mercurial: largefiles live in a *central store* out on the network +somewhere, and you only fetch the revisions that you need when you +need them. + +largefiles works by maintaining a "standin file" in .hglf/ for each +largefile. The standins are small (41 bytes: an SHA-1 hash plus +newline) and are tracked by Mercurial. Largefile revisions are +identified by the SHA-1 hash of their contents, which is written to +the standin. largefiles uses that revision ID to get/put largefile +revisions from/to the central store. This saves both disk space and +bandwidth, since you don't need to retrieve all historical revisions +of large files when you clone or pull. + +To start a new repository or add new large binary files, just add +--large to your ``hg add`` command. For example:: + + $ dd if=/dev/urandom of=randomdata count=2000 + $ hg add --large randomdata + $ hg commit -m 'add randomdata as a largefile' + +When you push a changeset that adds/modifies largefiles to a remote +repository, its largefile revisions will be uploaded along with it. +Note that the remote Mercurial must also have the largefiles extension +enabled for this to work. + +When you pull a changeset that affects largefiles from a remote +repository, Mercurial behaves as normal. However, when you update to +such a revision, any largefiles needed by that revision are downloaded +and cached (if they have never been downloaded before). This means +that network access may be required to update to changesets you have +not previously updated to. + +If you already have large files tracked by Mercurial without the +largefiles extension, you will need to convert your repository in +order to benefit from largefiles. This is done with the 'hg lfconvert' +command:: + + $ hg lfconvert --size 10 oldrepo newrepo + +In repositories that already have largefiles in them, any new file +over 10MB will automatically be added as a largefile. To change this +threshhold, set ``largefiles.size`` in your Mercurial config file to +the minimum size in megabytes to track as a largefile, or use the +--lfsize option to the add command (also in megabytes):: + + [largefiles] + size = 2 XXX wouldn't minsize be a better name? + + $ hg add --lfsize 2 + +The ``largefiles.patterns`` config option allows you to specify a list +of filename patterns (see ``hg help patterns``) that should always be +tracked as largefiles:: + + [largefiles] + patterns = + *.jpg + re:.*\.(png|bmp)$ + library.zip + content/audio/* + +Files that match one of these patterns will be added as largefiles +regardless of their size. +''' + +from mercurial import commands + +import lfcommands +import reposetup +import uisetup + +reposetup = reposetup.reposetup +uisetup = uisetup.uisetup + +commands.norepo += " lfconvert" + +cmdtable = lfcommands.cmdtable
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/basestore.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,202 @@ +# Copyright 2009-2010 Gregory P. Ward +# Copyright 2009-2010 Intelerad Medical Systems Incorporated +# Copyright 2010-2011 Fog Creek Software +# Copyright 2010-2011 Unity Technologies +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''base class for store implementations and store-related utility code''' + +import os +import tempfile +import binascii +import re + +from mercurial import util, node, hg +from mercurial.i18n import _ + +import lfutil + +class StoreError(Exception): + '''Raised when there is a problem getting files from or putting + files to a central store.''' + def __init__(self, filename, hash, url, detail): + self.filename = filename + self.hash = hash + self.url = url + self.detail = detail + + def longmessage(self): + if self.url: + return ('%s: %s\n' + '(failed URL: %s)\n' + % (self.filename, self.detail, self.url)) + else: + return ('%s: %s\n' + '(no default or default-push path set in hgrc)\n' + % (self.filename, self.detail)) + + def __str__(self): + return "%s: %s" % (self.url, self.detail) + +class basestore(object): + def __init__(self, ui, repo, url): + self.ui = ui + self.repo = repo + self.url = url + + def put(self, source, hash): + '''Put source file into the store under <filename>/<hash>.''' + raise NotImplementedError('abstract method') + + def exists(self, hash): + '''Check to see if the store contains the given hash.''' + raise NotImplementedError('abstract method') + + def get(self, files): + '''Get the specified largefiles from the store and write to local + files under repo.root. files is a list of (filename, hash) + tuples. Return (success, missing), lists of files successfuly + downloaded and those not found in the store. success is a list + of (filename, hash) tuples; missing is a list of filenames that + we could not get. (The detailed error message will already have + been presented to the user, so missing is just supplied as a + summary.)''' + success = [] + missing = [] + ui = self.ui + + at = 0 + for filename, hash in files: + ui.progress(_('getting largefiles'), at, unit='lfile', + total=len(files)) + at += 1 + ui.note(_('getting %s:%s\n') % (filename, hash)) + + cachefilename = lfutil.cachepath(self.repo, hash) + cachedir = os.path.dirname(cachefilename) + + # No need to pass mode='wb' to fdopen(), since mkstemp() already + # opened the file in binary mode. + (tmpfd, tmpfilename) = tempfile.mkstemp( + dir=cachedir, prefix=os.path.basename(filename)) + tmpfile = os.fdopen(tmpfd, 'w') + + try: + hhash = binascii.hexlify(self._getfile(tmpfile, filename, hash)) + except StoreError, err: + ui.warn(err.longmessage()) + hhash = "" + + if hhash != hash: + if hhash != "": + ui.warn(_('%s: data corruption (expected %s, got %s)\n') + % (filename, hash, hhash)) + tmpfile.close() # no-op if it's already closed + os.remove(tmpfilename) + missing.append(filename) + continue + + if os.path.exists(cachefilename): # Windows + os.remove(cachefilename) + os.rename(tmpfilename, cachefilename) + lfutil.linktosystemcache(self.repo, hash) + success.append((filename, hhash)) + + ui.progress(_('getting largefiles'), None) + return (success, missing) + + def verify(self, revs, contents=False): + '''Verify the existence (and, optionally, contents) of every big + file revision referenced by every changeset in revs. + Return 0 if all is well, non-zero on any errors.''' + write = self.ui.write + failed = False + + write(_('searching %d changesets for largefiles\n') % len(revs)) + verified = set() # set of (filename, filenode) tuples + + for rev in revs: + cctx = self.repo[rev] + cset = "%d:%s" % (cctx.rev(), node.short(cctx.node())) + + failed = lfutil.any_(self._verifyfile( + cctx, cset, contents, standin, verified) for standin in cctx) + + num_revs = len(verified) + num_lfiles = len(set([fname for (fname, fnode) in verified])) + if contents: + write(_('verified contents of %d revisions of %d largefiles\n') + % (num_revs, num_lfiles)) + else: + write(_('verified existence of %d revisions of %d largefiles\n') + % (num_revs, num_lfiles)) + + return int(failed) + + def _getfile(self, tmpfile, filename, hash): + '''Fetch one revision of one file from the store and write it + to tmpfile. Compute the hash of the file on-the-fly as it + downloads and return the binary hash. Close tmpfile. Raise + StoreError if unable to download the file (e.g. it does not + exist in the store).''' + raise NotImplementedError('abstract method') + + def _verifyfile(self, cctx, cset, contents, standin, verified): + '''Perform the actual verification of a file in the store. + ''' + raise NotImplementedError('abstract method') + +import localstore, wirestore + +_storeprovider = { + 'file': [localstore.localstore], + 'http': [wirestore.wirestore], + 'https': [wirestore.wirestore], + 'ssh': [wirestore.wirestore], + } + +_scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://') + +# During clone this function is passed the src's ui object +# but it needs the dest's ui object so it can read out of +# the config file. Use repo.ui instead. +def _openstore(repo, remote=None, put=False): + ui = repo.ui + + if not remote: + path = (getattr(repo, 'lfpullsource', None) or + ui.expandpath('default-push', 'default')) + + # ui.expandpath() leaves 'default-push' and 'default' alone if + # they cannot be expanded: fallback to the empty string, + # meaning the current directory. + if path == 'default-push' or path == 'default': + path = '' + remote = repo + else: + remote = hg.peer(repo, {}, path) + + # The path could be a scheme so use Mercurial's normal functionality + # to resolve the scheme to a repository and use its path + path = util.safehasattr(remote, 'url') and remote.url() or remote.path + + match = _scheme_re.match(path) + if not match: # regular filesystem path + scheme = 'file' + else: + scheme = match.group(1) + + try: + storeproviders = _storeprovider[scheme] + except KeyError: + raise util.Abort(_('unsupported URL scheme %r') % scheme) + + for class_obj in storeproviders: + try: + return class_obj(ui, repo, remote) + except lfutil.storeprotonotcapable: + pass + + raise util.Abort(_('%s does not appear to be a largefile store'), path)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/design.txt Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,49 @@ += largefiles - manage large binary files = +This extension is based off of Greg Ward's bfiles extension which can be found +at http://mercurial.selenic.com/wiki/BfilesExtension. + +== The largefile store == + +largefile stores are, in the typical use case, centralized servers that have +every past revision of a given binary file. Each largefile is identified by +its sha1 hash, and all interactions with the store take one of the following +forms. + +-Download a bfile with this hash +-Upload a bfile with this hash +-Check if the store has a bfile with this hash + +largefiles stores can take one of two forms: + +-Directories on a network file share +-Mercurial wireproto servers, either via ssh or http (hgweb) + +== The Local Repository == + +The local repository has a largefile cache in .hg/largefiles which holds a +subset of the largefiles needed. On a clone only the largefiles at tip are +downloaded. When largefiles are downloaded from the central store, a copy is +saved in this store. + +== The Global Cache == + +largefiles in a local repository cache are hardlinked to files in the global +cache. Before a file is downloaded we check if it is in the global cache. + +== Implementation Details == + +Each largefile has a standin which is in .hglf. The standin is tracked by +Mercurial. The standin contains the SHA1 hash of the largefile. When a +largefile is added/removed/copied/renamed/etc the same operation is applied to +the standin. Thus the history of the standin is the history of the largefile. + +For performance reasons, the contents of a standin are only updated before a +commit. Standins are added/removed/copied/renamed from add/remove/copy/rename +Mercurial commands but their contents will not be updated. The contents of a +standin will always be the hash of the largefile as of the last commit. To +support some commands (revert) some standins are temporarily updated but will +be changed back after the command is finished. + +A Mercurial dirstate object tracks the state of the largefiles. The dirstate +uses the last modified time and current size to detect if a file has changed +(without reading the entire contents of the file).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/lfcommands.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,481 @@ +# Copyright 2009-2010 Gregory P. Ward +# Copyright 2009-2010 Intelerad Medical Systems Incorporated +# Copyright 2010-2011 Fog Creek Software +# Copyright 2010-2011 Unity Technologies +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''High-level command function for lfconvert, plus the cmdtable.''' + +import os +import shutil + +from mercurial import util, match as match_, hg, node, context, error +from mercurial.i18n import _ + +import lfutil +import basestore + +# -- Commands ---------------------------------------------------------- + +def lfconvert(ui, src, dest, *pats, **opts): + '''convert a normal repository to a largefiles repository + + Convert repository SOURCE to a new repository DEST, identical to + SOURCE except that certain files will be converted as largefiles: + specifically, any file that matches any PATTERN *or* whose size is + above the minimum size threshold is converted as a largefile. The + size used to determine whether or not to track a file as a + largefile is the size of the first version of the file. The + minimum size can be specified either with --size or in + configuration as ``largefiles.size``. + + After running this command you will need to make sure that + largefiles is enabled anywhere you intend to push the new + repository. + + Use --tonormal to convert largefiles back to normal files; after + this, the DEST repository can be used without largefiles at all.''' + + if opts['tonormal']: + tolfile = False + else: + tolfile = True + size = lfutil.getminsize(ui, True, opts.get('size'), default=None) + try: + rsrc = hg.repository(ui, src) + if not rsrc.local(): + raise util.Abort(_('%s is not a local Mercurial repo') % src) + except error.RepoError, err: + ui.traceback() + raise util.Abort(err.args[0]) + if os.path.exists(dest): + if not os.path.isdir(dest): + raise util.Abort(_('destination %s already exists') % dest) + elif os.listdir(dest): + raise util.Abort(_('destination %s is not empty') % dest) + try: + ui.status(_('initializing destination %s\n') % dest) + rdst = hg.repository(ui, dest, create=True) + if not rdst.local(): + raise util.Abort(_('%s is not a local Mercurial repo') % dest) + except error.RepoError: + ui.traceback() + raise util.Abort(_('%s is not a repo') % dest) + + success = False + try: + # Lock destination to prevent modification while it is converted to. + # Don't need to lock src because we are just reading from its history + # which can't change. + dst_lock = rdst.lock() + + # Get a list of all changesets in the source. The easy way to do this + # is to simply walk the changelog, using changelog.nodesbewteen(). + # Take a look at mercurial/revlog.py:639 for more details. + # Use a generator instead of a list to decrease memory usage + ctxs = (rsrc[ctx] for ctx in rsrc.changelog.nodesbetween(None, + rsrc.heads())[0]) + revmap = {node.nullid: node.nullid} + if tolfile: + lfiles = set() + normalfiles = set() + if not pats: + pats = ui.config(lfutil.longname, 'patterns', default=()) + if pats: + pats = pats.split(' ') + if pats: + matcher = match_.match(rsrc.root, '', list(pats)) + else: + matcher = None + + lfiletohash = {} + for ctx in ctxs: + ui.progress(_('converting revisions'), ctx.rev(), + unit=_('revision'), total=rsrc['tip'].rev()) + _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, + lfiles, normalfiles, matcher, size, lfiletohash) + ui.progress(_('converting revisions'), None) + + if os.path.exists(rdst.wjoin(lfutil.shortname)): + shutil.rmtree(rdst.wjoin(lfutil.shortname)) + + for f in lfiletohash.keys(): + if os.path.isfile(rdst.wjoin(f)): + os.unlink(rdst.wjoin(f)) + try: + os.removedirs(os.path.dirname(rdst.wjoin(f))) + except OSError: + pass + + else: + for ctx in ctxs: + ui.progress(_('converting revisions'), ctx.rev(), + unit=_('revision'), total=rsrc['tip'].rev()) + _addchangeset(ui, rsrc, rdst, ctx, revmap) + + ui.progress(_('converting revisions'), None) + success = True + finally: + if not success: + # we failed, remove the new directory + shutil.rmtree(rdst.root) + dst_lock.release() + +def _addchangeset(ui, rsrc, rdst, ctx, revmap): + # Convert src parents to dst parents + parents = [] + for p in ctx.parents(): + parents.append(revmap[p.node()]) + while len(parents) < 2: + parents.append(node.nullid) + + # Generate list of changed files + files = set(ctx.files()) + if node.nullid not in parents: + mc = ctx.manifest() + mp1 = ctx.parents()[0].manifest() + mp2 = ctx.parents()[1].manifest() + files |= (set(mp1) | set(mp2)) - set(mc) + for f in mc: + if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None): + files.add(f) + + def getfilectx(repo, memctx, f): + if lfutil.standin(f) in files: + # if the file isn't in the manifest then it was removed + # or renamed, raise IOError to indicate this + try: + fctx = ctx.filectx(lfutil.standin(f)) + except error.LookupError: + raise IOError() + renamed = fctx.renamed() + if renamed: + renamed = lfutil.splitstandin(renamed[0]) + + hash = fctx.data().strip() + path = lfutil.findfile(rsrc, hash) + ### TODO: What if the file is not cached? + data = '' + fd = None + try: + fd = open(path, 'rb') + data = fd.read() + finally: + if fd: + fd.close() + return context.memfilectx(f, data, 'l' in fctx.flags(), + 'x' in fctx.flags(), renamed) + else: + try: + fctx = ctx.filectx(f) + except error.LookupError: + raise IOError() + renamed = fctx.renamed() + if renamed: + renamed = renamed[0] + data = fctx.data() + if f == '.hgtags': + newdata = [] + for line in data.splitlines(): + id, name = line.split(' ', 1) + newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]), + name)) + data = ''.join(newdata) + return context.memfilectx(f, data, 'l' in fctx.flags(), + 'x' in fctx.flags(), renamed) + + dstfiles = [] + for file in files: + if lfutil.isstandin(file): + dstfiles.append(lfutil.splitstandin(file)) + else: + dstfiles.append(file) + # Commit + mctx = context.memctx(rdst, parents, ctx.description(), dstfiles, + getfilectx, ctx.user(), ctx.date(), ctx.extra()) + ret = rdst.commitctx(mctx) + rdst.dirstate.setparents(ret) + revmap[ctx.node()] = rdst.changelog.tip() + +def _lfconvert_addchangeset(rsrc, rdst, ctx, revmap, lfiles, normalfiles, + matcher, size, lfiletohash): + # Convert src parents to dst parents + parents = [] + for p in ctx.parents(): + parents.append(revmap[p.node()]) + while len(parents) < 2: + parents.append(node.nullid) + + # Generate list of changed files + files = set(ctx.files()) + if node.nullid not in parents: + mc = ctx.manifest() + mp1 = ctx.parents()[0].manifest() + mp2 = ctx.parents()[1].manifest() + files |= (set(mp1) | set(mp2)) - set(mc) + for f in mc: + if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None): + files.add(f) + + dstfiles = [] + for f in files: + if f not in lfiles and f not in normalfiles: + islfile = _islfile(f, ctx, matcher, size) + # If this file was renamed or copied then copy + # the lfileness of its predecessor + if f in ctx.manifest(): + fctx = ctx.filectx(f) + renamed = fctx.renamed() + renamedlfile = renamed and renamed[0] in lfiles + islfile |= renamedlfile + if 'l' in fctx.flags(): + if renamedlfile: + raise util.Abort( + _('Renamed/copied largefile %s becomes symlink') + % f) + islfile = False + if islfile: + lfiles.add(f) + else: + normalfiles.add(f) + + if f in lfiles: + dstfiles.append(lfutil.standin(f)) + # largefile in manifest if it has not been removed/renamed + if f in ctx.manifest(): + if 'l' in ctx.filectx(f).flags(): + if renamed and renamed[0] in lfiles: + raise util.Abort(_('largefile %s becomes symlink') % f) + + # largefile was modified, update standins + fullpath = rdst.wjoin(f) + lfutil.createdir(os.path.dirname(fullpath)) + m = util.sha1('') + m.update(ctx[f].data()) + hash = m.hexdigest() + if f not in lfiletohash or lfiletohash[f] != hash: + try: + fd = open(fullpath, 'wb') + fd.write(ctx[f].data()) + finally: + if fd: + fd.close() + executable = 'x' in ctx[f].flags() + os.chmod(fullpath, lfutil.getmode(executable)) + lfutil.writestandin(rdst, lfutil.standin(f), hash, + executable) + lfiletohash[f] = hash + else: + # normal file + dstfiles.append(f) + + def getfilectx(repo, memctx, f): + if lfutil.isstandin(f): + # if the file isn't in the manifest then it was removed + # or renamed, raise IOError to indicate this + srcfname = lfutil.splitstandin(f) + try: + fctx = ctx.filectx(srcfname) + except error.LookupError: + raise IOError() + renamed = fctx.renamed() + if renamed: + # standin is always a largefile because largefile-ness + # doesn't change after rename or copy + renamed = lfutil.standin(renamed[0]) + + return context.memfilectx(f, lfiletohash[srcfname], 'l' in + fctx.flags(), 'x' in fctx.flags(), renamed) + else: + try: + fctx = ctx.filectx(f) + except error.LookupError: + raise IOError() + renamed = fctx.renamed() + if renamed: + renamed = renamed[0] + + data = fctx.data() + if f == '.hgtags': + newdata = [] + for line in data.splitlines(): + id, name = line.split(' ', 1) + newdata.append('%s %s\n' % (node.hex(revmap[node.bin(id)]), + name)) + data = ''.join(newdata) + return context.memfilectx(f, data, 'l' in fctx.flags(), + 'x' in fctx.flags(), renamed) + + # Commit + mctx = context.memctx(rdst, parents, ctx.description(), dstfiles, + getfilectx, ctx.user(), ctx.date(), ctx.extra()) + ret = rdst.commitctx(mctx) + rdst.dirstate.setparents(ret) + revmap[ctx.node()] = rdst.changelog.tip() + +def _islfile(file, ctx, matcher, size): + '''Return true if file should be considered a largefile, i.e. + matcher matches it or it is larger than size.''' + # never store special .hg* files as largefiles + if file == '.hgtags' or file == '.hgignore' or file == '.hgsigs': + return False + if matcher and matcher(file): + return True + try: + return ctx.filectx(file).size() >= size * 1024 * 1024 + except error.LookupError: + return False + +def uploadlfiles(ui, rsrc, rdst, files): + '''upload largefiles to the central store''' + + # Don't upload locally. All largefiles are in the system wide cache + # so the other repo can just get them from there. + if not files or rdst.local(): + return + + store = basestore._openstore(rsrc, rdst, put=True) + + at = 0 + files = filter(lambda h: not store.exists(h), files) + for hash in files: + ui.progress(_('uploading largefiles'), at, unit='largefile', + total=len(files)) + source = lfutil.findfile(rsrc, hash) + if not source: + raise util.Abort(_('largefile %s missing from store' + ' (needs to be uploaded)') % hash) + # XXX check for errors here + store.put(source, hash) + at += 1 + ui.progress(_('uploading largefiles'), None) + +def verifylfiles(ui, repo, all=False, contents=False): + '''Verify that every big file revision in the current changeset + exists in the central store. With --contents, also verify that + the contents of each big file revision are correct (SHA-1 hash + matches the revision ID). With --all, check every changeset in + this repository.''' + if all: + # Pass a list to the function rather than an iterator because we know a + # list will work. + revs = range(len(repo)) + else: + revs = ['.'] + + store = basestore._openstore(repo) + return store.verify(revs, contents=contents) + +def cachelfiles(ui, repo, node): + '''cachelfiles ensures that all largefiles needed by the specified revision + are present in the repository's largefile cache. + + returns a tuple (cached, missing). cached is the list of files downloaded + by this operation; missing is the list of files that were needed but could + not be found.''' + lfiles = lfutil.listlfiles(repo, node) + toget = [] + + for lfile in lfiles: + expectedhash = repo[node][lfutil.standin(lfile)].data().strip() + # if it exists and its hash matches, it might have been locally + # modified before updating and the user chose 'local'. in this case, + # it will not be in any store, so don't look for it. + if ((not os.path.exists(repo.wjoin(lfile)) or + expectedhash != lfutil.hashfile(repo.wjoin(lfile))) and + not lfutil.findfile(repo, expectedhash)): + toget.append((lfile, expectedhash)) + + if toget: + store = basestore._openstore(repo) + ret = store.get(toget) + return ret + + return ([], []) + +def updatelfiles(ui, repo, filelist=None, printmessage=True): + wlock = repo.wlock() + try: + lfdirstate = lfutil.openlfdirstate(ui, repo) + lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate) + + if filelist is not None: + lfiles = [f for f in lfiles if f in filelist] + + printed = False + if printmessage and lfiles: + ui.status(_('getting changed largefiles\n')) + printed = True + cachelfiles(ui, repo, '.') + + updated, removed = 0, 0 + for i in map(lambda f: _updatelfile(repo, lfdirstate, f), lfiles): + # increment the appropriate counter according to _updatelfile's + # return value + updated += i > 0 and i or 0 + removed -= i < 0 and i or 0 + if printmessage and (removed or updated) and not printed: + ui.status(_('getting changed largefiles\n')) + printed = True + + lfdirstate.write() + if printed and printmessage: + ui.status(_('%d largefiles updated, %d removed\n') % (updated, + removed)) + finally: + wlock.release() + +def _updatelfile(repo, lfdirstate, lfile): + '''updates a single largefile and copies the state of its standin from + the repository's dirstate to its state in the lfdirstate. + + returns 1 if the file was modified, -1 if the file was removed, 0 if the + file was unchanged, and None if the needed largefile was missing from the + cache.''' + ret = 0 + abslfile = repo.wjoin(lfile) + absstandin = repo.wjoin(lfutil.standin(lfile)) + if os.path.exists(absstandin): + if os.path.exists(absstandin+'.orig'): + shutil.copyfile(abslfile, abslfile+'.orig') + expecthash = lfutil.readstandin(repo, lfile) + if (expecthash != '' and + (not os.path.exists(abslfile) or + expecthash != lfutil.hashfile(abslfile))): + if not lfutil.copyfromcache(repo, expecthash, lfile): + return None # don't try to set the mode or update the dirstate + ret = 1 + mode = os.stat(absstandin).st_mode + if mode != os.stat(abslfile).st_mode: + os.chmod(abslfile, mode) + ret = 1 + else: + if os.path.exists(abslfile): + os.unlink(abslfile) + ret = -1 + state = repo.dirstate[lfutil.standin(lfile)] + if state == 'n': + lfdirstate.normal(lfile) + elif state == 'r': + lfdirstate.remove(lfile) + elif state == 'a': + lfdirstate.add(lfile) + elif state == '?': + lfdirstate.drop(lfile) + return ret + +# -- hg commands declarations ------------------------------------------------ + +cmdtable = { + 'lfconvert': (lfconvert, + [('s', 'size', '', + _('minimum size (MB) for files to be converted ' + 'as largefiles'), + 'SIZE'), + ('', 'tonormal', False, + _('convert from a largefiles repo to a normal repo')), + ], + _('hg lfconvert SOURCE DEST [FILE ...]')), + }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/lfutil.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,448 @@ +# Copyright 2009-2010 Gregory P. Ward +# Copyright 2009-2010 Intelerad Medical Systems Incorporated +# Copyright 2010-2011 Fog Creek Software +# Copyright 2010-2011 Unity Technologies +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''largefiles utility code: must not import other modules in this package.''' + +import os +import errno +import shutil +import stat +import hashlib + +from mercurial import dirstate, httpconnection, match as match_, util, scmutil +from mercurial.i18n import _ + +shortname = '.hglf' +longname = 'largefiles' + + +# -- Portability wrappers ---------------------------------------------- + +def dirstate_walk(dirstate, matcher, unknown=False, ignored=False): + return dirstate.walk(matcher, [], unknown, ignored) + +def repo_add(repo, list): + add = repo[None].add + return add(list) + +def repo_remove(repo, list, unlink=False): + def remove(list, unlink): + wlock = repo.wlock() + try: + if unlink: + for f in list: + try: + util.unlinkpath(repo.wjoin(f)) + except OSError, inst: + if inst.errno != errno.ENOENT: + raise + repo[None].forget(list) + finally: + wlock.release() + return remove(list, unlink=unlink) + +def repo_forget(repo, list): + forget = repo[None].forget + return forget(list) + +def findoutgoing(repo, remote, force): + from mercurial import discovery + common, _anyinc, _heads = discovery.findcommonincoming(repo, + remote, force=force) + return repo.changelog.findmissing(common) + +# -- Private worker functions ------------------------------------------ + +def getminsize(ui, assumelfiles, opt, default=10): + lfsize = opt + if not lfsize and assumelfiles: + lfsize = ui.config(longname, 'size', default=default) + if lfsize: + try: + lfsize = float(lfsize) + except ValueError: + raise util.Abort(_('largefiles: size must be number (not %s)\n') + % lfsize) + if lfsize is None: + raise util.Abort(_('minimum size for largefiles must be specified')) + return lfsize + +def link(src, dest): + try: + util.oslink(src, dest) + except OSError: + # if hardlinks fail, fallback on copy + shutil.copyfile(src, dest) + os.chmod(dest, os.stat(src).st_mode) + +def systemcachepath(ui, hash): + path = ui.config(longname, 'systemcache', None) + if path: + path = os.path.join(path, hash) + else: + if os.name == 'nt': + appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA')) + path = os.path.join(appdata, longname, hash) + elif os.name == 'posix': + path = os.path.join(os.getenv('HOME'), '.' + longname, hash) + else: + raise util.Abort(_('unknown operating system: %s\n') % os.name) + return path + +def insystemcache(ui, hash): + return os.path.exists(systemcachepath(ui, hash)) + +def findfile(repo, hash): + if incache(repo, hash): + repo.ui.note(_('Found %s in cache\n') % hash) + return cachepath(repo, hash) + if insystemcache(repo.ui, hash): + repo.ui.note(_('Found %s in system cache\n') % hash) + return systemcachepath(repo.ui, hash) + return None + +class largefiles_dirstate(dirstate.dirstate): + def __getitem__(self, key): + return super(largefiles_dirstate, self).__getitem__(unixpath(key)) + def normal(self, f): + return super(largefiles_dirstate, self).normal(unixpath(f)) + def remove(self, f): + return super(largefiles_dirstate, self).remove(unixpath(f)) + def add(self, f): + return super(largefiles_dirstate, self).add(unixpath(f)) + def drop(self, f): + return super(largefiles_dirstate, self).drop(unixpath(f)) + def forget(self, f): + return super(largefiles_dirstate, self).forget(unixpath(f)) + +def openlfdirstate(ui, repo): + ''' + Return a dirstate object that tracks largefiles: i.e. its root is + the repo root, but it is saved in .hg/largefiles/dirstate. + ''' + admin = repo.join(longname) + opener = scmutil.opener(admin) + if util.safehasattr(repo.dirstate, '_validate'): + lfdirstate = largefiles_dirstate(opener, ui, repo.root, + repo.dirstate._validate) + else: + lfdirstate = largefiles_dirstate(opener, ui, repo.root) + + # If the largefiles dirstate does not exist, populate and create + # it. This ensures that we create it on the first meaningful + # largefiles operation in a new clone. It also gives us an easy + # way to forcibly rebuild largefiles state: + # rm .hg/largefiles/dirstate && hg status + # Or even, if things are really messed up: + # rm -rf .hg/largefiles && hg status + if not os.path.exists(os.path.join(admin, 'dirstate')): + util.makedirs(admin) + matcher = getstandinmatcher(repo) + for standin in dirstate_walk(repo.dirstate, matcher): + lfile = splitstandin(standin) + hash = readstandin(repo, lfile) + lfdirstate.normallookup(lfile) + try: + if hash == hashfile(lfile): + lfdirstate.normal(lfile) + except IOError, err: + if err.errno != errno.ENOENT: + raise + + lfdirstate.write() + + return lfdirstate + +def lfdirstate_status(lfdirstate, repo, rev): + wlock = repo.wlock() + try: + match = match_.always(repo.root, repo.getcwd()) + s = lfdirstate.status(match, [], False, False, False) + unsure, modified, added, removed, missing, unknown, ignored, clean = s + for lfile in unsure: + if repo[rev][standin(lfile)].data().strip() != \ + hashfile(repo.wjoin(lfile)): + modified.append(lfile) + else: + clean.append(lfile) + lfdirstate.normal(lfile) + lfdirstate.write() + finally: + wlock.release() + return (modified, added, removed, missing, unknown, ignored, clean) + +def listlfiles(repo, rev=None, matcher=None): + '''return a list of largefiles in the working copy or the + specified changeset''' + + if matcher is None: + matcher = getstandinmatcher(repo) + + # ignore unknown files in working directory + return [splitstandin(f) + for f in repo[rev].walk(matcher) + if rev is not None or repo.dirstate[f] != '?'] + +def incache(repo, hash): + return os.path.exists(cachepath(repo, hash)) + +def createdir(dir): + if not os.path.exists(dir): + os.makedirs(dir) + +def cachepath(repo, hash): + return repo.join(os.path.join(longname, hash)) + +def copyfromcache(repo, hash, filename): + '''Copy the specified largefile from the repo or system cache to + filename in the repository. Return true on success or false if the + file was not found in either cache (which should not happened: + this is meant to be called only after ensuring that the needed + largefile exists in the cache).''' + path = findfile(repo, hash) + if path is None: + return False + util.makedirs(os.path.dirname(repo.wjoin(filename))) + shutil.copy(path, repo.wjoin(filename)) + return True + +def copytocache(repo, rev, file, uploaded=False): + hash = readstandin(repo, file) + if incache(repo, hash): + return + copytocacheabsolute(repo, repo.wjoin(file), hash) + +def copytocacheabsolute(repo, file, hash): + createdir(os.path.dirname(cachepath(repo, hash))) + if insystemcache(repo.ui, hash): + link(systemcachepath(repo.ui, hash), cachepath(repo, hash)) + else: + shutil.copyfile(file, cachepath(repo, hash)) + os.chmod(cachepath(repo, hash), os.stat(file).st_mode) + linktosystemcache(repo, hash) + +def linktosystemcache(repo, hash): + createdir(os.path.dirname(systemcachepath(repo.ui, hash))) + link(cachepath(repo, hash), systemcachepath(repo.ui, hash)) + +def getstandinmatcher(repo, pats=[], opts={}): + '''Return a match object that applies pats to the standin directory''' + standindir = repo.pathto(shortname) + if pats: + # patterns supplied: search standin directory relative to current dir + cwd = repo.getcwd() + if os.path.isabs(cwd): + # cwd is an absolute path for hg -R <reponame> + # work relative to the repository root in this case + cwd = '' + pats = [os.path.join(standindir, cwd, pat) for pat in pats] + elif os.path.isdir(standindir): + # no patterns: relative to repo root + pats = [standindir] + else: + # no patterns and no standin dir: return matcher that matches nothing + match = match_.match(repo.root, None, [], exact=True) + match.matchfn = lambda f: False + return match + return getmatcher(repo, pats, opts, showbad=False) + +def getmatcher(repo, pats=[], opts={}, showbad=True): + '''Wrapper around scmutil.match() that adds showbad: if false, + neuter the match object's bad() method so it does not print any + warnings about missing files or directories.''' + match = scmutil.match(repo[None], pats, opts) + + if not showbad: + match.bad = lambda f, msg: None + return match + +def composestandinmatcher(repo, rmatcher): + '''Return a matcher that accepts standins corresponding to the + files accepted by rmatcher. Pass the list of files in the matcher + as the paths specified by the user.''' + smatcher = getstandinmatcher(repo, rmatcher.files()) + isstandin = smatcher.matchfn + def composed_matchfn(f): + return isstandin(f) and rmatcher.matchfn(splitstandin(f)) + smatcher.matchfn = composed_matchfn + + return smatcher + +def standin(filename): + '''Return the repo-relative path to the standin for the specified big + file.''' + # Notes: + # 1) Most callers want an absolute path, but _create_standin() needs + # it repo-relative so lfadd() can pass it to repo_add(). So leave + # it up to the caller to use repo.wjoin() to get an absolute path. + # 2) Join with '/' because that's what dirstate always uses, even on + # Windows. Change existing separator to '/' first in case we are + # passed filenames from an external source (like the command line). + return shortname + '/' + filename.replace(os.sep, '/') + +def isstandin(filename): + '''Return true if filename is a big file standin. filename must be + in Mercurial's internal form (slash-separated).''' + return filename.startswith(shortname + '/') + +def splitstandin(filename): + # Split on / because that's what dirstate always uses, even on Windows. + # Change local separator to / first just in case we are passed filenames + # from an external source (like the command line). + bits = filename.replace(os.sep, '/').split('/', 1) + if len(bits) == 2 and bits[0] == shortname: + return bits[1] + else: + return None + +def updatestandin(repo, standin): + file = repo.wjoin(splitstandin(standin)) + if os.path.exists(file): + hash = hashfile(file) + executable = getexecutable(file) + writestandin(repo, standin, hash, executable) + +def readstandin(repo, filename, node=None): + '''read hex hash from standin for filename at given node, or working + directory if no node is given''' + return repo[node][standin(filename)].data().strip() + +def writestandin(repo, standin, hash, executable): + '''write hash to <repo.root>/<standin>''' + writehash(hash, repo.wjoin(standin), executable) + +def copyandhash(instream, outfile): + '''Read bytes from instream (iterable) and write them to outfile, + computing the SHA-1 hash of the data along the way. Close outfile + when done and return the binary hash.''' + hasher = util.sha1('') + for data in instream: + hasher.update(data) + outfile.write(data) + + # Blecch: closing a file that somebody else opened is rude and + # wrong. But it's so darn convenient and practical! After all, + # outfile was opened just to copy and hash. + outfile.close() + + return hasher.digest() + +def hashrepofile(repo, file): + return hashfile(repo.wjoin(file)) + +def hashfile(file): + if not os.path.exists(file): + return '' + hasher = util.sha1('') + fd = open(file, 'rb') + for data in blockstream(fd): + hasher.update(data) + fd.close() + return hasher.hexdigest() + +class limitreader(object): + def __init__(self, f, limit): + self.f = f + self.limit = limit + + def read(self, length): + if self.limit == 0: + return '' + length = length > self.limit and self.limit or length + self.limit -= length + return self.f.read(length) + + def close(self): + pass + +def blockstream(infile, blocksize=128 * 1024): + """Generator that yields blocks of data from infile and closes infile.""" + while True: + data = infile.read(blocksize) + if not data: + break + yield data + # same blecch as copyandhash() above + infile.close() + +def readhash(filename): + rfile = open(filename, 'rb') + hash = rfile.read(40) + rfile.close() + if len(hash) < 40: + raise util.Abort(_('bad hash in \'%s\' (only %d bytes long)') + % (filename, len(hash))) + return hash + +def writehash(hash, filename, executable): + util.makedirs(os.path.dirname(filename)) + if os.path.exists(filename): + os.unlink(filename) + wfile = open(filename, 'wb') + + try: + wfile.write(hash) + wfile.write('\n') + finally: + wfile.close() + if os.path.exists(filename): + os.chmod(filename, getmode(executable)) + +def getexecutable(filename): + mode = os.stat(filename).st_mode + return ((mode & stat.S_IXUSR) and + (mode & stat.S_IXGRP) and + (mode & stat.S_IXOTH)) + +def getmode(executable): + if executable: + return 0755 + else: + return 0644 + +def urljoin(first, second, *arg): + def join(left, right): + if not left.endswith('/'): + left += '/' + if right.startswith('/'): + right = right[1:] + return left + right + + url = join(first, second) + for a in arg: + url = join(url, a) + return url + +def hexsha1(data): + """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like + object data""" + h = hashlib.sha1() + for chunk in util.filechunkiter(data): + h.update(chunk) + return h.hexdigest() + +def httpsendfile(ui, filename): + return httpconnection.httpsendfile(ui, filename, 'rb') + +def unixpath(path): + '''Return a version of path normalized for use with the lfdirstate.''' + return os.path.normpath(path).replace(os.sep, '/') + +def islfilesrepo(repo): + return ('largefiles' in repo.requirements and + any_(shortname + '/' in f[0] for f in repo.store.datafiles())) + +def any_(gen): + for x in gen: + if x: + return True + return False + +class storeprotonotcapable(BaseException): + def __init__(self, storetypes): + self.storetypes = storetypes
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/localstore.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,71 @@ +# Copyright 2009-2010 Gregory P. Ward +# Copyright 2009-2010 Intelerad Medical Systems Incorporated +# Copyright 2010-2011 Fog Creek Software +# Copyright 2010-2011 Unity Technologies +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''store class for local filesystem''' + +import os + +from mercurial import util +from mercurial.i18n import _ + +import lfutil +import basestore + +class localstore(basestore.basestore): + '''Because there is a system-wide cache, the local store always + uses that cache. Since the cache is updated elsewhere, we can + just read from it here as if it were the store.''' + + def __init__(self, ui, repo, remote): + url = os.path.join(remote.path, '.hg', lfutil.longname) + super(localstore, self).__init__(ui, repo, util.expandpath(url)) + + def put(self, source, filename, hash): + '''Any file that is put must already be in the system-wide + cache so do nothing.''' + return + + def exists(self, hash): + return lfutil.insystemcache(self.repo.ui, hash) + + def _getfile(self, tmpfile, filename, hash): + if lfutil.insystemcache(self.ui, hash): + return lfutil.systemcachepath(self.ui, hash) + raise basestore.StoreError(filename, hash, '', + _("Can't get file locally")) + + def _verifyfile(self, cctx, cset, contents, standin, verified): + filename = lfutil.splitstandin(standin) + if not filename: + return False + fctx = cctx[standin] + key = (filename, fctx.filenode()) + if key in verified: + return False + + expecthash = fctx.data()[0:40] + verified.add(key) + if not lfutil.insystemcache(self.ui, expecthash): + self.ui.warn( + _('changeset %s: %s missing\n' + ' (looked for hash %s)\n') + % (cset, filename, expecthash)) + return True # failed + + if contents: + storepath = lfutil.systemcachepath(self.ui, expecthash) + actualhash = lfutil.hashfile(storepath) + if actualhash != expecthash: + self.ui.warn( + _('changeset %s: %s: contents differ\n' + ' (%s:\n' + ' expected hash %s,\n' + ' but got %s)\n') + % (cset, filename, storepath, expecthash, actualhash)) + return True # failed + return False
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/overrides.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,830 @@ +# Copyright 2009-2010 Gregory P. Ward +# Copyright 2009-2010 Intelerad Medical Systems Incorporated +# Copyright 2010-2011 Fog Creek Software +# Copyright 2010-2011 Unity Technologies +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''Overridden Mercurial commands and functions for the largefiles extension''' + +import os +import copy + +from mercurial import hg, commands, util, cmdutil, match as match_, node, \ + archival, error, merge +from mercurial.i18n import _ +from mercurial.node import hex +from hgext import rebase +import lfutil + +try: + from mercurial import scmutil +except ImportError: + pass + +import lfutil +import lfcommands + +def installnormalfilesmatchfn(manifest): + '''overrides scmutil.match so that the matcher it returns will ignore all + largefiles''' + oldmatch = None # for the closure + def override_match(repo, pats=[], opts={}, globbed=False, + default='relpath'): + match = oldmatch(repo, pats, opts, globbed, default) + m = copy.copy(match) + notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in + manifest) + m._files = filter(notlfile, m._files) + m._fmap = set(m._files) + orig_matchfn = m.matchfn + m.matchfn = lambda f: notlfile(f) and orig_matchfn(f) or None + return m + oldmatch = installmatchfn(override_match) + +def installmatchfn(f): + oldmatch = scmutil.match + setattr(f, 'oldmatch', oldmatch) + scmutil.match = f + return oldmatch + +def restorematchfn(): + '''restores scmutil.match to what it was before installnormalfilesmatchfn + was called. no-op if scmutil.match is its original function. + + Note that n calls to installnormalfilesmatchfn will require n calls to + restore matchfn to reverse''' + scmutil.match = getattr(scmutil.match, 'oldmatch', scmutil.match) + +# -- Wrappers: modify existing commands -------------------------------- + +# Add works by going through the files that the user wanted to add and +# checking if they should be added as largefiles. Then it makes a new +# matcher which matches only the normal files and runs the original +# version of add. +def override_add(orig, ui, repo, *pats, **opts): + large = opts.pop('large', None) + lfsize = lfutil.getminsize( + ui, lfutil.islfilesrepo(repo), opts.pop('lfsize', None)) + + lfmatcher = None + if os.path.exists(repo.wjoin(lfutil.shortname)): + lfpats = ui.configlist(lfutil.longname, 'patterns', default=[]) + if lfpats: + lfmatcher = match_.match(repo.root, '', list(lfpats)) + + lfnames = [] + m = scmutil.match(repo[None], pats, opts) + m.bad = lambda x, y: None + wctx = repo[None] + for f in repo.walk(m): + exact = m.exact(f) + lfile = lfutil.standin(f) in wctx + nfile = f in wctx + exists = lfile or nfile + + # Don't warn the user when they attempt to add a normal tracked file. + # The normal add code will do that for us. + if exact and exists: + if lfile: + ui.warn(_('%s already a largefile\n') % f) + continue + + if exact or not exists: + abovemin = (lfsize and + os.path.getsize(repo.wjoin(f)) >= lfsize * 1024 * 1024) + if large or abovemin or (lfmatcher and lfmatcher(f)): + lfnames.append(f) + if ui.verbose or not exact: + ui.status(_('adding %s as a largefile\n') % m.rel(f)) + + bad = [] + standins = [] + + # Need to lock, otherwise there could be a race condition between + # when standins are created and added to the repo. + wlock = repo.wlock() + try: + if not opts.get('dry_run'): + lfdirstate = lfutil.openlfdirstate(ui, repo) + for f in lfnames: + standinname = lfutil.standin(f) + lfutil.writestandin(repo, standinname, hash='', + executable=lfutil.getexecutable(repo.wjoin(f))) + standins.append(standinname) + if lfdirstate[f] == 'r': + lfdirstate.normallookup(f) + else: + lfdirstate.add(f) + lfdirstate.write() + bad += [lfutil.splitstandin(f) + for f in lfutil.repo_add(repo, standins) + if f in m.files()] + finally: + wlock.release() + + installnormalfilesmatchfn(repo[None].manifest()) + result = orig(ui, repo, *pats, **opts) + restorematchfn() + + return (result == 1 or bad) and 1 or 0 + +def override_remove(orig, ui, repo, *pats, **opts): + manifest = repo[None].manifest() + installnormalfilesmatchfn(manifest) + orig(ui, repo, *pats, **opts) + restorematchfn() + + after, force = opts.get('after'), opts.get('force') + if not pats and not after: + raise util.Abort(_('no files specified')) + m = scmutil.match(repo[None], pats, opts) + try: + repo.lfstatus = True + s = repo.status(match=m, clean=True) + finally: + repo.lfstatus = False + modified, added, deleted, clean = [[f for f in list + if lfutil.standin(f) in manifest] + for list in [s[0], s[1], s[3], s[6]]] + + def warn(files, reason): + for f in files: + ui.warn(_('not removing %s: file %s (use -f to force removal)\n') + % (m.rel(f), reason)) + + if force: + remove, forget = modified + deleted + clean, added + elif after: + remove, forget = deleted, [] + warn(modified + added + clean, _('still exists')) + else: + remove, forget = deleted + clean, [] + warn(modified, _('is modified')) + warn(added, _('has been marked for add')) + + for f in sorted(remove + forget): + if ui.verbose or not m.exact(f): + ui.status(_('removing %s\n') % m.rel(f)) + + # Need to lock because standin files are deleted then removed from the + # repository and we could race inbetween. + wlock = repo.wlock() + try: + lfdirstate = lfutil.openlfdirstate(ui, repo) + for f in remove: + if not after: + os.unlink(repo.wjoin(f)) + currentdir = os.path.split(f)[0] + while currentdir and not os.listdir(repo.wjoin(currentdir)): + os.rmdir(repo.wjoin(currentdir)) + currentdir = os.path.split(currentdir)[0] + lfdirstate.remove(f) + lfdirstate.write() + + forget = [lfutil.standin(f) for f in forget] + remove = [lfutil.standin(f) for f in remove] + lfutil.repo_forget(repo, forget) + lfutil.repo_remove(repo, remove, unlink=True) + finally: + wlock.release() + +def override_status(orig, ui, repo, *pats, **opts): + try: + repo.lfstatus = True + return orig(ui, repo, *pats, **opts) + finally: + repo.lfstatus = False + +def override_log(orig, ui, repo, *pats, **opts): + try: + repo.lfstatus = True + orig(ui, repo, *pats, **opts) + finally: + repo.lfstatus = False + +def override_verify(orig, ui, repo, *pats, **opts): + large = opts.pop('large', False) + all = opts.pop('lfa', False) + contents = opts.pop('lfc', False) + + result = orig(ui, repo, *pats, **opts) + if large: + result = result or lfcommands.verifylfiles(ui, repo, all, contents) + return result + +# Override needs to refresh standins so that update's normal merge +# will go through properly. Then the other update hook (overriding repo.update) +# will get the new files. Filemerge is also overriden so that the merge +# will merge standins correctly. +def override_update(orig, ui, repo, *pats, **opts): + lfdirstate = lfutil.openlfdirstate(ui, repo) + s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False, + False, False) + (unsure, modified, added, removed, missing, unknown, ignored, clean) = s + + # Need to lock between the standins getting updated and their + # largefiles getting updated + wlock = repo.wlock() + try: + if opts['check']: + mod = len(modified) > 0 + for lfile in unsure: + standin = lfutil.standin(lfile) + if repo['.'][standin].data().strip() != \ + lfutil.hashfile(repo.wjoin(lfile)): + mod = True + else: + lfdirstate.normal(lfile) + lfdirstate.write() + if mod: + raise util.Abort(_('uncommitted local changes')) + # XXX handle removed differently + if not opts['clean']: + for lfile in unsure + modified + added: + lfutil.updatestandin(repo, lfutil.standin(lfile)) + finally: + wlock.release() + return orig(ui, repo, *pats, **opts) + +# Override filemerge to prompt the user about how they wish to merge +# largefiles. This will handle identical edits, and copy/rename + +# edit without prompting the user. +def override_filemerge(origfn, repo, mynode, orig, fcd, fco, fca): + # Use better variable names here. Because this is a wrapper we cannot + # change the variable names in the function declaration. + fcdest, fcother, fcancestor = fcd, fco, fca + if not lfutil.isstandin(orig): + return origfn(repo, mynode, orig, fcdest, fcother, fcancestor) + else: + if not fcother.cmp(fcdest): # files identical? + return None + + # backwards, use working dir parent as ancestor + if fcancestor == fcother: + fcancestor = fcdest.parents()[0] + + if orig != fcother.path(): + repo.ui.status(_('merging %s and %s to %s\n') + % (lfutil.splitstandin(orig), + lfutil.splitstandin(fcother.path()), + lfutil.splitstandin(fcdest.path()))) + else: + repo.ui.status(_('merging %s\n') + % lfutil.splitstandin(fcdest.path())) + + if fcancestor.path() != fcother.path() and fcother.data() == \ + fcancestor.data(): + return 0 + if fcancestor.path() != fcdest.path() and fcdest.data() == \ + fcancestor.data(): + repo.wwrite(fcdest.path(), fcother.data(), fcother.flags()) + return 0 + + if repo.ui.promptchoice(_('largefile %s has a merge conflict\n' + 'keep (l)ocal or take (o)ther?') % + lfutil.splitstandin(orig), + (_('&Local'), _('&Other')), 0) == 0: + return 0 + else: + repo.wwrite(fcdest.path(), fcother.data(), fcother.flags()) + return 0 + +# Copy first changes the matchers to match standins instead of +# largefiles. Then it overrides util.copyfile in that function it +# checks if the destination largefile already exists. It also keeps a +# list of copied files so that the largefiles can be copied and the +# dirstate updated. +def override_copy(orig, ui, repo, pats, opts, rename=False): + # doesn't remove largefile on rename + if len(pats) < 2: + # this isn't legal, let the original function deal with it + return orig(ui, repo, pats, opts, rename) + + def makestandin(relpath): + path = scmutil.canonpath(repo.root, repo.getcwd(), relpath) + return os.path.join(os.path.relpath('.', repo.getcwd()), + lfutil.standin(path)) + + fullpats = scmutil.expandpats(pats) + dest = fullpats[-1] + + if os.path.isdir(dest): + if not os.path.isdir(makestandin(dest)): + os.makedirs(makestandin(dest)) + # This could copy both lfiles and normal files in one command, + # but we don't want to do that. First replace their matcher to + # only match normal files and run it, then replace it to just + # match largefiles and run it again. + nonormalfiles = False + nolfiles = False + try: + installnormalfilesmatchfn(repo[None].manifest()) + result = orig(ui, repo, pats, opts, rename) + except util.Abort, e: + if str(e) != 'no files to copy': + raise e + else: + nonormalfiles = True + result = 0 + finally: + restorematchfn() + + # The first rename can cause our current working directory to be removed. + # In that case there is nothing left to copy/rename so just quit. + try: + repo.getcwd() + except OSError: + return result + + try: + # When we call orig below it creates the standins but we don't add them + # to the dir state until later so lock during that time. + wlock = repo.wlock() + + manifest = repo[None].manifest() + oldmatch = None # for the closure + def override_match(repo, pats=[], opts={}, globbed=False, + default='relpath'): + newpats = [] + # The patterns were previously mangled to add the standin + # directory; we need to remove that now + for pat in pats: + if match_.patkind(pat) is None and lfutil.shortname in pat: + newpats.append(pat.replace(lfutil.shortname, '')) + else: + newpats.append(pat) + match = oldmatch(repo, newpats, opts, globbed, default) + m = copy.copy(match) + lfile = lambda f: lfutil.standin(f) in manifest + m._files = [lfutil.standin(f) for f in m._files if lfile(f)] + m._fmap = set(m._files) + orig_matchfn = m.matchfn + m.matchfn = lambda f: (lfutil.isstandin(f) and + lfile(lfutil.splitstandin(f)) and + orig_matchfn(lfutil.splitstandin(f)) or + None) + return m + oldmatch = installmatchfn(override_match) + listpats = [] + for pat in pats: + if match_.patkind(pat) is not None: + listpats.append(pat) + else: + listpats.append(makestandin(pat)) + + try: + origcopyfile = util.copyfile + copiedfiles = [] + def override_copyfile(src, dest): + if lfutil.shortname in src and lfutil.shortname in dest: + destlfile = dest.replace(lfutil.shortname, '') + if not opts['force'] and os.path.exists(destlfile): + raise IOError('', + _('destination largefile already exists')) + copiedfiles.append((src, dest)) + origcopyfile(src, dest) + + util.copyfile = override_copyfile + result += orig(ui, repo, listpats, opts, rename) + finally: + util.copyfile = origcopyfile + + lfdirstate = lfutil.openlfdirstate(ui, repo) + for (src, dest) in copiedfiles: + if lfutil.shortname in src and lfutil.shortname in dest: + srclfile = src.replace(lfutil.shortname, '') + destlfile = dest.replace(lfutil.shortname, '') + destlfiledir = os.path.dirname(destlfile) or '.' + if not os.path.isdir(destlfiledir): + os.makedirs(destlfiledir) + if rename: + os.rename(srclfile, destlfile) + lfdirstate.remove(os.path.relpath(srclfile, + repo.root)) + else: + util.copyfile(srclfile, destlfile) + lfdirstate.add(os.path.relpath(destlfile, + repo.root)) + lfdirstate.write() + except util.Abort, e: + if str(e) != 'no files to copy': + raise e + else: + nolfiles = True + finally: + restorematchfn() + wlock.release() + + if nolfiles and nonormalfiles: + raise util.Abort(_('no files to copy')) + + return result + +# When the user calls revert, we have to be careful to not revert any +# changes to other largefiles accidentally. This means we have to keep +# track of the largefiles that are being reverted so we only pull down +# the necessary largefiles. +# +# Standins are only updated (to match the hash of largefiles) before +# commits. Update the standins then run the original revert, changing +# the matcher to hit standins instead of largefiles. Based on the +# resulting standins update the largefiles. Then return the standins +# to their proper state +def override_revert(orig, ui, repo, *pats, **opts): + # Because we put the standins in a bad state (by updating them) + # and then return them to a correct state we need to lock to + # prevent others from changing them in their incorrect state. + wlock = repo.wlock() + try: + lfdirstate = lfutil.openlfdirstate(ui, repo) + (modified, added, removed, missing, unknown, ignored, clean) = \ + lfutil.lfdirstate_status(lfdirstate, repo, repo['.'].rev()) + for lfile in modified: + lfutil.updatestandin(repo, lfutil.standin(lfile)) + + try: + ctx = repo[opts.get('rev')] + oldmatch = None # for the closure + def override_match(ctxorrepo, pats=[], opts={}, globbed=False, + default='relpath'): + if util.safehasattr(ctxorrepo, 'match'): + ctx0 = ctxorrepo + else: + ctx0 = ctxorrepo[None] + match = oldmatch(ctxorrepo, pats, opts, globbed, default) + m = copy.copy(match) + def tostandin(f): + if lfutil.standin(f) in ctx0 or lfutil.standin(f) in ctx: + return lfutil.standin(f) + elif lfutil.standin(f) in repo[None]: + return None + return f + m._files = [tostandin(f) for f in m._files] + m._files = [f for f in m._files if f is not None] + m._fmap = set(m._files) + orig_matchfn = m.matchfn + def matchfn(f): + if lfutil.isstandin(f): + # We need to keep track of what largefiles are being + # matched so we know which ones to update later -- + # otherwise we accidentally revert changes to other + # largefiles. This is repo-specific, so duckpunch the + # repo object to keep the list of largefiles for us + # later. + if orig_matchfn(lfutil.splitstandin(f)) and \ + (f in repo[None] or f in ctx): + lfileslist = getattr(repo, '_lfilestoupdate', []) + lfileslist.append(lfutil.splitstandin(f)) + repo._lfilestoupdate = lfileslist + return True + else: + return False + return orig_matchfn(f) + m.matchfn = matchfn + return m + oldmatch = installmatchfn(override_match) + scmutil.match + matches = override_match(repo[None], pats, opts) + orig(ui, repo, *pats, **opts) + finally: + restorematchfn() + lfileslist = getattr(repo, '_lfilestoupdate', []) + lfcommands.updatelfiles(ui, repo, filelist=lfileslist, + printmessage=False) + + # empty out the largefiles list so we start fresh next time + repo._lfilestoupdate = [] + for lfile in modified: + if lfile in lfileslist: + if os.path.exists(repo.wjoin(lfutil.standin(lfile))) and lfile\ + in repo['.']: + lfutil.writestandin(repo, lfutil.standin(lfile), + repo['.'][lfile].data().strip(), + 'x' in repo['.'][lfile].flags()) + lfdirstate = lfutil.openlfdirstate(ui, repo) + for lfile in added: + standin = lfutil.standin(lfile) + if standin not in ctx and (standin in matches or opts.get('all')): + if lfile in lfdirstate: + lfdirstate.drop(lfile) + util.unlinkpath(repo.wjoin(standin)) + lfdirstate.write() + finally: + wlock.release() + +def hg_update(orig, repo, node): + result = orig(repo, node) + # XXX check if it worked first + lfcommands.updatelfiles(repo.ui, repo) + return result + +def hg_clean(orig, repo, node, show_stats=True): + result = orig(repo, node, show_stats) + lfcommands.updatelfiles(repo.ui, repo) + return result + +def hg_merge(orig, repo, node, force=None, remind=True): + result = orig(repo, node, force, remind) + lfcommands.updatelfiles(repo.ui, repo) + return result + +# When we rebase a repository with remotely changed largefiles, we need to +# take some extra care so that the largefiles are correctly updated in the +# working copy +def override_pull(orig, ui, repo, source=None, **opts): + if opts.get('rebase', False): + repo._isrebasing = True + try: + if opts.get('update'): + del opts['update'] + ui.debug('--update and --rebase are not compatible, ignoring ' + 'the update flag\n') + del opts['rebase'] + cmdutil.bailifchanged(repo) + revsprepull = len(repo) + origpostincoming = commands.postincoming + def _dummy(*args, **kwargs): + pass + commands.postincoming = _dummy + repo.lfpullsource = source + if not source: + source = 'default' + try: + result = commands.pull(ui, repo, source, **opts) + finally: + commands.postincoming = origpostincoming + revspostpull = len(repo) + if revspostpull > revsprepull: + result = result or rebase.rebase(ui, repo) + finally: + repo._isrebasing = False + else: + repo.lfpullsource = source + if not source: + source = 'default' + result = orig(ui, repo, source, **opts) + return result + +def override_rebase(orig, ui, repo, **opts): + repo._isrebasing = True + try: + orig(ui, repo, **opts) + finally: + repo._isrebasing = False + +def override_archive(orig, repo, dest, node, kind, decode=True, matchfn=None, + prefix=None, mtime=None, subrepos=None): + # No need to lock because we are only reading history and + # largefile caches, neither of which are modified. + lfcommands.cachelfiles(repo.ui, repo, node) + + if kind not in archival.archivers: + raise util.Abort(_("unknown archive type '%s'") % kind) + + ctx = repo[node] + + if kind == 'files': + if prefix: + raise util.Abort( + _('cannot give prefix when archiving to files')) + else: + prefix = archival.tidyprefix(dest, kind, prefix) + + def write(name, mode, islink, getdata): + if matchfn and not matchfn(name): + return + data = getdata() + if decode: + data = repo.wwritedata(name, data) + archiver.addfile(prefix + name, mode, islink, data) + + archiver = archival.archivers[kind](dest, mtime or ctx.date()[0]) + + if repo.ui.configbool("ui", "archivemeta", True): + def metadata(): + base = 'repo: %s\nnode: %s\nbranch: %s\n' % ( + hex(repo.changelog.node(0)), hex(node), ctx.branch()) + + tags = ''.join('tag: %s\n' % t for t in ctx.tags() + if repo.tagtype(t) == 'global') + if not tags: + repo.ui.pushbuffer() + opts = {'template': '{latesttag}\n{latesttagdistance}', + 'style': '', 'patch': None, 'git': None} + cmdutil.show_changeset(repo.ui, repo, opts).show(ctx) + ltags, dist = repo.ui.popbuffer().split('\n') + tags = ''.join('latesttag: %s\n' % t for t in ltags.split(':')) + tags += 'latesttagdistance: %s\n' % dist + + return base + tags + + write('.hg_archival.txt', 0644, False, metadata) + + for f in ctx: + ff = ctx.flags(f) + getdata = ctx[f].data + if lfutil.isstandin(f): + path = lfutil.findfile(repo, getdata().strip()) + f = lfutil.splitstandin(f) + + def getdatafn(): + try: + fd = open(path, 'rb') + return fd.read() + finally: + fd.close() + + getdata = getdatafn + write(f, 'x' in ff and 0755 or 0644, 'l' in ff, getdata) + + if subrepos: + for subpath in ctx.substate: + sub = ctx.sub(subpath) + try: + sub.archive(repo.ui, archiver, prefix) + except TypeError: + sub.archive(archiver, prefix) + + archiver.done() + +# If a largefile is modified, the change is not reflected in its +# standin until a commit. cmdutil.bailifchanged() raises an exception +# if the repo has uncommitted changes. Wrap it to also check if +# largefiles were changed. This is used by bisect and backout. +def override_bailifchanged(orig, repo): + orig(repo) + repo.lfstatus = True + modified, added, removed, deleted = repo.status()[:4] + repo.lfstatus = False + if modified or added or removed or deleted: + raise util.Abort(_('outstanding uncommitted changes')) + +# Fetch doesn't use cmdutil.bail_if_changed so override it to add the check +def override_fetch(orig, ui, repo, *pats, **opts): + repo.lfstatus = True + modified, added, removed, deleted = repo.status()[:4] + repo.lfstatus = False + if modified or added or removed or deleted: + raise util.Abort(_('outstanding uncommitted changes')) + return orig(ui, repo, *pats, **opts) + +def override_forget(orig, ui, repo, *pats, **opts): + installnormalfilesmatchfn(repo[None].manifest()) + orig(ui, repo, *pats, **opts) + restorematchfn() + m = scmutil.match(repo[None], pats, opts) + + try: + repo.lfstatus = True + s = repo.status(match=m, clean=True) + finally: + repo.lfstatus = False + forget = sorted(s[0] + s[1] + s[3] + s[6]) + forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()] + + for f in forget: + if lfutil.standin(f) not in repo.dirstate and not \ + os.path.isdir(m.rel(lfutil.standin(f))): + ui.warn(_('not removing %s: file is already untracked\n') + % m.rel(f)) + + for f in forget: + if ui.verbose or not m.exact(f): + ui.status(_('removing %s\n') % m.rel(f)) + + # Need to lock because standin files are deleted then removed from the + # repository and we could race inbetween. + wlock = repo.wlock() + try: + lfdirstate = lfutil.openlfdirstate(ui, repo) + for f in forget: + if lfdirstate[f] == 'a': + lfdirstate.drop(f) + else: + lfdirstate.remove(f) + lfdirstate.write() + lfutil.repo_remove(repo, [lfutil.standin(f) for f in forget], + unlink=True) + finally: + wlock.release() + +def getoutgoinglfiles(ui, repo, dest=None, **opts): + dest = ui.expandpath(dest or 'default-push', dest or 'default') + dest, branches = hg.parseurl(dest, opts.get('branch')) + revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev')) + if revs: + revs = [repo.lookup(rev) for rev in revs] + + remoteui = hg.remoteui + + try: + remote = hg.repository(remoteui(repo, opts), dest) + except error.RepoError: + return None + o = lfutil.findoutgoing(repo, remote, False) + if not o: + return None + o = repo.changelog.nodesbetween(o, revs)[0] + if opts.get('newest_first'): + o.reverse() + + toupload = set() + for n in o: + parents = [p for p in repo.changelog.parents(n) if p != node.nullid] + ctx = repo[n] + files = set(ctx.files()) + if len(parents) == 2: + mc = ctx.manifest() + mp1 = ctx.parents()[0].manifest() + mp2 = ctx.parents()[1].manifest() + for f in mp1: + if f not in mc: + files.add(f) + for f in mp2: + if f not in mc: + files.add(f) + for f in mc: + if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None): + files.add(f) + toupload = toupload.union( + set([f for f in files if lfutil.isstandin(f) and f in ctx])) + return toupload + +def override_outgoing(orig, ui, repo, dest=None, **opts): + orig(ui, repo, dest, **opts) + + if opts.pop('large', None): + toupload = getoutgoinglfiles(ui, repo, dest, **opts) + if toupload is None: + ui.status(_('largefiles: No remote repo\n')) + else: + ui.status(_('largefiles to upload:\n')) + for file in toupload: + ui.status(lfutil.splitstandin(file) + '\n') + ui.status('\n') + +def override_summary(orig, ui, repo, *pats, **opts): + orig(ui, repo, *pats, **opts) + + if opts.pop('large', None): + toupload = getoutgoinglfiles(ui, repo, None, **opts) + if toupload is None: + ui.status(_('largefiles: No remote repo\n')) + else: + ui.status(_('largefiles: %d to upload\n') % len(toupload)) + +def override_addremove(orig, ui, repo, *pats, **opts): + # Check if the parent or child has largefiles; if so, disallow + # addremove. If there is a symlink in the manifest then getting + # the manifest throws an exception: catch it and let addremove + # deal with it. + try: + manifesttip = set(repo['tip'].manifest()) + except util.Abort: + manifesttip = set() + try: + manifestworking = set(repo[None].manifest()) + except util.Abort: + manifestworking = set() + + # Manifests are only iterable so turn them into sets then union + for file in manifesttip.union(manifestworking): + if file.startswith(lfutil.shortname): + raise util.Abort( + _('addremove cannot be run on a repo with largefiles')) + + return orig(ui, repo, *pats, **opts) + +# Calling purge with --all will cause the largefiles to be deleted. +# Override repo.status to prevent this from happening. +def override_purge(orig, ui, repo, *dirs, **opts): + oldstatus = repo.status + def override_status(node1='.', node2=None, match=None, ignored=False, + clean=False, unknown=False, listsubrepos=False): + r = oldstatus(node1, node2, match, ignored, clean, unknown, + listsubrepos) + lfdirstate = lfutil.openlfdirstate(ui, repo) + modified, added, removed, deleted, unknown, ignored, clean = r + unknown = [f for f in unknown if lfdirstate[f] == '?'] + ignored = [f for f in ignored if lfdirstate[f] == '?'] + return modified, added, removed, deleted, unknown, ignored, clean + repo.status = override_status + orig(ui, repo, *dirs, **opts) + repo.status = oldstatus + +def override_rollback(orig, ui, repo, **opts): + result = orig(ui, repo, **opts) + merge.update(repo, node=None, branchmerge=False, force=True, + partial=lfutil.isstandin) + lfdirstate = lfutil.openlfdirstate(ui, repo) + lfiles = lfutil.listlfiles(repo) + oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev()) + for file in lfiles: + if file in oldlfiles: + lfdirstate.normallookup(file) + else: + lfdirstate.add(file) + lfdirstate.write() + return result
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/proto.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,160 @@ +# Copyright 2011 Fog Creek Software +# +# 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 +import tempfile +import urllib2 + +from mercurial import error, httprepo, util, wireproto +from mercurial.i18n import _ + +import lfutil + +LARGEFILES_REQUIRED_MSG = ('\nThis repository uses the largefiles extension.' + '\n\nPlease enable it in your Mercurial config ' + 'file.\n') + +def putlfile(repo, proto, sha): + '''Put a largefile into a repository's local cache and into the + system cache.''' + f = None + proto.redirect() + try: + try: + f = tempfile.NamedTemporaryFile(mode='wb+', prefix='hg-putlfile-') + proto.getfile(f) + f.seek(0) + if sha != lfutil.hexsha1(f): + return wireproto.pushres(1) + lfutil.copytocacheabsolute(repo, f.name, sha) + except IOError: + repo.ui.warn( + _('error: could not put received data into largefile store')) + return wireproto.pushres(1) + finally: + if f: + f.close() + + return wireproto.pushres(0) + +def getlfile(repo, proto, sha): + '''Retrieve a largefile from the repository-local cache or system + cache.''' + filename = lfutil.findfile(repo, sha) + if not filename: + raise util.Abort(_('requested largefile %s not present in cache') % sha) + f = open(filename, 'rb') + length = os.fstat(f.fileno())[6] + + # Since we can't set an HTTP content-length header here, and + # Mercurial core provides no way to give the length of a streamres + # (and reading the entire file into RAM would be ill-advised), we + # just send the length on the first line of the response, like the + # ssh proto does for string responses. + def generator(): + yield '%d\n' % length + for chunk in f: + yield chunk + return wireproto.streamres(generator()) + +def statlfile(repo, proto, sha): + '''Return '2\n' if the largefile is missing, '1\n' if it has a + mismatched checksum, or '0\n' if it is in good condition''' + filename = lfutil.findfile(repo, sha) + if not filename: + return '2\n' + fd = None + try: + fd = open(filename, 'rb') + return lfutil.hexsha1(fd) == sha and '0\n' or '1\n' + finally: + if fd: + fd.close() + +def wirereposetup(ui, repo): + class lfileswirerepository(repo.__class__): + def putlfile(self, sha, fd): + # unfortunately, httprepository._callpush tries to convert its + # input file-like into a bundle before sending it, so we can't use + # it ... + if issubclass(self.__class__, httprepo.httprepository): + try: + return int(self._call('putlfile', data=fd, sha=sha, + headers={'content-type':'application/mercurial-0.1'})) + except (ValueError, urllib2.HTTPError): + return 1 + # ... but we can't use sshrepository._call because the data= + # argument won't get sent, and _callpush does exactly what we want + # in this case: send the data straight through + else: + try: + ret, output = self._callpush("putlfile", fd, sha=sha) + if ret == "": + raise error.ResponseError(_('putlfile failed:'), + output) + return int(ret) + except IOError: + return 1 + except ValueError: + raise error.ResponseError( + _('putlfile failed (unexpected response):'), ret) + + def getlfile(self, sha): + stream = self._callstream("getlfile", sha=sha) + length = stream.readline() + try: + length = int(length) + except ValueError: + self._abort(error.ResponseError(_("unexpected response:"), + length)) + return (length, stream) + + def statlfile(self, sha): + try: + return int(self._call("statlfile", sha=sha)) + except (ValueError, urllib2.HTTPError): + # If the server returns anything but an integer followed by a + # newline, newline, it's not speaking our language; if we get + # an HTTP error, we can't be sure the largefile is present; + # either way, consider it missing. + return 2 + + repo.__class__ = lfileswirerepository + +# advertise the largefiles=serve capability +def capabilities(repo, proto): + return capabilities_orig(repo, proto) + ' largefiles=serve' + +# duplicate what Mercurial's new out-of-band errors mechanism does, because +# clients old and new alike both handle it well +def webproto_refuseclient(self, message): + self.req.header([('Content-Type', 'application/hg-error')]) + return message + +def sshproto_refuseclient(self, message): + self.ui.write_err('%s\n-\n' % message) + self.fout.write('\n') + self.fout.flush() + + return '' + +def heads(repo, proto): + if lfutil.islfilesrepo(repo): + return wireproto.ooberror(LARGEFILES_REQUIRED_MSG) + return wireproto.heads(repo, proto) + +def sshrepo_callstream(self, cmd, **args): + if cmd == 'heads' and self.capable('largefiles'): + cmd = 'lheads' + if cmd == 'batch' and self.capable('largefiles'): + args['cmds'] = args['cmds'].replace('heads ', 'lheads ') + return ssh_oldcallstream(self, cmd, **args) + +def httprepo_callstream(self, cmd, **args): + if cmd == 'heads' and self.capable('largefiles'): + cmd = 'lheads' + if cmd == 'batch' and self.capable('largefiles'): + args['cmds'] = args['cmds'].replace('heads ', 'lheads ') + return http_oldcallstream(self, cmd, **args)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/remotestore.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,106 @@ +# Copyright 2010-2011 Fog Creek Software +# Copyright 2010-2011 Unity Technologies +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''remote largefile store; the base class for servestore''' + +import urllib2 + +from mercurial import util +from mercurial.i18n import _ + +import lfutil +import basestore + +class remotestore(basestore.basestore): + '''a largefile store accessed over a network''' + def __init__(self, ui, repo, url): + super(remotestore, self).__init__(ui, repo, url) + + def put(self, source, hash): + if self._verify(hash): + return + if self.sendfile(source, hash): + raise util.Abort( + _('remotestore: could not put %s to remote store %s') + % (source, self.url)) + self.ui.debug( + _('remotestore: put %s to remote store %s') % (source, self.url)) + + def exists(self, hash): + return self._verify(hash) + + def sendfile(self, filename, hash): + self.ui.debug('remotestore: sendfile(%s, %s)\n' % (filename, hash)) + fd = None + try: + try: + fd = lfutil.httpsendfile(self.ui, filename) + except IOError, e: + raise util.Abort( + _('remotestore: could not open file %s: %s') + % (filename, str(e))) + return self._put(hash, fd) + finally: + if fd: + fd.close() + + def _getfile(self, tmpfile, filename, hash): + # quit if the largefile isn't there + stat = self._stat(hash) + if stat == 1: + raise util.Abort(_('remotestore: largefile %s is invalid') % hash) + elif stat == 2: + raise util.Abort(_('remotestore: largefile %s is missing') % hash) + + try: + length, infile = self._get(hash) + except urllib2.HTTPError, e: + # 401s get converted to util.Aborts; everything else is fine being + # turned into a StoreError + raise basestore.StoreError(filename, hash, self.url, str(e)) + except urllib2.URLError, e: + # This usually indicates a connection problem, so don't + # keep trying with the other files... they will probably + # all fail too. + raise util.Abort('%s: %s' % (self.url, e.reason)) + except IOError, e: + raise basestore.StoreError(filename, hash, self.url, str(e)) + + # Mercurial does not close its SSH connections after writing a stream + if length is not None: + infile = lfutil.limitreader(infile, length) + return lfutil.copyandhash(lfutil.blockstream(infile), tmpfile) + + def _verify(self, hash): + return not self._stat(hash) + + def _verifyfile(self, cctx, cset, contents, standin, verified): + filename = lfutil.splitstandin(standin) + if not filename: + return False + fctx = cctx[standin] + key = (filename, fctx.filenode()) + if key in verified: + return False + + verified.add(key) + + stat = self._stat(hash) + if not stat: + return False + elif stat == 1: + self.ui.warn( + _('changeset %s: %s: contents differ\n') + % (cset, filename)) + return True # failed + elif stat == 2: + self.ui.warn( + _('changeset %s: %s missing\n') + % (cset, filename)) + return True # failed + else: + raise RuntimeError('verify failed: unexpected response from ' + 'statlfile (%r)' % stat)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/reposetup.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,416 @@ +# Copyright 2009-2010 Gregory P. Ward +# Copyright 2009-2010 Intelerad Medical Systems Incorporated +# Copyright 2010-2011 Fog Creek Software +# Copyright 2010-2011 Unity Technologies +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''setup for largefiles repositories: reposetup''' +import copy +import types +import os +import re + +from mercurial import context, error, manifest, match as match_, \ + node, util +from mercurial.i18n import _ + +import lfcommands +import proto +import lfutil + +def reposetup(ui, repo): + # wire repositories should be given new wireproto functions but not the + # other largefiles modifications + if not repo.local(): + return proto.wirereposetup(ui, repo) + + for name in ('status', 'commitctx', 'commit', 'push'): + method = getattr(repo, name) + #if not (isinstance(method, types.MethodType) and + # method.im_func is repo.__class__.commitctx.im_func): + if (isinstance(method, types.FunctionType) and + method.func_name == 'wrap'): + ui.warn(_('largefiles: repo method %r appears to have already been' + ' wrapped by another extension: ' + 'largefiles may behave incorrectly\n') + % name) + + class lfiles_repo(repo.__class__): + lfstatus = False + def status_nolfiles(self, *args, **kwargs): + return super(lfiles_repo, self).status(*args, **kwargs) + + # When lfstatus is set, return a context that gives the names + # of largefiles instead of their corresponding standins and + # identifies the largefiles as always binary, regardless of + # their actual contents. + def __getitem__(self, changeid): + ctx = super(lfiles_repo, self).__getitem__(changeid) + if self.lfstatus: + class lfiles_manifestdict(manifest.manifestdict): + def __contains__(self, filename): + if super(lfiles_manifestdict, + self).__contains__(filename): + return True + return super(lfiles_manifestdict, + self).__contains__(lfutil.shortname+'/' + filename) + class lfiles_ctx(ctx.__class__): + def files(self): + filenames = super(lfiles_ctx, self).files() + return [re.sub('^\\'+lfutil.shortname+'/', '', + filename) for filename in filenames] + def manifest(self): + man1 = super(lfiles_ctx, self).manifest() + man1.__class__ = lfiles_manifestdict + return man1 + def filectx(self, path, fileid=None, filelog=None): + try: + result = super(lfiles_ctx, self).filectx(path, + fileid, filelog) + except error.LookupError: + # Adding a null character will cause Mercurial to + # identify this as a binary file. + result = super(lfiles_ctx, self).filectx( + lfutil.shortname + '/' + path, fileid, + filelog) + olddata = result.data + result.data = lambda: olddata() + '\0' + return result + ctx.__class__ = lfiles_ctx + return ctx + + # Figure out the status of big files and insert them into the + # appropriate list in the result. Also removes standin files + # from the listing. Revert to the original status if + # self.lfstatus is False. + def status(self, node1='.', node2=None, match=None, ignored=False, + clean=False, unknown=False, listsubrepos=False): + listignored, listclean, listunknown = ignored, clean, unknown + if not self.lfstatus: + try: + return super(lfiles_repo, self).status(node1, node2, match, + listignored, listclean, listunknown, listsubrepos) + except TypeError: + return super(lfiles_repo, self).status(node1, node2, match, + listignored, listclean, listunknown) + else: + # some calls in this function rely on the old version of status + self.lfstatus = False + if isinstance(node1, context.changectx): + ctx1 = node1 + else: + ctx1 = repo[node1] + if isinstance(node2, context.changectx): + ctx2 = node2 + else: + ctx2 = repo[node2] + working = ctx2.rev() is None + parentworking = working and ctx1 == self['.'] + + def inctx(file, ctx): + try: + if ctx.rev() is None: + return file in ctx.manifest() + ctx[file] + return True + except KeyError: + return False + + if match is None: + match = match_.always(self.root, self.getcwd()) + + # Create a copy of match that matches standins instead + # of largefiles. + def tostandin(file): + if inctx(lfutil.standin(file), ctx2): + return lfutil.standin(file) + return file + + m = copy.copy(match) + m._files = [tostandin(f) for f in m._files] + + # get ignored, clean, and unknown but remove them + # later if they were not asked for + try: + result = super(lfiles_repo, self).status(node1, node2, m, + True, True, True, listsubrepos) + except TypeError: + result = super(lfiles_repo, self).status(node1, node2, m, + True, True, True) + if working: + # hold the wlock while we read largefiles and + # update the lfdirstate + wlock = repo.wlock() + try: + # Any non-largefiles that were explicitly listed must be + # taken out or lfdirstate.status will report an error. + # The status of these files was already computed using + # super's status. + lfdirstate = lfutil.openlfdirstate(ui, self) + match._files = [f for f in match._files if f in + lfdirstate] + s = lfdirstate.status(match, [], listignored, + listclean, listunknown) + (unsure, modified, added, removed, missing, unknown, + ignored, clean) = s + if parentworking: + for lfile in unsure: + if ctx1[lfutil.standin(lfile)].data().strip() \ + != lfutil.hashfile(self.wjoin(lfile)): + modified.append(lfile) + else: + clean.append(lfile) + lfdirstate.normal(lfile) + lfdirstate.write() + else: + tocheck = unsure + modified + added + clean + modified, added, clean = [], [], [] + + for lfile in tocheck: + standin = lfutil.standin(lfile) + if inctx(standin, ctx1): + if ctx1[standin].data().strip() != \ + lfutil.hashfile(self.wjoin(lfile)): + modified.append(lfile) + else: + clean.append(lfile) + else: + added.append(lfile) + finally: + wlock.release() + + for standin in ctx1.manifest(): + if not lfutil.isstandin(standin): + continue + lfile = lfutil.splitstandin(standin) + if not match(lfile): + continue + if lfile not in lfdirstate: + removed.append(lfile) + # Handle unknown and ignored differently + lfiles = (modified, added, removed, missing, [], [], clean) + result = list(result) + # Unknown files + result[4] = [f for f in unknown + if (repo.dirstate[f] == '?' and + not lfutil.isstandin(f))] + # Ignored files must be ignored by both the dirstate and + # lfdirstate + result[5] = set(ignored).intersection(set(result[5])) + # combine normal files and largefiles + normals = [[fn for fn in filelist + if not lfutil.isstandin(fn)] + for filelist in result] + result = [sorted(list1 + list2) + for (list1, list2) in zip(normals, lfiles)] + else: + def toname(f): + if lfutil.isstandin(f): + return lfutil.splitstandin(f) + return f + result = [[toname(f) for f in items] for items in result] + + if not listunknown: + result[4] = [] + if not listignored: + result[5] = [] + if not listclean: + result[6] = [] + self.lfstatus = True + return result + + # As part of committing, copy all of the largefiles into the + # cache. + def commitctx(self, *args, **kwargs): + node = super(lfiles_repo, self).commitctx(*args, **kwargs) + ctx = self[node] + for filename in ctx.files(): + if lfutil.isstandin(filename) and filename in ctx.manifest(): + realfile = lfutil.splitstandin(filename) + lfutil.copytocache(self, ctx.node(), realfile) + + return node + + # Before commit, largefile standins have not had their + # contents updated to reflect the hash of their largefile. + # Do that here. + def commit(self, text="", user=None, date=None, match=None, + force=False, editor=False, extra={}): + orig = super(lfiles_repo, self).commit + + wlock = repo.wlock() + try: + if getattr(repo, "_isrebasing", False): + # We have to take the time to pull down the new + # largefiles now. Otherwise if we are rebasing, + # any largefiles that were modified in the + # destination changesets get overwritten, either + # by the rebase or in the first commit after the + # rebase. + lfcommands.updatelfiles(repo.ui, repo) + # Case 1: user calls commit with no specific files or + # include/exclude patterns: refresh and commit all files that + # are "dirty". + if ((match is None) or + (not match.anypats() and not match.files())): + # Spend a bit of time here to get a list of files we know + # are modified so we can compare only against those. + # It can cost a lot of time (several seconds) + # otherwise to update all standins if the largefiles are + # large. + lfdirstate = lfutil.openlfdirstate(ui, self) + dirtymatch = match_.always(repo.root, repo.getcwd()) + s = lfdirstate.status(dirtymatch, [], False, False, False) + modifiedfiles = [] + for i in s: + modifiedfiles.extend(i) + lfiles = lfutil.listlfiles(self) + # this only loops through largefiles that exist (not + # removed/renamed) + for lfile in lfiles: + if lfile in modifiedfiles: + if os.path.exists(self.wjoin(lfutil.standin(lfile))): + # this handles the case where a rebase is being + # performed and the working copy is not updated + # yet. + if os.path.exists(self.wjoin(lfile)): + lfutil.updatestandin(self, + lfutil.standin(lfile)) + lfdirstate.normal(lfile) + for lfile in lfdirstate: + if lfile in modifiedfiles: + if not os.path.exists( + repo.wjoin(lfutil.standin(lfile))): + lfdirstate.drop(lfile) + lfdirstate.write() + + return orig(text=text, user=user, date=date, match=match, + force=force, editor=editor, extra=extra) + + for f in match.files(): + if lfutil.isstandin(f): + raise util.Abort( + _('file "%s" is a largefile standin') % f, + hint=('commit the largefile itself instead')) + + # Case 2: user calls commit with specified patterns: refresh + # any matching big files. + smatcher = lfutil.composestandinmatcher(self, match) + standins = lfutil.dirstate_walk(self.dirstate, smatcher) + + # No matching big files: get out of the way and pass control to + # the usual commit() method. + if not standins: + return orig(text=text, user=user, date=date, match=match, + force=force, editor=editor, extra=extra) + + # Refresh all matching big files. It's possible that the + # commit will end up failing, in which case the big files will + # stay refreshed. No harm done: the user modified them and + # asked to commit them, so sooner or later we're going to + # refresh the standins. Might as well leave them refreshed. + lfdirstate = lfutil.openlfdirstate(ui, self) + for standin in standins: + lfile = lfutil.splitstandin(standin) + if lfdirstate[lfile] <> 'r': + lfutil.updatestandin(self, standin) + lfdirstate.normal(lfile) + else: + lfdirstate.drop(lfile) + lfdirstate.write() + + # Cook up a new matcher that only matches regular files or + # standins corresponding to the big files requested by the + # user. Have to modify _files to prevent commit() from + # complaining "not tracked" for big files. + lfiles = lfutil.listlfiles(repo) + match = copy.copy(match) + orig_matchfn = match.matchfn + + # Check both the list of largefiles and the list of + # standins because if a largefile was removed, it + # won't be in the list of largefiles at this point + match._files += sorted(standins) + + actualfiles = [] + for f in match._files: + fstandin = lfutil.standin(f) + + # ignore known largefiles and standins + if f in lfiles or fstandin in standins: + continue + + # append directory separator to avoid collisions + if not fstandin.endswith(os.sep): + fstandin += os.sep + + # prevalidate matching standin directories + if lfutil.any_(st for st in match._files + if st.startswith(fstandin)): + continue + actualfiles.append(f) + match._files = actualfiles + + def matchfn(f): + if orig_matchfn(f): + return f not in lfiles + else: + return f in standins + + match.matchfn = matchfn + return orig(text=text, user=user, date=date, match=match, + force=force, editor=editor, extra=extra) + finally: + wlock.release() + + def push(self, remote, force=False, revs=None, newbranch=False): + o = lfutil.findoutgoing(repo, remote, force) + if o: + toupload = set() + o = repo.changelog.nodesbetween(o, revs)[0] + for n in o: + parents = [p for p in repo.changelog.parents(n) + if p != node.nullid] + ctx = repo[n] + files = set(ctx.files()) + if len(parents) == 2: + mc = ctx.manifest() + mp1 = ctx.parents()[0].manifest() + mp2 = ctx.parents()[1].manifest() + for f in mp1: + if f not in mc: + files.add(f) + for f in mp2: + if f not in mc: + files.add(f) + for f in mc: + if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, + None): + files.add(f) + + toupload = toupload.union( + set([ctx[f].data().strip() + for f in files + if lfutil.isstandin(f) and f in ctx])) + lfcommands.uploadlfiles(ui, self, remote, toupload) + return super(lfiles_repo, self).push(remote, force, revs, + newbranch) + + repo.__class__ = lfiles_repo + + def checkrequireslfiles(ui, repo, **kwargs): + if 'largefiles' not in repo.requirements and lfutil.any_( + lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()): + # workaround bug in Mercurial 1.9 whereby requirements is + # a list on newly-cloned repos + repo.requirements = set(repo.requirements) + + repo.requirements |= set(['largefiles']) + repo._writerequirements() + + checkrequireslfiles(ui, repo) + + ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles) + ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/uisetup.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,138 @@ +# Copyright 2009-2010 Gregory P. Ward +# Copyright 2009-2010 Intelerad Medical Systems Incorporated +# Copyright 2010-2011 Fog Creek Software +# Copyright 2010-2011 Unity Technologies +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''setup for largefiles extension: uisetup''' + +from mercurial import archival, cmdutil, commands, extensions, filemerge, hg, \ + httprepo, localrepo, sshrepo, sshserver, util, wireproto +from mercurial.i18n import _ +from mercurial.hgweb import hgweb_mod, protocol + +import overrides +import proto + +def uisetup(ui): + # Disable auto-status for some commands which assume that all + # files in the result are under Mercurial's control + + entry = extensions.wrapcommand(commands.table, 'add', + overrides.override_add) + addopt = [('', 'large', None, _('add as largefile')), + ('', 'lfsize', '', _('add all files above this size (in megabytes)' + 'as largefiles (default: 10)'))] + entry[1].extend(addopt) + + entry = extensions.wrapcommand(commands.table, 'addremove', + overrides.override_addremove) + entry = extensions.wrapcommand(commands.table, 'remove', + overrides.override_remove) + entry = extensions.wrapcommand(commands.table, 'forget', + overrides.override_forget) + entry = extensions.wrapcommand(commands.table, 'status', + overrides.override_status) + entry = extensions.wrapcommand(commands.table, 'log', + overrides.override_log) + entry = extensions.wrapcommand(commands.table, 'rollback', + overrides.override_rollback) + entry = extensions.wrapcommand(commands.table, 'verify', + overrides.override_verify) + + verifyopt = [('', 'large', None, _('verify largefiles')), + ('', 'lfa', None, + _('verify all revisions of largefiles not just current')), + ('', 'lfc', None, + _('verify largefile contents not just existence'))] + entry[1].extend(verifyopt) + + entry = extensions.wrapcommand(commands.table, 'outgoing', + overrides.override_outgoing) + outgoingopt = [('', 'large', None, _('display outgoing largefiles'))] + entry[1].extend(outgoingopt) + entry = extensions.wrapcommand(commands.table, 'summary', + overrides.override_summary) + summaryopt = [('', 'large', None, _('display outgoing largefiles'))] + entry[1].extend(summaryopt) + + entry = extensions.wrapcommand(commands.table, 'update', + overrides.override_update) + entry = extensions.wrapcommand(commands.table, 'pull', + overrides.override_pull) + entry = extensions.wrapfunction(filemerge, 'filemerge', + overrides.override_filemerge) + entry = extensions.wrapfunction(cmdutil, 'copy', + overrides.override_copy) + + # Backout calls revert so we need to override both the command and the + # function + entry = extensions.wrapcommand(commands.table, 'revert', + overrides.override_revert) + entry = extensions.wrapfunction(commands, 'revert', + overrides.override_revert) + + # clone uses hg._update instead of hg.update even though they are the + # same function... so wrap both of them) + extensions.wrapfunction(hg, 'update', overrides.hg_update) + extensions.wrapfunction(hg, '_update', overrides.hg_update) + extensions.wrapfunction(hg, 'clean', overrides.hg_clean) + extensions.wrapfunction(hg, 'merge', overrides.hg_merge) + + extensions.wrapfunction(archival, 'archive', overrides.override_archive) + if util.safehasattr(cmdutil, 'bailifchanged'): + extensions.wrapfunction(cmdutil, 'bailifchanged', + overrides.override_bailifchanged) + else: + extensions.wrapfunction(cmdutil, 'bail_if_changed', + overrides.override_bailifchanged) + + # create the new wireproto commands ... + wireproto.commands['putlfile'] = (proto.putlfile, 'sha') + wireproto.commands['getlfile'] = (proto.getlfile, 'sha') + wireproto.commands['statlfile'] = (proto.statlfile, 'sha') + + # ... and wrap some existing ones + wireproto.commands['capabilities'] = (proto.capabilities, '') + wireproto.commands['heads'] = (proto.heads, '') + wireproto.commands['lheads'] = (wireproto.heads, '') + + # make putlfile behave the same as push and {get,stat}lfile behave + # the same as pull w.r.t. permissions checks + hgweb_mod.perms['putlfile'] = 'push' + hgweb_mod.perms['getlfile'] = 'pull' + hgweb_mod.perms['statlfile'] = 'pull' + + # the hello wireproto command uses wireproto.capabilities, so it won't see + # our largefiles capability unless we replace the actual function as well. + proto.capabilities_orig = wireproto.capabilities + wireproto.capabilities = proto.capabilities + + # these let us reject non-largefiles clients and make them display + # our error messages + protocol.webproto.refuseclient = proto.webproto_refuseclient + sshserver.sshserver.refuseclient = proto.sshproto_refuseclient + + # can't do this in reposetup because it needs to have happened before + # wirerepo.__init__ is called + proto.ssh_oldcallstream = sshrepo.sshrepository._callstream + proto.http_oldcallstream = httprepo.httprepository._callstream + sshrepo.sshrepository._callstream = proto.sshrepo_callstream + httprepo.httprepository._callstream = proto.httprepo_callstream + + # don't die on seeing a repo with the largefiles requirement + localrepo.localrepository.supported |= set(['largefiles']) + + # override some extensions' stuff as well + for name, module in extensions.extensions(): + if name == 'fetch': + extensions.wrapcommand(getattr(module, 'cmdtable'), 'fetch', + overrides.override_fetch) + if name == 'purge': + extensions.wrapcommand(getattr(module, 'cmdtable'), 'purge', + overrides.override_purge) + if name == 'rebase': + extensions.wrapcommand(getattr(module, 'cmdtable'), 'rebase', + overrides.override_rebase)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/usage.txt Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,51 @@ +Largefiles allows for tracking large, incompressible binary files in Mercurial +without requiring excessive bandwidth for clones and pulls. Files added as +largefiles are not tracked directly by Mercurial; rather, their revisions are +identified by a checksum, and Mercurial tracks these checksums. This way, when +you clone a repository or pull in changesets, the large files in older +revisions of the repository are not needed, and only the ones needed to update +to the current version are downloaded. This saves both disk space and +bandwidth. + +If you are starting a new repository or adding new large binary files, using +largefiles for them is as easy as adding '--large' to your hg add command. For +example: + +$ dd if=/dev/urandom of=thisfileislarge count=2000 +$ hg add --large thisfileislarge +$ hg commit -m 'add thisfileislarge, which is large, as a largefile' + +When you push a changeset that affects largefiles to a remote repository, its +largefile revisions will be uploaded along with it. Note that the remote +Mercurial must also have the largefiles extension enabled for this to work. + +When you pull a changeset that affects largefiles from a remote repository, +nothing different from Mercurial's normal behavior happens. However, when you +update to such a revision, any largefiles needed by that revision are +downloaded and cached if they have never been downloaded before. This means +that network access is required to update to revision you have not yet updated +to. + +If you already have large files tracked by Mercurial without the largefiles +extension, you will need to convert your repository in order to benefit from +largefiles. This is done with the 'hg lfconvert' command: + +$ hg lfconvert --size 10 oldrepo newrepo + +By default, in repositories that already have largefiles in them, any new file +over 10MB will automatically be added as largefiles. To change this +threshhold, set [largefiles].size in your Mercurial config file to the minimum +size in megabytes to track as a largefile, or use the --lfsize option to the +add command (also in megabytes): + +[largefiles] +size = 2 + +$ hg add --lfsize 2 + +The [largefiles].patterns config option allows you to specify specific +space-separated filename patterns (in shell glob syntax) that should always be +tracked as largefiles: + +[largefiles] +pattens = *.jpg *.{png,bmp} library.zip content/audio/*
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/largefiles/wirestore.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,29 @@ +# Copyright 2010-2011 Fog Creek Software +# +# This software may be used and distributed according to the terms of the +# GNU General Public License version 2 or any later version. + +'''largefile store working over Mercurial's wire protocol''' + +import lfutil +import remotestore + +class wirestore(remotestore.remotestore): + def __init__(self, ui, repo, remote): + cap = remote.capable('largefiles') + if not cap: + raise lfutil.storeprotonotcapable([]) + storetypes = cap.split(',') + if not 'serve' in storetypes: + raise lfutil.storeprotonotcapable(storetypes) + self.remote = remote + super(wirestore, self).__init__(ui, repo, remote.url()) + + def _put(self, hash, fd): + return self.remote.putlfile(hash, fd) + + def _get(self, hash): + return self.remote.getlfile(hash) + + def _stat(self, hash): + return self.remote.statlfile(hash)
--- a/hgext/mq.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/mq.py Sat Oct 15 14:30:50 2011 -0500 @@ -287,25 +287,31 @@ @util.propertycache def applied(self): - if os.path.exists(self.join(self.statuspath)): - def parselines(lines): - for l in lines: - entry = l.split(':', 1) - if len(entry) > 1: - n, name = entry - yield statusentry(bin(n), name) - elif l.strip(): - self.ui.warn(_('malformated mq status line: %s\n') % entry) - # else we ignore empty lines + def parselines(lines): + for l in lines: + entry = l.split(':', 1) + if len(entry) > 1: + n, name = entry + yield statusentry(bin(n), name) + elif l.strip(): + self.ui.warn(_('malformated mq status line: %s\n') % entry) + # else we ignore empty lines + try: lines = self.opener.read(self.statuspath).splitlines() return list(parselines(lines)) - return [] + except IOError, e: + if e.errno == errno.ENOENT: + return [] + raise @util.propertycache def fullseries(self): - if os.path.exists(self.join(self.seriespath)): - return self.opener.read(self.seriespath).splitlines() - return [] + try: + return self.opener.read(self.seriespath).splitlines() + except IOError, e: + if e.errno == errno.ENOENT: + return [] + raise @util.propertycache def series(self): @@ -626,6 +632,7 @@ self.ui.note(str(inst) + '\n') if not self.ui.verbose: self.ui.warn(_("patch failed, unable to continue (try -v)\n")) + self.ui.traceback() return (False, list(files), False) def apply(self, repo, series, list=False, update_status=True, @@ -938,7 +945,7 @@ p.write("# User " + user + "\n") if date: p.write("# Date %s %s\n\n" % date) - if hasattr(msg, '__call__'): + if util.safehasattr(msg, '__call__'): msg = msg() commitmsg = msg and msg or ("[mq]: %s" % patchfn) n = repo.commit(commitmsg, user, date, match=match, force=True) @@ -1010,12 +1017,10 @@ # if the exact patch name does not exist, we try a few # variations. If strict is passed, we try only #1 # - # 1) a number to indicate an offset in the series file + # 1) a number (as string) to indicate an offset in the series file # 2) a unique substring of the patch name was given # 3) patchname[-+]num to indicate an offset in the series file def lookup(self, patch, strict=False): - patch = patch and str(patch) - def partialname(s): if s in self.series: return s @@ -1034,8 +1039,6 @@ return self.series[0] return None - if patch is None: - return None if patch in self.series: return patch @@ -1095,12 +1098,12 @@ self.ui.warn(_('no patches in series\n')) return 0 - patch = self.lookup(patch) # Suppose our series file is: A B C and the current 'top' # patch is B. qpush C should be performed (moving forward) # qpush B is a NOP (no change) qpush A is an error (can't # go backwards with qpush) if patch: + patch = self.lookup(patch) info = self.isapplied(patch) if info and info[0] >= len(self.applied) - 1: self.ui.warn( @@ -1492,7 +1495,7 @@ n = repo.commit(message, user, ph.date, match=match, force=True) # only write patch after a successful commit - patchf.rename() + patchf.close() self.applied.append(statusentry(n, patchfn)) except: ctx = repo[cparents[0]] @@ -2675,7 +2678,11 @@ return 0 @command("strip", - [('f', 'force', None, _('force removal of changesets, discard ' + [ + ('r', 'rev', [], _('strip specified revision (optional, ' + 'can specify revisions without this ' + 'option)'), _('REV')), + ('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' @@ -2716,6 +2723,7 @@ backup = 'none' cl = repo.changelog + revs = list(revs) + opts.get('rev') revs = set(scmutil.revrange(repo, revs)) if not revs: raise util.Abort(_('empty revision set')) @@ -2867,7 +2875,7 @@ if i == 0: q.pop(repo, all=True) else: - q.pop(repo, i - 1) + q.pop(repo, str(i - 1)) break if popped: try: @@ -2915,6 +2923,7 @@ @command("qqueue", [('l', 'list', False, _('list all available queues')), + ('', 'active', False, _('print name of active queue')), ('c', 'create', False, _('create new queue')), ('', 'rename', False, _('rename active queue')), ('', 'delete', False, _('delete reference to queue')), @@ -2929,7 +2938,8 @@ Omitting a queue name or specifying -l/--list will show you the registered queues - by default the "normal" patches queue is registered. The currently - active queue will be marked with "(active)". + active queue will be marked with "(active)". Specifying --active will print + only the name of the active queue. To create a new queue, use -c/--create. The queue is automatically made active, except in the case where there are applied patches from the @@ -3022,8 +3032,11 @@ fh.close() util.rename(repo.join('patches.queues.new'), repo.join(_allqueues)) - if not name or opts.get('list'): + if not name or opts.get('list') or opts.get('active'): current = _getcurrent() + if opts.get('active'): + ui.write('%s\n' % (current,)) + return for queue in _getqueues(): ui.write('%s' % (queue,)) if queue == current and not ui.quiet:
--- a/hgext/notify.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/notify.py Sat Oct 15 14:30:50 2011 -0500 @@ -5,71 +5,115 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -'''hooks for sending email notifications at commit/push time +'''hooks for sending email push notifications -Subscriptions can be managed through a hgrc file. Default mode is to -print messages to stdout, for testing and configuring. +This extension let you run hooks sending email notifications when +changesets are being pushed, from the sending or receiving side. -To use, configure the notify extension and enable it in hgrc like -this:: - - [extensions] - notify = +First, enable the extension as explained in :hg:`help extensions`, and +register the hook you want to run. ``incoming`` and ``outgoing`` hooks +are run by the changesets receiver while the ``outgoing`` one is for +the sender:: [hooks] # one email for each incoming changeset incoming.notify = python:hgext.notify.hook - # batch emails when many changesets incoming at one time + # one email for all incoming changesets changegroup.notify = python:hgext.notify.hook - # batch emails when many changesets outgoing at one time (client side) + + # one email for all outgoing changesets outgoing.notify = python:hgext.notify.hook - [notify] - # config items go here - -Required configuration items:: - - config = /path/to/file # file containing subscriptions - -Optional configuration items:: - - test = True # print messages to stdout for testing - strip = 3 # number of slashes to strip for url paths - domain = example.com # domain to use if committer missing domain - style = ... # style file to use when formatting email - template = ... # template to use when formatting email - incoming = ... # template to use when run as incoming hook - outgoing = ... # template to use when run as outgoing hook - changegroup = ... # template to use when run as changegroup hook - maxdiff = 300 # max lines of diffs to include (0=none, -1=all) - maxsubject = 67 # truncate subject line longer than this - diffstat = True # add a diffstat before the diff content - sources = serve # notify if source of incoming changes in this list - # (serve == ssh or http, push, pull, bundle) - merge = False # send notification for merges (default True) - [email] - from = user@host.com # email address to send as if none given - [web] - baseurl = http://hgserver/... # root of hg web site for browsing commits - -The notify config file has same format as a regular hgrc file. It has -two sections so you can express subscriptions in whatever way is -handier for you. - -:: +Now the hooks are running, subscribers must be assigned to +repositories. Use the ``[usersubs]`` section to map repositories to a +given email or the ``[reposubs]`` section to map emails to a single +repository:: [usersubs] - # key is subscriber email, value is ","-separated list of glob patterns + # key is subscriber email, value is a comma-separated list of glob + # patterns user@host = pattern [reposubs] - # key is glob pattern, value is ","-separated list of subscriber emails + # key is glob pattern, value is a comma-separated list of subscriber + # emails pattern = user@host -Glob patterns are matched against path to repository root. +Glob patterns are matched against absolute path to repository +root. The subscriptions can be defined in their own file and +referenced with:: + + [notify] + config = /path/to/subscriptionsfile + +Alternatively, they can be added to Mercurial configuration files by +setting the previous entry to an empty value. + +At this point, notifications should be generated but will not be sent until you +set the ``notify.test`` entry to ``False``. + +Notifications content can be tweaked with the following configuration entries: + +notify.test + If ``True``, print messages to stdout instead of sending them. Default: True. + +notify.sources + Space separated list of change sources. Notifications are sent only + if it includes the incoming or outgoing changes source. Incoming + sources can be ``serve`` for changes coming from http or ssh, + ``pull`` for pulled changes, ``unbundle`` for changes added by + :hg:`unbundle` or ``push`` for changes being pushed + locally. Outgoing sources are the same except for ``unbundle`` which + is replaced by ``bundle``. Default: serve. + +notify.strip + Number of leading slashes to strip from url paths. By default, notifications + references repositories with their absolute path. ``notify.strip`` let you + turn them into relative paths. For example, ``notify.strip=3`` will change + ``/long/path/repository`` into ``repository``. Default: 0. + +notify.domain + If subscribers emails or the from email have no domain set, complete them + with this value. -If you like, you can put notify config file in repository that users -can push changes to, they can manage their own subscriptions. +notify.style + Style file to use when formatting emails. + +notify.template + Template to use when formatting emails. + +notify.incoming + Template to use when run as incoming hook, override ``notify.template``. + +notify.outgoing + Template to use when run as outgoing hook, override ``notify.template``. + +notify.changegroup + Template to use when running as changegroup hook, override + ``notify.template``. + +notify.maxdiff + Maximum number of diff lines to include in notification email. Set to 0 + to disable the diff, -1 to include all of it. Default: 300. + +notify.maxsubject + Maximum number of characters in emails subject line. Default: 67. + +notify.diffstat + Set to True to include a diffstat before diff content. Default: True. + +notify.merge + If True, send notifications for merge changesets. Default: True. + +If set, the following entries will also be used to customize the notifications: + +email.from + Email ``From`` address to use if none can be found in generated email content. + +web.baseurl + Root repository browsing URL to combine with repository paths when making + references. See also ``notify.strip``. + ''' from mercurial.i18n import _ @@ -167,9 +211,6 @@ return [mail.addressencode(self.ui, s, self.charsets, self.test) for s in sorted(subs)] - def url(self, path=None): - return self.ui.config('web', 'baseurl') + (path or self.root) - def node(self, ctx, **props): '''format one changeset, unless it is a suppressed merge.''' if not self.merge and len(ctx.parents()) > 1:
--- a/hgext/pager.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/pager.py Sat Oct 15 14:30:50 2011 -0500 @@ -58,7 +58,7 @@ from mercurial.i18n import _ def _runpager(p): - if not hasattr(os, 'fork'): + if not util.safehasattr(os, 'fork'): sys.stdout = util.popen(p, 'wb') if util.isatty(sys.stderr): sys.stderr = sys.stdout
--- a/hgext/patchbomb.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/patchbomb.py Sat Oct 15 14:30:50 2011 -0500 @@ -57,24 +57,15 @@ 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))) if default: prompt += ' [%s]' % default - prompt += rest - while True: - r = ui.prompt(prompt, default=default) - if r: - return r - if default is not None: - return default - ui.warn(_('Please enter a valid value.\n')) + return ui.prompt(prompt + rest, default) -def introneeded(opts, number): - '''is an introductory message required?''' +def introwanted(opts, number): + '''is an introductory message apparently wanted?''' return number > 1 or opts.get('intro') or opts.get('desc') -def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, +def makepatch(ui, repo, patchlines, opts, _charsets, idx, total, numbered, patchname=None): desc = [] @@ -141,7 +132,7 @@ flag = ' ' + flag subj = desc[0].strip().rstrip('. ') - if not introneeded(opts, total): + if not numbered: subj = '[PATCH%s] %s' % (flag, opts.get('subject') or subj) else: tlen = len(str(total)) @@ -352,51 +343,66 @@ ui.write(_('\nWrite the introductory message for the ' 'patch series.\n\n')) body = ui.edit(body, sender) - # Save serie description in case sendmail fails + # Save series description in case sendmail fails msgfile = repo.opener('last-email.txt', 'wb') msgfile.write(body) msgfile.close() return body def getpatchmsgs(patches, patchnames=None): - jumbo = [] msgs = [] ui.write(_('This patch series consists of %d patches.\n\n') % len(patches)) + # build the intro message, or skip it if the user declines + if introwanted(opts, len(patches)): + msg = makeintro(patches) + if msg: + msgs.append(msg) + + # are we going to send more than one message? + numbered = len(msgs) + len(patches) > 1 + + # now generate the actual patch messages name = None for i, p in enumerate(patches): - jumbo.extend(p) if patchnames: name = patchnames[i] msg = makepatch(ui, repo, p, opts, _charsets, i + 1, - len(patches), name) + len(patches), numbered, name) msgs.append(msg) - if introneeded(opts, len(patches)): - tlen = len(str(len(patches))) + return msgs + + def makeintro(patches): + tlen = len(str(len(patches))) - flag = ' '.join(opts.get('flag')) - if flag: - subj = '[PATCH %0*d of %d %s]' % (tlen, 0, len(patches), flag) - else: - subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches)) - subj += ' ' + (opts.get('subject') or - prompt(ui, 'Subject: ', rest=subj)) + flag = opts.get('flag') or '' + if flag: + flag = ' ' + ' '.join(flag) + prefix = '[PATCH %0*d of %d%s]' % (tlen, 0, len(patches), flag) + + subj = (opts.get('subject') or + prompt(ui, 'Subject: ', rest=prefix, default='')) + if not subj: + return None # skip intro if the user doesn't bother - body = '' - ds = patch.diffstat(jumbo) - if ds and opts.get('diffstat'): - body = '\n' + ds + subj = prefix + ' ' + subj - body = getdescription(body, sender) - msg = mail.mimeencode(ui, body, _charsets, opts.get('test')) - msg['Subject'] = mail.headencode(ui, subj, _charsets, - opts.get('test')) + body = '' + if opts.get('diffstat'): + # generate a cumulative diffstat of the whole patch series + diffstat = patch.diffstat(sum(patches, [])) + body = '\n' + diffstat + else: + diffstat = None - msgs.insert(0, (msg, subj, ds)) - return msgs + body = getdescription(body, sender) + msg = mail.mimeencode(ui, body, _charsets, opts.get('test')) + msg['Subject'] = mail.headencode(ui, subj, _charsets, + opts.get('test')) + return (msg, subj, diffstat) def getbundlemsgs(bundle): subj = (opts.get('subject') @@ -429,29 +435,33 @@ showaddrs = [] - def getaddrs(opt, prpt=None, default=None): - addrs = opts.get(opt.replace('-', '_')) - if opt != 'reply-to': - showaddr = '%s:' % opt.capitalize() - else: - showaddr = 'Reply-To:' - + def getaddrs(header, ask=False, default=None): + configkey = header.lower() + opt = header.replace('-', '_').lower() + addrs = opts.get(opt) if addrs: - showaddrs.append('%s %s' % (showaddr, ', '.join(addrs))) + showaddrs.append('%s: %s' % (header, ', '.join(addrs))) return mail.addrlistencode(ui, addrs, _charsets, opts.get('test')) - addrs = ui.config('email', opt) or ui.config('patchbomb', opt) or '' - if not addrs and prpt: - addrs = prompt(ui, prpt, default) + # not on the command line: fallback to config and then maybe ask + addr = (ui.config('email', configkey) or + ui.config('patchbomb', configkey) or + '') + if not addr and ask: + addr = prompt(ui, header, default=default) + if addr: + showaddrs.append('%s: %s' % (header, addr)) + return mail.addrlistencode(ui, [addr], _charsets, opts.get('test')) + else: + return default - if addrs: - showaddrs.append('%s %s' % (showaddr, addrs)) - return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test')) - - to = getaddrs('to', 'To') - cc = getaddrs('cc', 'Cc', '') - bcc = getaddrs('bcc') - replyto = getaddrs('reply-to') + to = getaddrs('To', ask=True) + if not to: + # we can get here in non-interactive mode + raise util.Abort(_('no recipient addresses provided')) + cc = getaddrs('Cc', ask=True, default='') or [] + bcc = getaddrs('Bcc') or [] + replyto = getaddrs('Reply-To') if opts.get('diffstat') or opts.get('confirm'): ui.write(_('\nFinal summary:\n\n'))
--- a/hgext/progress.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/progress.py Sat Oct 15 14:30:50 2011 -0500 @@ -27,6 +27,9 @@ [progress] delay = 3 # number of seconds (float) before showing the progress bar + changedelay = 1 # changedelay: minimum delay before showing a new topic. + # If set to less than 3 * refresh, that value will + # be used instead. refresh = 0.1 # time in seconds between refreshes of the progress bar format = topic bar number estimate # format of the progress bar width = <none> # if set, the maximum width of the progress information @@ -53,7 +56,7 @@ return ' '.join(s for s in args if s) def shouldprint(ui): - return (util.isatty(sys.stderr) or ui.configbool('progress', 'assume-tty')) + return util.isatty(sys.stderr) or ui.configbool('progress', 'assume-tty') def fmtremaining(seconds): if seconds < 60: @@ -105,9 +108,13 @@ self.printed = False self.lastprint = time.time() + float(self.ui.config( 'progress', 'delay', default=3)) + self.lasttopic = None self.indetcount = 0 self.refresh = float(self.ui.config( 'progress', 'refresh', default=0.1)) + self.changedelay = max(3 * self.refresh, + float(self.ui.config( + 'progress', 'changedelay', default=1))) self.order = self.ui.configlist( 'progress', 'format', default=['topic', 'bar', 'number', 'estimate']) @@ -184,6 +191,7 @@ else: out = spacejoin(head, tail) sys.stderr.write('\r' + out[:termwidth]) + self.lasttopic = topic sys.stderr.flush() def clear(self): @@ -248,10 +256,18 @@ self.topics.append(topic) self.topicstates[topic] = pos, item, unit, total if now - self.lastprint >= self.refresh and self.topics: - self.lastprint = now - self.show(now, topic, *self.topicstates[topic]) + if (self.lasttopic is None # first time we printed + # not a topic change + or topic == self.lasttopic + # it's been long enough we should print anyway + or now - self.lastprint >= self.changedelay): + self.lastprint = now + self.show(now, topic, *self.topicstates[topic]) + +_singleton = None def uisetup(ui): + global _singleton class progressui(ui.__class__): _progbar = None @@ -278,7 +294,9 @@ # we instantiate one globally shared progress bar to avoid # competing progress bars when multiple UI objects get created if not progressui._progbar: - progressui._progbar = progbar(ui) + if _singleton is None: + _singleton = progbar(ui) + progressui._progbar = _singleton def reposetup(ui, repo): uisetup(repo.ui)
--- a/hgext/rebase.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/rebase.py Sat Oct 15 14:30:50 2011 -0500 @@ -15,7 +15,7 @@ ''' from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks -from mercurial import extensions, copies, patch +from mercurial import extensions, patch from mercurial.commands import templateopts from mercurial.node import nullrev from mercurial.lock import release @@ -34,11 +34,15 @@ _('rebase from the base of the specified changeset ' '(up to greatest common ancestor of base and dest)'), _('REV')), + ('r', 'rev', [], + _('rebase these revisions'), + _('REV')), ('d', 'dest', '', _('rebase onto the specified changeset'), _('REV')), ('', 'collapse', False, _('collapse the rebased changesets')), ('m', 'message', '', _('use text as collapse commit message'), _('TEXT')), + ('e', 'edit', False, _('invoke editor on commit messages')), ('l', 'logfile', '', _('read collapse commit message from file'), _('FILE')), ('', 'keep', False, _('keep original changesets')), @@ -105,6 +109,10 @@ skipped = set() targetancestors = set() + editor = None + if opts.get('edit'): + editor = cmdutil.commitforceeditor + lock = wlock = None try: lock = repo.lock() @@ -114,6 +122,7 @@ destf = opts.get('dest', None) srcf = opts.get('source', None) basef = opts.get('base', None) + revf = opts.get('rev', []) contf = opts.get('continue') abortf = opts.get('abort') collapsef = opts.get('collapse', False) @@ -151,7 +160,13 @@ else: if srcf and basef: raise util.Abort(_('cannot specify both a ' + 'source and a base')) + if revf and basef: + raise util.Abort(_('cannot specify both a' 'revision and a base')) + if revf and srcf: + raise util.Abort(_('cannot specify both a' + 'revision and a source')) if detachf: if not srcf: raise util.Abort( @@ -160,7 +175,38 @@ raise util.Abort(_('cannot specify a base with detach')) cmdutil.bailifchanged(repo) - result = buildstate(repo, destf, srcf, basef, detachf) + + if not destf: + # Destination defaults to the latest revision in the + # current branch + branch = repo[None].branch() + dest = repo[branch] + else: + dest = repo[destf] + + if revf: + revgen = repo.set('%lr', revf) + elif srcf: + revgen = repo.set('(%r)::', srcf) + else: + base = basef or '.' + revgen = repo.set('(children(ancestor(%r, %d)) and ::(%r))::', + base, dest, base) + + rebaseset = [c.rev() for c in revgen] + + if not rebaseset: + repo.ui.debug('base is ancestor of destination') + result = None + elif not keepf and list(repo.set('first(children(%ld) - %ld)', + rebaseset, rebaseset)): + raise util.Abort( + _("can't remove original changesets with" + " unrebased descendants"), + hint=_('use --keep to keep original changesets')) + else: + result = buildstate(repo, dest, rebaseset, detachf) + if not result: # Empty state built, nothing to rebase ui.status(_('nothing to rebase\n')) @@ -215,9 +261,10 @@ 'resolve, then hg rebase --continue)')) finally: ui.setconfig('ui', 'forcemerge', '') - updatedirstate(repo, rev, target, p2) + cmdutil.duplicatecopies(repo, rev, target, p2) if not collapsef: - newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn) + newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn, + editor=editor) else: # Skip commit if we are collapsing repo.dirstate.setparents(repo[p1].node()) @@ -247,7 +294,7 @@ commitmsg += '\n* %s' % repo[rebased].description() commitmsg = ui.edit(commitmsg, repo.ui.username()) newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg, - extrafn=extrafn) + extrafn=extrafn, editor=editor) if 'qtip' in repo.tags(): updatemq(repo, state, skipped, **opts) @@ -301,21 +348,7 @@ external = p.rev() return external -def updatedirstate(repo, rev, p1, p2): - """Keep track of renamed files in the revision that is going to be rebased - """ - # Here we simulate the copies and renames in the source changeset - cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True) - m1 = repo[rev].manifest() - m2 = repo[p1].manifest() - for k, v in cop.iteritems(): - if k in m1: - if v in m1 or v in m2: - repo.dirstate.copy(v, k) - if v in m2 and v not in m1 and k in m2: - repo.dirstate.remove(v) - -def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None): +def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None): 'Commit the changes and store useful information in extra' try: repo.dirstate.setparents(repo[p1].node(), repo[p2].node()) @@ -327,7 +360,7 @@ extrafn(ctx, extra) # Commit might fail if unresolved files exist newrev = repo.commit(text=commitmsg, user=ctx.user(), - date=ctx.date(), extra=extra) + date=ctx.date(), extra=extra, editor=editor) repo.dirstate.setbranch(repo[newrev].branch()) return newrev except util.Abort: @@ -515,68 +548,47 @@ repo.ui.warn(_('rebase aborted\n')) return 0 -def buildstate(repo, dest, src, base, detach): - 'Define which revisions are going to be rebased and where' - targetancestors = set() - detachset = set() +def buildstate(repo, dest, rebaseset, detach): + '''Define which revisions are going to be rebased and where - if not dest: - # Destination defaults to the latest revision in the current branch - branch = repo[None].branch() - dest = repo[branch].rev() - else: - dest = repo[dest].rev() + repo: repo + dest: context + rebaseset: set of rev + detach: boolean''' # This check isn't strictly necessary, since mq detects commits over an # applied patch. But it prevents messing up the working directory when # a partially completed rebase is blocked by mq. - if 'qtip' in repo.tags() and (repo[dest].node() in + if 'qtip' in repo.tags() and (dest.node() in [s.node for s in repo.mq.applied]): raise util.Abort(_('cannot rebase onto an applied mq patch')) - 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 samebranch and commonbase == repo[dest]: - raise util.Abort(_('source is descendant of destination')) - source = repo[src].rev() - if detach: - # We need to keep track of source's ancestors up to the common base - srcancestors = set(repo.changelog.ancestors(source)) - baseancestors = set(repo.changelog.ancestors(commonbase.rev())) - detachset = srcancestors - baseancestors - detachset.discard(commonbase.rev()) - else: - if base: - cwd = repo[base].rev() - else: - cwd = repo['.'].rev() + detachset = set() + roots = list(repo.set('roots(%ld)', rebaseset)) + if not roots: + raise util.Abort(_('no matching revisions')) + if len(roots) > 1: + raise util.Abort(_("can't rebase multiple roots")) + root = roots[0] - if cwd == dest: - repo.ui.debug('source and destination are the same\n') - return None - - targetancestors = set(repo.changelog.ancestors(dest)) - if cwd in targetancestors: - repo.ui.debug('source is ancestor of destination\n') - return None + commonbase = root.ancestor(dest) + if commonbase == root: + raise util.Abort(_('source is ancestor of destination')) + if commonbase == dest: + samebranch = root.branch() == dest.branch() + if samebranch and root in dest.children(): + repo.ui.debug('source is a child of destination') + return None + # rebase on ancestor, force detach + detach = True + if detach: + detachset = [c.rev() for c in repo.set('::%d - ::%d - %d', + root, commonbase, root)] - cwdancestors = set(repo.changelog.ancestors(cwd)) - if dest in cwdancestors: - repo.ui.debug('source is descendant of destination\n') - return None - - cwdancestors.add(cwd) - rebasingbranch = cwdancestors - targetancestors - source = min(rebasingbranch) - - repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source)) - state = dict.fromkeys(repo.changelog.descendants(source), nullrev) + repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root)) + state = dict.fromkeys(rebaseset, nullrev) state.update(dict.fromkeys(detachset, nullmerge)) - state[source] = nullrev - return repo['.'].rev(), repo[dest].rev(), state + return repo['.'].rev(), dest.rev(), state def pullrebase(orig, ui, repo, *args, **opts): 'Call rebase after pull if the latter has been invoked with --rebase'
--- a/hgext/relink.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/relink.py Sat Oct 15 14:30:50 2011 -0500 @@ -36,7 +36,8 @@ command is running. (Both repositories will be locked against writes.) """ - if not hasattr(util, 'samefile') or not hasattr(util, 'samedevice'): + if (not util.safehasattr(util, 'samefile') or + not util.safehasattr(util, 'samedevice')): raise util.Abort(_('hardlinks are not supported on this system')) src = hg.repository(ui, ui.expandpath(origin or 'default-relink', origin or 'default'))
--- a/hgext/share.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/share.py Sat Oct 15 14:30:50 2011 -0500 @@ -6,7 +6,7 @@ '''share a common history between several working directories''' from mercurial.i18n import _ -from mercurial import hg, commands +from mercurial import hg, commands, util def share(ui, source, dest=None, noupdate=False): """create a new shared repository @@ -28,11 +28,46 @@ return hg.share(ui, source, dest, not noupdate) +def unshare(ui, repo): + """convert a shared repository to a normal one + + Copy the store data to the repo and remove the sharedpath data. + """ + + if repo.sharedpath == repo.path: + raise util.Abort(_("this is not a shared repo")) + + destlock = lock = None + lock = repo.lock() + try: + # we use locks here because if we race with commit, we + # can end up with extra data in the cloned revlogs that's + # not pointed to by changesets, thus causing verify to + # fail + + destlock = hg.copystore(ui, repo, repo.path) + + sharefile = repo.join('sharedpath') + util.rename(sharefile, sharefile + '.old') + + repo.requirements.discard('sharedpath') + repo._writerequirements() + finally: + destlock and destlock.release() + lock and lock.release() + + # update store, spath, sopener and sjoin of repo + repo.__init__(ui, repo.root) + cmdtable = { "share": (share, [('U', 'noupdate', None, _('do not create a working copy'))], _('[-U] SOURCE [DEST]')), + "unshare": + (unshare, + [], + ''), } commands.norepo += " share"
--- a/hgext/transplant.py Sun Oct 02 16:41:07 2011 -0500 +++ b/hgext/transplant.py Sat Oct 15 14:30:50 2011 -0500 @@ -81,6 +81,7 @@ self.opener = scmutil.opener(self.path) self.transplants = transplants(self.path, 'transplants', opener=self.opener) + self.editor = None def applied(self, repo, node, parent): '''returns True if a node is already an ancestor of parent @@ -105,10 +106,11 @@ diffopts = patch.diffopts(self.ui, opts) diffopts.git = True - lock = wlock = None + lock = wlock = tr = None try: wlock = repo.wlock() lock = repo.lock() + tr = repo.transaction('transplant') for rev in revs: node = revmap[rev] revstr = '%s:%s' % (rev, short(node)) @@ -172,12 +174,15 @@ finally: if patchfile: os.unlink(patchfile) + tr.close() if pulls: repo.pull(source, heads=pulls) merge.update(repo, pulls[-1], False, False, None) finally: self.saveseries(revmap, merges) self.transplants.write() + if tr: + tr.release() lock.release() wlock.release() @@ -253,7 +258,8 @@ else: m = match.exact(repo.root, '', files) - n = repo.commit(message, user, date, extra=extra, match=m) + n = repo.commit(message, user, date, extra=extra, match=m, + editor=self.editor) if not n: # Crash here to prevent an unclear crash later, in # transplants.write(). This can happen if patch.patch() @@ -304,7 +310,8 @@ revlog.hex(parents[0])) if merge: repo.dirstate.setparents(p1, parents[1]) - n = repo.commit(message, user, date, extra=extra) + n = repo.commit(message, user, date, extra=extra, + editor=self.editor) if not n: raise util.Abort(_('commit failed')) if not merge: @@ -461,6 +468,7 @@ ('a', 'all', None, _('pull all changesets up to BRANCH')), ('p', 'prune', [], _('skip over REV'), _('REV')), ('m', 'merge', [], _('merge at REV'), _('REV')), + ('e', 'edit', False, _('invoke editor on commit messages')), ('', 'log', None, _('append transplant info to log message')), ('c', 'continue', None, _('continue last transplant session ' 'after repair')), @@ -549,6 +557,8 @@ opts['filter'] = ui.config('transplant', 'filter') tp = transplanter(ui, repo) + if opts.get('edit'): + tp.editor = cmdutil.commitforceeditor p1, p2 = repo.dirstate.parents() if len(repo) > 0 and p1 == revlog.nullid:
--- a/mercurial/archival.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/archival.py Sat Oct 15 14:30:50 2011 -0500 @@ -195,7 +195,7 @@ return f = self.opener(name, "w", atomictemp=True) f.write(data) - f.rename() + f.close() destfile = os.path.join(self.basedir, name) os.chmod(destfile, mode)
--- a/mercurial/bdiff.c Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/bdiff.c Sat Oct 15 14:30:50 2011 -0500 @@ -366,11 +366,11 @@ static PyObject *bdiff(PyObject *self, PyObject *args) { - char *sa, *sb; + char *sa, *sb, *rb; PyObject *result = NULL; struct line *al, *bl; struct hunk l, *h; - char encode[12], *rb; + uint32_t encode[3]; int an, bn, len = 0, la, lb, count; if (!PyArg_ParseTuple(args, "s#s#:bdiff", &sa, &la, &sb, &lb)) @@ -407,9 +407,9 @@ for (h = l.next; h; h = h->next) { if (h->a1 != la || h->b1 != lb) { len = bl[h->b1].l - bl[lb].l; - *(uint32_t *)(encode) = htonl(al[la].l - al->l); - *(uint32_t *)(encode + 4) = htonl(al[h->a1].l - al->l); - *(uint32_t *)(encode + 8) = htonl(len); + encode[0] = htonl(al[la].l - al->l); + encode[1] = htonl(al[h->a1].l - al->l); + encode[2] = htonl(len); memcpy(rb, encode, 12); memcpy(rb + 12, bl[lb].l, len); rb += 12 + len;
--- a/mercurial/bookmarks.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/bookmarks.py Sat Oct 15 14:30:50 2011 -0500 @@ -26,7 +26,13 @@ bookmarks = {} try: for line in repo.opener('bookmarks'): - sha, refspec = line.strip().split(' ', 1) + line = line.strip() + if not line: + continue + if ' ' not in line: + repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n') % line) + continue + sha, refspec = line.split(' ', 1) refspec = encoding.tolocal(refspec) try: bookmarks[refspec] = repo.changelog.lookup(sha) @@ -84,7 +90,7 @@ file = repo.opener('bookmarks', 'w', atomictemp=True) for refspec, node in refs.iteritems(): file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec))) - file.rename() + file.close() # touch 00changelog.i so hgweb reloads bookmarks (no lock needed) try: @@ -115,7 +121,7 @@ try: file = repo.opener('bookmarks.current', 'w', atomictemp=True) file.write(encoding.fromlocal(mark)) - file.rename() + file.close() finally: wlock.release() repo._bookmarkcurrent = mark @@ -140,16 +146,15 @@ marks[mark] = new.node() update = True if update: - write(repo) + repo._writebookmarks(marks) def listbookmarks(repo): # We may try to list bookmarks on a repo type that does not # support it (e.g., statichttprepository). - if not hasattr(repo, '_bookmarks'): - return {} + marks = getattr(repo, '_bookmarks', {}) d = {} - for k, v in repo._bookmarks.iteritems(): + for k, v in marks.iteritems(): d[k] = hex(v) return d
--- a/mercurial/byterange.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/byterange.py Sat Oct 15 14:30:50 2011 -0500 @@ -103,9 +103,7 @@ """This effectively allows us to wrap at the instance level. Any attribute not found in _this_ object will be searched for in self.fo. This includes methods.""" - if hasattr(self.fo, name): - return getattr(self.fo, name) - raise AttributeError(name) + return getattr(self.fo, name) def tell(self): """Return the position within the range. @@ -170,10 +168,8 @@ offset is relative to the current position (self.realpos). """ assert offset >= 0 - if not hasattr(self.fo, 'seek'): - self._poor_mans_seek(offset) - else: - self.fo.seek(self.realpos + offset) + seek = getattr(self.fo, 'seek', self._poor_mans_seek) + seek(self.realpos + offset) self.realpos += offset def _poor_mans_seek(self, offset):
--- a/mercurial/cmdutil.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/cmdutil.py Sat Oct 15 14:30:50 2011 -0500 @@ -8,7 +8,7 @@ from node import hex, nullid, nullrev, short from i18n import _ import os, sys, errno, re, tempfile -import util, scmutil, templater, patch, error, templatekw, revlog +import util, scmutil, templater, patch, error, templatekw, revlog, copies import match as matchmod import subrepo @@ -75,6 +75,10 @@ modified, added, removed, deleted = repo.status()[:4] if modified or added or removed or deleted: raise util.Abort(_("outstanding uncommitted changes")) + ctx = repo[None] + for s in ctx.substate: + if ctx.sub(s).dirty(): + raise util.Abort(_("uncommitted changes in subrepo %s") % s) def logmessage(ui, opts): """ get the log message according to -m and -l option """ @@ -109,12 +113,13 @@ limit = None return limit -def makefilename(repo, pat, node, +def makefilename(repo, pat, node, desc=None, total=None, seqno=None, revwidth=None, pathname=None): node_expander = { 'H': lambda: hex(node), 'R': lambda: str(repo.changelog.rev(node)), 'h': lambda: short(node), + 'm': lambda: re.sub('[^\w]', '_', str(desc)) } expander = { '%': lambda: '%', @@ -154,14 +159,14 @@ raise util.Abort(_("invalid format spec '%%%s' in output filename") % inst.args[0]) -def makefileobj(repo, pat, node=None, total=None, +def makefileobj(repo, pat, node=None, desc=None, total=None, seqno=None, revwidth=None, mode='wb', pathname=None): writable = mode not in ('r', 'rb') if not pat or pat == '-': fp = writable and repo.ui.fout or repo.ui.fin - if hasattr(fp, 'fileno'): + if util.safehasattr(fp, 'fileno'): return os.fdopen(os.dup(fp.fileno()), mode) else: # if this fp can't be duped properly, return @@ -177,11 +182,11 @@ return getattr(self.f, attr) return wrappedfileobj(fp) - if hasattr(pat, 'write') and writable: + if util.safehasattr(pat, 'write') and writable: return pat - if hasattr(pat, 'read') and 'r' in mode: + if util.safehasattr(pat, 'read') and 'r' in mode: return pat - return open(makefilename(repo, pat, node, total, seqno, revwidth, + return open(makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname), mode) @@ -516,11 +521,13 @@ shouldclose = False if not fp: - fp = makefileobj(repo, template, node, total=total, seqno=seqno, - revwidth=revwidth, mode='ab') + desc_lines = ctx.description().rstrip().split('\n') + desc = desc_lines[0] #Commit always has a first line. + fp = makefileobj(repo, template, node, desc=desc, total=total, + seqno=seqno, revwidth=revwidth, mode='ab') if fp != template: shouldclose = True - if fp != sys.stdout and hasattr(fp, 'name'): + if fp != sys.stdout and util.safehasattr(fp, 'name'): repo.ui.note("%s\n" % fp.name) fp.write("# HG changeset patch\n") @@ -1173,6 +1180,19 @@ bad.extend(f for f in rejected if f in match.files()) return bad +def duplicatecopies(repo, rev, p1, p2): + "Reproduce copies found in the source revision in the dirstate for grafts" + # Here we simulate the copies and renames in the source changeset + cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True) + m1 = repo[rev].manifest() + m2 = repo[p1].manifest() + for k, v in cop.iteritems(): + if k in m1: + if v in m1 or v in m2: + repo.dirstate.copy(v, k) + if v in m2 and v not in m1 and k in m2: + repo.dirstate.remove(v) + def commit(ui, repo, commitfunc, pats, opts): '''commit the specified files or all outstanding changes''' date = opts.get('date')
--- a/mercurial/commands.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/commands.py Sat Oct 15 14:30:50 2011 -0500 @@ -119,6 +119,10 @@ ('', 'stat', None, _('output diffstat-style summary of changes')), ] +mergetoolopts = [ + ('t', 'tool', '', _('specify merge tool')), +] + similarityopts = [ ('s', 'similarity', '', _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY')) @@ -303,6 +307,18 @@ The archive type is automatically detected based on file extension (or override using -t/--type). + .. container:: verbose + + Examples: + + - create a zip file containing the 1.0 release:: + + hg archive -r 1.0 project-1.0.zip + + - create a tarball excluding .hg files:: + + hg archive project.tar.gz -X ".hg*" + Valid types are: :``files``: a directory full of files (default) @@ -348,10 +364,10 @@ @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')), + ('', 'parent', '', + _('parent to choose when backing out merge (DEPRECATED)'), _('REV')), ('r', 'rev', '', _('revision to backout'), _('REV')), - ] + walkopts + commitopts + commitopts2, + ] + mergetoolopts + walkopts + commitopts + commitopts2, _('[OPTION]... [-r] REV')) def backout(ui, repo, node=None, rev=None, **opts): '''reverse effect of earlier changeset @@ -363,15 +379,21 @@ is committed automatically. Otherwise, hg needs to merge the changes and the merged result is left uncommitted. - By default, the pending changeset will have one parent, - maintaining a linear history. With --merge, the pending changeset - will instead have two parents: the old parent of the working - directory and a new child of REV that simply undoes REV. - - Before version 1.7, the behavior without --merge was equivalent to - specifying --merge followed by :hg:`update --clean .` to cancel - the merge and leave the child of REV as a head to be merged - separately. + .. note:: + backout cannot be used to fix either an unwanted or + incorrect merge. + + .. container:: verbose + + By default, the pending changeset will have one parent, + maintaining a linear history. With --merge, the pending + changeset will instead have two parents: the old parent of the + working directory and a new child of REV that simply undoes REV. + + Before version 1.7, the behavior without --merge was equivalent + to specifying --merge followed by :hg:`update --clean .` to + cancel the merge and leave the child of REV as a head to be + merged separately. See :hg:`help dates` for a list of formats valid for -d/--date. @@ -403,8 +425,7 @@ raise util.Abort(_('cannot backout a change with no parents')) if p2 != nullid: if not opts.get('parent'): - raise util.Abort(_('cannot backout a merge changeset without ' - '--parent')) + raise util.Abort(_('cannot backout a merge changeset')) p = repo.lookup(opts['parent']) if p not in (p1, p2): raise util.Abort(_('%s is not a parent of %s') % @@ -486,6 +507,54 @@ (command not found) will abort the bisection, and any other non-zero exit status means the revision is bad. + .. container:: verbose + + Some examples: + + - start a bisection with known bad revision 12, and good revision 34:: + + hg bisect --bad 34 + hg bisect --good 12 + + - advance the current bisection by marking current revision as good or + bad:: + + hg bisect --good + hg bisect --bad + + - mark the current revision, or a known revision, to be skipped (eg. if + that revision is not usable because of another issue):: + + hg bisect --skip + hg bisect --skip 23 + + - forget the current bisection:: + + hg bisect --reset + + - use 'make && make tests' to automatically find the first broken + revision:: + + hg bisect --reset + hg bisect --bad 34 + hg bisect --good 12 + hg bisect --command 'make && make tests' + + - see all changesets whose states are already known in the current + bisection:: + + hg log -r "bisect(pruned)" + + - see all changesets that took part in the current bisection:: + + hg log -r "bisect(range)" + + - with the graphlog extension, you can even get a nice graph:: + + hg log --graph -r "bisect(range)" + + See :hg:`help revsets` for more about the `bisect()` keyword. + Returns 0 on success. """ def extendbisectrange(nodes, good): @@ -767,7 +836,6 @@ :hg:`commit --close-branch` to mark this branch as closed. .. note:: - Branch names are permanent. Use :hg:`bookmark` to create a light-weight bookmark instead. See :hg:`help glossary` for more information about named branches and bookmarks. @@ -977,56 +1045,84 @@ The location of the source is added to the new repository's ``.hg/hgrc`` file, as the default to be used for future pulls. - See :hg:`help urls` for valid source format details. - - It is possible to specify an ``ssh://`` URL as the destination, but no - ``.hg/hgrc`` and working directory will be created on the remote side. - Please see :hg:`help urls` for important details about ``ssh://`` URLs. - - A set of changesets (tags, or branch names) to pull may be specified - by listing each changeset (tag, or branch name) with -r/--rev. - If -r/--rev is used, the cloned repository will contain only a subset - of the changesets of the source repository. Only the set of changesets - defined by all -r/--rev options (including all their ancestors) - will be pulled into the destination repository. - No subsequent changesets (including subsequent tags) will be present - in the destination. - - Using -r/--rev (or 'clone src#rev dest') implies --pull, even for - local source repositories. - - For efficiency, hardlinks are used for cloning whenever the source - and destination are on the same filesystem (note this applies only - to the repository data, not to the working directory). Some - filesystems, such as AFS, implement hardlinking incorrectly, but - do not report errors. In these cases, use the --pull option to - avoid hardlinking. - - In some cases, you can clone repositories and the working directory - using full hardlinks with :: - - $ cp -al REPO REPOCLONE - - This is the fastest way to clone, but it is not always safe. The - operation is not atomic (making sure REPO is not modified during - the operation is up to you) and you have to make sure your editor - breaks hardlinks (Emacs and most Linux Kernel tools do so). Also, - this is not compatible with certain extensions that place their - metadata under the .hg directory, such as mq. - - Mercurial will update the working directory to the first applicable - revision from this list: - - a) null if -U or the source repository has no changesets - b) if -u . and the source repository is local, the first parent of - the source repository's working directory - c) the changeset specified with -u (if a branch name, this means the - latest head of that branch) - d) the changeset specified with -r - e) the tipmost head specified with -b - f) the tipmost head specified with the url#branch source syntax - g) the tipmost head of the default branch - h) tip + Only local paths and ``ssh://`` URLs are supported as + destinations. For ``ssh://`` destinations, no working directory or + ``.hg/hgrc`` will be created on the remote side. + + To pull only a subset of changesets, specify one or more revisions + identifiers with -r/--rev or branches with -b/--branch. The + resulting clone will contain only the specified changesets and + their ancestors. These options (or 'clone src#rev dest') imply + --pull, even for local source repositories. Note that specifying a + tag will include the tagged changeset but not the changeset + containing the tag. + + To check out a particular version, use -u/--update, or + -U/--noupdate to create a clone with no working directory. + + .. container:: verbose + + For efficiency, hardlinks are used for cloning whenever the + source and destination are on the same filesystem (note this + applies only to the repository data, not to the working + directory). Some filesystems, such as AFS, implement hardlinking + incorrectly, but do not report errors. In these cases, use the + --pull option to avoid hardlinking. + + In some cases, you can clone repositories and the working + directory using full hardlinks with :: + + $ cp -al REPO REPOCLONE + + This is the fastest way to clone, but it is not always safe. The + operation is not atomic (making sure REPO is not modified during + the operation is up to you) and you have to make sure your + editor breaks hardlinks (Emacs and most Linux Kernel tools do + so). Also, this is not compatible with certain extensions that + place their metadata under the .hg directory, such as mq. + + Mercurial will update the working directory to the first applicable + revision from this list: + + a) null if -U or the source repository has no changesets + b) if -u . and the source repository is local, the first parent of + the source repository's working directory + c) the changeset specified with -u (if a branch name, this means the + latest head of that branch) + d) the changeset specified with -r + e) the tipmost head specified with -b + f) the tipmost head specified with the url#branch source syntax + g) the tipmost head of the default branch + h) tip + + Examples: + + - clone a remote repository to a new directory named hg/:: + + hg clone http://selenic.com/hg + + - create a lightweight local clone:: + + hg clone project/ project-feature/ + + - clone from an absolute path on an ssh server (note double-slash):: + + hg clone ssh://user@server//home/projects/alpha/ + + - do a high-speed clone over a LAN while checking out a + specified version:: + + hg clone --uncompressed http://server/repo -u 1.5 + + - create a repository without changesets after a particular revision:: + + hg clone -r 04e544 experimental/ good/ + + - clone (and track) a particular named branch:: + + hg clone http://selenic.com/hg#stable + + See :hg:`help urls` for details on specifying URLs. Returns 0 on success. """ @@ -1102,8 +1198,8 @@ ctx = repo[node] parents = ctx.parents() - if bheads and not [x for x in parents - if x.node() in bheads and x.branch() == branch]: + if (bheads and node not in bheads and not + [x for x in parents if x.node() in bheads and x.branch() == branch]): ui.status(_('created new head\n')) # The message is not printed for initial roots. For the other # changesets, it is printed in the following situations: @@ -1656,8 +1752,9 @@ def debugignore(ui, repo, *values, **opts): """display the combined ignore pattern""" ignore = repo.dirstate._ignore - if hasattr(ignore, 'includepat'): - ui.write("%s\n" % ignore.includepat) + includepat = getattr(ignore, 'includepat', None) + if includepat is not None: + ui.write("%s\n" % includepat) else: raise util.Abort(_("no ignore patterns found")) @@ -1755,6 +1852,7 @@ % os.path.dirname(__file__)) try: import bdiff, mpatch, base85, osutil + dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes except Exception, inst: ui.write(" %s\n" % inst) ui.write(_(" One or more extensions could not be found")) @@ -1762,9 +1860,10 @@ problems += 1 # templates - ui.status(_("Checking templates...\n")) + import templater + p = templater.templatepath() + ui.status(_("Checking templates (%s)...\n") % ' '.join(p)) try: - import templater templater.templater(templater.templatepath("map-cmdline.default")) except Exception, inst: ui.write(" %s\n" % inst) @@ -2170,6 +2269,32 @@ Use the -g/--git option to generate diffs in the git extended diff format. For more information, read :hg:`help diffs`. + .. container:: verbose + + Examples: + + - compare a file in the current working directory to its parent:: + + hg diff foo.c + + - compare two historical versions of a directory, with rename info:: + + hg diff --git -r 1.0:1.2 lib/ + + - get change stats relative to the last change on some date:: + + hg diff --stat -r "date('may 2')" + + - diff all newly-added files that contain a keyword:: + + hg diff "set:added() and grep(GNU)" + + - compare a revision and its parents:: + + hg diff -c 9353 # compare against first parent + hg diff -r 9353^:9353 # same using revset syntax + hg diff -r 9353^2:9353 # compare against the second parent + Returns 0 on success. """ @@ -2225,6 +2350,7 @@ :``%R``: changeset revision number :``%b``: basename of the exporting repository :``%h``: short-form changeset hash (12 hexadecimal digits) + :``%m``: first line of the commit message (only alphanumeric characters) :``%n``: zero-padded sequence number, starting at 1 :``%r``: zero-padded changeset revision number @@ -2238,6 +2364,25 @@ With the --switch-parent option, the diff will be against the second parent. It can be useful to review a merge. + .. container:: verbose + + Examples: + + - use export and import to transplant a bugfix to the current + branch:: + + hg export -r 9353 | hg import - + + - export all the changesets between two revisions to a file with + rename information:: + + hg export --git -r 123:150 > changes.txt + + - split outgoing changes into a series of patches with + descriptive names:: + + hg export -r "outgoing()" -o "%n-%m.patch" + Returns 0 on success. """ changesets += tuple(opts.get('rev', [])) @@ -2265,6 +2410,18 @@ To undo a forget before the next commit, see :hg:`add`. + .. container:: verbose + + Examples: + + - forget newly-added binary files:: + + hg forget "set:added() and binary()" + + - forget files that would be excluded by .hgignore:: + + hg forget "set:hgignore()" + Returns 0 on success. """ @@ -2290,6 +2447,160 @@ repo[None].forget(forget) return errs +@command( + 'graft', + [('c', 'continue', False, _('resume interrupted graft')), + ('e', 'edit', False, _('invoke editor on commit messages')), + ('D', 'currentdate', False, + _('record the current date as commit date')), + ('U', 'currentuser', False, + _('record the current user as committer'), _('DATE'))] + + commitopts2 + mergetoolopts, + _('[OPTION]... REVISION...')) +def graft(ui, repo, *revs, **opts): + '''copy changes from other branches onto the current branch + + This command uses Mercurial's merge logic to copy individual + changes from other branches without merging branches in the + history graph. This is sometimes known as 'backporting' or + 'cherry-picking'. By default, graft will copy user, date, and + description from the source changesets. + + Changesets that are ancestors of the current revision, that have + already been grafted, or that are merges will be skipped. + + If a graft merge results in conflicts, the graft process is + aborted so that the current merge can be manually resolved. Once + all conflicts are addressed, the graft process can be continued + with the -c/--continue option. + + .. note:: + The -c/--continue option does not reapply earlier options. + + .. container:: verbose + + Examples: + + - copy a single change to the stable branch and edit its description:: + + hg update stable + hg graft --edit 9393 + + - graft a range of changesets with one exception, updating dates:: + + hg graft -D "2085::2093 and not 2091" + + - continue a graft after resolving conflicts:: + + hg graft -c + + - show the source of a grafted changeset:: + + hg log --debug -r tip + + Returns 0 on successful completion. + ''' + + if not opts.get('user') and opts.get('currentuser'): + opts['user'] = ui.username() + if not opts.get('date') and opts.get('currentdate'): + opts['date'] = "%d %d" % util.makedate() + + editor = None + if opts.get('edit'): + editor = cmdutil.commitforceeditor + + cont = False + if opts['continue']: + cont = True + if revs: + raise util.Abort(_("can't specify --continue and revisions")) + # read in unfinished revisions + try: + nodes = repo.opener.read('graftstate').splitlines() + revs = [repo[node].rev() for node in nodes] + except IOError, inst: + if inst.errno != errno.ENOENT: + raise + raise util.Abort(_("no graft state found, can't continue")) + else: + cmdutil.bailifchanged(repo) + if not revs: + raise util.Abort(_('no revisions specified')) + revs = scmutil.revrange(repo, revs) + + # check for merges + for ctx in repo.set('%ld and merge()', revs): + ui.warn(_('skipping ungraftable merge revision %s\n') % ctx.rev()) + revs.remove(ctx.rev()) + if not revs: + return -1 + + # check for ancestors of dest branch + for ctx in repo.set('::. and %ld', revs): + ui.warn(_('skipping ancestor revision %s\n') % ctx.rev()) + revs.remove(ctx.rev()) + if not revs: + return -1 + + # check ancestors for earlier grafts + ui.debug('scanning for existing transplants') + for ctx in repo.set("::. - ::%ld", revs): + n = ctx.extra().get('source') + if n and n in repo: + r = repo[n].rev() + ui.warn(_('skipping already grafted revision %s\n') % r) + revs.remove(r) + if not revs: + return -1 + + for pos, ctx in enumerate(repo.set("%ld", revs)): + current = repo['.'] + ui.status('grafting revision %s', ctx.rev()) + + # we don't merge the first commit when continuing + if not cont: + # perform the graft merge with p1(rev) as 'ancestor' + try: + # ui.forcemerge is an internal variable, do not document + repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', '')) + stats = mergemod.update(repo, ctx.node(), True, True, False, + ctx.p1().node()) + finally: + ui.setconfig('ui', 'forcemerge', '') + # drop the second merge parent + repo.dirstate.setparents(current.node(), nullid) + repo.dirstate.write() + # fix up dirstate for copies and renames + cmdutil.duplicatecopies(repo, ctx.rev(), current.node(), nullid) + # report any conflicts + if stats and stats[3] > 0: + # write out state for --continue + nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]] + repo.opener.write('graftstate', ''.join(nodelines)) + raise util.Abort( + _("unresolved conflicts, can't continue"), + hint=_('use hg resolve and hg graft --continue')) + else: + cont = False + + # commit + extra = {'source': ctx.hex()} + user = ctx.user() + if opts.get('user'): + user = opts['user'] + date = ctx.date() + if opts.get('date'): + date = opts['date'] + repo.commit(text=ctx.description(), user=user, + date=date, extra=extra, editor=editor) + + # remove state when we complete successfully + if os.path.exists(repo.join('graftstate')): + util.unlinkpath(repo.join('graftstate')) + + return 0 + @command('grep', [('0', 'print0', None, _('end fields with NUL')), ('', 'all', None, _('print all revisions that match')), @@ -2576,7 +2887,7 @@ [('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): +def help_(ui, name=None, 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. @@ -2586,14 +2897,67 @@ Returns 0 if successful. """ - option_lists = [] + textwidth = min(ui.termwidth(), 80) - 2 - def addglobalopts(aliases): + def optrst(options): + data = [] + multioccur = False + for option in options: + if len(option) == 5: + shortopt, longopt, default, desc, optlabel = option + else: + shortopt, longopt, default, desc = option + optlabel = _("VALUE") # default label + + if _("DEPRECATED") in desc and not ui.verbose: + continue + + so = '' + if shortopt: + so = '-' + shortopt + lo = '--' + longopt + if default: + desc += _(" (default: %s)") % default + + if isinstance(default, list): + lo += " %s [+]" % optlabel + multioccur = True + elif (default is not None) and not isinstance(default, bool): + lo += " %s" % optlabel + + data.append((so, lo, desc)) + + rst = minirst.maketable(data, 1) + + if multioccur: + rst += _("\n[+] marked option can be specified multiple times\n") + + return rst + + # list all option lists + def opttext(optlist, width): + rst = '' + if not optlist: + return '' + + for title, options in optlist: + rst += '\n%s\n' % title + if options: + rst += "\n" + rst += optrst(options) + rst += '\n' + + return '\n' + minirst.format(rst, width) + + def addglobalopts(optlist, aliases): + if ui.quiet: + return [] + if ui.verbose: - option_lists.append((_("global options:"), globalopts)) + optlist.append((_("global options:"), globalopts)) if name == 'shortlist': - option_lists.append((_('use "hg help" for the full list ' + optlist.append((_('use "hg help" for the full list ' 'of commands'), ())) else: if name == 'shortlist': @@ -2605,14 +2969,10 @@ msg = _('use "hg -v help%s" to show builtin aliases and ' 'global options') % (name and " " + name or "") else: - msg = _('use "hg -v help %s" to show global options') % name - option_lists.append((msg, ())) + msg = _('use "hg -v help %s" to show more info') % name + optlist.append((msg, ())) def helpcmd(name): - if with_version: - version_(ui) - ui.write('\n') - try: aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd) except error.AmbiguousCommand, inst: @@ -2620,7 +2980,7 @@ # except block, nor can be used inside a lambda. python issue4617 prefix = inst.args[0] select = lambda c: c.lstrip('^').startswith(prefix) - helplist(_('list of commands:\n\n'), select) + helplist(select) return # check if it's an invalid alias and display its error if it is @@ -2629,42 +2989,33 @@ entry[0](ui) return + rst = "" + # synopsis if len(entry) > 2: if entry[2].startswith('hg'): - ui.write("%s\n" % entry[2]) + rst += "%s\n" % entry[2] else: - ui.write('hg %s %s\n' % (aliases[0], entry[2])) + rst += 'hg %s %s\n' % (aliases[0], entry[2]) else: - ui.write('hg %s\n' % aliases[0]) + rst += 'hg %s\n' % aliases[0] # aliases if full and not ui.quiet and len(aliases) > 1: - ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:])) + rst += _("\naliases: %s\n") % ', '.join(aliases[1:]) # description doc = gettext(entry[0].__doc__) if not doc: doc = _("(no help text available)") - if hasattr(entry[0], 'definition'): # aliased command + if util.safehasattr(entry[0], 'definition'): # aliased command if entry[0].definition.startswith('!'): # shell alias 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 or not full: doc = doc.splitlines()[0] - keep = ui.verbose and ['verbose'] or [] - formatted, pruned = minirst.format(doc, textwidth, keep=keep) - ui.write("\n%s\n" % formatted) - if pruned: - ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name) - - if not ui.quiet: - # options - if entry[1]: - option_lists.append((_("options:\n"), entry[1])) - - addglobalopts(False) + rst += "\n" + doc + "\n" # check if this command shadows a non-trivial (multi-line) # extension help text @@ -2674,11 +3025,38 @@ 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) + rst += '\n%s\n' % msg except KeyError: pass - def helplist(header, select=None): + # options + if not ui.quiet and entry[1]: + rst += '\noptions:\n\n' + rst += optrst(entry[1]) + + if ui.verbose: + rst += '\nglobal options:\n\n' + rst += optrst(globalopts) + + keep = ui.verbose and ['verbose'] or [] + formatted, pruned = minirst.format(rst, textwidth, keep=keep) + ui.write(formatted) + + if not ui.verbose: + if not full: + ui.write(_('\nuse "hg help %s" to show the full help text\n') + % name) + elif not ui.quiet: + ui.write(_('\nuse "hg -v help %s" to show more info\n') % name) + + + def helplist(select=None): + # list of commands + if name == "shortlist": + header = _('basic commands:\n\n') + else: + header = _('list of commands:\n\n') + h = {} cmds = {} for c, e in table.iteritems(): @@ -2718,8 +3096,22 @@ initindent=' %-*s ' % (m, f), hangindent=' ' * (m + 4)))) - if not ui.quiet: - addglobalopts(True) + if not name: + text = help.listexts(_('enabled extensions:'), extensions.enabled()) + if text: + ui.write("\n%s" % minirst.format(text, textwidth)) + + ui.write(_("\nadditional help topics:\n\n")) + topics = [] + for names, header, doc in help.helptable: + topics.append((sorted(names, key=len, reverse=True)[0], header)) + topics_len = max([len(s[0]) for s in topics]) + for t, desc in topics: + ui.write(" %-*s %s\n" % (topics_len, t, desc)) + + optlist = [] + addglobalopts(optlist, True) + ui.write(opttext(optlist, textwidth)) def helptopic(name): for names, header, doc in help.helptable: @@ -2731,11 +3123,11 @@ # description if not doc: doc = _("(no help text available)") - if hasattr(doc, '__call__'): + if util.safehasattr(doc, '__call__'): doc = doc() ui.write("%s\n\n" % header) - ui.write("%s\n" % minirst.format(doc, textwidth, indent=4)) + ui.write("%s" % minirst.format(doc, textwidth, indent=4)) try: cmdutil.findcmd(name, table) ui.write(_('\nuse "hg help -c %s" to see help for ' @@ -2760,7 +3152,7 @@ ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head)) if tail: ui.write(minirst.format(tail, textwidth)) - ui.status('\n\n') + ui.status('\n') if mod: try: @@ -2768,7 +3160,7 @@ except AttributeError: ct = {} modcmds = set([c.split('|', 1)[0] for c in ct]) - helplist(_('list of commands:\n\n'), modcmds.__contains__) + helplist(modcmds.__contains__) else: ui.write(_('use "hg help extensions" for information on enabling ' 'extensions\n')) @@ -2780,7 +3172,7 @@ msg = help.listexts(_("'%s' is provided by the following " "extension:") % cmd, {ext: doc}, indent=4) ui.write(minirst.format(msg, textwidth)) - ui.write('\n\n') + ui.write('\n') ui.write(_('use "hg help extensions" for information on enabling ' 'extensions\n')) @@ -2803,87 +3195,12 @@ i = inst if i: raise i - else: # program name - if ui.verbose or with_version: - version_(ui) - else: - ui.status(_("Mercurial Distributed SCM\n")) + ui.status(_("Mercurial Distributed SCM\n")) ui.status('\n') - - # list of commands - if name == "shortlist": - header = _('basic commands:\n\n') - else: - header = _('list of commands:\n\n') - - helplist(header) - if name != 'shortlist': - text = help.listexts(_('enabled extensions:'), extensions.enabled()) - if text: - ui.write("\n%s\n" % minirst.format(text, textwidth)) - - # list all option lists - opt_output = [] - multioccur = False - for title, options in option_lists: - opt_output.append(("\n%s" % title, None)) - for option in options: - if len(option) == 5: - shortopt, longopt, default, desc, optlabel = option - else: - shortopt, longopt, default, desc = option - optlabel = _("VALUE") # default label - - if _("DEPRECATED") in desc and not ui.verbose: - continue - if isinstance(default, list): - numqualifier = " %s [+]" % optlabel - multioccur = True - elif (default is not None) and not isinstance(default, bool): - numqualifier = " %s" % optlabel - else: - numqualifier = "" - opt_output.append(("%2s%s" % - (shortopt and "-%s" % shortopt, - longopt and " --%s%s" % - (longopt, numqualifier)), - "%s%s" % (desc, - default - and _(" (default: %s)") % default - or ""))) - if multioccur: - msg = _("\n[+] marked option can be specified multiple times") - if ui.verbose and name != 'shortlist': - opt_output.append((msg, None)) - else: - opt_output.insert(-1, (msg, None)) - - if not name: - ui.write(_("\nadditional help topics:\n\n")) - topics = [] - for names, header, doc in help.helptable: - topics.append((sorted(names, key=len, reverse=True)[0], header)) - topics_len = max([len(s[0]) for s in topics]) - for t, desc in topics: - ui.write(" %-*s %s\n" % (topics_len, t, desc)) - - if opt_output: - colwidth = encoding.colwidth - # normalize: (opt or message, desc or None, width of opt) - entries = [desc and (opt, desc, colwidth(opt)) or (opt, None, 0) - for opt, desc in opt_output] - hanging = max([e[2] for e in entries]) - for opt, desc, width in entries: - if desc: - initindent = ' %s%s ' % (opt, ' ' * (hanging - width)) - hangindent = ' ' * (hanging + 3) - ui.write('%s\n' % (util.wrap(desc, textwidth, - initindent=initindent, - hangindent=hangindent))) - else: - ui.write("%s\n" % opt) + helplist() + @command('identify|id', [('r', 'rev', '', @@ -2909,6 +3226,22 @@ Specifying a path to a repository root or Mercurial bundle will cause lookup to operate on that repository/bundle. + .. container:: verbose + + Examples: + + - generate a build identifier for the working directory:: + + hg id --id > build-id.dat + + - find the revision corresponding to a tag:: + + hg id -n -r 1.3 + + - check the most recent revision of a remote repository:: + + hg id -r tip http://selenic.com/hg/ + Returns 0 if successful. """ @@ -3007,6 +3340,7 @@ _('directory strip option for patch. This has the same ' 'meaning as the corresponding patch option'), _('NUM')), ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')), + ('e', 'edit', False, _('invoke editor on commit messages')), ('f', 'force', None, _('skip check for outstanding uncommitted changes')), ('', 'no-commit', None, _("don't commit, just update the working directory")), @@ -3057,6 +3391,27 @@ a URL is specified, the patch will be downloaded from it. See :hg:`help dates` for a list of formats valid for -d/--date. + .. container:: verbose + + Examples: + + - import a traditional patch from a website and detect renames:: + + hg import -s 80 http://example.com/bugfix.patch + + - import a changeset from an hgweb server:: + + hg import http://www.selenic.com/hg/rev/5ca8c111e9aa + + - import all the patches in an Unix-style mbox:: + + hg import incoming-patches.mbox + + - attempt to exactly restore an exported changeset (not always + possible):: + + hg import --exact proposed-fix.patch + Returns 0 on success. """ patches = (patch1,) + patches @@ -3065,6 +3420,10 @@ if date: opts['date'] = util.parsedate(date) + editor = cmdutil.commiteditor + if opts.get('edit'): + editor = cmdutil.commitforceeditor + update = not opts.get('bypass') if not update and opts.get('no_commit'): raise util.Abort(_('cannot use --no-commit with --bypass')) @@ -3080,9 +3439,9 @@ if (opts.get('exact') or not opts.get('force')) and update: cmdutil.bailifchanged(repo) - d = opts["base"] + base = opts["base"] strip = opts["strip"] - wlock = lock = None + wlock = lock = tr = None msgs = [] def checkexact(repo, n, nodeid): @@ -3095,8 +3454,8 @@ patch.extract(ui, hunk) if not tmpname: - return None - commitid = _('to working directory') + return (None, None) + msg = _('applied to working directory') try: cmdline_message = cmdutil.logmessage(ui, opts) @@ -3151,11 +3510,8 @@ 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) + editor=editor) checkexact(repo, n, nodeid) - # Force a dirstate write so that the next transaction - # backups an up-to-date file. - repo.dirstate.write() else: if opts.get('exact') or opts.get('import_branch'): branch = branch or 'default' @@ -3181,45 +3537,52 @@ finally: store.close() if n: - commitid = short(n) - return commitid + msg = _('created %s') % short(n) + return (msg, n) finally: os.unlink(tmpname) try: wlock = repo.wlock() lock = repo.lock() + tr = repo.transaction('import') parents = repo.parents() - lastcommit = None - for p in patches: - pf = os.path.join(d, p) - - if pf == '-': - ui.status(_("applying patch from stdin\n")) - pf = ui.fin + for patchurl in patches: + if patchurl == '-': + ui.status(_('applying patch from stdin\n')) + patchfile = ui.fin + patchurl = 'stdin' # for error message else: - ui.status(_("applying %s\n") % p) - pf = url.open(ui, pf) + patchurl = os.path.join(base, patchurl) + ui.status(_('applying %s\n') % patchurl) + patchfile = url.open(ui, patchurl) haspatch = False - for hunk in patch.split(pf): - commitid = tryone(ui, hunk, parents) - if commitid: + for hunk in patch.split(patchfile): + (msg, node) = tryone(ui, hunk, parents) + if msg: haspatch = True - if lastcommit: - ui.status(_('applied %s\n') % lastcommit) - lastcommit = commitid + ui.note(msg + '\n') if update or opts.get('exact'): parents = repo.parents() else: - parents = [repo[commitid]] + parents = [repo[node]] if not haspatch: - raise util.Abort(_('no diffs found')) - + raise util.Abort(_('%s: no diffs found') % patchurl) + + tr.close() if msgs: repo.savecommitmessage('\n* * *\n'.join(msgs)) + except: + # wlock.release() indirectly calls dirstate.write(): since + # we're crashing, we do not want to change the working dir + # parent after all, so make sure it writes nothing + repo.dirstate.invalidate() + raise finally: + if tr: + tr.release() release(lock, wlock) @command('incoming|in', @@ -3356,18 +3719,14 @@ Print the revision history of the specified files or the entire project. + If no revision range is specified, the default is ``tip:0`` unless + --follow is set, in which case the working directory parent is + used as the starting revision. + File history is shown without following rename or copy history of files. Use -f/--follow with a filename to follow history across renames and copies. --follow without a filename will only show - ancestors or descendants of the starting revision. --follow-first - only follows the first parent of merge revisions. - - If no revision range is specified, the default is ``tip:0`` unless - --follow is set, in which case the working directory parent is - used as the starting revision. You can specify a revision set for - log, see :hg:`help revsets` for more information. - - See :hg:`help dates` for a list of formats valid for -d/--date. + ancestors or descendants of the starting revision. By default this command prints revision number and changeset id, tags, non-trivial parents, user, date and time, and a summary for @@ -3380,6 +3739,57 @@ its first parent. Also, only files different from BOTH parents will appear in files:. + .. note:: + for performance reasons, log FILE may omit duplicate changes + made on branches and will not show deletions. To see all + changes including duplicates and deletions, use the --removed + switch. + + .. container:: verbose + + Some examples: + + - changesets with full descriptions and file lists:: + + hg log -v + + - changesets ancestral to the working directory:: + + hg log -f + + - last 10 commits on the current branch:: + + hg log -l 10 -b . + + - changesets showing all modifications of a file, including removals:: + + hg log --removed file.c + + - all changesets that touch a directory, with diffs, excluding merges:: + + hg log -Mp lib/ + + - all revision numbers that match a keyword:: + + hg log -k bug --template "{rev}\\n" + + - check if a given changeset is included is a tagged release:: + + hg log -r "a21ccf and ancestor(1.9)" + + - find all changesets by some user in a date range:: + + hg log -k alice -d "may 2008 to jul 2008" + + - summary of all changesets after the last tag:: + + hg log -r "last(tagged())::" --template "{desc|firstline}\\n" + + See :hg:`help dates` for a list of formats valid for -d/--date. + + See :hg:`help revisions` and :hg:`help revsets` for more about + specifying revisions. + Returns 0 on success. """ @@ -3507,10 +3917,10 @@ @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)'))], + _('review revisions to merge (no merge is performed)')) + ] + mergetoolopts, _('[-P] [-f] [[-r] REV]')) def merge(ui, repo, node=None, **opts): """merge working directory with another revision @@ -3589,7 +3999,7 @@ try: # ui.forcemerge is an internal variable, do not document - ui.setconfig('ui', 'forcemerge', opts.get('tool', '')) + repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', '')) return hg.merge(repo, node, force=opts.get('force')) finally: ui.setconfig('ui', 'forcemerge', '') @@ -3935,31 +4345,36 @@ def remove(ui, repo, *pats, **opts): """remove the specified files on the next commit - Schedule the indicated files for removal from the repository. - - This only removes files from the current branch, not from the - entire project history. -A/--after can be used to remove only - files that have already been deleted, -f/--force can be used to - force deletion, and -Af can be used to remove files from the next - revision without deleting them from the working directory. - - The following table details the behavior of remove for different - file states (columns) and option combinations (rows). The file - states are Added [A], Clean [C], Modified [M] and Missing [!] (as - reported by :hg:`status`). The actions are Warn, Remove (from - branch) and Delete (from disk):: - - A C M ! - none W RD W R - -f R RD RD R - -A W W W R - -Af R R R R - - Note that remove never deletes files in Added [A] state from the - working directory, not even if option --force is specified. + Schedule the indicated files for removal from the current branch. This command schedules the files to be removed at the next commit. - To undo a remove before that, see :hg:`revert`. + To undo a remove before that, see :hg:`revert`. To undo added + files, see :hg:`forget`. + + .. container:: verbose + + -A/--after can be used to remove only files that have already + been deleted, -f/--force can be used to force deletion, and -Af + can be used to remove files from the next revision without + deleting them from the working directory. + + The following table details the behavior of remove for different + file states (columns) and option combinations (rows). The file + states are Added [A], Clean [C], Modified [M] and Missing [!] + (as reported by :hg:`status`). The actions are Warn, Remove + (from branch) and Delete (from disk): + + ======= == == == == + A C M ! + ======= == == == == + none W RD W R + -f R RD RD R + -A W W W R + -Af R R R R + ======= == == == == + + Note that remove never deletes files in Added [A] state from the + working directory, not even if option --force is specified. Returns 0 on success, 1 if any warnings encountered. """ @@ -3994,8 +4409,8 @@ ' to force removal)\n') % m.rel(f)) ret = 1 for f in added: - ui.warn(_('not removing %s: file has been marked for add (use -f' - ' to force removal)\n') % m.rel(f)) + ui.warn(_('not removing %s: file has been marked for add' + ' (use forget to undo)\n') % m.rel(f)) ret = 1 for f in sorted(list): @@ -4051,9 +4466,8 @@ ('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, + + mergetoolopts + walkopts, _('[OPTION]... [FILE]...')) def resolve(ui, repo, *pats, **opts): """redo merges or set/view the merge status of files @@ -4072,7 +4486,8 @@ performed for files already marked as resolved. Use ``--all/-a`` to select all unresolved files. ``--tool`` can be used to specify the merge tool used for the given files. It overrides the HGMERGE - environment variable and your configuration files. + environment variable and your configuration files. Previous file + contents are saved with a ``.orig`` suffix. - :hg:`resolve -m [FILE]`: mark a file as having been resolved (e.g. after having manually fixed-up the files). The default is @@ -4145,7 +4560,7 @@ [('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')), + ('C', 'no-backup', None, _('do not save backup copies of files')), ] + walkopts + dryrunopts, _('[OPTION]... [-r REV] [NAME]...')) def revert(ui, repo, *pats, **opts): @@ -4237,6 +4652,10 @@ def badfn(path, msg): if path in names: return + if path in repo[node].substate: + ui.warn("%s: %s\n" % (m.rel(path), + 'reverting subrepos is unsupported')) + return path_ = path + '/' for f in names: if f.startswith(path_): @@ -4381,7 +4800,8 @@ finally: wlock.release() -@command('rollback', dryrunopts) +@command('rollback', dryrunopts + + [('f', 'force', False, _('ignore safety measures'))]) def rollback(ui, repo, **opts): """roll back the last transaction (dangerous) @@ -4402,6 +4822,12 @@ - push (with this repository as the destination) - unbundle + It's possible to lose data with rollback: commit, update back to + an older changeset, and then rollback. The update removes the + changes you committed from the working directory, and rollback + removes them from history. To avoid data loss, you must pass + --force in this case. + This command is not intended for use on public repositories. Once changes are visible for pull by other users, rolling a transaction back locally is ineffective (someone else may already have pulled @@ -4411,7 +4837,8 @@ Returns 0 on success, 1 if no rollback data is available. """ - return repo.rollback(opts.get('dry_run')) + return repo.rollback(dryrun=opts.get('dry_run'), + force=opts.get('force')) @command('root', []) def root(ui, repo): @@ -4653,6 +5080,22 @@ I = ignored = origin of the previous file listed as A (added) + .. container:: verbose + + Examples: + + - show changes in the working directory relative to a changeset: + + hg status --rev 9353 + + - show all changes including copies in an existing changeset:: + + hg status --copies --change 9353 + + - get a NUL separated list of added files, suitable for xargs:: + + hg status -an0 + Returns 0 on success. """ @@ -4727,6 +5170,7 @@ ctx = repo[None] parents = ctx.parents() pnode = parents[0].node() + marks = [] for p in parents: # label with log.changeset (instead of log.parent) since this @@ -4735,7 +5179,7 @@ label='log.changeset') ui.write(' '.join(p.tags()), label='log.tag') if p.bookmarks(): - ui.write(' ' + ' '.join(p.bookmarks()), label='log.bookmark') + marks.extend(p.bookmarks()) if p.rev() == -1: if not len(repo): ui.write(_(' (empty repository)')) @@ -4754,6 +5198,20 @@ else: ui.status(m, label='log.branch') + if marks: + current = repo._bookmarkcurrent + ui.write(_('bookmarks:'), label='log.bookmark') + if current is not None: + try: + marks.remove(current) + ui.write(' *' + current, label='bookmarks.current') + except ValueError: + # current bookmark not in parent ctx marks + pass + for m in marks: + ui.write(' ' + m, label='log.bookmark') + ui.write('\n', label='log.bookmark') + st = list(repo.status(unknown=True))[:6] c = repo.dirstate.copies() @@ -4988,19 +5446,22 @@ for t, n in reversed(repo.tagslist()): if ui.quiet: - ui.write("%s\n" % t) + ui.write("%s\n" % t, label='tags.normal') continue hn = hexfunc(n) r = "%5d:%s" % (repo.changelog.rev(n), hn) + rev = ui.label(r, 'log.changeset') spaces = " " * (30 - encoding.colwidth(t)) + tag = ui.label(t, 'tags.normal') if ui.verbose: if repo.tagtype(t) == 'local': tagtype = " local" + tag = ui.label(t, 'tags.local') else: tagtype = "" - ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype)) + ui.write("%s%s %s%s\n" % (tag, spaces, rev, tagtype)) @command('tip', [('p', 'patch', None, _('show patch')),
--- a/mercurial/commandserver.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/commandserver.py Sat Oct 15 14:30:50 2011 -0500 @@ -185,6 +185,7 @@ copiedui = self.ui.copy() self.repo.baseui = copiedui self.repo.ui = self.repo.dirstate._ui = self.repoui.copy() + self.repo.invalidate() req = dispatch.request(args[:], copiedui, self.repo, self.cin, self.cout, self.cerr)
--- a/mercurial/demandimport.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/demandimport.py Sat Oct 15 14:30:50 2011 -0500 @@ -27,6 +27,17 @@ import __builtin__ _origimport = __import__ +nothing = object() + +try: + _origimport(__builtin__.__name__, {}, {}, None, -1) +except TypeError: # no level argument + def _import(name, globals, locals, fromlist, level): + "call _origimport with no level argument" + return _origimport(name, globals, locals, fromlist) +else: + _import = _origimport + class _demandmod(object): """module demand-loader and proxy""" def __init__(self, name, globals, locals): @@ -50,7 +61,7 @@ h, t = p, None if '.' in p: h, t = p.split('.', 1) - if not hasattr(mod, h): + if getattr(mod, h, nothing) is nothing: setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__)) elif t: subload(getattr(mod, h), t) @@ -81,20 +92,14 @@ def _demandimport(name, globals=None, locals=None, fromlist=None, level=-1): if not locals or name in ignore or fromlist == ('*',): # these cases we can't really delay - if level == -1: - return _origimport(name, globals, locals, fromlist) - else: - return _origimport(name, globals, locals, fromlist, level) + return _import(name, globals, locals, fromlist, level) elif not fromlist: # import a [as b] if '.' in name: # a.b base, rest = name.split('.', 1) # email.__init__ loading email.mime if globals and globals.get('__name__', None) == base: - if level != -1: - return _origimport(name, globals, locals, fromlist, level) - else: - return _origimport(name, globals, locals, fromlist) + return _import(name, globals, locals, fromlist, level) # if a is already demand-loaded, add b to its submodule list if base in locals: if isinstance(locals[base], _demandmod): @@ -109,12 +114,12 @@ mod = _origimport(name, globals, locals) # recurse down the module chain for comp in name.split('.')[1:]: - if not hasattr(mod, comp): + if getattr(mod, comp, nothing) is nothing: setattr(mod, comp, _demandmod(comp, mod.__dict__, mod.__dict__)) mod = getattr(mod, comp) for x in fromlist: # set requested submodules for demand load - if not hasattr(mod, x): + if getattr(mod, x, nothing) is nothing: setattr(mod, x, _demandmod(x, mod.__dict__, locals)) return mod @@ -137,6 +142,8 @@ # raise ImportError if x not defined '__main__', '_ssl', # conditional imports in the stdlib, issue1964 + 'rfc822', + 'mimetools', ] def enable(): @@ -146,4 +153,3 @@ def disable(): "disable global demand-loading of modules" __builtin__.__import__ = _origimport -
--- a/mercurial/dirstate.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/dirstate.py Sat Oct 15 14:30:50 2011 -0500 @@ -453,7 +453,7 @@ write(e) write(f) st.write(cs.getvalue()) - st.rename() + st.close() self._lastnormaltime = None self._dirty = self._dirtypl = False
--- a/mercurial/dispatch.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/dispatch.py Sat Oct 15 14:30:50 2011 -0500 @@ -123,6 +123,9 @@ else: ui.warn(_("hg: %s\n") % inst.args[1]) commands.help_(ui, 'shortlist') + except error.OutOfBandError, inst: + ui.warn("abort: remote error:\n") + ui.warn(''.join(inst.args)) except error.RepoError, inst: ui.warn(_("abort: %s!\n") % inst) if inst.hint: @@ -159,16 +162,16 @@ elif m in "zlib".split(): ui.warn(_("(is your Python install correct?)\n")) except IOError, inst: - if hasattr(inst, "code"): + if util.safehasattr(inst, "code"): ui.warn(_("abort: %s\n") % inst) - elif hasattr(inst, "reason"): + elif util.safehasattr(inst, "reason"): try: # usually it is in the form (errno, strerror) reason = inst.reason.args[1] 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: + elif util.safehasattr(inst, "args") and inst.args[0] == errno.EPIPE: if ui.debugflag: ui.warn(_("broken pipe\n")) elif getattr(inst, "strerror", None): @@ -338,7 +341,7 @@ ui.debug("alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)) - if hasattr(self, 'shell'): + if util.safehasattr(self, 'shell'): return self.fn(ui, *args, **opts) else: try: @@ -363,7 +366,7 @@ # definition might not exist or it might not be a cmdalias pass - cmdtable[aliasdef.cmd] = (aliasdef, aliasdef.opts, aliasdef.help) + cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help) if aliasdef.norepo: commands.norepo += ' %s' % alias @@ -483,15 +486,14 @@ lui = ui.copy() lui.readconfig(os.path.join(path, ".hg", "hgrc"), path) - if rpath: + if rpath and rpath[-1]: path = lui.expandpath(rpath[-1]) lui = ui.copy() lui.readconfig(os.path.join(path, ".hg", "hgrc"), path) return path, lui -def _checkshellalias(ui, args): - cwd = os.getcwd() +def _checkshellalias(lui, ui, args): norepo = commands.norepo options = {} @@ -503,12 +505,6 @@ if not args: return - _parseconfig(ui, options['config']) - if options['cwd']: - os.chdir(options['cwd']) - - path, lui = _getlocal(ui, [options['repository']]) - cmdtable = commands.table.copy() addaliases(lui, cmdtable) @@ -517,28 +513,22 @@ aliases, entry = cmdutil.findcmd(cmd, cmdtable, lui.config("ui", "strict")) except (error.AmbiguousCommand, error.UnknownCommand): commands.norepo = norepo - os.chdir(cwd) return cmd = aliases[0] fn = entry[0] - if cmd and hasattr(fn, 'shell'): + if cmd and util.safehasattr(fn, 'shell'): d = lambda: fn(ui, *args[1:]) return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d, [], {}) commands.norepo = norepo - os.chdir(cwd) _loaded = set() def _dispatch(req): args = req.args ui = req.ui - shellaliasfn = _checkshellalias(ui, args) - if shellaliasfn: - return shellaliasfn() - # read --config before doing anything else # (e.g. to change trust settings for reading .hg/hgrc) cfgs = _parseconfig(ui, _earlygetopt(['--config'], args)) @@ -551,6 +541,12 @@ rpath = _earlygetopt(["-R", "--repository", "--repo"], args) path, lui = _getlocal(ui, rpath) + # Now that we're operating in the right directory/repository with + # the right config settings, check for shell aliases + shellaliasfn = _checkshellalias(lui, ui, args) + if shellaliasfn: + return shellaliasfn() + # Configure extensions in phases: uisetup, extsetup, cmdtable, and # reposetup. Programs like TortoiseHg will call _dispatch several # times so we keep track of configured extensions in _loaded. @@ -635,10 +631,10 @@ for ui_ in uis: ui_.setconfig('web', 'cacerts', '') + if options['version']: + return commands.version_(ui) if options['help']: - return commands.help_(ui, cmd, options['version']) - elif options['version']: - return commands.version_(ui) + return commands.help_(ui, cmd) elif not cmd: return commands.help_(ui, 'shortlist')
--- a/mercurial/encoding.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/encoding.py Sat Oct 15 14:30:50 2011 -0500 @@ -139,7 +139,7 @@ and "WFA" or "WF") def colwidth(s): - "Find the column width of a UTF-8 string for display" + "Find the column width of a string for display in the local encoding" return ucolwidth(s.decode(encoding, 'replace')) def ucolwidth(d): @@ -149,6 +149,14 @@ return sum([eaw(c) in wide and 2 or 1 for c in d]) return len(d) +def getcols(s, start, c): + '''Use colwidth to find a c-column substring of s starting at byte + index start''' + for x in xrange(start + c, len(s)): + t = s[start:x] + if colwidth(t) == c: + return t + def lower(s): "best-effort encoding-aware case-folding of local string s" try:
--- a/mercurial/error.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/error.py Sat Oct 15 14:30:50 2011 -0500 @@ -39,6 +39,9 @@ class ConfigError(Abort): 'Exception raised when parsing config files' +class OutOfBandError(Exception): + 'Exception raised when a remote repo reports failure' + class ParseError(Exception): 'Exception raised when parsing config files (msg[, pos])'
--- a/mercurial/extensions.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/extensions.py Sat Oct 15 14:30:50 2011 -0500 @@ -69,7 +69,9 @@ return mod try: mod = importh("hgext.%s" % name) - except ImportError: + except ImportError, err: + ui.debug('could not import hgext.%s (%s): trying %s\n' + % (name, err, name)) mod = importh(name) _extensions[shortname] = mod _order.append(shortname) @@ -124,7 +126,7 @@ where orig is the original (wrapped) function, and *args, **kwargs are the arguments passed to it. ''' - assert hasattr(wrapper, '__call__') + assert util.safehasattr(wrapper, '__call__') aliases, entry = cmdutil.findcmd(command, table) for alias, e in table.iteritems(): if e is entry: @@ -177,12 +179,12 @@ your end users, you should play nicely with others by using the subclass trick. ''' - assert hasattr(wrapper, '__call__') + assert util.safehasattr(wrapper, '__call__') def wrap(*args, **kwargs): return wrapper(origfn, *args, **kwargs) origfn = getattr(container, funcname) - assert hasattr(origfn, '__call__') + assert util.safehasattr(origfn, '__call__') setattr(container, funcname, wrap) return origfn
--- a/mercurial/fancyopts.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/fancyopts.py Sat Oct 15 14:30:50 2011 -0500 @@ -75,7 +75,7 @@ # copy defaults to state if isinstance(default, list): state[name] = default[:] - elif hasattr(default, '__call__'): + elif getattr(default, '__call__', False): state[name] = None else: state[name] = default
--- a/mercurial/filemerge.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/filemerge.py Sat Oct 15 14:30:50 2011 -0500 @@ -34,7 +34,8 @@ p = util.findexe(p + _toolstr(ui, tool, "regappend")) if p: return p - return util.findexe(_toolstr(ui, tool, "executable", tool)) + exe = _toolstr(ui, tool, "executable", tool) + return util.findexe(util.expandpath(exe)) def _picktool(repo, ui, path, binary, symlink): def check(tool, pat, symlink, binary):
--- a/mercurial/hbisect.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/hbisect.py Sat Oct 15 14:30:50 2011 -0500 @@ -8,7 +8,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 os +import os, error from i18n import _ from node import short, hex import util @@ -35,17 +35,18 @@ # build visit array ancestors = [None] * (len(changelog) + 1) # an extra for [-1] - # set nodes descended from goodrev - ancestors[goodrev] = [] + # set nodes descended from goodrevs + for rev in goodrevs: + ancestors[rev] = [] for rev in xrange(goodrev + 1, len(changelog)): for prev in clparents(rev): if ancestors[prev] == []: ancestors[rev] = [] # clear good revs from array - for node in goodrevs: - ancestors[node] = None - for rev in xrange(len(changelog), -1, -1): + for rev in goodrevs: + ancestors[rev] = None + for rev in xrange(len(changelog), goodrev, -1): if ancestors[rev] is None: for prev in clparents(rev): ancestors[prev] = None @@ -149,7 +150,102 @@ for kind in state: for node in state[kind]: f.write("%s %s\n" % (kind, hex(node))) - f.rename() + f.close() finally: wlock.release() +def get(repo, status): + """ + Return a list of revision(s) that match the given status: + + - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip + - ``goods``, ``bads`` : csets topologicaly good/bad + - ``range`` : csets taking part in the bisection + - ``pruned`` : csets that are goods, bads or skipped + - ``untested`` : csets whose fate is yet unknown + - ``ignored`` : csets ignored due to DAG topology + """ + state = load_state(repo) + if status in ('good', 'bad', 'skip'): + return [repo.changelog.rev(n) for n in state[status]] + else: + # In the floowing sets, we do *not* call 'bisect()' with more + # than one level of recusrsion, because that can be very, very + # time consuming. Instead, we always develop the expression as + # much as possible. + + # 'range' is all csets that make the bisection: + # - have a good ancestor and a bad descendant, or conversely + # that's because the bisection can go either way + range = '( bisect(bad)::bisect(good) | bisect(good)::bisect(bad) )' + + _t = [c.rev() for c in repo.set('bisect(good)::bisect(bad)')] + # The sets of topologically good or bad csets + if len(_t) == 0: + # Goods are topologically after bads + goods = 'bisect(good)::' # Pruned good csets + bads = '::bisect(bad)' # Pruned bad csets + else: + # Goods are topologically before bads + goods = '::bisect(good)' # Pruned good csets + bads = 'bisect(bad)::' # Pruned bad csets + + # 'pruned' is all csets whose fate is already known: good, bad, skip + skips = 'bisect(skip)' # Pruned skipped csets + pruned = '( (%s) | (%s) | (%s) )' % (goods, bads, skips) + + # 'untested' is all cset that are- in 'range', but not in 'pruned' + untested = '( (%s) - (%s) )' % (range, pruned) + + # 'ignored' is all csets that were not used during the bisection + # due to DAG topology, but may however have had an impact. + # Eg., a branch merged between bads and goods, but whose branch- + # point is out-side of the range. + iba = '::bisect(bad) - ::bisect(good)' # Ignored bads' ancestors + iga = '::bisect(good) - ::bisect(bad)' # Ignored goods' ancestors + ignored = '( ( (%s) | (%s) ) - (%s) )' % (iba, iga, range) + + if status == 'range': + return [c.rev() for c in repo.set(range)] + elif status == 'pruned': + return [c.rev() for c in repo.set(pruned)] + elif status == 'untested': + return [c.rev() for c in repo.set(untested)] + elif status == 'ignored': + return [c.rev() for c in repo.set(ignored)] + elif status == "goods": + return [c.rev() for c in repo.set(goods)] + elif status == "bads": + return [c.rev() for c in repo.set(bads)] + + else: + raise error.ParseError(_('invalid bisect state')) + +def label(repo, node, short=False): + rev = repo.changelog.rev(node) + + # Try explicit sets + if rev in get(repo, 'good'): + return _('good') + if rev in get(repo, 'bad'): + return _('bad') + if rev in get(repo, 'skip'): + return _('skipped') + if rev in get(repo, 'untested'): + return _('untested') + if rev in get(repo, 'ignored'): + return _('ignored') + + # Try implicit sets + if rev in get(repo, 'goods'): + return _('good (implicit)') + if rev in get(repo, 'bads'): + return _('bad (implicit)') + + return None + +def shortlabel(label): + if label: + return label[0].upper() + + return None
--- a/mercurial/help.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/help.py Sat Oct 15 14:30:50 2011 -0500 @@ -31,7 +31,7 @@ """Return a delayed loader for help/topic.txt.""" def loader(): - if hasattr(sys, 'frozen'): + if util.mainfrozen(): module = sys.executable else: module = __file__
--- a/mercurial/help/config.txt Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/help/config.txt Sat Oct 15 14:30:50 2011 -0500 @@ -223,6 +223,10 @@ ``$HG_ARGS`` expand to the arguments given to Mercurial. In the ``hg echo foo`` call above, ``$HG_ARGS`` would expand to ``echo foo``. +.. note:: Some global configuration options such as ``-R`` are + processed before shell aliases and will thus not be passed to + aliases. + ``auth`` """""""" @@ -1261,6 +1265,12 @@ ``ipv6`` Whether to use IPv6. Default is False. +``logoimg`` + File name of the logo image that some templates display on each page. + The file name is relative to ``staticurl``. That is, the full path to + the logo image is "staticurl/logoimg". + If unset, ``hglogo.png`` will be used. + ``logourl`` Base URL to use for logos. If unset, ``http://mercurial.selenic.com/`` will be used.
--- a/mercurial/help/subrepos.txt Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/help/subrepos.txt Sat Oct 15 14:30:50 2011 -0500 @@ -1,13 +1,14 @@ Subrepositories let you nest external repositories or projects into a parent Mercurial repository, and make commands operate on them as a -group. External Mercurial and Subversion projects are currently -supported. +group. + +Mercurial currently supports Mercurial, Git, and Subversion +subrepositories. Subrepositories are made of three components: 1. Nested repository checkouts. They can appear anywhere in the - parent working directory, and are Mercurial clones or Subversion - checkouts. + parent working directory. 2. Nested repository references. They are defined in ``.hgsub`` and tell where the subrepository checkouts come from. Mercurial @@ -15,12 +16,15 @@ path/to/nested = https://example.com/nested/repo/path + Git and Subversion subrepos are also supported: + + path/to/nested = [git]git://example.com/nested/repo/path + path/to/nested = [svn]https://example.com/nested/trunk/path + where ``path/to/nested`` is the checkout location relatively to the parent Mercurial root, and ``https://example.com/nested/repo/path`` is the source repository path. The source can also reference a - filesystem path. Subversion repositories are defined with: - - path/to/nested = [svn]https://example.com/nested/trunk/path + filesystem path. Note that ``.hgsub`` does not exist by default in Mercurial repositories, you have to create and add it to the parent
--- a/mercurial/hg.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/hg.py Sat Oct 15 14:30:50 2011 -0500 @@ -98,9 +98,9 @@ hook(ui, repo) return repo -def peer(ui, opts, path, create=False): +def peer(uiorrepo, opts, path, create=False): '''return a repository peer for the specified path''' - rui = remoteui(ui, opts) + rui = remoteui(uiorrepo, opts) return repository(rui, path, create) def defaultdest(source): @@ -174,6 +174,36 @@ continue _update(r, uprev) +def copystore(ui, srcrepo, destpath): + '''copy files from store of srcrepo in destpath + + returns destlock + ''' + destlock = None + try: + hardlink = None + num = 0 + for f in srcrepo.store.copylist(): + src = os.path.join(srcrepo.sharedpath, f) + dst = os.path.join(destpath, f) + dstbase = os.path.dirname(dst) + if dstbase and not os.path.exists(dstbase): + os.mkdir(dstbase) + if os.path.exists(src): + if dst.endswith('data'): + # lock to avoid premature writing to the target + destlock = lock.lock(os.path.join(dstbase, "lock")) + hardlink, n = util.copyfiles(src, dst, hardlink) + num += n + if hardlink: + ui.debug("linked %d files\n" % num) + else: + ui.debug("copied %d files\n" % num) + return destlock + except: + release(destlock) + raise + def clone(ui, peeropts, source, dest=None, pull=False, rev=None, update=True, stream=False, branch=None): """Make a copy of an existing repository. @@ -287,24 +317,7 @@ % dest) raise - hardlink = None - num = 0 - for f in srcrepo.store.copylist(): - src = os.path.join(srcrepo.sharedpath, f) - dst = os.path.join(destpath, f) - dstbase = os.path.dirname(dst) - if dstbase and not os.path.exists(dstbase): - os.mkdir(dstbase) - if os.path.exists(src): - if dst.endswith('data'): - # lock to avoid premature writing to the target - destlock = lock.lock(os.path.join(dstbase, "lock")) - hardlink, n = util.copyfiles(src, dst, hardlink) - num += n - if hardlink: - ui.debug("linked %d files\n" % num) - else: - ui.debug("copied %d files\n" % num) + destlock = copystore(ui, srcrepo, destpath) # we need to re-init the repo after manually copying the data # into it @@ -537,7 +550,7 @@ def remoteui(src, opts): 'build a remote ui from ui or repo and opts' - if hasattr(src, 'baseui'): # looks like a repository + if util.safehasattr(src, 'baseui'): # looks like a repository dst = src.baseui.copy() # drop repo-specific config src = src.ui # copy target options from repo else: # assume it's a global ui object
--- a/mercurial/hgweb/hgweb_mod.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/hgweb/hgweb_mod.py Sat Oct 15 14:30:50 2011 -0500 @@ -7,7 +7,7 @@ # GNU General Public License version 2 or any later version. import os -from mercurial import ui, hg, hook, error, encoding, templater +from mercurial import ui, hg, hook, error, encoding, templater, util from common import get_stat, ErrorResponse, permhooks, caching from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR @@ -148,7 +148,7 @@ cmd = cmd[style + 1:] # avoid accepting e.g. style parameter as command - if hasattr(webcommands, cmd): + if util.safehasattr(webcommands, cmd): req.form['cmd'] = [cmd] else: cmd = '' @@ -236,6 +236,7 @@ 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/") + logoimg = self.config("web", "logoimg", "hglogo.png") staticurl = self.config("web", "staticurl") or req.url + 'static/' if not staticurl.endswith('/'): staticurl += '/' @@ -276,6 +277,7 @@ tmpl = templater.templater(mapfile, defaults={"url": req.url, "logourl": logourl, + "logoimg": logoimg, "staticurl": staticurl, "urlbase": urlbase, "repo": self.reponame,
--- a/mercurial/hgweb/hgwebdir_mod.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/hgweb/hgwebdir_mod.py Sat Oct 15 14:30:50 2011 -0500 @@ -51,6 +51,33 @@ yield (prefix + '/' + util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path +def geturlcgivars(baseurl, port): + """ + Extract CGI variables from baseurl + + >>> geturlcgivars("http://host.org/base", "80") + ('host.org', '80', '/base') + >>> geturlcgivars("http://host.org:8000/base", "80") + ('host.org', '8000', '/base') + >>> geturlcgivars('/base', 8000) + ('', '8000', '/base') + >>> geturlcgivars("base", '8000') + ('', '8000', '/base') + >>> geturlcgivars("http://host", '8000') + ('host', '8000', '/') + >>> geturlcgivars("http://host/", '8000') + ('host', '8000', '/') + """ + u = util.url(baseurl) + name = u.host or '' + if u.port: + port = u.port + path = u.path or "" + if not path.startswith('/'): + path = '/' + path + + return name, str(port), path + class hgwebdir(object): refreshinterval = 20 @@ -348,6 +375,7 @@ start = url[-1] == '?' and '&' or '?' sessionvars = webutil.sessionvars(vars, start) logourl = config('web', 'logourl', 'http://mercurial.selenic.com/') + logoimg = config('web', 'logoimg', 'hglogo.png') staticurl = config('web', 'staticurl') or url + 'static/' if not staticurl.endswith('/'): staticurl += '/' @@ -358,17 +386,14 @@ "motd": motd, "url": url, "logourl": logourl, + "logoimg": logoimg, "staticurl": staticurl, "sessionvars": sessionvars}) return tmpl def updatereqenv(self, env): if self._baseurl is not None: - u = util.url(self._baseurl) - env['SERVER_NAME'] = u.host - if u.port: - env['SERVER_PORT'] = u.port - path = u.path or "" - if not path.startswith('/'): - path = '/' + path + name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT']) + env['SERVER_NAME'] = name + env['SERVER_PORT'] = port env['SCRIPT_NAME'] = path
--- a/mercurial/hgweb/protocol.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/hgweb/protocol.py Sat Oct 15 14:30:50 2011 -0500 @@ -10,6 +10,7 @@ from common import HTTP_OK HGTYPE = 'application/mercurial-0.1' +HGERRTYPE = 'application/hg-error' class webproto(object): def __init__(self, req, ui): @@ -90,3 +91,7 @@ rsp = '0\n%s\n' % rsp.res req.respond(HTTP_OK, HGTYPE, length=len(rsp)) return [rsp] + elif isinstance(rsp, wireproto.ooberror): + rsp = rsp.message + req.respond(HTTP_OK, HGERRTYPE, length=len(rsp)) + return [rsp]
--- a/mercurial/hgweb/request.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/hgweb/request.py Sat Oct 15 14:30:50 2011 -0500 @@ -101,7 +101,7 @@ self.headers = [] def write(self, thing): - if hasattr(thing, "__iter__"): + if util.safehasattr(thing, "__iter__"): for part in thing: self.write(part) else:
--- a/mercurial/hgweb/server.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/hgweb/server.py Sat Oct 15 14:30:50 2011 -0500 @@ -246,9 +246,10 @@ try: from threading import activeCount + activeCount() # silence pyflakes _mixin = SocketServer.ThreadingMixIn except ImportError: - if hasattr(os, "fork"): + if util.safehasattr(os, "fork"): _mixin = SocketServer.ForkingMixIn else: class _mixin(object):
--- a/mercurial/hgweb/webutil.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/hgweb/webutil.py Sat Oct 15 14:30:50 2011 -0500 @@ -72,7 +72,7 @@ d['date'] = s.date() d['description'] = s.description() d['branch'] = s.branch() - if hasattr(s, 'path'): + if util.safehasattr(s, 'path'): d['file'] = s.path() yield d
--- a/mercurial/hgweb/wsgicgi.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/hgweb/wsgicgi.py Sat Oct 15 14:30:50 2011 -0500 @@ -78,5 +78,4 @@ for chunk in content: write(chunk) finally: - if hasattr(content, 'close'): - content.close() + getattr(content, 'close', lambda : None)()
--- a/mercurial/hook.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/hook.py Sat Oct 15 14:30:50 2011 -0500 @@ -21,14 +21,14 @@ ui.note(_("calling hook %s: %s\n") % (hname, funcname)) obj = funcname - if not hasattr(obj, '__call__'): + if not util.safehasattr(obj, '__call__'): d = funcname.rfind('.') if d == -1: raise util.Abort(_('%s hook is invalid ("%s" not in ' 'a module)') % (hname, funcname)) modname = funcname[:d] oldpaths = sys.path - if hasattr(sys, "frozen"): + if util.mainfrozen(): # binary installs require sys.path manipulation modpath, modfile = os.path.split(modname) if modpath and modfile: @@ -60,7 +60,7 @@ raise util.Abort(_('%s hook is invalid ' '("%s" is not defined)') % (hname, funcname)) - if not hasattr(obj, '__call__'): + if not util.safehasattr(obj, '__call__'): raise util.Abort(_('%s hook is invalid ' '("%s" is not callable)') % (hname, funcname)) @@ -99,7 +99,7 @@ env = {} for k, v in args.iteritems(): - if hasattr(v, '__call__'): + if util.safehasattr(v, '__call__'): v = v() if isinstance(v, dict): # make the dictionary element order stable across Python @@ -149,7 +149,7 @@ for hname, cmd in ui.configitems('hooks'): if hname.split('.')[0] != name or not cmd: continue - if hasattr(cmd, '__call__'): + if util.safehasattr(cmd, '__call__'): r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r elif cmd.startswith('python:'): if cmd.count(':') >= 2:
--- a/mercurial/httpclient/__init__.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/httpclient/__init__.py Sat Oct 15 14:30:50 2011 -0500 @@ -171,6 +171,14 @@ logger.info('cl: %r body: %r', self._content_len, self._body) try: data = self.sock.recv(INCOMING_BUFFER_SIZE) + # If the socket was readable and no data was read, that + # means the socket was closed. If this isn't a + # _CLOSE_IS_END socket, then something is wrong if we're + # here (we shouldn't enter _select() if the response is + # complete), so abort. + if not data and self._content_len != _LEN_CLOSE_IS_END: + raise HTTPRemoteClosedError( + 'server appears to have closed the socket mid-response') except socket.sslerror, e: if e.args[0] != socket.SSL_ERROR_WANT_READ: raise @@ -693,6 +701,11 @@ class HTTPProxyConnectFailedException(httplib.HTTPException): """Connecting to the HTTP proxy failed.""" + class HTTPStateError(httplib.HTTPException): """Invalid internal state encountered.""" + + +class HTTPRemoteClosedError(httplib.HTTPException): + """The server closed the remote socket in the middle of a response.""" # no-check-code
--- a/mercurial/httpclient/tests/simple_http_test.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/httpclient/tests/simple_http_test.py Sat Oct 15 14:30:50 2011 -0500 @@ -380,6 +380,21 @@ con.request('GET', '/') self.assertEqual(2, len(sockets)) + def test_server_closes_before_end_of_body(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + s = con.sock + s.data = ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'Connection: Keep-Alive\r\n', + 'Content-Length: 16', + '\r\n\r\n', + 'You can '] # Note: this is shorter than content-length + s.close_on_empty = True + con.request('GET', '/') + r1 = con.getresponse() + self.assertRaises(http.HTTPRemoteClosedError, r1.read) + def test_no_response_raises_response_not_ready(self): con = http.HTTPConnection('foo') self.assertRaises(http.httplib.ResponseNotReady, con.getresponse)
--- a/mercurial/httpclient/tests/test_chunked_transfer.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/httpclient/tests/test_chunked_transfer.py Sat Oct 15 14:30:50 2011 -0500 @@ -134,4 +134,20 @@ con.request('GET', '/') self.assertStringEqual('hi there\nthere\nthere\nthere\nthere\n', con.getresponse().read()) + + def testChunkedDownloadEarlyHangup(self): + con = http.HTTPConnection('1.2.3.4:80') + con._connect() + sock = con.sock + broken = chunkedblock('hi'*20)[:-1] + sock.data = ['HTTP/1.1 200 OK\r\n', + 'Server: BogusServer 1.0\r\n', + 'transfer-encoding: chunked', + '\r\n\r\n', + broken, + ] + sock.close_on_empty = True + con.request('GET', '/') + resp = con.getresponse() + self.assertRaises(http.HTTPRemoteClosedError, resp.read) # no-check-code
--- a/mercurial/httprepo.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/httprepo.py Sat Oct 15 14:30:50 2011 -0500 @@ -28,6 +28,7 @@ self.path = path self.caps = None self.handler = None + self.urlopener = None u = util.url(path) if u.query or u.fragment: raise util.Abort(_('unsupported URL component: "%s"') % @@ -42,10 +43,10 @@ self.urlopener = url.opener(ui, authinfo) def __del__(self): - for h in self.urlopener.handlers: - h.close() - if hasattr(h, "close_all"): - h.close_all() + if self.urlopener: + for h in self.urlopener.handlers: + h.close() + getattr(h, "close_all", lambda : None)() def url(self): return self.path @@ -139,6 +140,8 @@ proto = resp.headers.get('content-type', '') safeurl = util.hidepassword(self._url) + if proto.startswith('application/hg-error'): + raise error.OutOfBandError(resp.read()) # accept old "text/plain" and "application/hg-changegroup" for now if not (proto.startswith('application/mercurial-') or proto.startswith('text/plain') or
--- a/mercurial/i18n.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/i18n.py Sat Oct 15 14:30:50 2011 -0500 @@ -9,7 +9,7 @@ import gettext, sys, os # modelled after templater.templatepath: -if hasattr(sys, 'frozen'): +if getattr(sys, 'frozen', None) is not None: module = sys.executable else: module = __file__ @@ -61,4 +61,3 @@ _ = lambda message: message else: _ = gettext -
--- a/mercurial/keepalive.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/keepalive.py Sat Oct 15 14:30:50 2011 -0500 @@ -547,13 +547,14 @@ print "send:", repr(str) try: blocksize = 8192 - if hasattr(str,'read') : + read = getattr(str, 'read', None) + if read is not None: if self.debuglevel > 0: print "sendIng a read()able" - data = str.read(blocksize) + data = read(blocksize) while data: self.sock.sendall(data) - data = str.read(blocksize) + data = read(blocksize) else: self.sock.sendall(str) except socket.error, v:
--- a/mercurial/localrepo.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/localrepo.py Sat Oct 15 14:30:50 2011 -0500 @@ -10,13 +10,14 @@ import repo, changegroup, subrepo, discovery, pushkey import changelog, dirstate, filelog, manifest, context, bookmarks import lock, transaction, store, encoding -import scmutil, util, extensions, hook, error +import scmutil, util, extensions, hook, error, revset import match as matchmod import merge as mergemod import tags as tagsmod from lock import release import weakref, errno, os, time, inspect propertycache = util.propertycache +filecache = scmutil.filecache class localrepository(repo.repository): capabilities = set(('lookup', 'changegroupsubset', 'branchmap', 'pushkey', @@ -63,6 +64,7 @@ ) if self.ui.configbool('format', 'generaldelta', False): requirements.append("generaldelta") + requirements = set(requirements) else: raise error.RepoError(_("repository %s not found") % path) elif create: @@ -77,7 +79,7 @@ self.sharedpath = self.path try: - s = os.path.realpath(self.opener.read("sharedpath")) + s = os.path.realpath(self.opener.read("sharedpath").rstrip('\n')) if not os.path.exists(s): raise error.RepoError( _('.hg/sharedpath points to nonexistent directory %s') % s) @@ -95,21 +97,19 @@ if create: self._writerequirements() - # These two define the set of tags for this repository. _tags - # maps tag name to node; _tagtypes maps tag name to 'global' or - # 'local'. (Global tags are defined by .hgtags across all - # heads, and local tags are defined in .hg/localtags.) They - # constitute the in-memory cache of tags. - self._tags = None - self._tagtypes = None self._branchcache = None self._branchcachetip = None - self.nodetagscache = None self.filterpats = {} self._datafilters = {} self._transref = self._lockref = self._wlockref = None + # A cache for various files under .hg/ that tracks file changes, + # (used by the filecache decorator) + # + # Maps a property name to its util.filecacheentry + self._filecache = {} + def _applyrequirements(self, requirements): self.requirements = requirements openerreqs = set(('revlogv1', 'generaldelta')) @@ -159,15 +159,18 @@ parts.pop() return False - @util.propertycache + @filecache('bookmarks') def _bookmarks(self): return bookmarks.read(self) - @util.propertycache + @filecache('bookmarks.current') def _bookmarkcurrent(self): return bookmarks.readcurrent(self) - @propertycache + def _writebookmarks(self, marks): + bookmarks.write(self) + + @filecache('00changelog.i', True) def changelog(self): c = changelog.changelog(self.sopener) if 'HG_PENDING' in os.environ: @@ -176,11 +179,11 @@ c.readpending('00changelog.i.a') return c - @propertycache + @filecache('00manifest.i', True) def manifest(self): return manifest.manifest(self.sopener) - @propertycache + @filecache('dirstate') def dirstate(self): warned = [0] def validate(node): @@ -217,6 +220,17 @@ for i in xrange(len(self)): yield i + def set(self, expr, *args): + ''' + Yield a context for each matching revision, after doing arg + replacement via revset.formatspec + ''' + + expr = revset.formatspec(expr, *args) + m = revset.match(None, expr) + for r in m(self, range(len(self))): + yield self[r] + def url(self): return 'file:' + self.root @@ -249,8 +263,8 @@ fp.write('\n') for name in names: m = munge and munge(name) or name - if self._tagtypes and name in self._tagtypes: - old = self._tags.get(name, nullid) + if self._tagscache.tagtypes and name in self._tagscache.tagtypes: + old = self.tags().get(name, nullid) fp.write('%s %s\n' % (hex(old), m)) fp.write('%s %s\n' % (hex(node), m)) fp.close() @@ -325,12 +339,31 @@ self.tags() # instantiate the cache self._tag(names, node, message, local, user, date) + @propertycache + def _tagscache(self): + '''Returns a tagscache object that contains various tags related caches.''' + + # This simplifies its cache management by having one decorated + # function (this one) and the rest simply fetch things from it. + class tagscache(object): + def __init__(self): + # These two define the set of tags for this repository. tags + # maps tag name to node; tagtypes maps tag name to 'global' or + # 'local'. (Global tags are defined by .hgtags across all + # heads, and local tags are defined in .hg/localtags.) + # They constitute the in-memory cache of tags. + self.tags = self.tagtypes = None + + self.nodetagscache = self.tagslist = None + + cache = tagscache() + cache.tags, cache.tagtypes = self._findtags() + + return cache + def tags(self): '''return a mapping of tag to node''' - if self._tags is None: - (self._tags, self._tagtypes) = self._findtags() - - return self._tags + return self._tagscache.tags def _findtags(self): '''Do the hard work of finding tags. Return a pair of dicts @@ -379,27 +412,29 @@ None : tag does not exist ''' - self.tags() - - return self._tagtypes.get(tagname) + return self._tagscache.tagtypes.get(tagname) def tagslist(self): '''return a list of tags ordered by revision''' - l = [] - for t, n in self.tags().iteritems(): - r = self.changelog.rev(n) - l.append((r, t, n)) - return [(t, n) for r, t, n in sorted(l)] + if not self._tagscache.tagslist: + l = [] + for t, n in self.tags().iteritems(): + r = self.changelog.rev(n) + l.append((r, t, n)) + self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)] + + return self._tagscache.tagslist def nodetags(self, node): '''return the tags associated with a node''' - if not self.nodetagscache: - self.nodetagscache = {} + if not self._tagscache.nodetagscache: + nodetagscache = {} for t, n in self.tags().iteritems(): - self.nodetagscache.setdefault(n, []).append(t) - for tags in self.nodetagscache.itervalues(): + nodetagscache.setdefault(n, []).append(t) + for tags in nodetagscache.itervalues(): tags.sort() - return self.nodetagscache.get(node, []) + self._tagscache.nodetagscache = nodetagscache + return self._tagscache.nodetagscache.get(node, []) def nodebookmarks(self, node): marks = [] @@ -489,7 +524,7 @@ for label, nodes in branches.iteritems(): for node in nodes: f.write("%s %s\n" % (hex(node), encoding.fromlocal(label))) - f.rename() + f.close() except (IOError, OSError): pass @@ -722,67 +757,112 @@ finally: lock.release() - def rollback(self, dryrun=False): + def rollback(self, dryrun=False, force=False): wlock = lock = None try: wlock = self.wlock() lock = self.lock() if os.path.exists(self.sjoin("undo")): - try: - 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") % ( - int(args[0]) - 1, args[1], args[2]) - elif len(args) >= 2: - desc = _("repository tip rolled back to revision %s" - " (undo %s)\n") % ( - int(args[0]) - 1, args[1]) - except IOError: - desc = _("rolling back unknown transaction\n") - self.ui.status(desc) - if dryrun: - return - transaction.rollback(self.sopener, self.sjoin("undo"), - self.ui.warn) - util.rename(self.join("undo.dirstate"), self.join("dirstate")) - if os.path.exists(self.join('undo.bookmarks')): - util.rename(self.join('undo.bookmarks'), - self.join('bookmarks')) - try: - branch = self.opener.read("undo.branch") - self.dirstate.setbranch(branch) - except IOError: - self.ui.warn(_("named branch could not be reset, " - "current branch is still: %s\n") - % self.dirstate.branch()) - self.invalidate() - self.dirstate.invalidate() - self.destroyed() - parents = tuple([p.rev() for p in self.parents()]) - if len(parents) > 1: - self.ui.status(_("working directory now based on " - "revisions %d and %d\n") % parents) - else: - self.ui.status(_("working directory now based on " - "revision %d\n") % parents) + return self._rollback(dryrun, force) else: self.ui.warn(_("no rollback information available\n")) return 1 finally: release(lock, wlock) + def _rollback(self, dryrun, force): + ui = self.ui + try: + args = self.opener.read('undo.desc').splitlines() + (oldlen, desc, detail) = (int(args[0]), args[1], None) + if len(args) >= 3: + detail = args[2] + oldtip = oldlen - 1 + + if detail and ui.verbose: + msg = (_('repository tip rolled back to revision %s' + ' (undo %s: %s)\n') + % (oldtip, desc, detail)) + else: + msg = (_('repository tip rolled back to revision %s' + ' (undo %s)\n') + % (oldtip, desc)) + except IOError: + msg = _('rolling back unknown transaction\n') + desc = None + + if not force and self['.'] != self['tip'] and desc == 'commit': + raise util.Abort( + _('rollback of last commit while not checked out ' + 'may lose data'), hint=_('use -f to force')) + + ui.status(msg) + if dryrun: + return 0 + + parents = self.dirstate.parents() + transaction.rollback(self.sopener, self.sjoin('undo'), ui.warn) + if os.path.exists(self.join('undo.bookmarks')): + util.rename(self.join('undo.bookmarks'), + self.join('bookmarks')) + self.invalidate() + + parentgone = (parents[0] not in self.changelog.nodemap or + parents[1] not in self.changelog.nodemap) + if parentgone: + util.rename(self.join('undo.dirstate'), self.join('dirstate')) + try: + branch = self.opener.read('undo.branch') + self.dirstate.setbranch(branch) + except IOError: + ui.warn(_('named branch could not be reset: ' + 'current branch is still \'%s\'\n') + % self.dirstate.branch()) + + self.dirstate.invalidate() + self.destroyed() + parents = tuple([p.rev() for p in self.parents()]) + if len(parents) > 1: + ui.status(_('working directory now based on ' + 'revisions %d and %d\n') % parents) + else: + ui.status(_('working directory now based on ' + 'revision %d\n') % parents) + return 0 + def invalidatecaches(self): - self._tags = None - self._tagtypes = None - self.nodetagscache = None + try: + delattr(self, '_tagscache') + except AttributeError: + pass + self._branchcache = None # in UTF-8 self._branchcachetip = None + def invalidatedirstate(self): + '''Invalidates the dirstate, causing the next call to dirstate + to check if it was modified since the last time it was read, + rereading it if it has. + + This is different to dirstate.invalidate() that it doesn't always + rereads the dirstate. Use dirstate.invalidate() if you want to + explicitly read the dirstate again (i.e. restoring it to a previous + known good state).''' + try: + delattr(self, 'dirstate') + except AttributeError: + pass + def invalidate(self): - for a in ("changelog", "manifest", "_bookmarks", "_bookmarkcurrent"): - if a in self.__dict__: - delattr(self, a) + for k in self._filecache: + # dirstate is invalidated separately in invalidatedirstate() + if k == 'dirstate': + continue + + try: + delattr(self, k) + except AttributeError: + pass self.invalidatecaches() def _lock(self, lockname, wait, releasefn, acquirefn, desc): @@ -809,7 +889,14 @@ l.lock() return l - l = self._lock(self.sjoin("lock"), wait, self.store.write, + def unlock(): + self.store.write() + for k, ce in self._filecache.items(): + if k == 'dirstate': + continue + ce.refresh() + + l = self._lock(self.sjoin("lock"), wait, unlock, self.invalidate, _('repository %s') % self.origroot) self._lockref = weakref.ref(l) return l @@ -823,8 +910,14 @@ l.lock() return l - l = self._lock(self.join("wlock"), wait, self.dirstate.write, - self.dirstate.invalidate, _('working directory of %s') % + def unlock(): + self.dirstate.write() + ce = self._filecache.get('dirstate') + if ce: + ce.refresh() + + l = self._lock(self.join("wlock"), wait, unlock, + self.invalidatedirstate, _('working directory of %s') % self.origroot) self._wlockref = weakref.ref(l) return l
--- a/mercurial/lsprof.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/lsprof.py Sat Oct 15 14:30:50 2011 -0500 @@ -86,9 +86,7 @@ for k, v in list(sys.modules.iteritems()): if v is None: continue - if not hasattr(v, '__file__'): - continue - if not isinstance(v.__file__, str): + if not isinstance(getattr(v, '__file__', None), str): continue if v.__file__.startswith(code.co_filename): mname = _fn2mod[code.co_filename] = k
--- a/mercurial/mail.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/mail.py Sat Oct 15 14:30:50 2011 -0500 @@ -37,7 +37,7 @@ # backward compatible: when tls = true, we use starttls. starttls = tls == 'starttls' or util.parsebool(tls) smtps = tls == 'smtps' - if (starttls or smtps) and not hasattr(socket, 'ssl'): + if (starttls or smtps) and not util.safehasattr(socket, 'ssl'): raise util.Abort(_("can't use TLS: Python SSL support not installed")) if smtps: ui.note(_('(using smtps)\n'))
--- a/mercurial/match.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/match.py Sat Oct 15 14:30:50 2011 -0500 @@ -49,7 +49,6 @@ '<something>' - a pattern of the specified default type """ - self._ctx = None self._root = root self._cwd = cwd self._files = []
--- a/mercurial/mdiff.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/mdiff.py Sat Oct 15 14:30:50 2011 -0500 @@ -157,6 +157,7 @@ return 0 return ret + lastfunc = [0, ''] def yieldhunk(hunk): (astart, a2, bstart, b2, delta) = hunk aend = contextend(a2, len(l1)) @@ -165,13 +166,19 @@ func = "" if opts.showfunc: - # walk backwards from the start of the context - # to find a line starting with an alphanumeric char. - for x in xrange(astart - 1, -1, -1): - t = l1[x].rstrip() - if funcre.match(t): - func = ' ' + t[:40] + lastpos, func = lastfunc + # walk backwards from the start of the context up to the start of + # the previous hunk context until we find a line starting with an + # alphanumeric char. + for i in xrange(astart - 1, lastpos - 1, -1): + if l1[i][0].isalnum(): + func = ' ' + l1[i].rstrip()[:40] + lastfunc[1] = func break + # by recording this hunk's starting point as the next place to + # start looking for function lines, we avoid reading any line in + # the file more than once. + lastfunc[0] = astart yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen, bstart + 1, blen, func) @@ -180,9 +187,6 @@ for x in xrange(a2, aend): yield ' ' + l1[x] - if opts.showfunc: - funcre = re.compile('\w') - # bdiff.blocks gives us the matching sequences in the files. The loop # below finds the spaces between those matching sequences and translates # them into diff output.
--- a/mercurial/merge.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/merge.py Sat Oct 15 14:30:50 2011 -0500 @@ -273,7 +273,6 @@ action.sort(key=actionkey) # prescan for merges - u = repo.ui for a in action: f, m = a[:2] if m == 'm': # merge @@ -308,8 +307,8 @@ numupdates = len(action) for i, a in enumerate(action): f, m = a[:2] - u.progress(_('updating'), i + 1, item=f, total=numupdates, - unit=_('files')) + repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates, + unit=_('files')) if f and f[0] == "/": continue if m == "r": # remove @@ -377,7 +376,7 @@ repo.wopener.audit(f) util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags) ms.commit() - u.progress(_('updating'), None, total=numupdates, unit=_('files')) + repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files')) return updated, merged, removed, unresolved
--- a/mercurial/minirst.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/minirst.py Sat Oct 15 14:30:50 2011 -0500 @@ -18,17 +18,14 @@ when adding support for new constructs. """ -import re, sys +import re import util, encoding from i18n import _ - def replace(text, substs): - utext = text.decode(encoding.encoding) for f, t in substs: - utext = utext.replace(f, t) - return utext.encode(encoding.encoding) - + text = text.replace(f, t) + return text _blockre = re.compile(r"\n(?:\s*\n)+") @@ -39,14 +36,14 @@ has an 'indent' field and a 'lines' field. """ blocks = [] - for b in _blockre.split(text.strip()): + for b in _blockre.split(text.lstrip('\n').rstrip()): lines = b.splitlines() - indent = min((len(l) - len(l.lstrip())) for l in lines) - lines = [l[indent:] for l in lines] - blocks.append(dict(indent=indent, lines=lines)) + if lines: + indent = min((len(l) - len(l.lstrip())) for l in lines) + lines = [l[indent:] for l in lines] + blocks.append(dict(indent=indent, lines=lines)) return blocks - def findliteralblocks(blocks): """Finds literal blocks and adds a 'type' field to the blocks. @@ -103,6 +100,7 @@ r'((.*) +)(.*)$') _fieldre = re.compile(r':(?![: ])([^:]*)(?<! ):[ ]+(.*)') _definitionre = re.compile(r'[^ ]') +_tablere = re.compile(r'(=+\s+)*=+') def splitparagraphs(blocks): """Split paragraphs into lists.""" @@ -146,7 +144,6 @@ i += 1 return blocks - _fieldwidth = 12 def updatefieldlists(blocks): @@ -173,7 +170,6 @@ return blocks - def updateoptionlists(blocks): i = 0 while i < len(blocks): @@ -238,18 +234,67 @@ # Always delete "..container:: type" block del blocks[i] j = i + i -= 1 while j < len(blocks) and blocks[j]['indent'] > indent: if prune: del blocks[j] - i -= 1 # adjust outer index else: blocks[j]['indent'] -= adjustment j += 1 i += 1 return blocks, pruned +_sectionre = re.compile(r"""^([-=`:.'"~^_*+#])\1+$""") -_sectionre = re.compile(r"""^([-=`:.'"~^_*+#])\1+$""") +def findtables(blocks): + '''Find simple tables + + Only simple one-line table elements are supported + ''' + + for block in blocks: + # Searching for a block that looks like this: + # + # === ==== === + # A B C + # === ==== === <- optional + # 1 2 3 + # x y z + # === ==== === + if (block['type'] == 'paragraph' and + len(block['lines']) > 2 and + _tablere.match(block['lines'][0]) and + block['lines'][0] == block['lines'][-1]): + block['type'] = 'table' + block['header'] = False + div = block['lines'][0] + + # column markers are ASCII so we can calculate column + # position in bytes + columns = [x for x in xrange(len(div)) + if div[x] == '=' and (x == 0 or div[x - 1] == ' ')] + rows = [] + for l in block['lines'][1:-1]: + if l == div: + block['header'] = True + continue + row = [] + # we measure columns not in bytes or characters but in + # colwidth which makes things tricky + pos = columns[0] # leading whitespace is bytes + for n, start in enumerate(columns): + if n + 1 < len(columns): + width = columns[n + 1] - start + v = encoding.getcols(l, pos, width) # gather columns + pos += len(v) # calculate byte position of end + row.append(v.strip()) + else: + row.append(l[pos:].strip()) + rows.append(row) + + block['table'] = rows + + return blocks def findsections(blocks): """Finds sections. @@ -273,7 +318,6 @@ del block['lines'][1] return blocks - def inlineliterals(blocks): substs = [('``', '"')] for b in blocks: @@ -281,7 +325,6 @@ b['lines'] = [replace(l, substs) for l in b['lines']] return blocks - def hgrole(blocks): substs = [(':hg:`', '"hg '), ('`', '"')] for b in blocks: @@ -293,7 +336,6 @@ b['lines'] = [replace(l, substs) for l in b['lines']] return blocks - def addmargins(blocks): """Adds empty blocks for vertical spacing. @@ -366,7 +408,7 @@ hanging = block['optstrwidth'] initindent = '%s%s ' % (block['optstr'], ' ' * ((hanging - colwidth))) hangindent = ' ' * (encoding.colwidth(initindent) + 1) - return ' %s' % (util.wrap(desc, usablewidth, + return ' %s\n' % (util.wrap(desc, usablewidth, initindent=initindent, hangindent=hangindent)) @@ -381,25 +423,47 @@ defindent = indent + hang * ' ' text = ' '.join(map(str.strip, block['lines'])) - return '%s\n%s' % (indent + admonition, util.wrap(text, width=width, - initindent=defindent, - hangindent=defindent)) + return '%s\n%s\n' % (indent + admonition, + util.wrap(text, width=width, + initindent=defindent, + hangindent=defindent)) if block['type'] == 'margin': - return '' + return '\n' if block['type'] == 'literal': indent += ' ' - return indent + ('\n' + indent).join(block['lines']) + return indent + ('\n' + indent).join(block['lines']) + '\n' if block['type'] == 'section': underline = encoding.colwidth(block['lines'][0]) * block['underline'] - return "%s%s\n%s%s" % (indent, block['lines'][0],indent, underline) + return "%s%s\n%s%s\n" % (indent, block['lines'][0],indent, underline) + if block['type'] == 'table': + table = block['table'] + # compute column widths + widths = [max([encoding.colwidth(e) for e in c]) for c in zip(*table)] + text = '' + span = sum(widths) + len(widths) - 1 + indent = ' ' * block['indent'] + hang = ' ' * (len(indent) + span - widths[-1]) + + for row in table: + l = [] + for w, v in zip(widths, row): + pad = ' ' * (w - encoding.colwidth(v)) + l.append(v + pad) + l = ' '.join(l) + l = util.wrap(l, width=width, initindent=indent, hangindent=hang) + if not text and block['header']: + text = l + '\n' + indent + '-' * (min(width, span)) + '\n' + else: + text += l + "\n" + return text if block['type'] == 'definition': term = indent + block['lines'][0] hang = len(block['lines'][-1]) - len(block['lines'][-1].lstrip()) defindent = indent + hang * ' ' text = ' '.join(map(str.strip, block['lines'][1:])) - return '%s\n%s' % (term, util.wrap(text, width=width, - initindent=defindent, - hangindent=defindent)) + return '%s\n%s\n' % (term, util.wrap(text, width=width, + initindent=defindent, + hangindent=defindent)) subindent = indent if block['type'] == 'bullet': if block['lines'][0].startswith('| '): @@ -431,15 +495,103 @@ text = ' '.join(map(str.strip, block['lines'])) return util.wrap(text, width=width, initindent=indent, - hangindent=subindent) + hangindent=subindent) + '\n' + +def formathtml(blocks): + """Format RST blocks as HTML""" + + out = [] + headernest = '' + listnest = [] + def openlist(start, level): + if not listnest or listnest[-1][0] != start: + listnest.append((start, level)) + out.append('<%s>\n' % start) + + blocks = [b for b in blocks if b['type'] != 'margin'] + + for pos, b in enumerate(blocks): + btype = b['type'] + level = b['indent'] + lines = b['lines'] -def format(text, width, indent=0, keep=None): - """Parse and format the text according to width.""" + if btype == 'admonition': + admonition = _admonitiontitles[b['admonitiontitle']] + text = ' '.join(map(str.strip, lines)) + out.append('<p>\n<b>%s</b> %s\n</p>\n' % (admonition, text)) + elif btype == 'paragraph': + out.append('<p>\n%s\n</p>\n' % '\n'.join(lines)) + elif btype == 'margin': + pass + elif btype == 'literal': + out.append('<pre>\n%s\n</pre>\n' % '\n'.join(lines)) + elif btype == 'section': + i = b['underline'] + if i not in headernest: + headernest += i + level = headernest.index(i) + 1 + out.append('<h%d>%s</h%d>\n' % (level, lines[0], level)) + elif btype == 'table': + table = b['table'] + t = [] + for row in table: + l = [] + for v in zip(row): + if not t: + l.append('<th>%s</th>' % v) + else: + l.append('<td>%s</td>' % v) + t.append(' <tr>%s</tr>\n' % ''.join(l)) + out.append('<table>\n%s</table>\n' % ''.join(t)) + elif btype == 'definition': + openlist('dl', level) + term = lines[0] + text = ' '.join(map(str.strip, lines[1:])) + out.append(' <dt>%s\n <dd>%s\n' % (term, text)) + elif btype == 'bullet': + bullet, head = lines[0].split(' ', 1) + if bullet == '-': + openlist('ul', level) + else: + openlist('ol', level) + out.append(' <li> %s\n' % ' '.join([head] + lines[1:])) + elif btype == 'field': + openlist('dl', level) + key = b['key'] + text = ' '.join(map(str.strip, lines)) + out.append(' <dt>%s\n <dd>%s\n' % (key, text)) + elif btype == 'option': + openlist('dl', level) + opt = b['optstr'] + desc = ' '.join(map(str.strip, lines)) + out.append(' <dt>%s\n <dd>%s\n' % (opt, desc)) + + # close lists if indent level of next block is lower + if listnest: + start, level = listnest[-1] + if pos == len(blocks) - 1: + out.append('</%s>\n' % start) + listnest.pop() + else: + nb = blocks[pos + 1] + ni = nb['indent'] + if (ni < level or + (ni == level and + nb['type'] not in 'definition bullet field option')): + out.append('</%s>\n' % start) + listnest.pop() + + return ''.join(out) + +def parse(text, indent=0, keep=None): + """Parse text into a list of blocks""" + pruned = [] blocks = findblocks(text) for b in blocks: b['indent'] += indent blocks = findliteralblocks(blocks) + blocks = findtables(blocks) blocks, pruned = prunecontainers(blocks, keep or []) blocks = findsections(blocks) blocks = inlineliterals(blocks) @@ -450,33 +602,68 @@ blocks = addmargins(blocks) blocks = prunecomments(blocks) blocks = findadmonitions(blocks) - text = '\n'.join(formatblock(b, width) for b in blocks) + return blocks, pruned + +def formatblocks(blocks, width): + text = ''.join(formatblock(b, width) for b in blocks) + return text + +def format(text, width=80, indent=0, keep=None, style='plain'): + """Parse and format the text according to width.""" + blocks, pruned = parse(text, indent, keep or []) + if style == 'html': + text = formathtml(blocks) + else: + text = ''.join(formatblock(b, width) for b in blocks) if keep is None: return text else: return text, pruned - -if __name__ == "__main__": - from pprint import pprint - - def debug(func, *args): - blocks = func(*args) - print "*** after %s:" % func.__name__ - pprint(blocks) - print - return blocks +def getsections(blocks): + '''return a list of (section name, nesting level, blocks) tuples''' + nest = "" + level = 0 + secs = [] + for b in blocks: + if b['type'] == 'section': + i = b['underline'] + if i not in nest: + nest += i + level = nest.index(i) + 1 + nest = nest[:level] + secs.append((b['lines'][0], level, [b])) + else: + if not secs: + # add an initial empty section + secs = [('', 0, [])] + secs[-1][2].append(b) + return secs - text = sys.stdin.read() - blocks = debug(findblocks, text) - blocks = debug(findliteralblocks, blocks) - blocks, pruned = debug(prunecontainers, blocks, sys.argv[1:]) - blocks = debug(inlineliterals, blocks) - blocks = debug(splitparagraphs, blocks) - blocks = debug(updatefieldlists, blocks) - blocks = debug(updateoptionlists, blocks) - blocks = debug(findsections, blocks) - blocks = debug(addmargins, blocks) - blocks = debug(prunecomments, blocks) - blocks = debug(findadmonitions, blocks) - print '\n'.join(formatblock(b, 30) for b in blocks) +def decorateblocks(blocks, width): + '''generate a list of (section name, line text) pairs for search''' + lines = [] + for s in getsections(blocks): + section = s[0] + text = formatblocks(s[2], width) + lines.append([(section, l) for l in text.splitlines(True)]) + return lines + +def maketable(data, indent=0, header=False): + '''Generate an RST table for the given table data''' + + widths = [max(encoding.colwidth(e) for e in c) for c in zip(*data)] + indent = ' ' * indent + div = indent + ' '.join('=' * w for w in widths) + '\n' + + out = [div] + for row in data: + l = [] + for w, v in zip(widths, row): + pad = ' ' * (w - encoding.colwidth(v)) + l.append(v + pad) + out.append(indent + ' '.join(l) + "\n") + if header and len(data) > 1: + out.insert(2, div) + out.append(div) + return ''.join(out)
--- a/mercurial/osutil.c Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/osutil.c Sat Oct 15 14:30:50 2011 -0500 @@ -12,6 +12,7 @@ #include <fcntl.h> #include <stdio.h> #include <string.h> +#include <errno.h> #ifdef _WIN32 #include <windows.h> @@ -288,7 +289,8 @@ #endif if (pathlen >= PATH_MAX) { - PyErr_SetString(PyExc_ValueError, "path too long"); + errno = ENAMETOOLONG; + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); goto error_value; } strncpy(fullpath, path, PATH_MAX);
--- a/mercurial/patch.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/patch.py Sat Oct 15 14:30:50 2011 -0500 @@ -126,7 +126,7 @@ mimeheaders = ['content-type'] - if not hasattr(stream, 'next'): + if not util.safehasattr(stream, 'next'): # http responses, for example, have readline but not next stream = fiter(stream) @@ -1619,27 +1619,36 @@ def difflabel(func, *args, **kw): '''yields 2-tuples of (output, label) based on the output of func()''' - prefixes = [('diff', 'diff.diffline'), - ('copy', 'diff.extended'), - ('rename', 'diff.extended'), - ('old', 'diff.extended'), - ('new', 'diff.extended'), - ('deleted', 'diff.extended'), - ('---', 'diff.file_a'), - ('+++', 'diff.file_b'), - ('@@', 'diff.hunk'), - ('-', 'diff.deleted'), - ('+', 'diff.inserted')] - + headprefixes = [('diff', 'diff.diffline'), + ('copy', 'diff.extended'), + ('rename', 'diff.extended'), + ('old', 'diff.extended'), + ('new', 'diff.extended'), + ('deleted', 'diff.extended'), + ('---', 'diff.file_a'), + ('+++', 'diff.file_b')] + textprefixes = [('@', 'diff.hunk'), + ('-', 'diff.deleted'), + ('+', 'diff.inserted')] + head = False for chunk in func(*args, **kw): lines = chunk.split('\n') for i, line in enumerate(lines): if i != 0: yield ('\n', '') + if head: + if line.startswith('@'): + head = False + else: + if line and not line[0] in ' +-@': + head = True stripline = line - if line and line[0] in '+-': + if not head and line and line[0] in '+-': # highlight trailing whitespace, but only in changed lines stripline = line.rstrip() + prefixes = textprefixes + if head: + prefixes = headprefixes for prefix, label in prefixes: if stripline.startswith(prefix): yield (stripline, label)
--- a/mercurial/posix.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/posix.py Sat Oct 15 14:30:50 2011 -0500 @@ -84,6 +84,21 @@ # Turn off all +x bits os.chmod(f, s & 0666) +def copymode(src, dst, mode=None): + '''Copy the file mode from the file at path src to dst. + If src doesn't exist, we're using mode instead. If mode is None, we're + using umask.''' + try: + st_mode = os.lstat(src).st_mode & 0777 + except OSError, inst: + if inst.errno != errno.ENOENT: + raise + st_mode = mode + if st_mode is None: + st_mode = ~umask + st_mode &= 0666 + os.chmod(dst, st_mode) + def checkexec(path): """ Check whether the given path is on a filesystem with UNIX-like exec flags @@ -241,7 +256,9 @@ for path in os.environ.get('PATH', '').split(os.pathsep): executable = findexisting(os.path.join(path, command)) if executable is not None: - return executable + st = os.stat(executable) + if (st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)): + return executable return None def setsignalhandler(): @@ -325,3 +342,45 @@ except ImportError: pass return 80 + +def makedir(path, notindexed): + os.mkdir(path) + +def unlinkpath(f): + """unlink and remove the directory if it is empty""" + os.unlink(f) + # try removing directories that might now be empty + try: + os.removedirs(os.path.dirname(f)) + except OSError: + pass + +def lookupreg(key, name=None, scope=None): + return None + +def hidewindow(): + """Hide current shell window. + + Used to hide the window opened when starting asynchronous + child process under Windows, unneeded on other systems. + """ + pass + +class cachestat(object): + def __init__(self, path): + self.stat = os.stat(path) + + def cacheable(self): + return bool(self.stat.st_ino) + + def __eq__(self, other): + try: + return self.stat == other.stat + except AttributeError: + return False + + def __ne__(self, other): + return not self == other + +def executablepath(): + return None # available on Windows only
--- a/mercurial/pure/parsers.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/pure/parsers.py Sat Oct 15 14:30:50 2011 -0500 @@ -36,7 +36,7 @@ s = struct.calcsize(indexformatng) index = [] cache = None - n = off = 0 + off = 0 l = len(data) - s append = index.append @@ -45,7 +45,6 @@ while off <= l: e = _unpack(indexformatng, data[off:off + s]) append(e) - n += 1 if e[1] < 0: break off += e[1] + s @@ -53,7 +52,6 @@ while off <= l: e = _unpack(indexformatng, data[off:off + s]) append(e) - n += 1 off += s if off != len(data):
--- a/mercurial/repair.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/repair.py Sat Oct 15 14:30:50 2011 -0500 @@ -11,9 +11,8 @@ from mercurial.i18n import _ import os -def _bundle(repo, bases, heads, node, suffix, compress=True): +def _bundle(repo, cg, node, suffix, compress=True): """create a bundle with the specified revisions as a backup""" - cg = repo.changegroupsubset(bases, heads, 'strip') backupdir = repo.join("strip-backup") if not os.path.isdir(backupdir): os.mkdir(backupdir) @@ -83,11 +82,9 @@ saveheads.add(r) saveheads = [cl.node(r) for r in saveheads] - # compute base nodes - if saverevs: - descendants = set(cl.descendants(*saverevs)) - saverevs.difference_update(descendants) - savebases = [cl.node(r) for r in saverevs] + # compute common nodes + savecommon = set(cl.node(p) for r in saverevs for p in cl.parentrevs(r) + if p not in saverevs and p not in tostrip) bm = repo._bookmarks updatebm = [] @@ -99,12 +96,14 @@ # create a changegroup for all the branches we need to keep backupfile = None if backup == "all": - backupfile = _bundle(repo, [node], cl.heads(), node, 'backup') + allnodes=[cl.node(r) for r in xrange(striprev, len(cl))] + cg = repo._changegroup(allnodes, 'strip') + backupfile = _bundle(repo, cg, node, 'backup') repo.ui.status(_("saved backup bundle to %s\n") % backupfile) - if saveheads or savebases: + if saveheads or savecommon: # do not compress partial bundle if we remove it from disk later - chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp', - compress=keeppartialbundle) + cg = repo.getbundle('strip', common=savecommon, heads=saveheads) + chgrpfile = _bundle(repo, cg, node, 'temp', compress=keeppartialbundle) mfst = repo.manifest @@ -128,7 +127,7 @@ tr.abort() raise - if saveheads or savebases: + if saveheads or savecommon: ui.note(_("adding branch\n")) f = open(chgrpfile, "rb") gen = changegroup.readbundle(f, chgrpfile)
--- a/mercurial/revlog.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/revlog.py Sat Oct 15 14:30:50 2011 -0500 @@ -226,9 +226,10 @@ self._nodepos = None v = REVLOG_DEFAULT_VERSION - if hasattr(opener, 'options'): - if 'revlogv1' in opener.options: - if 'generaldelta' in opener.options: + opts = getattr(opener, 'options', None) + if opts is not None: + if 'revlogv1' in opts: + if 'generaldelta' in opts: v |= REVLOGGENERALDELTA else: v = 0 @@ -945,9 +946,9 @@ e = self._io.packentry(self.index[i], self.node, self.version, i) fp.write(e) - # if we don't call rename, the temp file will never replace the + # if we don't call close, the temp file will never replace the # real index - fp.rename() + fp.close() tr.replace(self.indexfile, trindex * self._io.size) self._chunkclear()
--- a/mercurial/revset.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/revset.py Sat Oct 15 14:30:50 2011 -0500 @@ -6,7 +6,7 @@ # GNU General Public License version 2 or any later version. import re -import parser, util, error, discovery, hbisect +import parser, util, error, discovery, hbisect, node import bookmarks as bookmarksmod import match as matchmod from i18n import _ @@ -235,15 +235,24 @@ 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). +def bisect(repo, subset, x): + """``bisect(string)`` + Changesets marked in the specified bisect status: + + - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip + - ``goods``, ``bads`` : csets topologicaly good/bad + - ``range`` : csets taking part in the bisection + - ``pruned`` : csets that are goods, bads or skipped + - ``untested`` : csets whose fate is yet unknown + - ``ignored`` : csets ignored due to DAG topology """ - 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] + status = getstring(x, _("bisect requires a string")).lower() + return [r for r in subset if r in hbisect.get(repo, status)] + +# Backward-compatibility +# - no help entry so that we do not advertise it any more +def bisected(repo, subset, x): + return bisect(repo, subset, x) def bookmark(repo, subset, x): """``bookmark([name])`` @@ -407,6 +416,12 @@ return [r for r in subset if r in s] +def first(repo, subset, x): + """``first(set, [n])`` + An alias for limit(). + """ + return limit(repo, subset, x) + def follow(repo, subset, x): """``follow([file])`` An alias for ``::.`` (ancestors of the working copy's first parent). @@ -513,14 +528,16 @@ return l def limit(repo, subset, x): - """``limit(set, n)`` - First n members of set. + """``limit(set, [n])`` + First n members of set, defaulting to 1. """ # i18n: "limit" is a keyword - l = getargs(x, 2, 2, _("limit requires two arguments")) + l = getargs(x, 1, 2, _("limit requires one or two arguments")) try: - # i18n: "limit" is a keyword - lim = int(getstring(l[1], _("limit requires a number"))) + lim = 1 + if len(l) == 2: + # i18n: "limit" is a keyword + lim = int(getstring(l[1], _("limit requires a number"))) except (TypeError, ValueError): # i18n: "limit" is a keyword raise error.ParseError(_("limit expects a number")) @@ -529,14 +546,16 @@ return [r for r in os if r in ss] def last(repo, subset, x): - """``last(set, n)`` - Last n members of set. + """``last(set, [n])`` + Last n members of set, defaulting to 1. """ # i18n: "last" is a keyword - l = getargs(x, 2, 2, _("last requires two arguments")) + l = getargs(x, 1, 2, _("last requires one or two arguments")) try: - # i18n: "last" is a keyword - lim = int(getstring(l[1], _("last requires a number"))) + lim = 1 + if len(l) == 2: + # i18n: "last" is a keyword + lim = int(getstring(l[1], _("last requires a number"))) except (TypeError, ValueError): # i18n: "last" is a keyword raise error.ParseError(_("last expects a number")) @@ -827,6 +846,7 @@ "ancestor": ancestor, "ancestors": ancestors, "author": author, + "bisect": bisect, "bisected": bisected, "bookmark": bookmark, "branch": branch, @@ -838,6 +858,7 @@ "descendants": descendants, "file": hasfile, "filelog": filelog, + "first": first, "follow": follow, "grep": grep, "head": head, @@ -951,7 +972,7 @@ w = 100 # very slow elif f == "ancestor": w = 1 * smallbonus - elif f in "reverse limit": + elif f in "reverse limit first": w = 0 elif f in "sort": w = 10 # assume most sorts look at changelog @@ -1019,11 +1040,87 @@ tree, pos = parse(spec) if (pos != len(spec)): raise error.ParseError(_("invalid token"), pos) - tree = findaliases(ui, tree) + if ui: + tree = findaliases(ui, tree) weight, tree = optimize(tree, True) def mfunc(repo, subset): return getset(repo, subset, tree) return mfunc +def formatspec(expr, *args): + ''' + This is a convenience function for using revsets internally, and + escapes arguments appropriately. Aliases are intentionally ignored + so that intended expression behavior isn't accidentally subverted. + + Supported arguments: + + %r = revset expression, parenthesized + %d = int(arg), no quoting + %s = string(arg), escaped and single-quoted + %b = arg.branch(), escaped and single-quoted + %n = hex(arg), single-quoted + %% = a literal '%' + + Prefixing the type with 'l' specifies a parenthesized list of that type. + + >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()")) + '(10 or 11):: and ((this()) or (that()))' + >>> formatspec('%d:: and not %d::', 10, 20) + '10:: and not 20::' + >>> formatspec('keyword(%s)', 'foo\\xe9') + "keyword('foo\\\\xe9')" + >>> b = lambda: 'default' + >>> b.branch = b + >>> formatspec('branch(%b)', b) + "branch('default')" + >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd']) + "root(('a' or 'b' or 'c' or 'd'))" + ''' + + def quote(s): + return repr(str(s)) + + def argtype(c, arg): + if c == 'd': + return str(int(arg)) + elif c == 's': + return quote(arg) + elif c == 'r': + parse(arg) # make sure syntax errors are confined + return '(%s)' % arg + elif c == 'n': + return quote(node.hex(arg)) + elif c == 'b': + return quote(arg.branch()) + + ret = '' + pos = 0 + arg = 0 + while pos < len(expr): + c = expr[pos] + if c == '%': + pos += 1 + d = expr[pos] + if d == '%': + ret += d + elif d in 'dsnbr': + ret += argtype(d, args[arg]) + arg += 1 + elif d == 'l': + # a list of some type + pos += 1 + d = expr[pos] + lv = ' or '.join(argtype(d, e) for e in args[arg]) + ret += '(%s)' % lv + arg += 1 + else: + raise util.Abort('unexpected revspec format character %s' % d) + else: + ret += c + pos += 1 + + return ret + # tell hggettext to extract docstrings from these functions: i18nfunctions = symbols.values()
--- a/mercurial/scmutil.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/scmutil.py Sat Oct 15 14:30:50 2011 -0500 @@ -324,10 +324,10 @@ def errhandler(err): if err.filename == path: raise err - if followsym and hasattr(os.path, 'samestat'): + samestat = getattr(os.path, 'samestat', None) + if followsym and samestat is not None: def adddir(dirlst, dirname): match = False - samestat = os.path.samestat dirstat = os.stat(dirname) for lstdirstat in dirlst: if samestat(dirstat, lstdirstat): @@ -709,3 +709,95 @@ raise error.RequirementError(_("unknown repository format: " "requires features '%s' (upgrade Mercurial)") % "', '".join(missings)) return requirements + +class filecacheentry(object): + def __init__(self, path): + self.path = path + self.cachestat = filecacheentry.stat(self.path) + + if self.cachestat: + self._cacheable = self.cachestat.cacheable() + else: + # None means we don't know yet + self._cacheable = None + + def refresh(self): + if self.cacheable(): + self.cachestat = filecacheentry.stat(self.path) + + def cacheable(self): + if self._cacheable is not None: + return self._cacheable + + # we don't know yet, assume it is for now + return True + + def changed(self): + # no point in going further if we can't cache it + if not self.cacheable(): + return True + + newstat = filecacheentry.stat(self.path) + + # we may not know if it's cacheable yet, check again now + if newstat and self._cacheable is None: + self._cacheable = newstat.cacheable() + + # check again + if not self._cacheable: + return True + + if self.cachestat != newstat: + self.cachestat = newstat + return True + else: + return False + + @staticmethod + def stat(path): + try: + return util.cachestat(path) + except OSError, e: + if e.errno != errno.ENOENT: + raise + +class filecache(object): + '''A property like decorator that tracks a file under .hg/ for updates. + + Records stat info when called in _filecache. + + On subsequent calls, compares old stat info with new info, and recreates + the object when needed, updating the new stat info in _filecache. + + Mercurial either atomic renames or appends for files under .hg, + so to ensure the cache is reliable we need the filesystem to be able + to tell us if a file has been replaced. If it can't, we fallback to + recreating the object on every call (essentially the same behaviour as + propertycache).''' + def __init__(self, path, instore=False): + self.path = path + self.instore = instore + + def __call__(self, func): + self.func = func + self.name = func.__name__ + return self + + def __get__(self, obj, type=None): + entry = obj._filecache.get(self.name) + + if entry: + if entry.changed(): + entry.obj = self.func(obj) + else: + path = self.instore and obj.sjoin(self.path) or obj.join(self.path) + + # We stat -before- creating the object so our cache doesn't lie if + # a writer modified between the time we read and stat + entry = filecacheentry(path) + entry.obj = self.func(obj) + + obj._filecache[self.name] = entry + + setattr(obj, self.name, entry.obj) + return entry.obj
--- a/mercurial/simplemerge.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/simplemerge.py Sat Oct 15 14:30:50 2011 -0500 @@ -445,7 +445,7 @@ out.write(line) if not opts.get('print'): - out.rename() + out.close() if m3.conflicts: if not opts.get('quiet'):
--- a/mercurial/sshrepo.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/sshrepo.py Sat Oct 15 14:30:50 2011 -0500 @@ -164,6 +164,17 @@ def _recv(self): l = self.pipei.readline() + if l == '\n': + err = [] + while True: + line = self.pipee.readline() + if line == '-\n': + break + err.extend([line]) + if len(err) > 0: + # strip the trailing newline added to the last line server-side + err[-1] = err[-1][:-1] + self._abort(error.OutOfBandError(*err)) self.readerr() try: l = int(l)
--- a/mercurial/sshserver.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/sshserver.py Sat Oct 15 14:30:50 2011 -0500 @@ -82,6 +82,12 @@ def sendpusherror(self, rsp): self.sendresponse(rsp.res) + def sendooberror(self, rsp): + self.ui.ferr.write('%s\n-\n' % rsp.message) + self.ui.ferr.flush() + self.fout.write('\n') + self.fout.flush() + def serve_forever(self): try: while self.serve_one(): @@ -96,6 +102,7 @@ wireproto.streamres: sendstream, wireproto.pushres: sendpushresponse, wireproto.pusherr: sendpusherror, + wireproto.ooberror: sendooberror, } def serve_one(self):
--- a/mercurial/sslutil.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/sslutil.py Sat Oct 15 14:30:50 2011 -0500 @@ -22,6 +22,8 @@ def ssl_wrap_socket(sock, key_file, cert_file, cert_reqs=CERT_REQUIRED, ca_certs=None): + if not util.safehasattr(socket, 'ssl'): + raise util.Abort(_('Python SSL support not found')) if ca_certs: raise util.Abort(_( 'certificate checking requires Python 2.6'))
--- a/mercurial/statichttprepo.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/statichttprepo.py Sat Oct 15 14:30:50 2011 -0500 @@ -31,15 +31,11 @@ try: f = self.opener.open(req) data = f.read() - if hasattr(f, 'getcode'): - # python 2.6+ - code = f.getcode() - elif hasattr(f, 'code'): - # undocumented attribute, seems to be set in 2.4 and 2.5 - code = f.code - else: - # Don't know how to check, hope for the best. - code = 206 + # Python 2.6+ defines a getcode() function, and 2.4 and + # 2.5 appear to always have an undocumented code attribute + # set. If we can't read either of those, fall back to 206 + # and hope for the best. + code = getattr(f, 'getcode', lambda : getattr(f, 'code', 206))() except urllib2.HTTPError, inst: num = inst.code == 404 and errno.ENOENT or None raise IOError(num, inst) @@ -125,6 +121,7 @@ self.encodepats = None self.decodepats = None self.capabilities.difference_update(["pushkey"]) + self._filecache = {} def url(self): return self._url
--- a/mercurial/store.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/store.py Sat Oct 15 14:30:50 2011 -0500 @@ -345,7 +345,7 @@ fp = self.opener('fncache', mode='wb', atomictemp=True) for p in self.entries: fp.write(encodedir(p) + '\n') - fp.rename() + fp.close() self._dirty = False def add(self, fn):
--- a/mercurial/subrepo.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/subrepo.py Sat Oct 15 14:30:50 2011 -0500 @@ -50,15 +50,7 @@ if err.errno != errno.ENOENT: raise - state = {} - for path, src in p[''].items(): - kind = 'hg' - if src.startswith('['): - if ']' not in src: - raise util.Abort(_('missing ] in subrepo source')) - kind, src = src.split(']', 1) - kind = kind[1:] - + def remap(src): for pattern, repl in p.items('subpaths'): # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub # does a string decode. @@ -72,7 +64,34 @@ except re.error, e: raise util.Abort(_("bad subrepository pattern in %s: %s") % (p.source('subpaths', pattern), e)) + return src + state = {} + for path, src in p[''].items(): + kind = 'hg' + if src.startswith('['): + if ']' not in src: + raise util.Abort(_('missing ] in subrepo source')) + kind, src = src.split(']', 1) + kind = kind[1:] + src = src.lstrip() # strip any extra whitespace after ']' + + if not util.url(src).isabs(): + parent = _abssource(ctx._repo, abort=False) + if parent: + parent = util.url(parent) + parent.path = posixpath.join(parent.path or '', src) + parent.path = posixpath.normpath(parent.path) + joined = str(parent) + # Remap the full joined path and use it if it changes, + # else remap the original source. + remapped = remap(joined) + if remapped == joined: + src = remap(src) + else: + src = remapped + + src = remap(src) state[path] = (src.strip(), rev.get(path, ''), kind) return state @@ -181,23 +200,23 @@ def reporelpath(repo): """return path to this (sub)repo as seen from outermost repo""" parent = repo - while hasattr(parent, '_subparent'): + while util.safehasattr(parent, '_subparent'): parent = parent._subparent p = parent.root.rstrip(os.sep) return repo.root[len(p) + 1:] def subrelpath(sub): """return path to this subrepo as seen from outermost repo""" - if hasattr(sub, '_relpath'): + if util.safehasattr(sub, '_relpath'): return sub._relpath - if not hasattr(sub, '_repo'): + if not util.safehasattr(sub, '_repo'): return sub._path return reporelpath(sub._repo) def _abssource(repo, push=False, abort=True): """return pull/push path of repo - either based on parent repo .hgsub info or on the top repo config. Abort or return None if no source found.""" - if hasattr(repo, '_subparent'): + if util.safehasattr(repo, '_subparent'): source = util.url(repo._subsource) if source.isabs(): return str(source) @@ -209,7 +228,7 @@ parent.path = posixpath.normpath(parent.path) return str(parent) else: # recursion reached top repo - if hasattr(repo, '_subtoppath'): + if util.safehasattr(repo, '_subtoppath'): return repo._subtoppath if push and repo.ui.config('paths', 'default-push'): return repo.ui.config('paths', 'default-push') @@ -530,9 +549,13 @@ self._state = state self._ctx = ctx self._ui = ctx._repo.ui + self._exe = util.findexe('svn') + if not self._exe: + raise util.Abort(_("'svn' executable not found for subrepo '%s'") + % self._path) def _svncommand(self, commands, filename='', failok=False): - cmd = ['svn'] + cmd = [self._exe] extrakw = {} if not self._ui.interactive(): # Making stdin be a pipe should prevent svn from behaving @@ -810,9 +833,10 @@ for b in branches: if b.startswith('refs/remotes/'): continue - remote = self._gitcommand(['config', 'branch.%s.remote' % b]) + bname = b.split('/', 2)[2] + remote = self._gitcommand(['config', 'branch.%s.remote' % bname]) if remote: - ref = self._gitcommand(['config', 'branch.%s.merge' % b]) + ref = self._gitcommand(['config', 'branch.%s.merge' % bname]) tracking['refs/remotes/%s/%s' % (remote, ref.split('/', 2)[2])] = b return tracking
--- a/mercurial/tags.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/tags.py Sat Oct 15 14:30:50 2011 -0500 @@ -287,6 +287,6 @@ cachefile.write("%s %s\n" % (hex(node), name)) try: - cachefile.rename() + cachefile.close() except (OSError, IOError): pass
--- a/mercurial/templatefilters.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templatefilters.py Sat Oct 15 14:30:50 2011 -0500 @@ -7,6 +7,7 @@ import cgi, re, os, time, urllib import encoding, node, util +import hbisect def addbreaks(text): """:addbreaks: Any text. Add an XHTML "<br />" tag before the end of @@ -188,13 +189,13 @@ return '"%s"' % jsonescape(u) elif isinstance(obj, unicode): return '"%s"' % jsonescape(obj) - elif hasattr(obj, 'keys'): + elif util.safehasattr(obj, 'keys'): out = [] for k, v in obj.iteritems(): s = '%s: %s' % (json(k), json(v)) out.append(s) return '{' + ', '.join(out) + '}' - elif hasattr(obj, '__iter__'): + elif util.safehasattr(obj, '__iter__'): out = [] for i in obj: out.append(json(i)) @@ -268,6 +269,14 @@ """ return text[:12] +def shortbisect(text): + """:shortbisect: Any text. Treats `text` as a bisection status, and + returns a single-character representing the status (G: good, B: bad, + S: skipped, U: untested, I: ignored). Returns single space if `text` + is not a valid bisection status. + """ + return hbisect.shortlabel(text) or ' ' + def shortdate(text): """:shortdate: Date. Returns a date like "2006-09-18".""" return util.shortdate(text) @@ -279,7 +288,7 @@ """: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): + if util.safehasattr(thing, '__iter__') and not isinstance(thing, str): return "".join([stringify(t) for t in thing if t is not None]) return str(thing) @@ -347,6 +356,7 @@ "rfc3339date": rfc3339date, "rfc822date": rfc822date, "short": short, + "shortbisect": shortbisect, "shortdate": shortdate, "stringescape": stringescape, "stringify": stringify,
--- a/mercurial/templatekw.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templatekw.py Sat Oct 15 14:30:50 2011 -0500 @@ -7,6 +7,7 @@ from node import hex import patch, util, error +import hbisect def showlist(name, values, plural=None, **args): '''expand set of values. @@ -145,6 +146,10 @@ """:author: String. The unmodified author of the changeset.""" return ctx.user() +def showbisect(repo, ctx, templ, **args): + """:bisect: String. The changeset bisection status.""" + return hbisect.label(repo, ctx.node()) + def showbranch(**args): """:branch: String. The name of the branch on which the changeset was committed. @@ -288,6 +293,7 @@ # revcache - a cache dictionary for the current revision keywords = { 'author': showauthor, + 'bisect': showbisect, 'branch': showbranch, 'branches': showbranches, 'bookmarks': showbookmarks,
--- a/mercurial/templater.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templater.py Sat Oct 15 14:30:50 2011 -0500 @@ -135,7 +135,7 @@ v = mapping.get(key) if v is None: v = context._defaults.get(key, '') - if hasattr(v, '__call__'): + if util.safehasattr(v, '__call__'): return v(**mapping) return v @@ -172,14 +172,14 @@ def buildfunc(exp, context): n = getsymbol(exp[1]) args = [compileexp(x, context) for x in getlist(exp[2])] + if n in funcs: + f = funcs[n] + return (f, args) if n in context._filters: if len(args) != 1: raise error.ParseError(_("filter %s expects one argument") % n) f = context._filters[n] return (runfilter, (args[0][0], args[0][1], f)) - elif n in context._funcs: - f = context._funcs[n] - return (f, args) methods = { "string": lambda e, c: (runstring, e[1]), @@ -191,6 +191,9 @@ "func": buildfunc, } +funcs = { +} + # template engine path = ['templates', '../templates'] @@ -200,14 +203,14 @@ '''yield a single stream from a possibly nested set of iterators''' if isinstance(thing, str): yield thing - elif not hasattr(thing, '__iter__'): + elif not util.safehasattr(thing, '__iter__'): if thing is not None: yield str(thing) else: for i in thing: if isinstance(i, str): yield i - elif not hasattr(i, '__iter__'): + elif not util.safehasattr(i, '__iter__'): if i is not None: yield str(i) elif i is not None: @@ -338,7 +341,7 @@ normpaths = [] # executable version (py2exe) doesn't support __file__ - if hasattr(sys, 'frozen'): + if util.mainfrozen(): module = sys.executable else: module = __file__
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/templates/map-cmdline.bisect Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,25 @@ +changeset = 'changeset: {rev}:{node|short}\nbisect: {bisect}\n{branches}{bookmarks}{tags}{parents}user: {author}\ndate: {date|date}\nsummary: {desc|firstline}\n\n' +changeset_quiet = '{bisect|shortbisect} {rev}:{node|short}\n' +changeset_verbose = 'changeset: {rev}:{node|short}\nbisect: {bisect}\n{branches}{bookmarks}{tags}{parents}user: {author}\ndate: {date|date}\n{files}{file_copies_switch}description:\n{desc|strip}\n\n\n' +changeset_debug = 'changeset: {rev}:{node}\nbisect: {bisect}\n{branches}{bookmarks}{tags}{parents}{manifest}user: {author}\ndate: {date|date}\n{file_mods}{file_adds}{file_dels}{file_copies_switch}{extras}description:\n{desc|strip}\n\n\n' +start_files = 'files: ' +file = ' {file}' +end_files = '\n' +start_file_mods = 'files: ' +file_mod = ' {file_mod}' +end_file_mods = '\n' +start_file_adds = 'files+: ' +file_add = ' {file_add}' +end_file_adds = '\n' +start_file_dels = 'files-: ' +file_del = ' {file_del}' +end_file_dels = '\n' +start_file_copies = 'copies: ' +file_copy = ' {name} ({source})' +end_file_copies = '\n' +parent = 'parent: {rev}:{node|formatnode}\n' +manifest = 'manifest: {rev}:{node}\n' +branch = 'branch: {branch}\n' +tag = 'tag: {tag}\n' +bookmark = 'bookmark: {bookmark}\n' +extra = 'extra: {key}={value|stringescape}\n'
--- a/mercurial/templates/monoblue/footer.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/monoblue/footer.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -9,7 +9,7 @@ </div> <div id="powered-by"> - <p><a href="{logourl}" 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}{logoimg}" width=75 height=90 border=0 alt="mercurial"></a></p> </div> <div id="corner-top-left"></div>
--- a/mercurial/templates/monoblue/index.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/monoblue/index.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -26,7 +26,7 @@ </div> <div id="powered-by"> - <p><a href="{logourl}" 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}{logoimg}" width=75 height=90 border=0 alt="mercurial"></a></p> </div> <div id="corner-top-left"></div>
--- a/mercurial/templates/paper/bookmarks.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/bookmarks.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/branches.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/branches.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/changeset.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/changeset.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -6,7 +6,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/error.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/error.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -7,7 +7,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" width=75 height=90 border=0 alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/fileannotate.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/fileannotate.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -7,7 +7,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/filediff.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/filediff.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -7,7 +7,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/filelog.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/filelog.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/filerevision.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/filerevision.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -7,7 +7,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/graph.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/graph.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -12,7 +12,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/help.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/help.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/helptopics.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/helptopics.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/index.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/index.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -6,7 +6,7 @@ <div class="container"> <div class="menu"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" width=75 height=90 border=0 alt="mercurial" /></a> </div> <div class="main"> <h2>Mercurial Repositories</h2>
--- a/mercurial/templates/paper/manifest.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/manifest.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -7,7 +7,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/search.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/search.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -7,7 +7,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a> +<img src="{staticurl}{logoimg}" width=75 height=90 border=0 alt="mercurial"></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/paper/shortlog.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/shortlog.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li class="active">log</li>
--- a/mercurial/templates/paper/tags.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/paper/tags.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -11,7 +11,7 @@ <div class="menu"> <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" alt="mercurial" /></a> +<img src="{staticurl}{logoimg}" alt="mercurial" /></a> </div> <ul> <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
--- a/mercurial/templates/spartan/footer.tmpl Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/templates/spartan/footer.tmpl Sat Oct 15 14:30:50 2011 -0500 @@ -2,7 +2,7 @@ {motd} <div class="logo"> <a href="{logourl}"> -<img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a> +<img src="{staticurl}{logoimg}" width=75 height=90 border=0 alt="mercurial"></a> </div> </body>
--- a/mercurial/ui.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/ui.py Sat Oct 15 14:30:50 2011 -0500 @@ -46,7 +46,7 @@ def copy(self): return self.__class__(self) - def _is_trusted(self, fp, f): + def _trusted(self, fp, f): st = util.fstat(fp) if util.isowner(st): return True @@ -75,7 +75,7 @@ raise cfg = config.config() - trusted = sections or trust or self._is_trusted(fp, filename) + trusted = sections or trust or self._trusted(fp, filename) try: cfg.read(filename, fp, sections=sections, remap=remap) @@ -155,7 +155,19 @@ return self._data(untrusted).source(section, name) or 'none' def config(self, section, name, default=None, untrusted=False): - value = self._data(untrusted).get(section, name, default) + if isinstance(name, list): + alternates = name + else: + alternates = [name] + + for n in alternates: + value = self._data(untrusted).get(section, name, None) + if value is not None: + name = n + break + else: + value = default + if self.debugflag and not untrusted and self._reportuntrusted: uvalue = self._ucfg.get(section, name) if uvalue is not None and uvalue != value: @@ -164,12 +176,14 @@ return value def configpath(self, section, name, default=None, untrusted=False): - 'get a path config item, expanded relative to config file' + 'get a path config item, expanded relative to repo root or config file' v = self.config(section, name, default, untrusted) + if v is None: + return None if not os.path.isabs(v) or "://" not in v: src = self.configsource(section, name, untrusted) if ':' in src: - base = os.path.dirname(src.rsplit(':')) + base = os.path.dirname(src.rsplit(':')[0]) v = os.path.join(base, os.path.expanduser(v)) return v
--- a/mercurial/url.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/url.py Sat Oct 15 14:30:50 2011 -0500 @@ -135,7 +135,7 @@ orgsend(self, data) return _sendfile -has_https = hasattr(urllib2, 'HTTPSHandler') +has_https = util.safehasattr(urllib2, 'HTTPSHandler') if has_https: try: _create_connection = socket.create_connection @@ -192,8 +192,8 @@ # general transaction handler to support different ways to handle # HTTPS proxying before and after Python 2.6.3. def _generic_start_transaction(handler, h, req): - if hasattr(req, '_tunnel_host') and req._tunnel_host: - tunnel_host = req._tunnel_host + tunnel_host = getattr(req, '_tunnel_host', None) + if tunnel_host: if tunnel_host[:7] not in ['http://', 'https:/']: tunnel_host = 'https://' + tunnel_host new_tunnel = True
--- a/mercurial/util.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/util.py Sat Oct 15 14:30:50 2011 -0500 @@ -19,6 +19,58 @@ import os, time, calendar, textwrap, signal import imp, socket, urllib +if os.name == 'nt': + import windows as platform +else: + import posix as platform + +cachestat = platform.cachestat +checkexec = platform.checkexec +checklink = platform.checklink +copymode = platform.copymode +executablepath = platform.executablepath +expandglobs = platform.expandglobs +explainexit = platform.explainexit +findexe = platform.findexe +gethgcmd = platform.gethgcmd +getuser = platform.getuser +groupmembers = platform.groupmembers +groupname = platform.groupname +hidewindow = platform.hidewindow +isexec = platform.isexec +isowner = platform.isowner +localpath = platform.localpath +lookupreg = platform.lookupreg +makedir = platform.makedir +nlinks = platform.nlinks +normpath = platform.normpath +nulldev = platform.nulldev +openhardlinks = platform.openhardlinks +oslink = platform.oslink +parsepatchoutput = platform.parsepatchoutput +pconvert = platform.pconvert +popen = platform.popen +posixfile = platform.posixfile +quotecommand = platform.quotecommand +realpath = platform.realpath +rename = platform.rename +samedevice = platform.samedevice +samefile = platform.samefile +samestat = platform.samestat +setbinary = platform.setbinary +setflags = platform.setflags +setsignalhandler = platform.setsignalhandler +shellquote = platform.shellquote +spawndetached = platform.spawndetached +sshargs = platform.sshargs +statfiles = platform.statfiles +termwidth = platform.termwidth +testpid = platform.testpid +umask = platform.umask +unlink = platform.unlink +unlinkpath = platform.unlinkpath +username = platform.username + # Python compatibility def sha1(s): @@ -307,8 +359,8 @@ The code supports py2exe (most common, Windows only) and tools/freeze (portable, not much used). """ - return (hasattr(sys, "frozen") or # new py2exe - hasattr(sys, "importers") or # old py2exe + return (safehasattr(sys, "frozen") or # new py2exe + safehasattr(sys, "importers") or # old py2exe imp.is_frozen("__main__")) # tools/freeze def hgexecutable(): @@ -318,10 +370,13 @@ """ if _hgexecutable is None: hg = os.environ.get('HG') + mainmod = sys.modules['__main__'] if hg: _sethgexecutable(hg) elif mainfrozen(): _sethgexecutable(sys.executable) + elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg': + _sethgexecutable(mainmod.__file__) else: exe = findexe('hg') or os.path.basename(sys.argv[0]) _sethgexecutable(exe) @@ -394,18 +449,6 @@ return check -def makedir(path, notindexed): - os.mkdir(path) - -def unlinkpath(f): - """unlink and remove the directory if it is empty""" - os.unlink(f) - # try removing directories that might now be empty - try: - os.removedirs(os.path.dirname(f)) - except OSError: - pass - def copyfile(src, dest): "copy a file, preserving mode and atime/mtime" if os.path.islink(src): @@ -491,22 +534,10 @@ return _("filename ends with '%s', which is not allowed " "on Windows") % t -def lookupreg(key, name=None, scope=None): - return None - -def hidewindow(): - """Hide current shell window. - - Used to hide the window opened when starting asynchronous - child process under Windows, unneeded on other systems. - """ - pass - if os.name == 'nt': checkosfilename = checkwinfilename - from windows import * else: - from posix import * + checkosfilename = platform.checkosfilename def makelock(info, pathname): try: @@ -690,16 +721,7 @@ # Temporary files are created with mode 0600, which is usually not # what we want. If the original file already exists, just copy # its mode. Otherwise, manually obey umask. - try: - st_mode = os.lstat(name).st_mode & 0777 - except OSError, inst: - if inst.errno != errno.ENOENT: - raise - st_mode = createmode - if st_mode is None: - st_mode = ~umask - st_mode &= 0666 - os.chmod(temp, st_mode) + copymode(name, temp, createmode) if emptyok: return temp try: @@ -726,11 +748,10 @@ '''writeable file object that atomically updates a file 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. + close() when you are done writing, and atomictempfile will rename + the temporary copy to the original name, making the changes + visible. If the object is destroyed without being closed, all your + writes are discarded. ''' def __init__(self, name, mode='w+b', createmode=None): self.__name = name # permanent name @@ -742,12 +763,12 @@ self.write = self._fp.write self.fileno = self._fp.fileno - def rename(self): + def close(self): if not self._fp.closed: self._fp.close() rename(self._tempname, localpath(self.__name)) - def close(self): + def discard(self): if not self._fp.closed: try: os.unlink(self._tempname) @@ -756,24 +777,25 @@ self._fp.close() def __del__(self): - if hasattr(self, '_fp'): # constructor actually did something - self.close() + if safehasattr(self, '_fp'): # constructor actually did something + self.discard() def makedirs(name, mode=None): """recursive directory creation with parent mode inheritance""" - parent = os.path.abspath(os.path.dirname(name)) try: os.mkdir(name) - if mode is not None: - os.chmod(name, mode) - return except OSError, err: if err.errno == errno.EEXIST: return - if not name or parent == name or err.errno != errno.ENOENT: + if err.errno != errno.ENOENT or not name: + raise + parent = os.path.dirname(os.path.abspath(name)) + if parent == name: raise - makedirs(parent, mode) - makedirs(name, mode) + makedirs(parent, mode) + os.mkdir(name) + if mode is not None: + os.chmod(name, mode) def readfile(path): fp = open(path, 'rb') @@ -1303,8 +1325,9 @@ def handler(signum, frame): terminated.add(os.wait()) prevhandler = None - if hasattr(signal, 'SIGCHLD'): - prevhandler = signal.signal(signal.SIGCHLD, handler) + SIGCHLD = getattr(signal, 'SIGCHLD', None) + if SIGCHLD is not None: + prevhandler = signal.signal(SIGCHLD, handler) try: pid = spawndetached(args) while not condfn(): @@ -1648,8 +1671,10 @@ self.user, self.passwd = user, passwd if not self.user: return (s, None) - # authinfo[1] is passed to urllib2 password manager, and its URIs - # must not contain credentials. + # authinfo[1] is passed to urllib2 password manager, and its + # URIs must not contain credentials. The host is passed in the + # URIs list because Python < 2.4.3 uses only that to search for + # a password. return (s, (None, (s, self.host), self.user, self.passwd or ''))
--- a/mercurial/windows.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/windows.py Sat Oct 15 14:30:50 2011 -0500 @@ -9,6 +9,22 @@ import osutil import errno, msvcrt, os, re, sys +import win32 +executablepath = win32.executablepath +getuser = win32.getuser +hidewindow = win32.hidewindow +lookupreg = win32.lookupreg +makedir = win32.makedir +nlinks = win32.nlinks +oslink = win32.oslink +samedevice = win32.samedevice +samefile = win32.samefile +setsignalhandler = win32.setsignalhandler +spawndetached = win32.spawndetached +termwidth = win32.termwidth +testpid = win32.testpid +unlink = win32.unlink + nulldev = 'NUL:' umask = 002 @@ -90,6 +106,9 @@ def setflags(f, l, x): pass +def copymode(src, dst, mode=None): + pass + def checkexec(path): return False @@ -99,8 +118,9 @@ def setbinary(fd): # When run without console, pipes may expose invalid # fileno(), usually set to -1. - if hasattr(fd, 'fileno') and fd.fileno() >= 0: - msvcrt.setmode(fd.fileno(), os.O_BINARY) + fno = getattr(fd, 'fileno', None) + if fno is not None and fno() >= 0: + msvcrt.setmode(fno(), os.O_BINARY) def pconvert(path): return '/'.join(path.split(os.sep)) @@ -281,6 +301,14 @@ # Don't support groups on Windows for now raise KeyError() -from win32 import * +def isexec(f): + return False + +class cachestat(object): + def __init__(self, path): + pass + + def cacheable(self): + return False expandglobs = True
--- a/mercurial/wireproto.py Sun Oct 02 16:41:07 2011 -0500 +++ b/mercurial/wireproto.py Sat Oct 15 14:30:50 2011 -0500 @@ -10,14 +10,13 @@ from node import bin, hex import changegroup as changegroupmod import repo, error, encoding, util, store -import pushkey as pushkeymod # abstract batching support class future(object): '''placeholder for a value to be set later''' def set(self, value): - if hasattr(self, 'value'): + if util.safehasattr(self, 'value'): raise error.RepoError("future is already set") self.value = value @@ -58,8 +57,9 @@ req, rsp = [], [] for name, args, opts, resref in self.calls: mtd = getattr(self.remote, name) - if hasattr(mtd, 'batchable'): - batchable = getattr(mtd, 'batchable')(mtd.im_self, *args, **opts) + batchablefn = getattr(mtd, 'batchable', None) + if batchablefn is not None: + batchable = batchablefn(mtd.im_self, *args, **opts) encargsorres, encresref = batchable.next() if encresref: req.append((name, encargsorres,)) @@ -334,6 +334,10 @@ def __init__(self, res): self.res = res +class ooberror(object): + def __init__(self, message): + self.message = message + def dispatch(repo, proto, command): func, spec = commands[command] args = proto.getargs(spec) @@ -375,6 +379,8 @@ result = func(repo, proto, *[data[k] for k in keys]) else: result = func(repo, proto) + if isinstance(result, ooberror): + return result res.append(escapearg(result)) return ';'.join(res) @@ -454,7 +460,7 @@ return "capabilities: %s\n" % (capabilities(repo, proto)) def listkeys(repo, proto, namespace): - d = pushkeymod.list(repo, encoding.tolocal(namespace)).items() + d = repo.listkeys(encoding.tolocal(namespace)).items() t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v)) for k, v in d]) return t @@ -484,9 +490,8 @@ else: new = encoding.tolocal(new) # normal path - r = pushkeymod.push(repo, - encoding.tolocal(namespace), encoding.tolocal(key), - encoding.tolocal(old), new) + r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key), + encoding.tolocal(old), new) return '%s\n' % int(r) def _allowstream(ui):
--- a/setup.py Sun Oct 02 16:41:07 2011 -0500 +++ b/setup.py Sat Oct 15 14:30:50 2011 -0500 @@ -5,7 +5,7 @@ # 'python setup.py --help' for more options import sys, platform -if not hasattr(sys, 'version_info') or sys.version_info < (2, 4, 0, 'final'): +if getattr(sys, 'version_info', (0, 0, 0)) < (2, 4, 0, 'final'): raise SystemExit("Mercurial requires Python 2.4 or later.") if sys.version_info[0] >= 3: @@ -342,7 +342,8 @@ packages = ['mercurial', 'mercurial.hgweb', 'mercurial.httpclient', 'mercurial.httpclient.tests', - 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf'] + 'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf', + 'hgext.largefiles'] pymodules = []
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/heredoctest.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,13 @@ +import doctest, tempfile, os, sys + +if __name__ == "__main__": + fd, name = tempfile.mkstemp(suffix='hg-tst') + + try: + os.write(fd, sys.stdin.read()) + os.close(fd) + failures, _ = doctest.testfile(name, module_relative=False) + if failures: + sys.exit(1) + finally: + os.remove(name)
--- a/tests/hghave Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/hghave Sat Oct 15 14:30:50 2011 -0500 @@ -101,6 +101,16 @@ def has_fifo(): return hasattr(os, "mkfifo") +def has_cacheable_fs(): + from mercurial import util + + fd, path = tempfile.mkstemp(prefix=tempprefix) + os.close(fd) + try: + return util.cachestat(path).cacheable() + finally: + os.remove(path) + def has_lsprof(): try: import _lsprof @@ -200,6 +210,7 @@ "baz": (has_baz, "GNU Arch baz client"), "bzr": (has_bzr, "Canonical's Bazaar client"), "bzr114": (has_bzr114, "Canonical's Bazaar client >= 1.14"), + "cacheable": (has_cacheable_fs, "cacheable filesystem"), "cvs": (has_cvs, "cvs client/server"), "darcs": (has_darcs, "darcs client"), "docutils": (has_docutils, "Docutils text processing library"),
--- a/tests/run-tests.py Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/run-tests.py Sat Oct 15 14:30:50 2011 -0500 @@ -340,10 +340,7 @@ """Terminate subprocess (with fallback for Python versions < 2.6)""" vlog('# Terminating process %d' % proc.pid) try: - if hasattr(proc, 'terminate'): - proc.terminate() - else: - os.kill(proc.pid, signal.SIGTERM) + getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))() except OSError: pass @@ -524,6 +521,26 @@ def stringescape(s): return escapesub(escapef, s) +def transformtst(lines): + inblock = False + for l in lines: + if inblock: + if l.startswith(' $ ') or not l.startswith(' '): + inblock = False + yield ' > EOF\n' + yield l + else: + yield ' > ' + l[2:] + else: + if l.startswith(' >>> '): + inblock = True + yield ' $ %s -m heredoctest <<EOF\n' % PYTHON + yield ' > ' + l[2:] + else: + yield l + if inblock: + yield ' > EOF\n' + def tsttest(test, wd, options, replacements): t = open(test) out = [] @@ -533,7 +550,7 @@ pos = prepos = -1 after = {} expected = {} - for n, l in enumerate(t): + for n, l in enumerate(transformtst(t)): if not l.endswith('\n'): l += '\n' if l.startswith(' $ '): # commands @@ -726,6 +743,7 @@ rename(testpath + ".err", testpath) else: rename(testpath + ".err", testpath + ".out") + result('p', test) return result('f', (test, msg)) @@ -835,7 +853,7 @@ refout = None # to match "out is None" elif os.path.exists(ref): f = open(ref, "r") - refout = splitnewlines(f.read()) + refout = list(transformtst(splitnewlines(f.read()))) f.close() else: refout = []
--- a/tests/sitecustomize.py Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/sitecustomize.py Sat Oct 15 14:30:50 2011 -0500 @@ -1,6 +1,5 @@ try: import coverage - if hasattr(coverage, 'process_startup'): - coverage.process_startup() + getattr(coverage, 'process_startup', lambda: None)() except ImportError: pass
--- a/tests/test-acl.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-acl.t Sat Oct 15 14:30:50 2011 -0500 @@ -121,7 +121,6 @@ updating the branch cache checking for updated bookmarks repository tip rolled back to revision 0 (undo push) - working directory now based on revision 0 0:6675d58eff77 @@ -179,7 +178,6 @@ updating the branch cache checking for updated bookmarks repository tip rolled back to revision 0 (undo push) - working directory now based on revision 0 0:6675d58eff77 @@ -234,20 +232,20 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "fred" 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: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: allowing changeset f9cafe1212c8 + acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" - acl: allowing changeset 911600dab2ae + acl: path access granted: "911600dab2ae" updating the branch cache checking for updated bookmarks repository tip rolled back to revision 0 (undo push) - working directory now based on revision 0 0:6675d58eff77 @@ -302,16 +300,16 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "fred" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: acl.allow enabled, 0 entries for user fred acl: acl.deny not enabled acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: user fred not allowed on foo/file.txt - error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374 + error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374") transaction abort! rollback completed - abort: acl: access denied for changeset ef1ea85a6374 + abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374") no rollback information available 0:6675d58eff77 @@ -367,20 +365,20 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "fred" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: acl.allow enabled, 1 entries for user fred acl: acl.deny not enabled acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: allowing changeset ef1ea85a6374 + acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: allowing changeset f9cafe1212c8 + acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" - acl: user fred not allowed on quux/file.py - error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae + error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae") transaction abort! rollback completed - abort: acl: access denied for changeset 911600dab2ae + abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae") no rollback information available 0:6675d58eff77 @@ -437,16 +435,16 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "barney" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: acl.allow enabled, 0 entries for user barney acl: acl.deny enabled, 0 entries for user barney acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: user barney not allowed on foo/file.txt - error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374 + error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374") transaction abort! rollback completed - abort: acl: access denied for changeset ef1ea85a6374 + abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374") no rollback information available 0:6675d58eff77 @@ -504,20 +502,20 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "fred" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: acl.allow enabled, 1 entries for user fred acl: acl.deny enabled, 1 entries for user fred acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: allowing changeset ef1ea85a6374 + acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: allowing changeset f9cafe1212c8 + acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" - acl: user fred not allowed on quux/file.py - error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae + error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae") transaction abort! rollback completed - abort: acl: access denied for changeset 911600dab2ae + abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae") no rollback information available 0:6675d58eff77 @@ -576,18 +574,18 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "fred" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: acl.allow enabled, 1 entries for user fred acl: acl.deny enabled, 2 entries for user fred acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: allowing changeset ef1ea85a6374 + acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: user fred denied on foo/Bar/file.txt - error: pretxnchangegroup.acl hook failed: acl: access denied for changeset f9cafe1212c8 + error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") transaction abort! rollback completed - abort: acl: access denied for changeset f9cafe1212c8 + abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") no rollback information available 0:6675d58eff77 @@ -645,16 +643,16 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "barney" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: acl.allow enabled, 0 entries for user barney acl: acl.deny enabled, 0 entries for user barney acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: user barney not allowed on foo/file.txt - error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374 + error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374") transaction abort! rollback completed - abort: acl: access denied for changeset ef1ea85a6374 + abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374") no rollback information available 0:6675d58eff77 @@ -716,20 +714,20 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "barney" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: acl.allow enabled, 1 entries for user barney acl: acl.deny enabled, 0 entries for user barney acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: allowing changeset ef1ea85a6374 + acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: allowing changeset f9cafe1212c8 + acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" - acl: allowing changeset 911600dab2ae + acl: path access granted: "911600dab2ae" updating the branch cache checking for updated bookmarks repository tip rolled back to revision 0 (undo push) - working directory now based on revision 0 0:6675d58eff77 @@ -791,20 +789,20 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "wilma" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: acl.allow enabled, 1 entries for user wilma acl: acl.deny enabled, 0 entries for user wilma acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: allowing changeset ef1ea85a6374 + acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: allowing changeset f9cafe1212c8 + acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" - acl: user wilma not allowed on quux/file.py - error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae + error: pretxnchangegroup.acl hook failed: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae") transaction abort! rollback completed - abort: acl: access denied for changeset 911600dab2ae + abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae") no rollback information available 0:6675d58eff77 @@ -869,6 +867,7 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "barney" error: pretxnchangegroup.acl hook raised an exception: [Errno 2] No such file or directory: '../acl.config' transaction abort! rollback completed @@ -941,20 +940,20 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "betty" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: acl.allow enabled, 1 entries for user betty acl: acl.deny enabled, 0 entries for user betty acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: allowing changeset ef1ea85a6374 + acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: allowing changeset f9cafe1212c8 + acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" - acl: user betty not allowed on quux/file.py - error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae + error: pretxnchangegroup.acl hook failed: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae") transaction abort! rollback completed - abort: acl: access denied for changeset 911600dab2ae + abort: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae") no rollback information available 0:6675d58eff77 @@ -1025,20 +1024,20 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "barney" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: acl.allow enabled, 1 entries for user barney acl: acl.deny enabled, 0 entries for user barney acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: allowing changeset ef1ea85a6374 + acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: allowing changeset f9cafe1212c8 + acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" - acl: allowing changeset 911600dab2ae + acl: path access granted: "911600dab2ae" updating the branch cache checking for updated bookmarks repository tip rolled back to revision 0 (undo push) - working directory now based on revision 0 0:6675d58eff77 @@ -1101,20 +1100,20 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "fred" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: acl.allow enabled, 1 entries for user fred acl: acl.deny not enabled acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: allowing changeset ef1ea85a6374 + acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: allowing changeset f9cafe1212c8 + acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" - acl: allowing changeset 911600dab2ae + acl: path access granted: "911600dab2ae" updating the branch cache checking for updated bookmarks repository tip rolled back to revision 0 (undo push) - working directory now based on revision 0 0:6675d58eff77 @@ -1173,18 +1172,18 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "fred" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: acl.allow enabled, 1 entries for user fred acl: acl.deny enabled, 1 entries for user fred acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: allowing changeset ef1ea85a6374 + acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: user fred denied on foo/Bar/file.txt - error: pretxnchangegroup.acl hook failed: acl: access denied for changeset f9cafe1212c8 + error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") transaction abort! rollback completed - abort: acl: access denied for changeset f9cafe1212c8 + abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") no rollback information available 0:6675d58eff77 @@ -1247,21 +1246,21 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "fred" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: "group1" not defined in [acl.groups] acl: acl.allow enabled, 1 entries for user fred acl: acl.deny not enabled acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: allowing changeset ef1ea85a6374 + acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: allowing changeset f9cafe1212c8 + acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" - acl: allowing changeset 911600dab2ae + acl: path access granted: "911600dab2ae" updating the branch cache checking for updated bookmarks repository tip rolled back to revision 0 (undo push) - working directory now based on revision 0 0:6675d58eff77 @@ -1320,6 +1319,7 @@ files: 3/3 chunks (100.00%) added 3 changesets with 3 changes to 3 files calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "fred" acl: acl.allow.branches not enabled acl: acl.deny.branches not enabled acl: "group1" not defined in [acl.groups] @@ -1327,13 +1327,12 @@ acl: "group1" not defined in [acl.groups] acl: acl.deny enabled, 1 entries for user fred acl: branch access granted: "ef1ea85a6374" on branch "default" - acl: allowing changeset ef1ea85a6374 + acl: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: user fred denied on foo/Bar/file.txt - error: pretxnchangegroup.acl hook failed: acl: access denied for changeset f9cafe1212c8 + error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") transaction abort! rollback completed - abort: acl: access denied for changeset f9cafe1212c8 + abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8") no rollback information available 0:6675d58eff77 @@ -1441,22 +1440,22 @@ 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: checking access for user "astro" 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: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: allowing changeset f9cafe1212c8 + acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" - acl: allowing changeset 911600dab2ae + acl: path access granted: "911600dab2ae" acl: branch access granted: "e8fc755d4d82" on branch "foobar" - acl: allowing changeset e8fc755d4d82 + acl: path access granted: "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 @@ -1521,16 +1520,17 @@ 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: checking access for user "astro" 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: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: allowing changeset f9cafe1212c8 + acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" - acl: allowing changeset 911600dab2ae + acl: path access granted: "911600dab2ae" error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82") transaction abort! rollback completed @@ -1598,6 +1598,7 @@ 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: checking access for user "astro" acl: acl.allow.branches enabled, 0 entries for user astro acl: acl.deny.branches not enabled acl: acl.allow not enabled @@ -1671,6 +1672,7 @@ 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: checking access for user "astro" acl: acl.allow.branches enabled, 0 entries for user astro acl: acl.deny.branches not enabled acl: acl.allow not enabled @@ -1738,22 +1740,22 @@ 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: checking access for user "george" 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: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: allowing changeset f9cafe1212c8 + acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" - acl: allowing changeset 911600dab2ae + acl: path access granted: "911600dab2ae" acl: branch access granted: "e8fc755d4d82" on branch "foobar" - acl: allowing changeset e8fc755d4d82 + acl: path access granted: "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 @@ -1823,22 +1825,22 @@ 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: checking access for user "george" 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: path access granted: "ef1ea85a6374" acl: branch access granted: "f9cafe1212c8" on branch "default" - acl: allowing changeset f9cafe1212c8 + acl: path access granted: "f9cafe1212c8" acl: branch access granted: "911600dab2ae" on branch "default" - acl: allowing changeset 911600dab2ae + acl: path access granted: "911600dab2ae" acl: branch access granted: "e8fc755d4d82" on branch "foobar" - acl: allowing changeset e8fc755d4d82 + acl: path access granted: "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 @@ -1907,6 +1909,7 @@ 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: checking access for user "george" acl: acl.allow.branches not enabled acl: acl.deny.branches enabled, 1 entries for user george acl: acl.allow not enabled
--- a/tests/test-alias.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-alias.t Sat Oct 15 14:30:50 2011 -0500 @@ -4,6 +4,8 @@ > graphlog= > > [alias] + > # should clobber ci but not commit (issue2993) + > ci = version > myinit = init > cleanstatus = status -c > unknown = bargle @@ -113,7 +115,7 @@ no rollback information available $ echo foo > foo - $ hg ci -Amfoo + $ hg commit -Amfoo adding foo @@ -195,7 +197,7 @@ $ hg echo2 foo $ echo bar > bar - $ hg ci -qA -m bar + $ hg commit -qA -m bar $ hg count . 1 $ hg count 'branch(default)' @@ -251,7 +253,7 @@ $ hg --cwd .. count 'branch(default)' 2 $ hg echo --cwd .. - --cwd .. + repo specific shell aliases @@ -305,7 +307,7 @@ $ hg rt foo hg rt: invalid arguments - hg rt + hg rt alias for: hg root
--- a/tests/test-atomictempfile.py Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-atomictempfile.py Sat Oct 15 14:30:50 2011 -0500 @@ -12,22 +12,21 @@ assert basename in glob.glob('.foo-*') file.write('argh\n') - file.rename() + file.close() 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(): +# discard() removes the temp file without making the write permanent +def test2_discard(): if os.path.exists('foo'): os.remove('foo') file = atomictempfile('foo') (dir, basename) = os.path.split(file._tempname) file.write('yo\n') - file.close() + file.discard() assert not os.path.isfile('foo') assert basename not in os.listdir('.') @@ -45,5 +44,5 @@ if __name__ == '__main__': test1_simple() - test2_close() + test2_discard() test3_oops()
--- a/tests/test-backout.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-backout.t Sat Oct 15 14:30:50 2011 -0500 @@ -182,7 +182,7 @@ backout of merge should fail $ hg backout 4 - abort: cannot backout a merge changeset without --parent + abort: cannot backout a merge changeset [255] backout of merge with bad parent should fail
--- a/tests/test-bisect.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-bisect.t Sat Oct 15 14:30:50 2011 -0500 @@ -377,7 +377,7 @@ date: Thu Jan 01 00:00:06 1970 +0000 summary: msg 6 - $ hg log -r "bisected(good)" + $ hg log -r "bisect(good)" changeset: 0:b99c7b9c8e11 user: test date: Thu Jan 01 00:00:00 1970 +0000 @@ -388,13 +388,13 @@ date: Thu Jan 01 00:00:05 1970 +0000 summary: msg 5 - $ hg log -r "bisected(bad)" + $ hg log -r "bisect(bad)" changeset: 6:a3d5c6fdf0d3 user: test date: Thu Jan 01 00:00:06 1970 +0000 summary: msg 6 - $ hg log -r "bisected(skip)" + $ hg log -r "bisect(skip)" changeset: 1:5cd978ea5149 user: test date: Thu Jan 01 00:00:01 1970 +0000 @@ -416,6 +416,15 @@ summary: msg 4 +test legacy bisected() keyword + + $ hg log -r "bisected(bad)" + changeset: 6:a3d5c6fdf0d3 + user: test + date: Thu Jan 01 00:00:06 1970 +0000 + summary: msg 6 + + $ set +e test invalid command
--- a/tests/test-bisect2.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-bisect2.t Sat Oct 15 14:30:50 2011 -0500 @@ -252,6 +252,25 @@ $ hg bisect -b 17 # -> update to rev 6 Testing changeset 6:a214d5d3811a (15 changesets remaining, ~3 tests) 0 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg log -q -r 'bisect(pruned)' + 0:33b1f9bc8bc5 + 17:228c06deef46 + $ hg log -q -r 'bisect(untested)' + 1:4ca5088da217 + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 8:dab8161ac8fc + 9:3c77083deb4a + 10:429fcd26f52d + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 15:857b178a7cf3 + 16:609d82a7ebae + $ hg log -q -r 'bisect(ignored)' $ hg bisect -g # -> update to rev 13 Testing changeset 13:b0a32c86eb31 (9 changesets remaining, ~3 tests) 3 files updated, 0 files merged, 1 files removed, 0 files unresolved @@ -271,6 +290,58 @@ date: Thu Jan 01 00:00:09 1970 +0000 summary: 9 + $ hg log -q -r 'bisect(range)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 8:dab8161ac8fc + 9:3c77083deb4a + 10:429fcd26f52d + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 15:857b178a7cf3 + 16:609d82a7ebae + 17:228c06deef46 + $ hg log -q -r 'bisect(pruned)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 8:dab8161ac8fc + 9:3c77083deb4a + 10:429fcd26f52d + 13:b0a32c86eb31 + 15:857b178a7cf3 + 16:609d82a7ebae + 17:228c06deef46 + 18:d42e18c7bc9b + $ hg log -q -r 'bisect(untested)' + 11:82ca6f06eccd + 12:9f259202bbe7 + $ hg log -q -r 'bisect(goods)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 8:dab8161ac8fc + $ hg log -q -r 'bisect(bads)' + 9:3c77083deb4a + 10:429fcd26f52d + 15:857b178a7cf3 + 16:609d82a7ebae + 17:228c06deef46 + 18:d42e18c7bc9b complex bisect test 2 # first good rev is 13 @@ -282,9 +353,31 @@ $ hg bisect -s # -> update to rev 10 Testing changeset 10:429fcd26f52d (13 changesets remaining, ~3 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -q -r 'bisect(pruned)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 6:a214d5d3811a + 18:d42e18c7bc9b $ hg bisect -b # -> update to rev 12 Testing changeset 12:9f259202bbe7 (5 changesets remaining, ~2 tests) 3 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg log -q -r 'bisect(pruned)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 8:dab8161ac8fc + 9:3c77083deb4a + 10:429fcd26f52d + 18:d42e18c7bc9b + $ hg log -q -r 'bisect(untested)' + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 15:857b178a7cf3 $ hg bisect -b # -> update to rev 13 Testing changeset 13:b0a32c86eb31 (3 changesets remaining, ~1 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -295,6 +388,21 @@ date: Thu Jan 01 00:00:13 1970 +0000 summary: 13 + $ hg log -q -r 'bisect(range)' + 1:4ca5088da217 + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 8:dab8161ac8fc + 9:3c77083deb4a + 10:429fcd26f52d + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 15:857b178a7cf3 + 18:d42e18c7bc9b complex bisect test 3 @@ -306,6 +414,11 @@ $ hg bisect -b 16 # -> update to rev 6 Testing changeset 6:a214d5d3811a (13 changesets remaining, ~3 tests) 2 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg log -q -r 'bisect(pruned)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 16:609d82a7ebae + 17:228c06deef46 $ hg bisect -g # -> update to rev 13 Testing changeset 13:b0a32c86eb31 (8 changesets remaining, ~3 tests) 3 files updated, 0 files merged, 1 files removed, 0 files unresolved @@ -315,12 +428,25 @@ $ hg bisect -s # -> update to rev 12 Testing changeset 12:9f259202bbe7 (8 changesets remaining, ~3 tests) 3 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg log -q -r 'bisect(pruned)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 10:429fcd26f52d + 13:b0a32c86eb31 + 16:609d82a7ebae + 17:228c06deef46 $ hg bisect -g # -> update to rev 9 Testing changeset 9:3c77083deb4a (5 changesets remaining, ~2 tests) 1 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg bisect -s # -> update to rev 15 Testing changeset 15:857b178a7cf3 (5 changesets remaining, ~2 tests) 3 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -q -r 'bisect(ignored)' $ hg bisect -b Due to skipped revisions, the first bad revision could be any of: changeset: 9:3c77083deb4a @@ -347,6 +473,22 @@ date: Thu Jan 01 00:00:15 1970 +0000 summary: merge 10,13 + $ hg log -q -r 'bisect(range)' + 1:4ca5088da217 + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 8:dab8161ac8fc + 9:3c77083deb4a + 10:429fcd26f52d + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 15:857b178a7cf3 + 16:609d82a7ebae + $ hg log -q -r 'bisect(ignored)' complex bisect test 4 @@ -364,9 +506,40 @@ $ hg bisect -b # -> update to rev 15 Testing changeset 15:857b178a7cf3 (3 changesets remaining, ~1 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -q -r 'bisect(pruned)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 8:dab8161ac8fc + 9:3c77083deb4a + 10:429fcd26f52d + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 17:228c06deef46 $ hg bisect -s # -> update to rev 16 Testing changeset 16:609d82a7ebae (3 changesets remaining, ~1 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -q -r 'bisect(pruned)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 8:dab8161ac8fc + 9:3c77083deb4a + 10:429fcd26f52d + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 15:857b178a7cf3 + 17:228c06deef46 $ hg bisect -s Due to skipped revisions, the first good revision could be any of: changeset: 15:857b178a7cf3 @@ -386,6 +559,33 @@ date: Thu Jan 01 00:00:17 1970 +0000 summary: 17 + $ hg log -q -r 'bisect(range)' + 8:dab8161ac8fc + 9:3c77083deb4a + 10:429fcd26f52d + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 15:857b178a7cf3 + 16:609d82a7ebae + 17:228c06deef46 + $ hg log -q -r 'bisect(pruned)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 8:dab8161ac8fc + 9:3c77083deb4a + 10:429fcd26f52d + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 15:857b178a7cf3 + 16:609d82a7ebae + 17:228c06deef46 test unrelated revs: @@ -394,6 +594,15 @@ $ hg bisect -g 14 abort: starting revisions are not directly related [255] + $ hg log -q -r 'bisect(range)' + $ hg log -q -r 'bisect(pruned)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 7:50c76098bbf2 + 14:faa450606157 $ hg bisect --reset end at merge: 17 bad, 11 good (but 9 is first bad) @@ -403,6 +612,14 @@ $ hg bisect -g 11 Testing changeset 13:b0a32c86eb31 (5 changesets remaining, ~2 tests) 3 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg log -q -r 'bisect(ignored)' + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 9:3c77083deb4a + 10:429fcd26f52d $ hg bisect -g Testing changeset 15:857b178a7cf3 (3 changesets remaining, ~1 tests) 3 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -418,12 +635,69 @@ Not all ancestors of this changeset have been checked. Use bisect --extend to continue the bisection from the common ancestor, dab8161ac8fc. + $ hg log -q -r 'bisect(range)' + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 15:857b178a7cf3 + 16:609d82a7ebae + 17:228c06deef46 + $ hg log -q -r 'bisect(pruned)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 8:dab8161ac8fc + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 15:857b178a7cf3 + 16:609d82a7ebae + 17:228c06deef46 + 18:d42e18c7bc9b + $ hg log -q -r 'bisect(untested)' + $ hg log -q -r 'bisect(ignored)' + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 9:3c77083deb4a + 10:429fcd26f52d $ hg bisect --extend Extending search to changeset 8:dab8161ac8fc 2 files updated, 0 files merged, 2 files removed, 0 files unresolved + $ hg log -q -r 'bisect(untested)' + $ hg log -q -r 'bisect(ignored)' + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + 9:3c77083deb4a + 10:429fcd26f52d $ hg bisect -g # dab8161ac8fc Testing changeset 9:3c77083deb4a (3 changesets remaining, ~1 tests) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -q -r 'bisect(untested)' + 9:3c77083deb4a + 10:429fcd26f52d + $ hg log -q -r 'bisect(ignored)' + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + $ hg log -q -r 'bisect(goods)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 8:dab8161ac8fc + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + $ hg log -q -r 'bisect(bads)' + 15:857b178a7cf3 + 16:609d82a7ebae + 17:228c06deef46 + 18:d42e18c7bc9b $ hg bisect -b The first bad revision is: changeset: 9:3c77083deb4a @@ -431,3 +705,91 @@ date: Thu Jan 01 00:00:09 1970 +0000 summary: 9 + $ hg log -q -r 'bisect(range)' + 8:dab8161ac8fc + 9:3c77083deb4a + 10:429fcd26f52d + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 15:857b178a7cf3 + 16:609d82a7ebae + 17:228c06deef46 + $ hg log -q -r 'bisect(pruned)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 8:dab8161ac8fc + 9:3c77083deb4a + 10:429fcd26f52d + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 15:857b178a7cf3 + 16:609d82a7ebae + 17:228c06deef46 + 18:d42e18c7bc9b + $ hg log -q -r 'bisect(untested)' + $ hg log -q -r 'bisect(ignored)' + 2:051e12f87bf1 + 3:0950834f0a9c + 4:5c668c22234f + 5:385a529b6670 + 6:a214d5d3811a + $ hg log -q -r 'bisect(goods)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 8:dab8161ac8fc + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + $ hg log -q -r 'bisect(bads)' + 9:3c77083deb4a + 10:429fcd26f52d + 15:857b178a7cf3 + 16:609d82a7ebae + 17:228c06deef46 + 18:d42e18c7bc9b + +user adds irrelevant but consistent information (here: -g 2) to bisect state + + $ hg bisect -r + $ hg bisect -b 13 + $ hg bisect -g 8 + Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests) + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -q -r 'bisect(untested)' + 11:82ca6f06eccd + 12:9f259202bbe7 + $ hg bisect -g 2 + Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests) + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg log -q -r 'bisect(untested)' + 11:82ca6f06eccd + 12:9f259202bbe7 + $ hg bisect -b + The first bad revision is: + changeset: 11:82ca6f06eccd + parent: 8:dab8161ac8fc + user: test + date: Thu Jan 01 00:00:11 1970 +0000 + summary: 11 + + $ hg log -q -r 'bisect(range)' + 8:dab8161ac8fc + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + $ hg log -q -r 'bisect(pruned)' + 0:33b1f9bc8bc5 + 1:4ca5088da217 + 2:051e12f87bf1 + 8:dab8161ac8fc + 11:82ca6f06eccd + 12:9f259202bbe7 + 13:b0a32c86eb31 + 14:faa450606157 + 15:857b178a7cf3 + 16:609d82a7ebae + 17:228c06deef46 + 18:d42e18c7bc9b + $ hg log -q -r 'bisect(untested)'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-bisect3.t Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,232 @@ +# Here we create a simple DAG which has just enough of the required +# topology to test all the bisection status labels: +# +# 13--14 +# / +# 0--1--2--3---------9--10--11--12 +# \ / +# 4--5--6--7--8 + + + $ hg init + + $ echo '0' >a + $ hg add a + $ hg ci -u test -d '0 0' -m '0' + $ echo '1' >a + $ hg ci -u test -d '0 1' -m '1' + +branch 2-3 + + $ echo '2' >b + $ hg add b + $ hg ci -u test -d '0 2' -m '2' + $ echo '3' >b + $ hg ci -u test -d '0 3' -m '3' + +branch 4-8 + + $ hg up -r 1 + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo '4' >c + $ hg add c + $ hg ci -u test -d '0 4' -m '4' + created new head + $ echo '5' >c + $ hg ci -u test -d '0 5' -m '5' + $ echo '6' >c + $ hg ci -u test -d '0 6' -m '6' + $ echo '7' >c + $ hg ci -u test -d '0 7' -m '7' + $ echo '8' >c + $ hg ci -u test -d '0 8' -m '8' + +merge + + $ hg merge -r 3 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg ci -u test -d '0 9' -m '9=8+3' + + $ echo '10' >a + $ hg ci -u test -d '0 10' -m '10' + $ echo '11' >a + $ hg ci -u test -d '0 11' -m '11' + $ echo '12' >a + $ hg ci -u test -d '0 12' -m '12' + +unrelated branch + + $ hg up -r 3 + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ echo '13' >d + $ hg add d + $ hg ci -u test -d '0 13' -m '13' + created new head + $ echo '14' >d + $ hg ci -u test -d '0 14' -m '14' + +mark changesets + + $ hg bisect --reset + $ hg bisect --good 4 + $ hg bisect --good 6 + $ hg bisect --bad 12 + Testing changeset 9:8bcbdb072033 (6 changesets remaining, ~2 tests) + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg bisect --bad 10 + Testing changeset 8:3cd112f87d77 (4 changesets remaining, ~2 tests) + 0 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg bisect --skip 7 + Testing changeset 8:3cd112f87d77 (4 changesets remaining, ~2 tests) + 0 files updated, 0 files merged, 0 files removed, 0 files unresolved + +test template + + $ hg log --template '{rev}:{node|short} {bisect}\n' + 14:cecd84203acc + 13:86f7c8cdb6df + 12:a76089b5f47c bad + 11:5c3eb122d29c bad (implicit) + 10:b097cef2be03 bad + 9:8bcbdb072033 untested + 8:3cd112f87d77 untested + 7:577e237a73bd skipped + 6:e597fa2707c5 good + 5:b9cea37a76bc good (implicit) + 4:da6b357259d7 good + 3:e7f031aee8ca ignored + 2:b1ad1b6bcc5c ignored + 1:37f42ae8b45e good (implicit) + 0:b4e73ffab476 good (implicit) + $ hg log --template '{bisect|shortbisect} {rev}:{node|short}\n' + 14:cecd84203acc + 13:86f7c8cdb6df + B 12:a76089b5f47c + B 11:5c3eb122d29c + B 10:b097cef2be03 + U 9:8bcbdb072033 + U 8:3cd112f87d77 + S 7:577e237a73bd + G 6:e597fa2707c5 + G 5:b9cea37a76bc + G 4:da6b357259d7 + I 3:e7f031aee8ca + I 2:b1ad1b6bcc5c + G 1:37f42ae8b45e + G 0:b4e73ffab476 + +test style + + $ hg log --style bisect + changeset: 14:cecd84203acc + bisect: + tag: tip + user: test + date: Wed Dec 31 23:59:46 1969 -0000 + summary: 14 + + changeset: 13:86f7c8cdb6df + bisect: + parent: 3:e7f031aee8ca + user: test + date: Wed Dec 31 23:59:47 1969 -0000 + summary: 13 + + changeset: 12:a76089b5f47c + bisect: bad + user: test + date: Wed Dec 31 23:59:48 1969 -0000 + summary: 12 + + changeset: 11:5c3eb122d29c + bisect: bad (implicit) + user: test + date: Wed Dec 31 23:59:49 1969 -0000 + summary: 11 + + changeset: 10:b097cef2be03 + bisect: bad + user: test + date: Wed Dec 31 23:59:50 1969 -0000 + summary: 10 + + changeset: 9:8bcbdb072033 + bisect: untested + parent: 8:3cd112f87d77 + parent: 3:e7f031aee8ca + user: test + date: Wed Dec 31 23:59:51 1969 -0000 + summary: 9=8+3 + + changeset: 8:3cd112f87d77 + bisect: untested + user: test + date: Wed Dec 31 23:59:52 1969 -0000 + summary: 8 + + changeset: 7:577e237a73bd + bisect: skipped + user: test + date: Wed Dec 31 23:59:53 1969 -0000 + summary: 7 + + changeset: 6:e597fa2707c5 + bisect: good + user: test + date: Wed Dec 31 23:59:54 1969 -0000 + summary: 6 + + changeset: 5:b9cea37a76bc + bisect: good (implicit) + user: test + date: Wed Dec 31 23:59:55 1969 -0000 + summary: 5 + + changeset: 4:da6b357259d7 + bisect: good + parent: 1:37f42ae8b45e + user: test + date: Wed Dec 31 23:59:56 1969 -0000 + summary: 4 + + changeset: 3:e7f031aee8ca + bisect: ignored + user: test + date: Wed Dec 31 23:59:57 1969 -0000 + summary: 3 + + changeset: 2:b1ad1b6bcc5c + bisect: ignored + user: test + date: Wed Dec 31 23:59:58 1969 -0000 + summary: 2 + + changeset: 1:37f42ae8b45e + bisect: good (implicit) + user: test + date: Wed Dec 31 23:59:59 1969 -0000 + summary: 1 + + changeset: 0:b4e73ffab476 + bisect: good (implicit) + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: 0 + + $ hg log --quiet --style bisect + 14:cecd84203acc + 13:86f7c8cdb6df + B 12:a76089b5f47c + B 11:5c3eb122d29c + B 10:b097cef2be03 + U 9:8bcbdb072033 + U 8:3cd112f87d77 + S 7:577e237a73bd + G 6:e597fa2707c5 + G 5:b9cea37a76bc + G 4:da6b357259d7 + I 3:e7f031aee8ca + I 2:b1ad1b6bcc5c + G 1:37f42ae8b45e + G 0:b4e73ffab476
--- a/tests/test-bookmarks.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-bookmarks.t Sat Oct 15 14:30:50 2011 -0500 @@ -239,9 +239,10 @@ test summary $ hg summary - parent: 2:db815d6d32e6 tip Y Z x y + parent: 2:db815d6d32e6 tip 2 branch: default + bookmarks: *Z Y x y commit: (clean) update: 1 new changesets, 2 branch heads (merge) @@ -342,3 +343,19 @@ * Z 3:125c9a1d6df6 x y 2:db815d6d32e6 +test wrongly formated bookmark + + $ echo '' >> .hg/bookmarks + $ hg bookmarks + X2 1:925d80f479bb + Y 2:db815d6d32e6 + * Z 3:125c9a1d6df6 + x y 2:db815d6d32e6 + $ echo "Ican'thasformatedlines" >> .hg/bookmarks + $ hg bookmarks + malformed line in .hg/bookmarks: "Ican'thasformatedlines" + X2 1:925d80f479bb + Y 2:db815d6d32e6 + * Z 3:125c9a1d6df6 + x y 2:db815d6d32e6 +
--- a/tests/test-bundle-r.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-bundle-r.t Sat Oct 15 14:30:50 2011 -0500 @@ -154,7 +154,6 @@ 4 files, 9 changesets, 7 total revisions $ hg rollback repository tip rolled back to revision 4 (undo pull) - working directory now based on revision -1 $ cd .. should fail @@ -232,7 +231,6 @@ 4 files, 9 changesets, 7 total revisions $ hg rollback repository tip rolled back to revision 2 (undo unbundle) - working directory now based on revision 2 revision 2 @@ -257,7 +255,6 @@ 2 files, 5 changesets, 5 total revisions $ hg rollback repository tip rolled back to revision 2 (undo unbundle) - working directory now based on revision 2 $ hg unbundle ../test-bundle-branch2.hg adding changesets adding manifests @@ -277,7 +274,6 @@ 3 files, 7 changesets, 6 total revisions $ hg rollback repository tip rolled back to revision 2 (undo unbundle) - working directory now based on revision 2 $ hg unbundle ../test-bundle-cset-7.hg adding changesets adding manifests
--- a/tests/test-bundle.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-bundle.t Sat Oct 15 14:30:50 2011 -0500 @@ -90,7 +90,6 @@ $ hg -R empty rollback repository tip rolled back to revision -1 (undo pull) - working directory now based on revision -1 Pull full.hg into empty again (using --cwd) @@ -121,7 +120,6 @@ $ hg -R empty rollback repository tip rolled back to revision -1 (undo pull) - working directory now based on revision -1 Pull full.hg into empty again (using -R) @@ -219,7 +217,6 @@ $ hg rollback repository tip rolled back to revision -1 (undo pull) - working directory now based on revision -1 $ cd .. Log -R bundle:empty+full.hg
--- a/tests/test-check-pyflakes.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-check-pyflakes.t Sat Oct 15 14:30:50 2011 -0500 @@ -1,14 +1,6 @@ $ "$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) 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-commandserver.py Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-commandserver.py Sat Oct 15 14:30:50 2011 -0500 @@ -154,6 +154,30 @@ 'hooks.pre-identify=python:test-commandserver.hook', 'id'], input=cStringIO.StringIO('some input')) +def outsidechanges(server): + readchannel(server) + os.system('echo a >> a && hg ci -Am2') + runcommand(server, ['tip']) + +def bookmarks(server): + readchannel(server) + runcommand(server, ['bookmarks']) + + # changes .hg/bookmarks + os.system('hg bookmark -i bm1') + os.system('hg bookmark -i bm2') + runcommand(server, ['bookmarks']) + + # changes .hg/bookmarks.current + os.system('hg upd bm1 -q') + runcommand(server, ['bookmarks']) + +def tagscache(server): + readchannel(server) + runcommand(server, ['id', '-t', '-r', '0']) + os.system('hg tag -r 0 foo') + runcommand(server, ['id', '-t', '-r', '0']) + if __name__ == '__main__': os.system('hg init') @@ -169,3 +193,6 @@ hgrc.close() check(localhgrc) check(hookoutput) + check(outsidechanges) + check(bookmarks) + check(tagscache)
--- a/tests/test-commandserver.py.out Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-commandserver.py.out Sat Oct 15 14:30:50 2011 -0500 @@ -52,3 +52,16 @@ hook talking now try to read something: 'some input' eff892de26ec tip +changeset: 1:d3a0a68be6de +tag: tip +user: test +date: Thu Jan 01 00:00:00 1970 +0000 +summary: 2 + +no bookmarks set + bm1 1:d3a0a68be6de + bm2 1:d3a0a68be6de + * bm1 1:d3a0a68be6de + bm2 1:d3a0a68be6de + +foo
--- a/tests/test-convert-authormap.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-convert-authormap.t Sat Oct 15 14:30:50 2011 -0500 @@ -27,7 +27,7 @@ sorting... converting... 0 foo - Writing author map file new/.hg/authormap + Writing author map file $TESTTMP/new/.hg/authormap $ cat new/.hg/authormap user name=Long User Name $ hg -Rnew log @@ -44,7 +44,7 @@ $ hg init new $ mv authormap.txt new/.hg/authormap $ hg convert orig new - Ignoring bad line in author map file new/.hg/authormap: this line is ignored + Ignoring bad line in author map file $TESTTMP/new/.hg/authormap: this line is ignored scanning source... sorting... converting...
--- a/tests/test-convert-cvs.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-convert-cvs.t Sat Oct 15 14:30:50 2011 -0500 @@ -112,7 +112,6 @@ 1 import filtering out empty revision repository tip rolled back to revision 0 (undo commit) - working directory now based on revision -1 0 ci0 updating tags $ hgcat b/c
--- a/tests/test-convert-filemap.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-convert-filemap.t Sat Oct 15 14:30:50 2011 -0500 @@ -86,8 +86,7 @@ bc3eca3f47023a3e70ca0d8cc95a22a6827db19d 644 quux $ hg debugrename copied copied renamed from foo:2ed2a3912a0b24502043eae84ee4b279c18b90dd - $ echo - + $ cd .. $ splitrepo() > {
--- a/tests/test-convert-git.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-convert-git.t Sat Oct 15 14:30:50 2011 -0500 @@ -198,8 +198,6 @@ 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux - $ echo - test binary conversion (issue 1359) @@ -226,8 +224,6 @@ $ python -c 'print len(file("b", "rb").read())' 4096 $ cd .. - $ echo - test author vs committer
--- a/tests/test-convert-svn-move.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-convert-svn-move.t Sat Oct 15 14:30:50 2011 -0500 @@ -167,6 +167,7 @@ > [progress] > assume-tty = 1 > delay = 0 + > changedelay = 0 > format = topic bar number > refresh = 0 > width = 60
--- a/tests/test-convert-svn-sink.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-convert-svn-sink.t Sat Oct 15 14:30:50 2011 -0500 @@ -219,8 +219,9 @@ newlink $ hg --cwd a rm b - $ echo % remove - % remove + +Remove + $ hg --cwd a ci -d '4 0' -m 'remove a file' $ hg --cwd a tip -q 4:07b2e34a5b17
--- a/tests/test-convert.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-convert.t Sat Oct 15 14:30:50 2011 -0500 @@ -249,18 +249,18 @@ options: - -s --source-type TYPE source repository type - -d --dest-type TYPE destination repository type - -r --rev REV import up to target revision REV - -A --authormap FILE remap usernames using this file - --filemap FILE remap file names using contents of file - --splicemap FILE splice synthesized history into place - --branchmap FILE change branch names while converting - --branchsort try to sort changesets by branches - --datesort try to sort changesets by date - --sourcesort preserve source changesets order + -s --source-type TYPE source repository type + -d --dest-type TYPE destination repository type + -r --rev REV import up to target revision REV + -A --authormap FILE remap usernames using this file + --filemap FILE remap file names using contents of file + --splicemap FILE splice synthesized history into place + --branchmap FILE change branch names while converting + --branchsort try to sort changesets by branches + --datesort try to sort changesets by date + --sourcesort preserve source changesets order - use "hg -v help convert" to show global options + use "hg -v help convert" to show more info $ hg init a $ cd a $ echo a > a
--- a/tests/test-debugcomplete.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-debugcomplete.t Sat Oct 15 14:30:50 2011 -0500 @@ -17,6 +17,7 @@ diff export forget + graft grep heads help @@ -196,7 +197,7 @@ forget: include, exclude init: ssh, remotecmd, insecure log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, hidden, patch, git, limit, no-merges, stat, style, template, include, exclude - merge: force, tool, rev, preview + merge: force, rev, preview, tool pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure remove: after, force, include, exclude @@ -206,7 +207,7 @@ update: clean, check, date, rev 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 + backout: merge, parent, rev, tool, include, exclude, message, logfile, date, user bisect: reset, good, bad, skip, extend, command, noupdate bookmarks: force, rev, delete, rename, inactive branch: force, clean @@ -242,11 +243,12 @@ debugsub: rev debugwalk: include, exclude debugwireargs: three, four, five, ssh, remotecmd, insecure + graft: continue, edit, currentdate, currentuser, date, user, tool 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: extension, command identify: rev, num, id, branch, tags, bookmarks - import: strip, base, force, no-commit, bypass, exact, import-branch, message, logfile, date, user, similarity + import: strip, base, edit, force, no-commit, bypass, 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 locate: rev, print0, fullpath, include, exclude manifest: rev, all @@ -255,9 +257,9 @@ paths: recover: rename: after, force, include, exclude, dry-run - resolve: all, list, mark, unmark, tool, no-status, include, exclude + resolve: all, list, mark, unmark, no-status, tool, include, exclude revert: all, date, rev, no-backup, include, exclude, dry-run - rollback: dry-run + rollback: dry-run, force root: showconfig: untrusted tag: force, local, rev, remove, edit, message, date, user
--- a/tests/test-diff-color.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-diff-color.t Sat Oct 15 14:30:50 2011 -0500 @@ -94,8 +94,7 @@ a c \x1b[0;33mrecord this change to 'a'? [Ynsfdaq?]\x1b[0m (esc) - $ echo - + $ echo "[extensions]" >> $HGRCPATH $ echo "mq=" >> $HGRCPATH $ hg rollback @@ -123,5 +122,3 @@ a c \x1b[0;33mrecord this change to 'a'? [Ynsfdaq?]\x1b[0m (esc) - $ echo -
--- a/tests/test-dispatch.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-dispatch.t Sat Oct 15 14:30:50 2011 -0500 @@ -25,11 +25,11 @@ options: - -o --output FORMAT print output to file with formatted name - -r --rev REV print the given revision - --decode apply any matching decode filter - -I --include PATTERN [+] include names matching the given patterns - -X --exclude PATTERN [+] exclude names matching the given patterns + -o --output FORMAT print output to file with formatted name + -r --rev REV print the given revision + --decode apply any matching decode filter + -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
--- a/tests/test-doctest.py Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-doctest.py Sat Oct 15 14:30:50 2011 -0500 @@ -33,3 +33,6 @@ import hgext.convert.cvsps doctest.testmod(hgext.convert.cvsps) + +import mercurial.revset +doctest.testmod(mercurial.revset)
--- a/tests/test-duplicateoptions.py Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-duplicateoptions.py Sat Oct 15 14:30:50 2011 -0500 @@ -19,9 +19,15 @@ u = ui.ui() extensions.loadall(u) +globalshort = set() +globallong = set() +for option in commands.globalopts: + option[0] and globalshort.add(option[0]) + option[1] and globallong.add(option[1]) + for cmd, entry in commands.table.iteritems(): - seenshort = set() - seenlong = set() + seenshort = globalshort.copy() + seenlong = globallong.copy() for option in entry[1]: if (option[0] and option[0] in seenshort) or \ (option[1] and option[1] in seenlong):
--- a/tests/test-encoding-align.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-encoding-align.t Sat Oct 15 14:30:50 2011 -0500 @@ -46,20 +46,20 @@ check alignment of option descriptions in help $ hg help showoptlist - hg showoptlist + hg showoptlist dummy command to show option descriptions options: - -s --opt1 \xe7\x9f\xad\xe5\x90\x8d short width \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d (esc) - -m --opt2 MIDDLE_ middle width MIDDLE_ MIDDLE_ MIDDLE_ MIDDLE_ MIDDLE_ - MIDDLE_ MIDDLE_ MIDDLE_ - -l --opt3 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d long width \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc) - \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc) - \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc) + -s --opt1 \xe7\x9f\xad\xe5\x90\x8d short width \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d (esc) + -m --opt2 MIDDLE_ middle width MIDDLE_ MIDDLE_ MIDDLE_ MIDDLE_ MIDDLE_ + MIDDLE_ MIDDLE_ MIDDLE_ + -l --opt3 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d long width \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc) + \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc) + \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc) - use "hg -v help showoptlist" to show global options + use "hg -v help showoptlist" to show more info $ rm -f s; touch s
--- a/tests/test-encoding-textwrap.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-encoding-textwrap.t Sat Oct 15 14:30:50 2011 -0500 @@ -57,7 +57,7 @@ (1-1) display Japanese full-width characters in cp932 $ COLUMNS=60 hg --encoding cp932 --config extensions.show=./show.py help show_full_ja - hg show_full_ja + hg show_full_ja \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf (esc) @@ -67,12 +67,12 @@ \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf\x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf\x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf (esc) \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf (esc) - use "hg -v help show_full_ja" to show global options + use "hg -v help show_full_ja" to show more info (1-2) display Japanese full-width characters in utf-8 $ COLUMNS=60 hg --encoding utf-8 --config extensions.show=./show.py help show_full_ja - hg show_full_ja + hg show_full_ja \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 (esc) @@ -82,13 +82,13 @@ \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 (esc) \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 (esc) - use "hg -v help show_full_ja" to show global options + use "hg -v help show_full_ja" to show more info (1-3) display Japanese half-width characters in cp932 $ COLUMNS=60 hg --encoding cp932 --config extensions.show=./show.py help show_half_ja - hg show_half_ja + hg show_half_ja \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 (esc) @@ -98,12 +98,12 @@ \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 (esc) \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 (esc) - use "hg -v help show_half_ja" to show global options + use "hg -v help show_half_ja" to show more info (1-4) display Japanese half-width characters in utf-8 $ COLUMNS=60 hg --encoding utf-8 --config extensions.show=./show.py help show_half_ja - hg show_half_ja + hg show_half_ja \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 (esc) @@ -113,7 +113,7 @@ \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 (esc) \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 (esc) - use "hg -v help show_half_ja" to show global options + use "hg -v help show_half_ja" to show more info @@ -124,7 +124,7 @@ (2-1-1) display Japanese ambiguous-width characters in cp932 $ COLUMNS=60 hg --encoding cp932 --config extensions.show=./show.py help show_ambig_ja - hg show_ambig_ja + hg show_ambig_ja \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc) @@ -134,12 +134,12 @@ \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc) \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc) - use "hg -v help show_ambig_ja" to show global options + use "hg -v help show_ambig_ja" to show more info (2-1-2) display Japanese ambiguous-width characters in utf-8 $ COLUMNS=60 hg --encoding utf-8 --config extensions.show=./show.py help show_ambig_ja - hg show_ambig_ja + hg show_ambig_ja \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc) @@ -149,12 +149,12 @@ \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc) \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc) - use "hg -v help show_ambig_ja" to show global options + use "hg -v help show_ambig_ja" to show more info (2-1-3) display Russian ambiguous-width characters in cp1251 $ COLUMNS=60 hg --encoding cp1251 --config extensions.show=./show.py help show_ambig_ru - hg show_ambig_ru + hg show_ambig_ru \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc) @@ -164,12 +164,12 @@ \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc) \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc) - use "hg -v help show_ambig_ru" to show global options + use "hg -v help show_ambig_ru" to show more info (2-1-4) display Russian ambiguous-width characters in utf-8 $ COLUMNS=60 hg --encoding utf-8 --config extensions.show=./show.py help show_ambig_ru - hg show_ambig_ru + hg show_ambig_ru \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc) @@ -179,7 +179,7 @@ \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc) \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc) - use "hg -v help show_ambig_ru" to show global options + use "hg -v help show_ambig_ru" to show more info (2-2) treat width of ambiguous characters as wide @@ -187,7 +187,7 @@ (2-2-1) display Japanese ambiguous-width characters in cp932 $ COLUMNS=60 HGENCODINGAMBIGUOUS=wide hg --encoding cp932 --config extensions.show=./show.py help show_ambig_ja - hg show_ambig_ja + hg show_ambig_ja \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc) @@ -200,12 +200,12 @@ \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc) \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc) - use "hg -v help show_ambig_ja" to show global options + use "hg -v help show_ambig_ja" to show more info (2-2-2) display Japanese ambiguous-width characters in utf-8 $ COLUMNS=60 HGENCODINGAMBIGUOUS=wide hg --encoding utf-8 --config extensions.show=./show.py help show_ambig_ja - hg show_ambig_ja + hg show_ambig_ja \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc) @@ -218,12 +218,12 @@ \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc) \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc) - use "hg -v help show_ambig_ja" to show global options + use "hg -v help show_ambig_ja" to show more info (2-2-3) display Russian ambiguous-width characters in cp1251 $ COLUMNS=60 HGENCODINGAMBIGUOUS=wide hg --encoding cp1251 --config extensions.show=./show.py help show_ambig_ru - hg show_ambig_ru + hg show_ambig_ru \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc) \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc) @@ -236,12 +236,12 @@ \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc) \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc) - use "hg -v help show_ambig_ru" to show global options + use "hg -v help show_ambig_ru" to show more info (2-2-4) display Russian ambiguous-width charactes in utf-8 $ COLUMNS=60 HGENCODINGAMBIGUOUS=wide hg --encoding utf-8 --config extensions.show=./show.py help show_ambig_ru - hg show_ambig_ru + hg show_ambig_ru \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc) \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc) @@ -254,4 +254,4 @@ \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc) \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc) - use "hg -v help show_ambig_ru" to show global options + use "hg -v help show_ambig_ru" to show more info
--- a/tests/test-eol-hook.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-eol-hook.t Sat Oct 15 14:30:50 2011 -0500 @@ -161,7 +161,6 @@ 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
--- a/tests/test-eol.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-eol.t Sat Oct 15 14:30:50 2011 -0500 @@ -441,3 +441,82 @@ warning: ignoring .hgeol file due to parse error at .hgeol:1: bad $ hg status ? .hgeol.orig + +Test eol.only-consistent can be specified in .hgeol + + $ cd $TESTTMP + $ hg init only-consistent + $ cd only-consistent + $ printf "first\nsecond\r\n" > a.txt + $ hg add a.txt + $ cat > .hgeol << EOF + > [eol] + > only-consistent = True + > EOF + $ hg commit -m 'inconsistent' + abort: inconsistent newline style in a.txt + + [255] + $ cat > .hgeol << EOF + > [eol] + > only-consistent = False + > EOF + $ hg commit -m 'consistent' + + +Test trailing newline + + $ cat >> $HGRCPATH <<EOF + > [extensions] + > eol= + > EOF + +setup repository + + $ cd $TESTTMP + $ hg init trailing + $ cd trailing + $ cat > .hgeol <<EOF + > [patterns] + > **.txt = native + > [eol] + > fix-trailing-newline = False + > EOF + +add text without trailing newline + + $ printf "first\nsecond" > a.txt + $ hg commit --addremove -m 'checking in' + adding .hgeol + adding a.txt + $ rm a.txt + $ hg update -C -q + $ cat a.txt + first + second (no-eol) + + $ cat > .hgeol <<EOF + > [patterns] + > **.txt = native + > [eol] + > fix-trailing-newline = True + > EOF + $ printf "third\nfourth" > a.txt + $ hg commit -m 'checking in with newline fix' + $ rm a.txt + $ hg update -C -q + $ cat a.txt + third + fourth + +append a line without trailing newline + + $ printf "fifth" >> a.txt + $ hg commit -m 'adding another line line' + $ rm a.txt + $ hg update -C -q + $ cat a.txt + third + fourth + fifth +
--- a/tests/test-export.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-export.t Sat Oct 15 14:30:50 2011 -0500 @@ -7,7 +7,7 @@ > hg ci -m "foo-$i" > done - $ for out in "%nof%N" "%%%H" "%b-%R" "%h" "%r"; do + $ for out in "%nof%N" "%%%H" "%b-%R" "%h" "%r" "%m"; do > echo > echo "# foo-$out.patch" > hg export -v -o "foo-$out.patch" 2:tip @@ -77,6 +77,19 @@ foo-09.patch foo-10.patch foo-11.patch + + # foo-%m.patch + exporting patches: + foo-foo_2.patch + foo-foo_3.patch + foo-foo_4.patch + foo-foo_5.patch + foo-foo_6.patch + foo-foo_7.patch + foo-foo_8.patch + foo-foo_9.patch + foo-foo_10.patch + foo-foo_11.patch Exporting 4 changesets to a file: @@ -108,3 +121,11 @@ foo-9 +foo-10 +Checking if only alphanumeric characters are used in the file name (%m option): + + $ echo "line" >> foo + $ hg commit -m " !\"#$%&(,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\`abcdefghijklmnopqrstuvwxyz{|}~" + $ hg export -v -o %m.patch tip + exporting patch: + ____________0123456789_______ABCDEFGHIJKLMNOPQRSTUVWXYZ______abcdefghijklmnopqrstuvwxyz____.patch +
--- a/tests/test-extdiff.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-extdiff.t Sat Oct 15 14:30:50 2011 -0500 @@ -39,15 +39,15 @@ options: - -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 + -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 falabala" to show global options + use "hg -v help falabala" to show more info $ hg ci -d '0 0' -mtest1
--- a/tests/test-extension.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-extension.t Sat Oct 15 14:30:50 2011 -0500 @@ -182,23 +182,24 @@ yet another foo command global options: - -R --repository REPO repository root directory or name of overlay bundle - file - --cwd DIR change working directory - -y --noninteractive do not prompt, automatically pick the first choice - for all prompts - -q --quiet suppress output - -v --verbose enable additional output - --config CONFIG [+] set/override config option (use 'section.name=value') - --debug enable debugging output - --debugger start debugger - --encoding ENCODE set the charset encoding (default: ascii) - --encodingmode MODE set the charset encoding mode (default: strict) - --traceback always print a traceback on exception - --time time how long the command takes - --profile print command execution profile - --version output version information and exit - -h --help display help and exit + + -R --repository REPO repository root directory or name of overlay bundle + file + --cwd DIR change working directory + -y --noninteractive do not prompt, automatically pick the first choice for + all prompts + -q --quiet suppress output + -v --verbose enable additional output + --config CONFIG [+] set/override config option (use 'section.name=value') + --debug enable debugging output + --debugger start debugger + --encoding ENCODE set the charset encoding (default: ascii) + --encodingmode MODE set the charset encoding mode (default: strict) + --traceback always print a traceback on exception + --time time how long the command takes + --profile print command execution profile + --version output version information and exit + -h --help display help and exit [+] marked option can be specified multiple times @@ -213,23 +214,24 @@ yet another foo command global options: - -R --repository REPO repository root directory or name of overlay bundle - file - --cwd DIR change working directory - -y --noninteractive do not prompt, automatically pick the first choice - for all prompts - -q --quiet suppress output - -v --verbose enable additional output - --config CONFIG [+] set/override config option (use 'section.name=value') - --debug enable debugging output - --debugger start debugger - --encoding ENCODE set the charset encoding (default: ascii) - --encodingmode MODE set the charset encoding mode (default: strict) - --traceback always print a traceback on exception - --time time how long the command takes - --profile print command execution profile - --version output version information and exit - -h --help display help and exit + + -R --repository REPO repository root directory or name of overlay bundle + file + --cwd DIR change working directory + -y --noninteractive do not prompt, automatically pick the first choice for + all prompts + -q --quiet suppress output + -v --verbose enable additional output + --config CONFIG [+] set/override config option (use 'section.name=value') + --debug enable debugging output + --debugger start debugger + --encoding ENCODE set the charset encoding (default: ascii) + --encodingmode MODE set the charset encoding mode (default: strict) + --traceback always print a traceback on exception + --time time how long the command takes + --profile print command execution profile + --version output version information and exit + -h --help display help and exit [+] marked option can be specified multiple times $ echo 'debugextension = !' >> $HGRCPATH @@ -260,16 +262,16 @@ 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 + -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 + use "hg -v help extdiff" to show more info $ hg help --extension extdiff extdiff extension - command to allow external programs to compare revisions @@ -371,7 +373,7 @@ multirevs command - use "hg -v help multirevs" to show global options + use "hg -v help multirevs" to show more info $ hg multirevs hg multirevs: invalid arguments
--- a/tests/test-fetch.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-fetch.t Sat Oct 15 14:30:50 2011 -0500 @@ -1,5 +1,3 @@ -adjust to non-default HGPORT, e.g. with run-tests.py -j - $ echo "[extensions]" >> $HGRCPATH $ echo "fetch=" >> $HGRCPATH @@ -7,7 +5,7 @@ $ hg init a $ echo a > a/a - $ hg --cwd a commit -d '1 0' -Ama + $ hg --cwd a commit -Ama adding a $ hg clone a b updating to branch default @@ -16,10 +14,10 @@ updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo b > a/b - $ hg --cwd a commit -d '2 0' -Amb + $ hg --cwd a commit -Amb adding b $ hg --cwd a parents -q - 1:97d72e5f12c7 + 1:d2ae7f538514 should pull one change @@ -32,9 +30,9 @@ added 1 changesets with 1 changes to 1 files 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg --cwd b parents -q - 1:97d72e5f12c7 + 1:d2ae7f538514 $ echo c > c/c - $ hg --cwd c commit -d '3 0' -Amc + $ hg --cwd c commit -Amc adding c $ hg clone c d updating to branch default @@ -48,39 +46,37 @@ message, making every commit appear different. should merge c into a - $ hg --cwd c fetch -d '4 0' -m 'automated merge' ../a + $ hg --cwd c fetch -d '0 0' -m 'automated merge' ../a pulling from ../a searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) - updating to 2:97d72e5f12c7 + updating to 2:d2ae7f538514 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - merging with 1:5e056962225c + merging with 1:d36c0562f908 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - new changeset 3:cd3a41621cf0 merges remote changes with local + new changeset 3:a323a0c43ec4 merges remote changes with local $ ls c a b c - $ netstat -tnap 2>/dev/null | grep $HGPORT | grep LISTEN - [1] $ hg --cwd a serve -a localhost -p $HGPORT -d --pid-file=hg.pid $ cat a/hg.pid >> "$DAEMON_PIDS" fetch over http, no auth - $ hg --cwd d fetch -d '5 0' http://localhost:$HGPORT/ + $ hg --cwd d fetch http://localhost:$HGPORT/ pulling from http://localhost:$HGPORT/ searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) - updating to 2:97d72e5f12c7 + updating to 2:d2ae7f538514 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - merging with 1:5e056962225c + merging with 1:d36c0562f908 1 files updated, 0 files merged, 0 files removed, 0 files unresolved new changeset 3:* merges remote changes with local (glob) $ hg --cwd d tip --template '{desc}\n' @@ -88,16 +84,16 @@ fetch over http with auth (should be hidden in desc) - $ hg --cwd e fetch -d '5 0' http://user:password@localhost:$HGPORT/ + $ hg --cwd e fetch http://user:password@localhost:$HGPORT/ pulling from http://user:***@localhost:$HGPORT/ searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) - updating to 2:97d72e5f12c7 + updating to 2:d2ae7f538514 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - merging with 1:5e056962225c + merging with 1:d36c0562f908 1 files updated, 0 files merged, 0 files removed, 0 files unresolved new changeset 3:* merges remote changes with local (glob) $ hg --cwd e tip --template '{desc}\n' @@ -109,17 +105,17 @@ updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo f > f/f - $ hg --cwd f ci -d '6 0' -Amf + $ hg --cwd f ci -Amf adding f $ echo g > g/g - $ hg --cwd g ci -d '6 0' -Amg + $ hg --cwd g ci -Amg adding g $ hg clone -q f h $ hg clone -q g i should merge f into g - $ hg --cwd g fetch -d '7 0' --switch -m 'automated merge' ../f + $ hg --cwd g fetch -d '0 0' --switch -m 'automated merge' ../f pulling from ../f searching for changes adding changesets @@ -127,9 +123,9 @@ adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) 0 files updated, 0 files merged, 0 files removed, 0 files unresolved - merging with 3:cc6a3744834d + merging with 3:6343ca3eff20 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - new changeset 4:55aa4f32ec59 merges remote changes with local + new changeset 4:f7faa0b7d3c6 merges remote changes with local $ rm i/g should abort, because i is modified @@ -142,21 +138,19 @@ $ hg init nbase $ echo base > nbase/a - $ hg -R nbase ci -d '1 0' -Am base + $ hg -R nbase ci -Am base adding a $ hg -R nbase branch a marked working directory as branch a $ echo a > nbase/a - $ hg -R nbase ci -d '2 0' -m a + $ hg -R nbase ci -m a $ hg -R nbase up -C 0 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R nbase branch b marked working directory as branch b $ echo b > nbase/b - $ hg -R nbase ci -Ad '3 0' -m b + $ hg -R nbase ci -Am b adding b - $ echo - pull in change on foreign branch @@ -169,10 +163,10 @@ $ hg -R n1 up -C a 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo aa > n1/a - $ hg -R n1 ci -d '4 0' -m a1 + $ hg -R n1 ci -m a1 $ hg -R n2 up -C b 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R n2 fetch -d '9 0' -m 'merge' n1 + $ hg -R n2 fetch -m 'merge' n1 pulling from n1 searching for changes adding changesets @@ -185,8 +179,6 @@ $ hg -R n2 parents --template '{rev}\n' 2 $ rm -fr n1 n2 - $ echo - pull in changes on both foreign and local branches @@ -199,14 +191,14 @@ $ hg -R n1 up -C a 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo aa > n1/a - $ hg -R n1 ci -d '4 0' -m a1 + $ hg -R n1 ci -m a1 $ hg -R n1 up -C b 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo bb > n1/b - $ hg -R n1 ci -d '5 0' -m b1 + $ hg -R n1 ci -m b1 $ hg -R n2 up -C b 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ hg -R n2 fetch -d '9 0' -m 'merge' n1 + $ hg -R n2 fetch -m 'merge' n1 pulling from n1 searching for changes adding changesets @@ -220,8 +212,6 @@ $ hg -R n2 parents --template '{rev}\n' 4 $ rm -fr n1 n2 - $ echo - pull changes on foreign (2 new heads) and local (1 new head) branches with a local change @@ -235,33 +225,33 @@ $ hg -R n1 up -C a 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo a1 > n1/a - $ hg -R n1 ci -d '4 0' -m a1 + $ hg -R n1 ci -m a1 $ hg -R n1 up -C b 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo bb > n1/b - $ hg -R n1 ci -d '5 0' -m b1 + $ hg -R n1 ci -m b1 $ hg -R n1 up -C 1 1 files updated, 0 files merged, 1 files removed, 0 files unresolved $ echo a2 > n1/a - $ hg -R n1 ci -d '6 0' -m a2 + $ hg -R n1 ci -m a2 created new head $ hg -R n2 up -C b 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo change >> n2/c - $ hg -R n2 ci -Ad '7 0' -m local + $ hg -R n2 ci -A -m local adding c - $ hg -R n2 fetch -d '9 0' -m 'merge' n1 + $ hg -R n2 fetch -d '0 0' -m 'merge' n1 pulling from n1 searching for changes adding changesets adding manifests adding file changes added 3 changesets with 3 changes to 2 files (+2 heads) - updating to 5:708c6cce3d26 + updating to 5:3c4a837a864f 1 files updated, 0 files merged, 1 files removed, 0 files unresolved - merging with 3:d83427717b1f + merging with 3:1267f84a9ea5 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - new changeset 7:48f1a33f52af merges remote changes with local + new changeset 7:2cf2a1261f21 merges remote changes with local parent should be 7 (new merge changeset) @@ -283,21 +273,21 @@ $ hg -R n1 merge b 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) - $ hg -R n1 ci -d '4 0' -m merge + $ hg -R n1 ci -m merge $ hg -R n1 up -C 2 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo c > n1/a - $ hg -R n1 ci -d '5 0' -m c + $ hg -R n1 ci -m c $ hg -R n1 up -C 2 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo cc > n1/a - $ hg -R n1 ci -d '6 0' -m cc + $ hg -R n1 ci -m cc created new head $ hg -R n2 up -C b 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo change >> n2/b - $ hg -R n2 ci -Ad '7 0' -m local - $ hg -R n2 fetch -d '9 0' -m 'merge' n1 + $ hg -R n2 ci -A -m local + $ hg -R n2 fetch -m 'merge' n1 pulling from n1 searching for changes adding changesets @@ -326,7 +316,7 @@ $ hg -R n1 ci -m next $ hg -R n2 branch topic marked working directory as branch topic - $ hg -R n2 fetch -d '0 0' -m merge n1 + $ hg -R n2 fetch -m merge n1 abort: working dir not at branch tip (use "hg update" to check out branch tip) [255] @@ -393,8 +383,6 @@ new changeset 3:* merges remote changes with local (glob) $ hg --cwd i1726r2 heads default --template '{rev}\n' 3 - $ echo - test issue2047 @@ -415,4 +403,3 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files - $ "$TESTDIR/killdaemons.py"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-filecache.py Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,94 @@ +import sys, os, subprocess + +if subprocess.call(['%s/hghave' % os.environ['TESTDIR'], 'cacheable']): + sys.exit(80) + +from mercurial import util, scmutil, extensions + +filecache = scmutil.filecache + +class fakerepo(object): + def __init__(self): + self._filecache = {} + + def join(self, p): + return p + + def sjoin(self, p): + return p + + @filecache('x') + def cached(self): + print 'creating' + + def invalidate(self): + for k in self._filecache: + try: + delattr(self, k) + except AttributeError: + pass + +def basic(repo): + # file doesn't exist, calls function + repo.cached + + repo.invalidate() + # file still doesn't exist, uses cache + repo.cached + + # create empty file + f = open('x', 'w') + f.close() + repo.invalidate() + # should recreate the object + repo.cached + + f = open('x', 'w') + f.write('a') + f.close() + repo.invalidate() + # should recreate the object + repo.cached + + repo.invalidate() + # stats file again, nothing changed, reuses object + repo.cached + + # atomic replace file, size doesn't change + # hopefully st_mtime doesn't change as well so this doesn't use the cache + # because of inode change + f = scmutil.opener('.')('x', 'w', atomictemp=True) + f.write('b') + f.close() + + repo.invalidate() + repo.cached + +def fakeuncacheable(): + def wrapcacheable(orig, *args, **kwargs): + return False + + def wrapinit(orig, *args, **kwargs): + pass + + originit = extensions.wrapfunction(util.cachestat, '__init__', wrapinit) + origcacheable = extensions.wrapfunction(util.cachestat, 'cacheable', + wrapcacheable) + + try: + os.remove('x') + except: + pass + + basic(fakerepo()) + + util.cachestat.cacheable = origcacheable + util.cachestat.__init__ = originit + +print 'basic:' +print +basic(fakerepo()) +print +print 'fakeuncacheable:' +print +fakeuncacheable()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-filecache.py.out Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,15 @@ +basic: + +creating +creating +creating +creating + +fakeuncacheable: + +creating +creating +creating +creating +creating +creating
--- a/tests/test-globalopts.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-globalopts.t Sat Oct 15 14:30:50 2011 -0500 @@ -296,6 +296,7 @@ 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 + graft copy changes from other branches onto the current branch grep search for a pattern in specified files and revisions heads show current repository heads or show branch heads help show help for a given topic or a help overview @@ -377,6 +378,7 @@ 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 + graft copy changes from other branches onto the current branch grep search for a pattern in specified files and revisions heads show current repository heads or show branch heads help show help for a given topic or a help overview
--- a/tests/test-help.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-help.t Sat Oct 15 14:30:50 2011 -0500 @@ -66,6 +66,7 @@ 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 + graft copy changes from other branches onto the current branch grep search for a pattern in specified files and revisions heads show current repository heads or show branch heads help show help for a given topic or a help overview @@ -141,6 +142,7 @@ 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 + graft copy changes from other branches onto the current branch grep search for a pattern in specified files and revisions heads show current repository heads or show branch heads help show help for a given topic or a help overview @@ -199,12 +201,7 @@ Test short command list with verbose option $ hg -v help shortlist - Mercurial Distributed SCM (version *) (glob) - (see http://mercurial.selenic.com for more information) - - Copyright (C) 2005-2011 Matt Mackall and others - This is free software; see the source for copying conditions. There is NO - warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + Mercurial Distributed SCM basic commands: @@ -244,23 +241,24 @@ update working directory (or switch revisions) global options: - -R --repository REPO repository root directory or name of overlay bundle - file - --cwd DIR change working directory - -y --noninteractive do not prompt, automatically pick the first choice - for all prompts - -q --quiet suppress output - -v --verbose enable additional output - --config CONFIG [+] set/override config option (use 'section.name=value') - --debug enable debugging output - --debugger start debugger - --encoding ENCODE set the charset encoding (default: ascii) - --encodingmode MODE set the charset encoding mode (default: strict) - --traceback always print a traceback on exception - --time time how long the command takes - --profile print command execution profile - --version output version information and exit - -h --help display help and exit + + -R --repository REPO repository root directory or name of overlay bundle + file + --cwd DIR change working directory + -y --noninteractive do not prompt, automatically pick the first choice for + all prompts + -q --quiet suppress output + -v --verbose enable additional output + --config CONFIG [+] set/override config option (use 'section.name=value') + --debug enable debugging output + --debugger start debugger + --encoding ENCODE set the charset encoding (default: ascii) + --encodingmode MODE set the charset encoding mode (default: strict) + --traceback always print a traceback on exception + --time time how long the command takes + --profile print command execution profile + --version output version information and exit + -h --help display help and exit [+] marked option can be specified multiple times @@ -280,18 +278,16 @@ 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 - -X --exclude PATTERN [+] exclude names matching the given patterns - -S --subrepos recurse into subrepositories - -n --dry-run do not perform actions, just print output + -I --include PATTERN [+] include names matching the given patterns + -X --exclude PATTERN [+] exclude names matching the given patterns + -S --subrepos recurse into subrepositories + -n --dry-run do not perform actions, just print output [+] marked option can be specified multiple times - use "hg -v help add" to show global options + use "hg -v help add" to show more info Verbose help for add @@ -323,30 +319,32 @@ options: - -I --include PATTERN [+] include names matching the given patterns - -X --exclude PATTERN [+] exclude names matching the given patterns - -S --subrepos recurse into subrepositories - -n --dry-run do not perform actions, just print output + -I --include PATTERN [+] include names matching the given patterns + -X --exclude PATTERN [+] exclude names matching the given patterns + -S --subrepos recurse into subrepositories + -n --dry-run do not perform actions, just print output + + [+] marked option can be specified multiple times global options: - -R --repository REPO repository root directory or name of overlay bundle - file - --cwd DIR change working directory - -y --noninteractive do not prompt, automatically pick the first choice - for all prompts - -q --quiet suppress output - -v --verbose enable additional output - --config CONFIG [+] set/override config option (use - 'section.name=value') - --debug enable debugging output - --debugger start debugger - --encoding ENCODE set the charset encoding (default: ascii) - --encodingmode MODE set the charset encoding mode (default: strict) - --traceback always print a traceback on exception - --time time how long the command takes - --profile print command execution profile - --version output version information and exit - -h --help display help and exit + + -R --repository REPO repository root directory or name of overlay bundle + file + --cwd DIR change working directory + -y --noninteractive do not prompt, automatically pick the first choice for + all prompts + -q --quiet suppress output + -v --verbose enable additional output + --config CONFIG [+] set/override config option (use 'section.name=value') + --debug enable debugging output + --debugger start debugger + --encoding ENCODE set the charset encoding (default: ascii) + --encodingmode MODE set the charset encoding mode (default: strict) + --traceback always print a traceback on exception + --time time how long the command takes + --profile print command execution profile + --version output version information and exit + -h --help display help and exit [+] marked option can be specified multiple times @@ -359,32 +357,6 @@ Copyright (C) 2005-2011 Matt Mackall and others This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - - hg add [OPTION]... [FILE]... - - 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 - -X --exclude PATTERN [+] exclude names matching the given patterns - -S --subrepos recurse into subrepositories - -n --dry-run do not perform actions, just print output - - [+] marked option can be specified multiple times - - use "hg -v help add" to show global options $ hg add --skjdfks hg add: option --skjdfks not recognized @@ -394,10 +366,10 @@ options: - -I --include PATTERN [+] include names matching the given patterns - -X --exclude PATTERN [+] exclude names matching the given patterns - -S --subrepos recurse into subrepositories - -n --dry-run do not perform actions, just print output + -I --include PATTERN [+] include names matching the given patterns + -X --exclude PATTERN [+] exclude names matching the given patterns + -S --subrepos recurse into subrepositories + -n --dry-run do not perform actions, just print output [+] marked option can be specified multiple times @@ -430,7 +402,7 @@ Returns 0 on success, 1 if errors are encountered. - use "hg -v help verify" to show global options + use "hg -v help verify" to show more info $ hg help diff hg diff [OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]... @@ -465,25 +437,25 @@ options: - -r --rev REV [+] revision - -c --change REV change made by revision - -a --text treat all files as text - -g --git use git extended diff format - --nodates omit dates from diff headers - -p --show-function show which function each change is in - --reverse produce a diff that undoes the changes - -w --ignore-all-space ignore white space when comparing lines - -b --ignore-space-change ignore changes in the amount of white space - -B --ignore-blank-lines ignore changes whose lines are all blank - -U --unified NUM number of lines of context to show - --stat output diffstat-style summary of changes - -I --include PATTERN [+] include names matching the given patterns - -X --exclude PATTERN [+] exclude names matching the given patterns - -S --subrepos recurse into subrepositories + -r --rev REV [+] revision + -c --change REV change made by revision + -a --text treat all files as text + -g --git use git extended diff format + --nodates omit dates from diff headers + -p --show-function show which function each change is in + --reverse produce a diff that undoes the changes + -w --ignore-all-space ignore white space when comparing lines + -b --ignore-space-change ignore changes in the amount of white space + -B --ignore-blank-lines ignore changes whose lines are all blank + -U --unified NUM number of lines of context to show + --stat output diffstat-style summary of changes + -I --include PATTERN [+] include names matching the given patterns + -X --exclude PATTERN [+] exclude names matching the given patterns + -S --subrepos recurse into subrepositories [+] marked option can be specified multiple times - use "hg -v help diff" to show global options + use "hg -v help diff" to show more info $ hg help status hg status [OPTION]... [FILE]... @@ -527,26 +499,26 @@ options: - -A --all show status of all files - -m --modified show only modified files - -a --added show only added files - -r --removed show only removed files - -d --deleted show only deleted (but tracked) files - -c --clean show only files without changes - -u --unknown show only unknown (not tracked) files - -i --ignored show only ignored files - -n --no-status hide status prefix - -C --copies show source of copied files - -0 --print0 end filenames with NUL, for use with xargs - --rev REV [+] show difference from revision - --change REV list the changed files of a revision - -I --include PATTERN [+] include names matching the given patterns - -X --exclude PATTERN [+] exclude names matching the given patterns - -S --subrepos recurse into subrepositories + -A --all show status of all files + -m --modified show only modified files + -a --added show only added files + -r --removed show only removed files + -d --deleted show only deleted (but tracked) files + -c --clean show only files without changes + -u --unknown show only unknown (not tracked) files + -i --ignored show only ignored files + -n --no-status hide status prefix + -C --copies show source of copied files + -0 --print0 end filenames with NUL, for use with xargs + --rev REV [+] show difference from revision + --change REV list the changed files of a revision + -I --include PATTERN [+] include names matching the given patterns + -X --exclude PATTERN [+] exclude names matching the given patterns + -S --subrepos recurse into subrepositories [+] marked option can be specified multiple times - use "hg -v help status" to show global options + use "hg -v help status" to show more info $ hg -q help status hg status [OPTION]... [FILE]... @@ -630,7 +602,7 @@ (no help text available) - use "hg -v help nohelp" to show global options + use "hg -v help nohelp" to show more info Test that default list of commands omits extension commands @@ -656,6 +628,7 @@ 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 + graft copy changes from other branches onto the current branch grep search for a pattern in specified files and revisions heads show current repository heads or show branch heads help show help for a given topic or a help overview
--- a/tests/test-highlight.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-highlight.t Sat Oct 15 14:30:50 2011 -0500 @@ -520,8 +520,6 @@ $ echo "" >> b $ echo "" >> b $ diff -u b a - $ echo - hgweb filerevision, raw @@ -531,8 +529,6 @@ $ echo "" >> b $ hg cat primes.py >> b $ diff -u b a - $ echo - hgweb highlightcss friendly
--- a/tests/test-hook.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-hook.t Sat Oct 15 14:30:50 2011 -0500 @@ -277,7 +277,6 @@ (run 'hg update' to get a working copy) $ hg rollback repository tip rolled back to revision 3 (undo pull) - working directory now based on revision 0 preoutgoing hook can prevent outgoing changes
--- a/tests/test-import-bypass.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-import-bypass.t Sat Oct 15 14:30:50 2011 -0500 @@ -61,8 +61,7 @@ @ 0:07f494440405 test 0 0 - default - adda $ hg rollback - repository tip rolled back to revision 1 (undo commit) - working directory now based on revision 0 + repository tip rolled back to revision 1 (undo import) Test --import-branch @@ -74,8 +73,7 @@ @ 0:07f494440405 test 0 0 - default - adda $ hg rollback - repository tip rolled back to revision 1 (undo commit) - working directory now based on revision 0 + repository tip rolled back to revision 1 (undo import) Test --strip @@ -97,8 +95,7 @@ > EOF applying patch from stdin $ hg rollback - repository tip rolled back to revision 1 (undo commit) - working directory now based on revision 0 + repository tip rolled back to revision 1 (undo import) Test unsupported combinations @@ -174,7 +171,6 @@ $ hg import --bypass ../patch1.diff ../patch2.diff applying ../patch1.diff applying ../patch2.diff - applied 16581080145e $ shortlog o 3:bc8ca3f8a7c4 test 0 0 - default - addf | @@ -199,7 +195,6 @@ $ hg import --bypass --exact ../patch1.diff ../patch2.diff applying ../patch1.diff applying ../patch2.diff - applied 16581080145e $ shortlog o 3:d60cb8989666 test 0 0 - foo - addf |
--- a/tests/test-import.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-import.t Sat Oct 15 14:30:50 2011 -0500 @@ -199,7 +199,6 @@ $ hg init b $ hg --cwd a export 0:tip | hg --cwd b import - applying patch from stdin - applied 80971e65b431 $ hg --cwd a id 1d4bd90af0e4 tip $ hg --cwd b id @@ -356,15 +355,20 @@ $ hg clone -qr0 a b $ hg --cwd b parents --template 'parent: {rev}\n' parent: 0 - $ hg --cwd b import ../patch1 ../patch2 + $ hg --cwd b import -v ../patch1 ../patch2 applying ../patch1 + patching file a + a + created 1d4bd90af0e4 applying ../patch2 - applied 1d4bd90af0e4 + patching file a + a + created 6d019af21222 $ hg --cwd b rollback - repository tip rolled back to revision 1 (undo commit) - working directory now based on revision 1 + repository tip rolled back to revision 0 (undo import) + working directory now based on revision 0 $ hg --cwd b parents --template 'parent: {rev}\n' - parent: 1 + parent: 0 $ rm -r b @@ -433,6 +437,7 @@ applying fuzzy-tip.patch patching file a Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines). + applied to working directory $ hg revert -a reverting a @@ -449,6 +454,7 @@ applying fuzzy-tip.patch patching file a Hunk #1 succeeded at 1 with fuzz 2 (offset -2 lines). + applied to working directory $ cd .. @@ -651,6 +657,7 @@ removing a adding b recording removal of a as rename to b (88% similar) + applied to working directory $ hg st -C A b a @@ -665,6 +672,7 @@ patching file b removing a adding b + applied to working directory $ hg st -C A b R a @@ -680,6 +688,7 @@ adding a $ hg ci -m "commit" $ cat > a.patch <<EOF + > add a, b > diff --git a/a b/a > --- a/a > +++ b/a @@ -690,9 +699,25 @@ > EOF $ hg import --no-commit a.patch applying a.patch + +apply a good patch followed by an empty patch (mainly to ensure +that dirstate is *not* updated when import crashes) + $ hg update -q -C . + $ rm b + $ touch empty.patch + $ hg import a.patch empty.patch + applying a.patch + applying empty.patch + transaction abort! + rollback completed + abort: empty.patch: no diffs found + [255] + $ hg tip --template '{rev} {desc|firstline}\n' + 0 commit + $ hg -q status + M a $ cd .. - create file when source is not /dev/null $ cat > create.patch <<EOF
--- a/tests/test-init.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-init.t Sat Oct 15 14:30:50 2011 -0500 @@ -19,8 +19,8 @@ store created 00changelog.i created revlogv1 + fncache store - fncache dotencode $ echo this > local/foo $ hg ci --cwd local -A -m "init" @@ -48,8 +48,8 @@ store created 00changelog.i created revlogv1 + fncache store - fncache test failure @@ -145,8 +145,8 @@ store created 00changelog.i created revlogv1 + fncache store - fncache dotencode prepare test of init of url configured from paths @@ -162,8 +162,8 @@ store created 00changelog.i created revlogv1 + fncache store - fncache dotencode verify that clone also expand urls @@ -175,8 +175,8 @@ store created 00changelog.i created revlogv1 + fncache store - fncache dotencode clone bookmarks
--- a/tests/test-install.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-install.t Sat Oct 15 14:30:50 2011 -0500 @@ -2,7 +2,7 @@ $ hg debuginstall Checking encoding (ascii)... Checking installed modules (*/mercurial)... (glob) - Checking templates... + Checking templates (*/mercurial/templates)... (glob) Checking commit editor... Checking username... No problems detected @@ -11,7 +11,7 @@ $ HGUSER= hg debuginstall Checking encoding (ascii)... Checking installed modules (*/mercurial)... (glob) - Checking templates... + Checking templates (*/mercurial/templates)... (glob) Checking commit editor... Checking username... no username supplied (see "hg help config")
--- a/tests/test-keyword.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-keyword.t Sat Oct 15 14:30:50 2011 -0500 @@ -825,7 +825,7 @@ ignore $Id$ $ hg rollback - repository tip rolled back to revision 2 (undo commit) + repository tip rolled back to revision 2 (undo import) working directory now based on revision 2 $ hg update --clean 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-largefiles.t Sat Oct 15 14:30:50 2011 -0500 @@ -0,0 +1,445 @@ + $ cat >> $HGRCPATH <<EOF + > [extensions] + > largefiles= + > purge= + > rebase= + > [largefiles] + > size=2 + > patterns=glob:**.dat + > EOF + +Create the repo with a couple of revisions of both large and normal +files (testing that status correctly shows largefiles. + + $ hg init a + $ cd a + $ mkdir sub + $ echo normal1 > normal1 + $ echo normal2 > sub/normal2 + $ echo large1 > large1 + $ echo large2 > sub/large2 + $ hg add normal1 sub/normal2 + $ hg add --large large1 sub/large2 + $ hg commit -m "add files" + $ echo normal11 > normal1 + $ echo normal22 > sub/normal2 + $ echo large11 > large1 + $ echo large22 > sub/large2 + $ hg st + M large1 + M normal1 + M sub/large2 + M sub/normal2 + $ hg commit -m "edit files" + +Verify that committing new versions of largefiles results in correct +largefile contents, and also that non-largefiles are not affected +badly. + + $ cat normal1 + normal11 + $ cat large1 + large11 + $ cat sub/normal2 + normal22 + $ cat sub/large2 + large22 + +Verify removing largefiles and normal files works on largefile repos. + + $ hg remove normal1 large1 + $ hg commit -m "remove files" + $ ls + sub + +Test copying largefiles. + + $ hg cp sub/normal2 normal1 + $ hg cp sub/large2 large1 + $ hg commit -m "copy files" + $ cat normal1 + normal22 + $ cat large1 + large22 + +Test moving largefiles and verify that normal files are also unaffected. + + $ hg mv normal1 normal3 + $ hg mv large1 large3 + $ hg mv sub/normal2 sub/normal4 + $ hg mv sub/large2 sub/large4 + $ hg commit -m "move files" + $ cat normal3 + normal22 + $ cat large3 + large22 + $ cat sub/normal4 + normal22 + $ cat sub/large4 + large22 + +Test archiving the various revisions. These hit corner cases known with +archiving. + + $ hg archive -r 0 ../archive0 + $ hg archive -r 1 ../archive1 + $ hg archive -r 2 ../archive2 + $ hg archive -r 3 ../archive3 + $ hg archive -r 4 ../archive4 + $ cd ../archive0 + $ cat normal1 + normal1 + $ cat large1 + large1 + $ cat sub/normal2 + normal2 + $ cat sub/large2 + large2 + $ cd ../archive1 + $ cat normal1 + normal11 + $ cat large1 + large11 + $ cat sub/normal2 + normal22 + $ cat sub/large2 + large22 + $ cd ../archive2 + $ ls + sub + $ cat sub/normal2 + normal22 + $ cat sub/large2 + large22 + $ cd ../archive3 + $ cat normal1 + normal22 + $ cat large1 + large22 + $ cat sub/normal2 + normal22 + $ cat sub/large2 + large22 + $ cd ../archive4 + $ cat normal3 + normal22 + $ cat large3 + large22 + $ cat sub/normal4 + normal22 + $ cat sub/large4 + large22 + +Test a separate commit corner case (specifying files to commit) and check +that the commited files have the right value. + + $ cd ../a + $ echo normal3 > normal3 + $ echo large3 > large3 + $ echo normal4 > sub/normal4 + $ echo large4 > sub/large4 + $ hg commit normal3 large3 sub/normal4 sub/large4 -m "edit files again" + $ cat normal3 + normal3 + $ cat large3 + large3 + $ cat sub/normal4 + normal4 + $ cat sub/large4 + large4 + +Test one more commit corner case that has been known to break (comitting from +a sub-directory of the repo). + + $ cd ../a + $ echo normal33 > normal3 + $ echo large33 > large3 + $ echo normal44 > sub/normal4 + $ echo large44 > sub/large4 + $ cd sub + $ hg commit -m "edit files yet again" + $ cat ../normal3 + normal33 + $ cat ../large3 + large33 + $ cat normal4 + normal44 + $ cat large4 + large44 + +Check that committing standins is not allowed. + + $ cd .. + $ echo large3 > large3 + $ hg commit .hglf/large3 -m "try to commit standin" + abort: file ".hglf/large3" is a largefile standin + (commit the largefile itself instead) + [255] + +Test some cornercases for adding largefiles. + + $ echo large5 > large5 + $ hg add --large large5 + $ hg add --large large5 + large5 already a largefile + $ mkdir sub2 + $ echo large6 > sub2/large6 + $ echo large7 > sub2/large7 + $ hg add --large sub2 + adding sub2/large6 as a largefile + adding sub2/large7 as a largefile + $ hg st + M large3 + A large5 + A sub2/large6 + A sub2/large7 + +Test that files get added as largefiles based on .hgrc settings + + $ echo testdata > test.dat + $ dd bs=3145728 count=1 if=/dev/zero of=reallylarge > /dev/null 2> /dev/null + $ hg add + adding reallylarge as a largefile + adding test.dat as a largefile + $ dd bs=1048576 count=1 if=/dev/zero of=reallylarge2 > /dev/null 2> /dev/null + +Test that specifying the --lsize command on the comand-line works + + $ hg add --lfsize 1 + adding reallylarge2 as a largefile + +Test forget on largefiles. + + $ hg forget large3 large5 test.dat reallylarge reallylarge2 + $ hg st + A sub2/large6 + A sub2/large7 + R large3 + ? large5 + ? reallylarge + ? reallylarge2 + ? test.dat + $ hg commit -m "add/edit more largefiles" + $ hg st + ? large3 + ? large5 + ? reallylarge + ? reallylarge2 + ? test.dat + +Test purge with largefiles (verify that largefiles get populated in the +working copy correctly after a purge) + + $ hg purge --all + $ cat sub/large4 + large44 + $ cat sub2/large6 + large6 + $ cat sub2/large7 + large7 + +Test cloning a largefiles repo. + + $ cd .. + $ hg clone a b + updating to branch default + 5 files updated, 0 files merged, 0 files removed, 0 files unresolved + getting changed largefiles + 3 largefiles updated, 0 removed + $ cd b + $ hg log + changeset: 7:daea875e9014 + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add/edit more largefiles + + changeset: 6:4355d653f84f + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: edit files yet again + + changeset: 5:9d5af5072dbd + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: edit files again + + changeset: 4:74c02385b94c + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: move files + + changeset: 3:9e8fbc4bce62 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: copy files + + changeset: 2:51a0ae4d5864 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: remove files + + changeset: 1:ce8896473775 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: edit files + + changeset: 0:30d30fe6a5be + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add files + + $ cat normal3 + normal33 + $ cat sub/normal4 + normal44 + $ cat sub/large4 + large44 + $ cat sub2/large6 + large6 + $ cat sub2/large7 + large7 + $ cd .. + $ hg clone a -r 3 c + adding changesets + adding manifests + adding file changes + added 4 changesets with 10 changes to 4 files + updating to branch default + 4 files updated, 0 files merged, 0 files removed, 0 files unresolved + getting changed largefiles + 2 largefiles updated, 0 removed + $ cd c + $ hg log + changeset: 3:9e8fbc4bce62 + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: copy files + + changeset: 2:51a0ae4d5864 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: remove files + + changeset: 1:ce8896473775 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: edit files + + changeset: 0:30d30fe6a5be + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add files + + $ cat normal1 + normal22 + $ cat large1 + large22 + $ cat sub/normal2 + normal22 + $ cat sub/large2 + large22 + +Test that old revisions of a clone have correct largefiles content. This also +tsts update. + + $ hg update -r 1 + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + getting changed largefiles + 1 largefiles updated, 0 removed + $ cat large1 + large11 + $ cat sub/large2 + large22 + +Test that rebasing between two repositories does not revert largefiles to old +revisions (this was a very bad bug that took a lot of work to fix). + + $ cd .. + $ hg clone a d + updating to branch default + 5 files updated, 0 files merged, 0 files removed, 0 files unresolved + getting changed largefiles + 3 largefiles updated, 0 removed + $ cd b + $ echo large4-modified > sub/large4 + $ echo normal3-modified > normal3 + $ hg commit -m "modify normal file and largefile in repo b" + $ cd ../d + $ echo large6-modified > sub2/large6 + $ echo normal4-modified > sub/normal4 + $ hg commit -m "modify normal file largefile in repo d" + $ hg pull --rebase ../b + pulling from ../b + searching for changes + adding changesets + adding manifests + adding file changes + added 1 changesets with 2 changes to 2 files (+1 heads) + getting changed largefiles + 1 largefiles updated, 0 removed + saved backup bundle to $TESTTMP/d/.hg/strip-backup/f574fb32bb45-backup.hg + nothing to rebase + $ hg log + changeset: 9:598410d3eb9a + tag: tip + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: modify normal file largefile in repo d + + changeset: 8:a381d2c8c80e + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: modify normal file and largefile in repo b + + changeset: 7:daea875e9014 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add/edit more largefiles + + changeset: 6:4355d653f84f + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: edit files yet again + + changeset: 5:9d5af5072dbd + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: edit files again + + changeset: 4:74c02385b94c + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: move files + + changeset: 3:9e8fbc4bce62 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: copy files + + changeset: 2:51a0ae4d5864 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: remove files + + changeset: 1:ce8896473775 + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: edit files + + changeset: 0:30d30fe6a5be + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: add files + + $ cat normal3 + normal3-modified + $ cat sub/normal4 + normal4-modified + $ cat sub/large4 + large4-modified + $ cat sub2/large6 + large6-modified + $ cat sub2/large7 + large7
--- a/tests/test-mactext.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-mactext.t Sat Oct 15 14:30:50 2011 -0500 @@ -19,13 +19,11 @@ [hooks] pretxncommit.cr = python:hgext.win32text.forbidcr pretxnchangegroup.cr = python:hgext.win32text.forbidcr - $ echo - + $ echo hello > f $ hg add f $ hg ci -m 1 - $ echo - + $ python unix2mac.py f $ hg ci -m 2 Attempt to commit or push text file(s) using CR line endings
--- a/tests/test-merge-tools.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-merge-tools.t Sat Oct 15 14:30:50 2011 -0500 @@ -31,8 +31,7 @@ $ hg commit -Am "revision 3" created new head $ echo "[merge-tools]" > .hg/hgrc - $ echo - + $ beforemerge() { > cat .hg/hgrc > echo "# hg update -C 1" @@ -44,7 +43,6 @@ > echo "# hg stat" > hg stat > rm -f f.orig - > echo > } $ domerge() { > beforemerge @@ -52,14 +50,9 @@ > hg merge $* > aftermerge > } - $ echo - Tool selection - $ echo - - default is internal merge: $ beforemerge @@ -88,7 +81,6 @@ # hg stat M f ? f.orig - simplest hgrc using false for merge: @@ -108,7 +100,6 @@ # hg stat M f ? f.orig - true with higher .priority gets precedence: @@ -127,7 +118,6 @@ space # hg stat M f - unless lowered on command line: @@ -147,7 +137,6 @@ # hg stat M f ? f.orig - or false set higher on command line: @@ -167,7 +156,6 @@ # hg stat M f ? f.orig - or true.executable not found in PATH: @@ -187,7 +175,6 @@ # hg stat M f ? f.orig - or true.executable with bogus path: @@ -207,7 +194,6 @@ # hg stat M f ? f.orig - but true.executable set to cat found in PATH works: @@ -233,7 +219,6 @@ space # hg stat M f - and true.executable set to cat with path works: @@ -258,15 +243,33 @@ space # hg stat M f - - $ echo - + +environment variables in true.executable are handled: + + $ cat > $HGTMP/merge.sh <<EOF + > #!/bin/sh + > echo 'custom merge tool' + > EOF + $ chmod +x $HGTMP/merge.sh + $ domerge -r 2 --config merge-tools.true.executable='$HGTMP/merge.sh' + [merge-tools] + false.whatever= + true.priority=1 + true.executable=cat + # hg update -C 1 + # hg merge -r 2 --config merge-tools.true.executable=$HGTMP/merge.sh + merging f + custom merge tool + 0 files updated, 1 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + # cat f + revision 1 + space + # hg stat + M f Tool selection and merge-patterns - $ echo - - merge-patterns specifies new tool false: $ domerge -r 2 --config merge-patterns.f=false @@ -286,7 +289,6 @@ # hg stat M f ? f.orig - merge-patterns specifies executable not found in PATH and gets warning: @@ -308,7 +310,6 @@ # hg stat M f ? f.orig - merge-patterns specifies executable with bogus path and gets warning: @@ -330,15 +331,9 @@ # hg stat M f ? f.orig - - $ echo - ui.merge overrules priority - $ echo - - ui.merge specifies false: $ domerge -r 2 --config ui.merge=false @@ -358,7 +353,6 @@ # hg stat M f ? f.orig - ui.merge specifies internal:fail: @@ -376,7 +370,6 @@ space # hg stat M f - ui.merge specifies internal:local: @@ -394,7 +387,6 @@ space # hg stat M f - ui.merge specifies internal:other: @@ -412,7 +404,6 @@ space # hg stat M f - ui.merge specifies internal:prompt: @@ -432,7 +423,6 @@ space # hg stat M f - ui.merge specifies internal:dump: @@ -455,7 +445,6 @@ ? f.local ? f.orig ? f.other - f.base: @@ -475,8 +464,6 @@ revision 2 space $ rm f.base f.local f.other - $ echo - ui.merge specifies internal:other but is overruled by pattern for false: @@ -497,15 +484,9 @@ # hg stat M f ? f.orig - - $ echo - Premerge - $ echo - - ui.merge specifies internal:other but is overruled by --tool=false $ domerge -r 2 --config ui.merge=internal:other --tool=false @@ -525,7 +506,7 @@ # hg stat M f ? f.orig - + HGMERGE specifies internal:other but is overruled by --tool=false $ HGMERGE=internal:other ; export HGMERGE @@ -546,7 +527,7 @@ # hg stat M f ? f.orig - + $ unset HGMERGE # make sure HGMERGE doesn't interfere with remaining tests Default is silent simplemerge: @@ -567,7 +548,6 @@ revision 3 # hg stat M f - .premerge=True is same: @@ -587,7 +567,6 @@ revision 3 # hg stat M f - .premerge=False executes merge-tool: @@ -613,16 +592,11 @@ space # hg stat M f - - $ echo - Tool execution - $ echo - - $ echo '# set tools.args explicit to include $base $local $other $output:' # default '$local $base $other' - # set tools.args explicit to include $base $local $other $output: +set tools.args explicit to include $base $local $other $output: + $ beforemerge [merge-tools] false.whatever= @@ -655,9 +629,9 @@ space # hg stat M f - - $ echo '# Merge with "echo mergeresult > $local":' - # Merge with "echo mergeresult > $local": + +Merge with "echo mergeresult > $local": + $ beforemerge [merge-tools] false.whatever= @@ -673,9 +647,9 @@ mergeresult # hg stat M f - - $ echo '# - and $local is the file f:' - # - and $local is the file f: + +- and $local is the file f: + $ beforemerge [merge-tools] false.whatever= @@ -691,9 +665,9 @@ mergeresult # hg stat M f - - $ echo '# Merge with "echo mergeresult > $output" - the variable is a bit magic:' - # Merge with "echo mergeresult > $output" - the variable is a bit magic: + +Merge with "echo mergeresult > $output" - the variable is a bit magic: + $ beforemerge [merge-tools] false.whatever= @@ -709,7 +683,6 @@ mergeresult # hg stat M f - Merge using tool with a path that must be quoted: @@ -739,15 +712,9 @@ space # hg stat M f - - $ echo - Merge post-processing - $ echo - - cat is a bad merge-tool and doesn't change: $ domerge -y -r 2 --config merge-tools.true.checkchanged=1 @@ -775,4 +742,3 @@ # hg stat M f ? f.orig -
--- a/tests/test-merge1.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-merge1.t Sat Oct 15 14:30:50 2011 -0500 @@ -110,8 +110,7 @@ $ hg merge 2 abort: outstanding uncommitted changes (use 'hg status' to list changes) [255] - $ echo %% merge expected! - %% merge expected! +merge expected! $ hg merge -f 2 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit)
--- a/tests/test-minirst.py Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-minirst.py Sat Oct 15 14:30:50 2011 -0500 @@ -1,19 +1,30 @@ from pprint import pprint from mercurial import minirst -def debugformat(title, text, width, **kwargs): - print "%s formatted to fit within %d characters:" % (title, width) +def debugformat(text, form, **kwargs): + if form == 'html': + print "html format:" + out = minirst.format(text, style=form, **kwargs) + else: + print "%d column format:" % form + out = minirst.format(text, width=form, **kwargs) + print "-" * 70 - formatted = minirst.format(text, width, **kwargs) - if type(formatted) == tuple: - print formatted[0] + if type(out) == tuple: + print out[0][:-1] print "-" * 70 - pprint(formatted[1]) + pprint(out[1]) else: - print formatted + print out[:-1] print "-" * 70 print +def debugformats(title, text, **kwargs): + print "== %s ==" % title + debugformat(text, 60, **kwargs) + debugformat(text, 30, **kwargs) + debugformat(text, 'html', **kwargs) + paragraphs = """ This is some text in the first paragraph. @@ -23,9 +34,7 @@ \n \n \nThe third and final paragraph. """ -debugformat('paragraphs', paragraphs, 60) -debugformat('paragraphs', paragraphs, 30) - +debugformats('paragraphs', paragraphs) definitions = """ A Term @@ -40,9 +49,7 @@ Definition. """ -debugformat('definitions', definitions, 60) -debugformat('definitions', definitions, 30) - +debugformats('definitions', definitions) literals = r""" The fully minimized form is the most @@ -66,9 +73,7 @@ with '::' disappears in the final output. """ -debugformat('literals', literals, 60) -debugformat('literals', literals, 30) - +debugformats('literals', literals) lists = """ - This is the first list item. @@ -112,9 +117,7 @@ | This is the second line. """ -debugformat('lists', lists, 60) -debugformat('lists', lists, 30) - +debugformats('lists', lists) options = """ There is support for simple option lists, @@ -140,9 +143,7 @@ --foo bar baz """ -debugformat('options', options, 60) -debugformat('options', options, 30) - +debugformats('options', options) fields = """ :a: First item. @@ -155,8 +156,7 @@ :much too large: This key is big enough to get its own line. """ -debugformat('fields', fields, 60) -debugformat('fields', fields, 30) +debugformats('fields', fields) containers = """ Normal output. @@ -174,14 +174,14 @@ Debug output. """ -debugformat('containers (normal)', containers, 60) -debugformat('containers (verbose)', containers, 60, keep=['verbose']) -debugformat('containers (debug)', containers, 60, keep=['debug']) -debugformat('containers (verbose debug)', containers, 60, +debugformats('containers (normal)', containers) +debugformats('containers (verbose)', containers, keep=['verbose']) +debugformats('containers (debug)', containers, keep=['debug']) +debugformats('containers (verbose debug)', containers, keep=['verbose', 'debug']) roles = """Please see :hg:`add`.""" -debugformat('roles', roles, 60) +debugformats('roles', roles) sections = """ @@ -197,7 +197,7 @@ Markup: ``foo`` and :hg:`help` ------------------------------ """ -debugformat('sections', sections, 20) +debugformats('sections', sections) admonitions = """ @@ -214,7 +214,7 @@ This is danger """ -debugformat('admonitions', admonitions, 30) +debugformats('admonitions', admonitions) comments = """ Some text. @@ -230,4 +230,15 @@ Empty comment above """ -debugformat('comments', comments, 30) +debugformats('comments', comments) + + +data = [['a', 'b', 'c'], + ['1', '2', '3'], + ['foo', 'bar', 'baz this list is very very very long man']] + +table = minirst.maketable(data, 2, True) + +print table + +debugformats('table', table)
--- a/tests/test-minirst.py.out Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-minirst.py.out Sat Oct 15 14:30:50 2011 -0500 @@ -1,4 +1,5 @@ -paragraphs formatted to fit within 60 characters: +== paragraphs == +60 column format: ---------------------------------------------------------------------- This is some text in the first paragraph. @@ -8,7 +9,7 @@ The third and final paragraph. ---------------------------------------------------------------------- -paragraphs formatted to fit within 30 characters: +30 column format: ---------------------------------------------------------------------- This is some text in the first paragraph. @@ -21,7 +22,23 @@ The third and final paragraph. ---------------------------------------------------------------------- -definitions formatted to fit within 60 characters: +html format: +---------------------------------------------------------------------- +<p> +This is some text in the first paragraph. +</p> +<p> +A small indented paragraph. +It is followed by some lines +containing random whitespace. +</p> +<p> +The third and final paragraph. +</p> +---------------------------------------------------------------------- + +== definitions == +60 column format: ---------------------------------------------------------------------- A Term Definition. The indented lines make up the definition. @@ -35,7 +52,7 @@ Definition. ---------------------------------------------------------------------- -definitions formatted to fit within 30 characters: +30 column format: ---------------------------------------------------------------------- A Term Definition. The indented @@ -54,7 +71,20 @@ Definition. ---------------------------------------------------------------------- -literals formatted to fit within 60 characters: +html format: +---------------------------------------------------------------------- +<dl> + <dt>A Term + <dd>Definition. The indented lines make up the definition. + <dt>Another Term + <dd>Another definition. The final line in the definition determines the indentation, so this will be indented with four spaces. + <dt>A Nested/Indented Term + <dd>Definition. +</dl> +---------------------------------------------------------------------- + +== literals == +60 column format: ---------------------------------------------------------------------- The fully minimized form is the most convenient form: @@ -74,7 +104,7 @@ with '::' disappears in the final output. ---------------------------------------------------------------------- -literals formatted to fit within 30 characters: +30 column format: ---------------------------------------------------------------------- The fully minimized form is the most convenient form: @@ -96,7 +126,35 @@ with '::' disappears in the final output. ---------------------------------------------------------------------- -lists formatted to fit within 60 characters: +html format: +---------------------------------------------------------------------- +<p> +The fully minimized form is the most +convenient form: +</p> +<pre> +Hello + literal + world +</pre> +<p> +In the partially minimized form a paragraph +simply ends with space-double-colon. +</p> +<pre> +//////////////////////////////////////// +long un-wrapped line in a literal block +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ +</pre> +<pre> +This literal block is started with '::', + the so-called expanded form. The paragraph + with '::' disappears in the final output. +</pre> +---------------------------------------------------------------------- + +== lists == +60 column format: ---------------------------------------------------------------------- - This is the first list item. @@ -131,7 +189,7 @@ This is the second line. ---------------------------------------------------------------------- -lists formatted to fit within 30 characters: +30 column format: ---------------------------------------------------------------------- - This is the first list item. @@ -175,7 +233,53 @@ This is the second line. ---------------------------------------------------------------------- -options formatted to fit within 60 characters: +html format: +---------------------------------------------------------------------- +<ul> + <li> This is the first list item. +<p> +Second paragraph in the first list item. +</p> + <li> List items need not be separated by a blank line. + <li> And will be rendered without one in any case. +</ul> +<p> +We can have indented lists: +</p> +<ul> + <li> This is an indented list item + <li> Another indented list item: +<pre> +- A literal block in the middle + of an indented list. +</pre> +<pre> +(The above is not a list item since we are in the literal block.) +</pre> +</ul> +<pre> +Literal block with no indentation (apart from +the two spaces added to all literal blocks). +</pre> +<ol> + <li> This is an enumerated list (first item). + <li> Continuing with the second item. + <li> foo + <li> bar + <li> Another + <li> List +</ol> +<p> +Line blocks are also a form of list: +</p> +<ol> + <li> This is the first line. The line continues here. + <li> This is the second line. +</ol> +---------------------------------------------------------------------- + +== options == +60 column format: ---------------------------------------------------------------------- There is support for simple option lists, but only with long options: @@ -202,7 +306,7 @@ --foo bar baz ---------------------------------------------------------------------- -options formatted to fit within 30 characters: +30 column format: ---------------------------------------------------------------------- There is support for simple option lists, but only with @@ -274,7 +378,41 @@ --foo bar baz ---------------------------------------------------------------------- -fields formatted to fit within 60 characters: +html format: +---------------------------------------------------------------------- +<p> +There is support for simple option lists, +but only with long options: +</p> +<dl> + <dt>-X --exclude filter + <dd>an option with a short and long option with an argument + <dt>-I --include + <dd>an option with both a short option and a long option + <dt> --all + <dd>Output all. + <dt> --both + <dd>Output both (this description is quite long). + <dt> --long + <dd>Output all day long. + <dt> --par + <dd>This option has two paragraphs in its description. This is the first. +<p> +This is the second. Blank lines may be omitted between +options (as above) or left in (as here). +</p> +</dl> +<p> +The next paragraph looks like an option list, but lacks the two-space +marker after the option. It is treated as a normal paragraph: +</p> +<p> +--foo bar baz +</p> +---------------------------------------------------------------------- + +== fields == +60 column format: ---------------------------------------------------------------------- a First item. ab Second item. Indentation and wrapping is handled @@ -288,7 +426,7 @@ This key is big enough to get its own line. ---------------------------------------------------------------------- -fields formatted to fit within 30 characters: +30 column format: ---------------------------------------------------------------------- a First item. ab Second item. Indentation @@ -307,12 +445,45 @@ own line. ---------------------------------------------------------------------- -containers (normal) formatted to fit within 60 characters: +html format: +---------------------------------------------------------------------- +<dl> + <dt>a + <dd>First item. + <dt>ab + <dd>Second item. Indentation and wrapping is handled automatically. +</dl> +<p> +Next list: +</p> +<dl> + <dt>small + <dd>The larger key below triggers full indentation here. + <dt>much too large + <dd>This key is big enough to get its own line. +</dl> +---------------------------------------------------------------------- + +== containers (normal) == +60 column format: ---------------------------------------------------------------------- Normal output. ---------------------------------------------------------------------- -containers (verbose) formatted to fit within 60 characters: +30 column format: +---------------------------------------------------------------------- +Normal output. +---------------------------------------------------------------------- + +html format: +---------------------------------------------------------------------- +<p> +Normal output. +</p> +---------------------------------------------------------------------- + +== containers (verbose) == +60 column format: ---------------------------------------------------------------------- Normal output. @@ -321,7 +492,29 @@ ['debug', 'debug'] ---------------------------------------------------------------------- -containers (debug) formatted to fit within 60 characters: +30 column format: +---------------------------------------------------------------------- +Normal output. + +Verbose output. +---------------------------------------------------------------------- +['debug', 'debug'] +---------------------------------------------------------------------- + +html format: +---------------------------------------------------------------------- +<p> +Normal output. +</p> +<p> +Verbose output. +</p> +---------------------------------------------------------------------- +['debug', 'debug'] +---------------------------------------------------------------------- + +== containers (debug) == +60 column format: ---------------------------------------------------------------------- Normal output. @@ -330,7 +523,29 @@ ['verbose'] ---------------------------------------------------------------------- -containers (verbose debug) formatted to fit within 60 characters: +30 column format: +---------------------------------------------------------------------- +Normal output. + +Initial debug output. +---------------------------------------------------------------------- +['verbose'] +---------------------------------------------------------------------- + +html format: +---------------------------------------------------------------------- +<p> +Normal output. +</p> +<p> +Initial debug output. +</p> +---------------------------------------------------------------------- +['verbose'] +---------------------------------------------------------------------- + +== containers (verbose debug) == +60 column format: ---------------------------------------------------------------------- Normal output. @@ -343,12 +558,57 @@ [] ---------------------------------------------------------------------- -roles formatted to fit within 60 characters: +30 column format: +---------------------------------------------------------------------- +Normal output. + +Initial debug output. + +Verbose output. + +Debug output. +---------------------------------------------------------------------- +[] +---------------------------------------------------------------------- + +html format: +---------------------------------------------------------------------- +<p> +Normal output. +</p> +<p> +Initial debug output. +</p> +<p> +Verbose output. +</p> +<p> +Debug output. +</p> +---------------------------------------------------------------------- +[] +---------------------------------------------------------------------- + +== roles == +60 column format: ---------------------------------------------------------------------- Please see "hg add". ---------------------------------------------------------------------- -sections formatted to fit within 20 characters: +30 column format: +---------------------------------------------------------------------- +Please see "hg add". +---------------------------------------------------------------------- + +html format: +---------------------------------------------------------------------- +<p> +Please see "hg add". +</p> +---------------------------------------------------------------------- + +== sections == +60 column format: ---------------------------------------------------------------------- Title ===== @@ -363,7 +623,46 @@ --------------------------- ---------------------------------------------------------------------- -admonitions formatted to fit within 30 characters: +30 column format: +---------------------------------------------------------------------- +Title +===== + +Section +------- + +Subsection +'''''''''' + +Markup: "foo" and "hg help" +--------------------------- +---------------------------------------------------------------------- + +html format: +---------------------------------------------------------------------- +<h1>Title</h1> +<h2>Section</h2> +<h3>Subsection</h3> +<h2>Markup: "foo" and "hg help"</h2> +---------------------------------------------------------------------- + +== admonitions == +60 column format: +---------------------------------------------------------------------- +Note: + This is a note + + - Bullet 1 + - Bullet 2 + + Warning! + This is a warning Second input line of warning + +!Danger! + This is danger +---------------------------------------------------------------------- + +30 column format: ---------------------------------------------------------------------- Note: This is a note @@ -379,7 +678,34 @@ This is danger ---------------------------------------------------------------------- -comments formatted to fit within 30 characters: +html format: +---------------------------------------------------------------------- +<p> +<b>Note:</b> This is a note +</p> +<ul> + <li> Bullet 1 + <li> Bullet 2 +</ul> +<p> +<b>Warning!</b> This is a warning Second input line of warning +</p> +<p> +<b>!Danger!</b> This is danger +</p> +---------------------------------------------------------------------- + +== comments == +60 column format: +---------------------------------------------------------------------- +Some text. + + Some indented text. + +Empty comment above +---------------------------------------------------------------------- + +30 column format: ---------------------------------------------------------------------- Some text. @@ -388,3 +714,51 @@ Empty comment above ---------------------------------------------------------------------- +html format: +---------------------------------------------------------------------- +<p> +Some text. +</p> +<p> +Some indented text. +</p> +<p> +Empty comment above +</p> +---------------------------------------------------------------------- + + === === ======================================== + a b c + === === ======================================== + 1 2 3 + foo bar baz this list is very very very long man + === === ======================================== + +== table == +60 column format: +---------------------------------------------------------------------- + a b c + ------------------------------------------------ + 1 2 3 + foo bar baz this list is very very very long man +---------------------------------------------------------------------- + +30 column format: +---------------------------------------------------------------------- + a b c + ------------------------------ + 1 2 3 + foo bar baz this list is + very very very long + man +---------------------------------------------------------------------- + +html format: +---------------------------------------------------------------------- +<table> + <tr><th>a</th><th>b</th><th>c</th></tr> + <tr><td>1</td><td>2</td><td>3</td></tr> + <tr><td>foo</td><td>bar</td><td>baz this list is very very very long man</td></tr> +</table> +---------------------------------------------------------------------- +
--- a/tests/test-mq-guards.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-mq-guards.t Sat Oct 15 14:30:50 2011 -0500 @@ -434,3 +434,71 @@ $ hg --config extensions.color= --config color.mode=ansi qseries -m --color=always \x1b[0;31;1mb.patch\x1b[0m (esc) + + +excercise cornercases in "qselect --reapply" + + $ hg qpop -a + popping c.patch + popping new.patch + patch queue now empty + $ hg qguard -- new.patch -not-new + $ hg qguard -- c.patch -not-c + $ hg qguard -- d.patch -not-d + $ hg qpush -a + applying new.patch + applying c.patch + applying d.patch + patch d.patch is empty + now at: d.patch + $ hg qguard -l + new.patch: -not-new + c.patch: -not-c + d.patch: -not-d + $ hg qselect --reapply not-d + popping guarded patches + popping d.patch + now at: c.patch + reapplying unguarded patches + cannot push 'd.patch' - guarded by '-not-d' + $ hg qser -v + 0 A new.patch + 1 A c.patch + 2 G d.patch + $ hg qselect --reapply -n + guards deactivated + $ hg qpush + applying d.patch + patch d.patch is empty + now at: d.patch + $ hg qser -v + 0 A new.patch + 1 A c.patch + 2 A d.patch + $ hg qselect --reapply not-c + popping guarded patches + popping d.patch + popping c.patch + now at: new.patch + reapplying unguarded patches + applying d.patch + patch d.patch is empty + now at: d.patch + $ hg qser -v + 0 A new.patch + 1 G c.patch + 2 A d.patch + $ hg qselect --reapply not-new + popping guarded patches + popping d.patch + popping new.patch + patch queue now empty + reapplying unguarded patches + applying c.patch + applying d.patch + patch d.patch is empty + now at: d.patch + $ hg qser -v + 0 G new.patch + 1 A c.patch + 2 A d.patch
--- a/tests/test-mq-qimport-fail-cleanup.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-mq-qimport-fail-cleanup.t Sat Oct 15 14:30:50 2011 -0500 @@ -16,14 +16,10 @@ > a > +b > EOF - $ echo - empty series $ hg qseries - $ echo - qimport valid patch followed by invalid patch @@ -31,8 +27,6 @@ adding b.patch to series file abort: unable to read file fakepatch [255] - $ echo - valid patches before fail added to series
--- a/tests/test-mq-qrefresh-interactive.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-mq-qrefresh-interactive.t Sat Oct 15 14:30:50 2011 -0500 @@ -31,22 +31,22 @@ options: - -e --edit edit commit message - -g --git use git extended diff format - -s --short refresh only files already in the patch and - specified files - -U --currentuser add/update author field in patch with current user - -u --user USER add/update author field in patch with given user - -D --currentdate add/update date field in patch with current date - -d --date DATE add/update date field in patch with given date - -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 + -e --edit edit commit message + -g --git use git extended diff format + -s --short refresh only files already in the patch and + specified files + -U --currentuser add/update author field in patch with current user + -u --user USER add/update author field in patch with given user + -D --currentdate add/update date field in patch with current date + -d --date DATE add/update date field in patch with given date + -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 [+] marked option can be specified multiple times - use "hg -v help qrefresh" to show global options + use "hg -v help qrefresh" to show more info help qrefresh (record) @@ -75,23 +75,23 @@ options: - -e --edit edit commit message - -g --git use git extended diff format - -s --short refresh only files already in the patch and - specified files - -U --currentuser add/update author field in patch with current user - -u --user USER add/update author field in patch with given user - -D --currentdate add/update date field in patch with current date - -d --date DATE add/update date field in patch with given date - -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 - -i --interactive interactively select changes to refresh + -e --edit edit commit message + -g --git use git extended diff format + -s --short refresh only files already in the patch and + specified files + -U --currentuser add/update author field in patch with current user + -u --user USER add/update author field in patch with given user + -D --currentdate add/update date field in patch with current date + -d --date DATE add/update date field in patch with given date + -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 + -i --interactive interactively select changes to refresh [+] marked option can be specified multiple times - use "hg -v help qrefresh" to show global options + use "hg -v help qrefresh" to show more info $ hg init a $ cd a
--- a/tests/test-notify-changegroup.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-notify-changegroup.t Sat Oct 15 14:30:50 2011 -0500 @@ -72,4 +72,55 @@ @@ -0,0 +1,2 @@ +a +a + $ hg --cwd a rollback + repository tip rolled back to revision -1 (undo push) +unbundle with unrelated source + + $ hg --cwd b bundle ../test.hg ../a + searching for changes + 2 changesets found + $ hg --cwd a unbundle ../test.hg + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 1 files + (run 'hg update' to get a working copy) + $ hg --cwd a rollback + repository tip rolled back to revision -1 (undo unbundle) + +unbundle with correct source + + $ hg --config notify.sources=unbundle --cwd a unbundle ../test.hg 2>&1 | + > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),' + adding changesets + adding manifests + adding file changes + added 2 changesets with 2 changes to 1 files + Content-Type: text/plain; charset="us-ascii" + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + Date: * (glob) + Subject: * (glob) + From: test + X-Hg-Notification: changeset cb9a9f314b8b + Message-Id: <*> (glob) + To: baz, foo@bar + + changeset cb9a9f314b8b in $TESTTMP/a + details: $TESTTMP/a?cmd=changeset;node=cb9a9f314b8b + summary: a + + changeset ba677d0156c1 in $TESTTMP/a + details: $TESTTMP/a?cmd=changeset;node=ba677d0156c1 + summary: b + + diffs (6 lines): + + diff -r 000000000000 -r ba677d0156c1 a + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:00 1970 +0000 + @@ -0,0 +1,2 @@ + +a + +a + (run 'hg update' to get a working copy)
--- a/tests/test-notify.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-notify.t Sat Oct 15 14:30:50 2011 -0500 @@ -17,67 +17,111 @@ > * = baz > EOF $ hg help notify - notify extension - hooks for sending email notifications at commit/push time - - Subscriptions can be managed through a hgrc file. Default mode is to print - messages to stdout, for testing and configuring. + notify extension - hooks for sending email push notifications - To use, configure the notify extension and enable it in hgrc like this: + This extension let you run hooks sending email notifications when changesets + are being pushed, from the sending or receiving side. - [extensions] - notify = + First, enable the extension as explained in "hg help extensions", and register + the hook you want to run. "incoming" and "outgoing" hooks are run by the + changesets receiver while the "outgoing" one is for the sender: [hooks] # one email for each incoming changeset incoming.notify = python:hgext.notify.hook - # batch emails when many changesets incoming at one time + # one email for all incoming changesets changegroup.notify = python:hgext.notify.hook - # batch emails when many changesets outgoing at one time (client side) + + # one email for all outgoing changesets outgoing.notify = python:hgext.notify.hook - [notify] - # config items go here - - Required configuration items: - - config = /path/to/file # file containing subscriptions - - Optional configuration items: - - test = True # print messages to stdout for testing - strip = 3 # number of slashes to strip for url paths - domain = example.com # domain to use if committer missing domain - style = ... # style file to use when formatting email - template = ... # template to use when formatting email - incoming = ... # template to use when run as incoming hook - outgoing = ... # template to use when run as outgoing hook - changegroup = ... # template to use when run as changegroup hook - maxdiff = 300 # max lines of diffs to include (0=none, -1=all) - maxsubject = 67 # truncate subject line longer than this - diffstat = True # add a diffstat before the diff content - sources = serve # notify if source of incoming changes in this list - # (serve == ssh or http, push, pull, bundle) - merge = False # send notification for merges (default True) - [email] - from = user@host.com # email address to send as if none given - [web] - baseurl = http://hgserver/... # root of hg web site for browsing commits - - The notify config file has same format as a regular hgrc file. It has two - sections so you can express subscriptions in whatever way is handier for you. + Now the hooks are running, subscribers must be assigned to repositories. Use + the "[usersubs]" section to map repositories to a given email or the + "[reposubs]" section to map emails to a single repository: [usersubs] - # key is subscriber email, value is ","-separated list of glob patterns + # key is subscriber email, value is a comma-separated list of glob + # patterns user@host = pattern [reposubs] - # key is glob pattern, value is ","-separated list of subscriber emails + # key is glob pattern, value is a comma-separated list of subscriber + # emails pattern = user@host - Glob patterns are matched against path to repository root. + Glob patterns are matched against absolute path to repository root. The + subscriptions can be defined in their own file and referenced with: + + [notify] + config = /path/to/subscriptionsfile + + Alternatively, they can be added to Mercurial configuration files by setting + the previous entry to an empty value. + + At this point, notifications should be generated but will not be sent until + you set the "notify.test" entry to "False". + + Notifications content can be tweaked with the following configuration entries: + + notify.test + If "True", print messages to stdout instead of sending them. Default: True. + + notify.sources + Space separated list of change sources. Notifications are sent only if it + includes the incoming or outgoing changes source. Incoming sources can be + "serve" for changes coming from http or ssh, "pull" for pulled changes, + "unbundle" for changes added by "hg unbundle" or "push" for changes being + pushed locally. Outgoing sources are the same except for "unbundle" which is + replaced by "bundle". Default: serve. + + notify.strip + Number of leading slashes to strip from url paths. By default, notifications + references repositories with their absolute path. "notify.strip" let you + turn them into relative paths. For example, "notify.strip=3" will change + "/long/path/repository" into "repository". Default: 0. + + notify.domain + If subscribers emails or the from email have no domain set, complete them + with this value. - If you like, you can put notify config file in repository that users can push - changes to, they can manage their own subscriptions. + notify.style + Style file to use when formatting emails. + + notify.template + Template to use when formatting emails. + + notify.incoming + Template to use when run as incoming hook, override "notify.template". + + notify.outgoing + Template to use when run as outgoing hook, override "notify.template". + + notify.changegroup + Template to use when running as changegroup hook, override + "notify.template". + + notify.maxdiff + Maximum number of diff lines to include in notification email. Set to 0 to + disable the diff, -1 to include all of it. Default: 300. + + notify.maxsubject + Maximum number of characters in emails subject line. Default: 67. + + notify.diffstat + Set to True to include a diffstat before diff content. Default: True. + + notify.merge + If True, send notifications for merge changesets. Default: True. + + If set, the following entries will also be used to customize the + notifications: + + email.from + Email "From" address to use if none can be found in generated email content. + + web.baseurl + Root repository browsing URL to combine with repository paths when making + references. See also "notify.strip". no commands defined $ hg init a @@ -156,7 +200,6 @@ $ hg --cwd b rollback repository tip rolled back to revision 0 (undo pull) - working directory now based on revision 0 $ hg --cwd b pull ../a 2>&1 | grep 'error.*\.notify\.conf' > /dev/null && echo pull failed pull failed $ touch ".notify.conf" @@ -165,7 +208,6 @@ $ hg --cwd b rollback repository tip rolled back to revision 0 (undo pull) - working directory now based on revision 0 $ hg --traceback --cwd b pull ../a | \ > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),' pulling from ../a @@ -210,7 +252,6 @@ $ hg --cwd b rollback repository tip rolled back to revision 0 (undo pull) - working directory now based on revision 0 $ hg --traceback --cwd b pull ../a | \ > python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),' pulling from ../a
--- a/tests/test-patch-offset.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-patch-offset.t Sat Oct 15 14:30:50 2011 -0500 @@ -69,6 +69,7 @@ Hunk #2 succeeded at 87 (offset 34 lines). Hunk #3 succeeded at 109 (offset 34 lines). a + created 189885cecb41 compare imported changes against reference file
--- a/tests/test-patch.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-patch.t Sat Oct 15 14:30:50 2011 -0500 @@ -37,7 +37,7 @@ $ hg --cwd b import -v ../a.diff applying ../a.diff Using custom patch - + applied to working directory Issue2417: hg import with # comments in description
--- a/tests/test-patchbomb.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-patchbomb.t Sat Oct 15 14:30:50 2011 -0500 @@ -1469,13 +1469,71 @@ +ff2c9fa2018b15fa74b33363bda9527323e2a99f two +ff2c9fa2018b15fa74b33363bda9527323e2a99f two.diff - +no intro message in non-interactive mode $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar --in-reply-to baz \ - > -r 0:1 + > -r 0:1 | fixheaders This patch series consists of 2 patches. - abort: Subject: [PATCH 0 of 2] Please enter a valid value - [255] + Subject: [PATCH 0 of 2] + + Displaying [PATCH 1 of 2] a ... + Content-Type: text/plain; charset="us-ascii" + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + Subject: [PATCH 1 of 2] a + X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab + Message-Id: <8580ff50825a50c8f716.60@ + In-Reply-To: <baz> + References: <baz> + User-Agent: Mercurial-patchbomb + Date: Thu, 01 Jan 1970 00:01:00 +0000 + From: quux + To: foo + Cc: bar + + # HG changeset patch + # User test + # Date 1 0 + # Node ID 8580ff50825a50c8f716709acdf8de0deddcd6ab + # Parent 0000000000000000000000000000000000000000 + a + + diff -r 000000000000 -r 8580ff50825a a + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/a Thu Jan 01 00:00:01 1970 +0000 + @@ -0,0 +1,1 @@ + +a + + Displaying [PATCH 2 of 2] b ... + Content-Type: text/plain; charset="us-ascii" + MIME-Version: 1.0 + Content-Transfer-Encoding: 7bit + Subject: [PATCH 2 of 2] b + X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9 + Message-Id: <97d72e5f12c7e84f8506.61@ + In-Reply-To: <8580ff50825a50c8f716.60@ + References: <8580ff50825a50c8f716.60@ + User-Agent: Mercurial-patchbomb + Date: Thu, 01 Jan 1970 00:01:01 +0000 + From: quux + To: foo + Cc: bar + + # HG changeset patch + # User test + # Date 2 0 + # Node ID 97d72e5f12c7e84f85064aa72e5a297142c36ed9 + # Parent 8580ff50825a50c8f716709acdf8de0deddcd6ab + b + + diff -r 8580ff50825a -r 97d72e5f12c7 b + --- /dev/null Thu Jan 01 00:00:00 1970 +0000 + +++ b/b Thu Jan 01 00:00:02 1970 +0000 + @@ -0,0 +1,1 @@ + +b + + + $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar --in-reply-to baz \ > -s test -r 0:1 | fixheaders
--- a/tests/test-progress.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-progress.t Sat Oct 15 14:30:50 2011 -0500 @@ -9,16 +9,28 @@ > total = loops > if opts.get('total', None): > total = int(opts.get('total')) + > nested = False + > if opts.get('nested', None): + > nested = True > loops = abs(loops) > > for i in range(loops): > ui.progress('loop', i, 'loop.%d' % i, 'loopnum', total) + > if opts.get('parallel'): + > ui.progress('other', i, 'other.%d' % i, 'othernum', total) + > if nested: + > for j in range(2): + > ui.progress('nested', j, 'nested.%d' % j, 'nestnum', 2) + > ui.progress('nested', None, 'nested.done', 'nestnum', 2) > ui.progress('loop', None, 'loop.done', 'loopnum', total) > > commands.norepo += " loop" > > cmdtable = { - > "loop": (loop, [('', 'total', '', 'override for total')], + > "loop": (loop, [('', 'total', '', 'override for total'), + > ('', 'nested', False, 'show nested results'), + > ('', 'parallel', False, 'show parallel sets of results'), + > ], > 'hg loop LOOPS'), > } > EOF @@ -47,6 +59,42 @@ loop [===============================> ] 2/3 \r (esc) + +test nested short-lived topics (which shouldn't display with nestdelay): + + $ hg -y loop 3 --nested 2>&1 | \ + > python $TESTDIR/filtercr.py + + loop [ ] 0/3 + loop [===============> ] 1/3 + loop [===============================> ] 2/3 + \r (esc) + + + $ hg --config progress.changedelay=0 -y loop 3 --nested 2>&1 | \ + > python $TESTDIR/filtercr.py + + loop [ ] 0/3 + nested [ ] 0/2 + nested [======================> ] 1/2 + loop [===============> ] 1/3 + nested [ ] 0/2 + nested [======================> ] 1/2 + loop [===============================> ] 2/3 + nested [ ] 0/2 + nested [======================> ] 1/2 + \r (esc) + + +test two topics being printed in parallel (as when we're doing a local +--pull clone, where you get the unbundle and bundle progress at the +same time): + $ hg loop 3 --parallel 2>&1 | python $TESTDIR/filtercr.py + + loop [ ] 0/3 + loop [===============> ] 1/3 + loop [===============================> ] 2/3 + \r (esc) test refresh is taken in account $ hg -y --config progress.refresh=100 loop 3 2>&1 | $TESTDIR/filtercr.py
--- a/tests/test-push-http.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-push-http.t Sat Oct 15 14:30:50 2011 -0500 @@ -64,7 +64,6 @@ % 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 httpheader capability @@ -81,7 +80,6 @@ % 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 @@ -98,7 +96,6 @@ % 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
--- a/tests/test-qrecord.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-qrecord.t Sat Oct 15 14:30:50 2011 -0500 @@ -54,23 +54,23 @@ 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 - -w --ignore-all-space ignore white space when comparing lines - -b --ignore-space-change ignore changes in the amount of white space - -B --ignore-blank-lines ignore changes whose lines are all blank + -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 + -w --ignore-all-space ignore white space when comparing lines + -b --ignore-space-change ignore changes in the amount of white space + -B --ignore-blank-lines ignore changes whose lines are all blank [+] marked option can be specified multiple times - use "hg -v help record" to show global options + use "hg -v help record" to show more info help (no mq, so no qrecord) @@ -81,7 +81,7 @@ See "hg help qnew" & "hg help record" for more information and usage. - use "hg -v help qrecord" to show global options + use "hg -v help qrecord" to show more info $ hg init a @@ -113,7 +113,7 @@ See "hg help qnew" & "hg help record" for more information and usage. - use "hg -v help qrecord" to show global options + use "hg -v help qrecord" to show more info help (mq present) @@ -129,24 +129,24 @@ options: - -e --edit edit commit message - -g --git use git extended diff format - -U --currentuser add "From: <current user>" to patch - -u --user USER add "From: <USER>" to patch - -D --currentdate add "Date: <current date>" to patch - -d --date DATE add "Date: <DATE>" to patch - -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 - -w --ignore-all-space ignore white space when comparing lines - -b --ignore-space-change ignore changes in the amount of white space - -B --ignore-blank-lines ignore changes whose lines are all blank - --mq operate on patch repository + -e --edit edit commit message + -g --git use git extended diff format + -U --currentuser add "From: <current user>" to patch + -u --user USER add "From: <USER>" to patch + -D --currentdate add "Date: <current date>" to patch + -d --date DATE add "Date: <DATE>" to patch + -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 + -w --ignore-all-space ignore white space when comparing lines + -b --ignore-space-change ignore changes in the amount of white space + -B --ignore-blank-lines ignore changes whose lines are all blank + --mq operate on patch repository [+] marked option can be specified multiple times - use "hg -v help qrecord" to show global options + use "hg -v help qrecord" to show more info $ cd a
--- a/tests/test-rebase-collapse.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-rebase-collapse.t Sat Oct 15 14:30:50 2011 -0500 @@ -74,12 +74,12 @@ $ cd .. -Rebasing G onto H: +Rebasing E onto H: $ hg clone -q -u . a a2 $ cd a2 - $ hg rebase --base 6 --collapse + $ hg rebase --source 4 --collapse saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob) $ hg tglog @@ -115,7 +115,7 @@ abort: message can only be specified with collapse [255] - $ hg rebase --base 6 --collapse -m 'custom message' + $ hg rebase --source 4 --collapse -m 'custom message' saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob) $ hg tglog
--- a/tests/test-rebase-detach.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-rebase-detach.t Sat Oct 15 14:30:50 2011 -0500 @@ -281,3 +281,25 @@ |/ o 0: 'A' + + $ hg rebase -d 5 -s 7 + saved backup bundle to $TESTTMP/a5/.hg/strip-backup/13547172c9c0-backup.hg + $ hg tglog + @ 8: 'D' + | + o 7: 'C' + | + | o 6: 'B' + |/ + o 5: 'extra branch' + + o 4: 'H' + | + | o 3: 'G' + |/| + o | 2: 'F' + | | + | o 1: 'E' + |/ + o 0: 'A' +
--- a/tests/test-rebase-parameters.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-rebase-parameters.t Sat Oct 15 14:30:50 2011 -0500 @@ -51,8 +51,8 @@ $ cd a1 $ hg rebase -s 8 -d 7 - abort: source is descendant of destination - [255] + nothing to rebase + [1] $ hg rebase --continue --abort abort: cannot use both abort and continue @@ -67,7 +67,7 @@ [255] $ hg rebase --base 5 --source 4 - abort: cannot specify both a revision and a base + abort: cannot specify both a source and a base [255] $ hg rebase @@ -76,7 +76,7 @@ $ hg up -q 7 - $ hg rebase + $ hg rebase --traceback nothing to rebase [1]
--- a/tests/test-rebase-scenario-global.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-rebase-scenario-global.t Sat Oct 15 14:30:50 2011 -0500 @@ -212,8 +212,8 @@ $ cd a7 $ hg rebase -s 6 -d 5 - abort: source is descendant of destination - [255] + nothing to rebase + [1] F onto G - rebase onto a descendant: @@ -248,3 +248,261 @@ nothing to rebase [1] +C onto A - rebase onto an ancestor: + + $ hg rebase -d 0 -s 2 + saved backup bundle to $TESTTMP/a7/.hg/strip-backup/5fddd98957c8-backup.hg + $ hg tglog + @ 7: 'D' + | + o 6: 'C' + | + | o 5: 'H' + | | + | | o 4: 'G' + | |/| + | o | 3: 'F' + |/ / + | o 2: 'E' + |/ + | o 1: 'B' + |/ + o 0: 'A' + + $ cd .. + +Test for revset + +We need a bit different graph +All destination are B + + $ hg init ah + $ cd ah + $ hg unbundle $TESTDIR/bundles/rebase-revset.hg + adding changesets + adding manifests + adding file changes + added 9 changesets with 9 changes to 9 files (+2 heads) + (run 'hg heads' to see heads, 'hg merge' to merge) + $ hg tglog + o 8: 'I' + | + o 7: 'H' + | + o 6: 'G' + | + | o 5: 'F' + | | + | o 4: 'E' + |/ + o 3: 'D' + | + o 2: 'C' + | + | o 1: 'B' + |/ + o 0: 'A' + + $ cd .. + + +Simple case with keep: + +Source on have two descendant heads but ask for one + + $ hg clone -q -u . ah ah1 + $ cd ah1 + $ hg rebase -r '2::8' -d 1 + abort: can't remove original changesets with unrebased descendants + (use --keep to keep original changesets) + [255] + $ hg rebase -r '2::8' -d 1 --keep + $ hg tglog + @ 13: 'I' + | + o 12: 'H' + | + o 11: 'G' + | + o 10: 'D' + | + o 9: 'C' + | + | o 8: 'I' + | | + | o 7: 'H' + | | + | o 6: 'G' + | | + | | o 5: 'F' + | | | + | | o 4: 'E' + | |/ + | o 3: 'D' + | | + | o 2: 'C' + | | + o | 1: 'B' + |/ + o 0: 'A' + + + $ cd .. + +Base on have one descendant heads we ask for but common ancestor have two + + $ hg clone -q -u . ah ah2 + $ cd ah2 + $ hg rebase -r '3::8' -d 1 + abort: can't remove original changesets with unrebased descendants + (use --keep to keep original changesets) + [255] + $ hg rebase -r '3::8' -d 1 --keep + $ hg tglog + @ 12: 'I' + | + o 11: 'H' + | + o 10: 'G' + | + o 9: 'D' + |\ + | | o 8: 'I' + | | | + | | o 7: 'H' + | | | + | | o 6: 'G' + | | | + | | | o 5: 'F' + | | | | + | | | o 4: 'E' + | | |/ + | | o 3: 'D' + | |/ + | o 2: 'C' + | | + o | 1: 'B' + |/ + o 0: 'A' + + + $ cd .. + +rebase subset + + $ hg clone -q -u . ah ah3 + $ cd ah3 + $ hg rebase -r '3::7' -d 1 + abort: can't remove original changesets with unrebased descendants + (use --keep to keep original changesets) + [255] + $ hg rebase -r '3::7' -d 1 --keep + $ hg tglog + @ 11: 'H' + | + o 10: 'G' + | + o 9: 'D' + |\ + | | o 8: 'I' + | | | + | | o 7: 'H' + | | | + | | o 6: 'G' + | | | + | | | o 5: 'F' + | | | | + | | | o 4: 'E' + | | |/ + | | o 3: 'D' + | |/ + | o 2: 'C' + | | + o | 1: 'B' + |/ + o 0: 'A' + + + $ cd .. + +rebase subset with multiple head + + $ hg clone -q -u . ah ah4 + $ cd ah4 + $ hg rebase -r '3::(7+5)' -d 1 + abort: can't remove original changesets with unrebased descendants + (use --keep to keep original changesets) + [255] + $ hg rebase -r '3::(7+5)' -d 1 --keep + $ hg tglog + @ 13: 'H' + | + o 12: 'G' + | + | o 11: 'F' + | | + | o 10: 'E' + |/ + o 9: 'D' + |\ + | | o 8: 'I' + | | | + | | o 7: 'H' + | | | + | | o 6: 'G' + | | | + | | | o 5: 'F' + | | | | + | | | o 4: 'E' + | | |/ + | | o 3: 'D' + | |/ + | o 2: 'C' + | | + o | 1: 'B' + |/ + o 0: 'A' + + + $ cd .. + +More advanced tests + +rebase on ancestor with revset + + $ hg clone -q -u . ah ah5 + $ cd ah5 + $ hg rebase -r '6::' -d 2 + saved backup bundle to $TESTTMP/ah5/.hg/strip-backup/3d8a618087a7-backup.hg + $ hg tglog + @ 8: 'I' + | + o 7: 'H' + | + o 6: 'G' + | + | o 5: 'F' + | | + | o 4: 'E' + | | + | o 3: 'D' + |/ + o 2: 'C' + | + | o 1: 'B' + |/ + o 0: 'A' + + $ cd .. + + +rebase with multiple root. +We rebase E and G on B +We would expect heads are I, F if it was supported + + $ hg clone -q -u . ah ah6 + $ cd ah6 + $ hg rebase -r '(4+6)::' -d 1 + abort: can't rebase multiple roots + [255] + $ cd ..
--- a/tests/test-remove.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-remove.t Sat Oct 15 14:30:50 2011 -0500 @@ -29,7 +29,7 @@ $ echo b > bar $ hg add bar $ remove bar - not removing bar: file has been marked for add (use -f to force removal) + not removing bar: file has been marked for add (use forget to undo) exit code: 1 A bar ./bar
--- a/tests/test-rollback.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-rollback.t Sat Oct 15 14:30:50 2011 -0500 @@ -1,9 +1,9 @@ - +setup repo $ hg init t $ cd t $ echo a > a - $ hg add a - $ hg commit -m "test" + $ hg commit -Am'add a' + adding a $ hg verify checking changesets checking manifests @@ -11,12 +11,14 @@ checking files 1 files, 1 changesets, 1 total revisions $ hg parents - changeset: 0:acb14030fe0a + changeset: 0:1f0dee641bb7 tag: tip user: test date: Thu Jan 01 00:00:00 1970 +0000 - summary: test + summary: add a + +rollback to null revision $ hg status $ hg rollback repository tip rolled back to revision -1 (undo commit) @@ -31,22 +33,23 @@ $ hg status A a -Test issue 902 +Two changesets this time so we rollback to a real changeset + $ hg commit -m'add a again' + $ echo a >> a + $ hg commit -m'modify a' - $ hg commit -m "test2" +Test issue 902 (current branch is preserved) $ hg branch test marked working directory as branch test $ hg rollback - repository tip rolled back to revision -1 (undo commit) - working directory now based on revision -1 + repository tip rolled back to revision 0 (undo commit) + working directory now based on revision 0 $ hg branch default Test issue 1635 (commit message saved) -.hg/last-message.txt: - $ cat .hg/last-message.txt ; echo - test2 + modify a Test rollback of hg before issue 902 was fixed @@ -55,12 +58,41 @@ marked working directory as branch test $ rm .hg/undo.branch $ hg rollback - repository tip rolled back to revision -1 (undo commit) - named branch could not be reset, current branch is still: test - working directory now based on revision -1 + repository tip rolled back to revision 0 (undo commit) + named branch could not be reset: current branch is still 'test' + working directory now based on revision 0 $ hg branch test +working dir unaffected by rollback: do not restore dirstate et. al. + $ hg log --template '{rev} {branch} {desc|firstline}\n' + 0 default add a again + $ hg status + M a + $ hg bookmark foo + $ hg commit -m'modify a again' + $ echo b > b + $ hg commit -Am'add b' + adding b + $ hg log --template '{rev} {branch} {desc|firstline}\n' + 2 test add b + 1 test modify a again + 0 default add a again + $ hg update default + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg bookmark bar + $ cat .hg/undo.branch ; echo + test + $ hg rollback -f + repository tip rolled back to revision 1 (undo commit) + $ hg id -n + 0 + $ hg branch + default + $ cat .hg/bookmarks.current ; echo + bar + $ hg bookmark --delete foo + rollback by pretxncommit saves commit message (issue 1635) $ echo a >> a @@ -69,9 +101,6 @@ rollback completed abort: pretxncommit hook exited with status * (glob) [255] - -.hg/last-message.txt: - $ cat .hg/last-message.txt ; echo precious commit message @@ -102,18 +131,53 @@ adding changesets adding manifests adding file changes - added 1 changesets with 1 changes to 1 files - updating to branch test + added 3 changesets with 2 changes to 1 files (+1 heads) + updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd u $ hg id default - 1df294f7b1a2 + 068774709090 now rollback and observe that 'hg serve' reloads the repository and presents the correct tip changeset: $ hg -R ../t rollback - repository tip rolled back to revision -1 (undo commit) - working directory now based on revision -1 + repository tip rolled back to revision 1 (undo commit) + working directory now based on revision 0 $ hg id default - 000000000000 + 791dd2169706 + +update to older changeset and then refuse rollback, because +that would lose data (issue2998) + $ cd ../t + $ hg -q update + $ rm `hg status -un` + $ template='{rev}:{node|short} [{branch}] {desc|firstline}\n' + $ echo 'valuable new file' > b + $ echo 'valuable modification' >> a + $ hg commit -A -m'a valuable change' + adding b + $ hg update 0 + 1 files updated, 0 files merged, 1 files removed, 0 files unresolved + $ hg rollback + abort: rollback of last commit while not checked out may lose data + (use -f to force) + [255] + $ hg tip -q + 2:4d9cd3795eea + $ hg rollback -f + repository tip rolled back to revision 1 (undo commit) + $ hg status + $ hg log --removed b # yep, it's gone + +same again, but emulate an old client that doesn't write undo.desc + $ hg -q update + $ echo 'valuable modification redux' >> a + $ hg commit -m'a valuable change redux' + $ rm .hg/undo.desc + $ hg update 0 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg rollback + rolling back unknown transaction + $ cat a + a
--- a/tests/test-run-tests.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-run-tests.t Sat Oct 15 14:30:50 2011 -0500 @@ -16,6 +16,20 @@ $ foo bar +Doctest commands: + + >>> print 'foo' + foo + $ echo interleaved + interleaved + >>> for c in 'xyz': + ... print c + x + y + z + >>> print + <BLANKLINE> + Regular expressions: $ echo foobarbaz
--- a/tests/test-setdiscovery.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-setdiscovery.t Sat Oct 15 14:30:50 2011 -0500 @@ -288,7 +288,7 @@ reading DAG from stdin $ hg heads -t --template . | wc -c - *261 (re) + \s*261 (re) $ hg clone -b a . a adding changesets
--- a/tests/test-share.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-share.t Sat Oct 15 14:30:50 2011 -0500 @@ -28,6 +28,15 @@ $ cat .hg/sharedpath; echo $TESTTMP/repo1/.hg +trailing newline on .hg/sharedpath is ok + $ hg tip -q + 0:d3873e73d99e + $ echo '' >> .hg/sharedpath + $ cat .hg/sharedpath + $TESTTMP/repo1/.hg + $ hg tip -q + 0:d3873e73d99e + commit in shared clone $ echo a >> a @@ -97,3 +106,21 @@ -rw-r--r-- 2 b + +test unshare command + + $ hg unshare + $ test -d .hg/store + $ test -f .hg/sharedpath + [1] + $ hg unshare + abort: this is not a shared repo + [255] + +check that a change does not propagate + + $ echo b >> b + $ hg commit -m'change in unshared' + $ cd ../repo1 + $ hg id -r tip + c2e0ac586386 tip
--- a/tests/test-subrepo-paths.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-subrepo-paths.t Sat Oct 15 14:30:50 2011 -0500 @@ -1,19 +1,22 @@ $ hg init outer $ cd outer + $ echo '[paths]' >> .hg/hgrc + $ echo 'default = http://example.net/' >> .hg/hgrc + hg debugsub with no remapping - $ echo 'sub = http://example.net/libfoo' > .hgsub + $ echo 'sub = libfoo' > .hgsub $ hg add .hgsub $ hg debugsub path sub - source http://example.net/libfoo + source libfoo revision hg debugsub with remapping - $ echo '[subpaths]' > .hg/hgrc + $ echo '[subpaths]' >> .hg/hgrc $ printf 'http://example.net/lib(.*) = C:\\libs\\\\1-lib\\\n' >> .hg/hgrc $ hg debugsub @@ -30,6 +33,21 @@ source C:\libs\bar-lib\ revision +test absolute source path -- testing with a URL is important since +standard os.path.join wont treat that as an absolute path + + $ echo 'abs = http://example.net/abs' > .hgsub + $ hg debugsub + path abs + source http://example.net/abs + revision + + $ echo 'abs = /abs' > .hgsub + $ hg debugsub + path abs + source /abs + revision + test bad subpaths pattern $ cat > .hg/hgrc <<EOF
--- a/tests/test-subrepo.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-subrepo.t Sat Oct 15 14:30:50 2011 -0500 @@ -34,6 +34,18 @@ $ hg ci -m1 committing subrepository s +Revert can't (yet) revert subrepos: + + $ echo b > s/a + $ hg revert s + s: reverting subrepos is unsupported + +Revert currently ignores subrepos by default + + $ hg revert -a + $ hg revert -R s -a -C + reverting s/a + Issue2022: update -C $ echo b > s/a @@ -52,6 +64,14 @@ commit: (clean) update: (current) +commands that require a clean repo should respect subrepos + + $ echo b >> s/a + $ hg backout tip + abort: uncommitted changes in subrepo s + [255] + $ hg revert -C -R s s/a + add sub sub $ echo ss = ss > s/.hgsub
--- a/tests/test-symlink-os-yes-fs-no.py Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-symlink-os-yes-fs-no.py Sat Oct 15 14:30:50 2011 -0500 @@ -5,7 +5,7 @@ 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"): +if not getattr(os, "symlink", False): sys.exit(80) # SKIPPED_STATUS defined in run-tests.py # clone with symlink support
--- a/tests/test-tags.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-tags.t Sat Oct 15 14:30:50 2011 -0500 @@ -135,8 +135,6 @@ $ echo >> .hgtags $ echo "foo bar" >> .hgtags $ echo "a5a5 invalid" >> .hg/localtags - $ echo "committing .hgtags:" - committing .hgtags: $ cat .hgtags acb14030fe0a21b60322c440ad2d20cf7685a376 first spam
--- a/tests/test-transplant.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-transplant.t Sat Oct 15 14:30:50 2011 -0500 @@ -81,6 +81,19 @@ 1 0 +rollback the transplant + $ hg rollback + repository tip rolled back to revision 4 (undo transplant) + working directory now based on revision 1 + $ hg tip -q + 4:a53251cdf717 + $ hg parents -q + 1:d11e3596cc1a + $ hg status + ? b1 + ? b2 + ? b3 + $ hg clone ../t ../prune updating to branch default 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-url-rev.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-url-rev.t Sat Oct 15 14:30:50 2011 -0500 @@ -102,7 +102,6 @@ $ cd clone $ hg rollback repository tip rolled back to revision 1 (undo push) - working directory now based on revision 1 $ hg -q incoming 2:faba9097cad4 @@ -147,10 +146,6 @@ $ hg rollback repository tip rolled back to revision 1 (undo pull) - working directory now based on revision 1 - - $ hg up -C 0 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg parents -q 0:1f0dee641bb7 @@ -205,4 +200,8 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: new head of branch foo +Test handling of invalid urls + $ hg id http://foo/?bar + abort: unsupported URL component: "bar" + [255]
--- a/tests/test-walkrepo.py Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-walkrepo.py Sat Oct 15 14:30:50 2011 -0500 @@ -5,7 +5,7 @@ from os.path import join as pjoin u = ui.ui() -sym = hasattr(os, 'symlink') and hasattr(os.path, 'samestat') +sym = getattr(os, 'symlink', False) and getattr(os.path, 'samestat', False) hg.repository(u, 'top1', create=1) mkdir('subdir')
--- a/tests/test-win32text.t Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/test-win32text.t Sat Oct 15 14:30:50 2011 -0500 @@ -16,16 +16,14 @@ [hooks] pretxncommit.crlf = python:hgext.win32text.forbidcrlf pretxnchangegroup.crlf = python:hgext.win32text.forbidcrlf - $ echo - + $ echo hello > f $ hg add f commit should succeed $ hg ci -m 1 - $ echo - + $ hg clone . ../zoz updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved @@ -41,8 +39,7 @@ rollback completed abort: pretxncommit.crlf hook failed [255] - $ echo - + $ mv .hg/hgrc .hg/hgrc.bak commits should succeed @@ -50,8 +47,6 @@ $ hg ci -m 2 $ hg cp f g $ hg ci -m 2.2 - $ echo - push should fail @@ -84,8 +79,7 @@ rollback completed abort: pretxnchangegroup.crlf hook failed [255] - $ echo - + $ mv .hg/hgrc.bak .hg/hgrc $ echo hello > f $ hg rm g @@ -93,8 +87,6 @@ commit should succeed $ hg ci -m 2.3 - $ echo - push should succeed @@ -105,8 +97,6 @@ adding manifests adding file changes added 3 changesets with 3 changes to 2 files - $ echo - and now for something completely different @@ -124,12 +114,10 @@ $ hg revert -a forgetting d/f2 $ rm d/f2 - $ echo - + $ hg rem f $ hg ci -m 4 - $ echo - + $ python -c 'file("bin", "wb").write("hello\x00\x0D\x0A")' $ hg add bin $ hg ci -m 5 @@ -183,13 +171,10 @@ 1 - $ echo - $ hg clone . dupe updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved - $ echo - + $ for x in a b c d; do echo content > dupe/$x; done $ hg -R dupe add adding dupe/a @@ -274,8 +259,6 @@ 1 - $ echo - $ hg pull dupe pulling from dupe searching for changes @@ -306,8 +289,7 @@ rollback completed abort: pretxnchangegroup.crlf hook failed [255] - $ echo - + $ hg log -v changeset: 5:f0b1c8d75fce tag: tip @@ -358,8 +340,6 @@ 1 - $ echo - $ rm .hg/hgrc $ (echo some; echo text) > f3 $ python -c 'file("f4.bat", "wb").write("rem empty\x0D\x0A")' @@ -372,8 +352,7 @@ text $ cat f4.bat rem empty\r (esc) - $ echo - + $ echo '[extensions]' >> .hg/hgrc $ echo 'win32text = ' >> .hg/hgrc $ echo '[decode]' >> .hg/hgrc @@ -415,8 +394,7 @@ text\r (esc) $ cat f4.bat rem empty\r (esc) - $ echo - + $ python -c 'file("f5.sh", "wb").write("# empty\x0D\x0A")' $ hg add f5.sh $ hg ci -m 7
--- a/tests/tinyproxy.py Sun Oct 02 16:41:07 2011 -0500 +++ b/tests/tinyproxy.py Sat Oct 15 14:30:50 2011 -0500 @@ -23,7 +23,8 @@ def handle(self): (ip, port) = self.client_address - if hasattr(self, 'allowed_clients') and ip not in self.allowed_clients: + allowed = getattr(self, 'allowed_clients', None) + if allowed is not None and ip not in allowed: self.raw_requestline = self.rfile.readline() if self.parse_request(): self.send_error(403)