# HG changeset patch # User Matt Mackall # Date 1152124105 18000 # Node ID 2748253b49c28a9df0395cbe30baa8b07aea52b9 # Parent bf67d0f6531c5ed29ababbbfd62f5371f3fa9c85# Parent d8560b458f76cf10d1b9289d22b18edf44ccc18d Merge context patches diff --git a/Makefile b/Makefile --- a/Makefile +++ b/Makefile @@ -37,12 +37,20 @@ clean: rm -f MANIFEST mercurial/__version__.py mercurial/*.so tests/*.err $(MAKE) -C doc clean -install: all +install: install-bin install-doc + +install-bin: build $(PYTHON) setup.py install --prefix="$(PREFIX)" --force + +install-doc: doc cd doc && $(MAKE) $(MFLAGS) install -install-home: all +install-home: install-home-bin install-home-doc + +install-home-bin: build $(PYTHON) setup.py install --home="$(HOME)" --force + +install-home-doc: doc cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install dist: tests dist-notests @@ -57,5 +65,5 @@ test-%: cd tests && $(PYTHON) run-tests.py $@ -.PHONY: help all local build doc clean install install-home dist dist-notests tests - +.PHONY: help all local build doc clean install install-bin install-doc \ + install-home install-home-bin install-home-doc dist dist-notests tests diff --git a/contrib/macosx/Readme.html b/contrib/macosx/Readme.html --- a/contrib/macosx/Readme.html +++ b/contrib/macosx/Readme.html @@ -18,13 +18,10 @@


This is not a stand-alone version of Mercurial.


-

To use it, you must have the “official unofficial” MacPython 2.4.1 installed.

+

To use it, you must have the Universal MacPython 2.4.3 from www.python.org installed.


-

You can download MacPython 2.4.1 from here:

-

http://python.org/ftp/python/2.4.1/MacPython-OSX-2.4.1-1.dmg

-


-

For more information on MacPython, go here:

-

http://undefined.org/python

+

You can download MacPython 2.4.3 from here:

+

http://www.python.org/ftp/python/2.4.3/Universal-MacPython-2.4.3-2006-04-07.dmg


After you install


diff --git a/contrib/macosx/Welcome.html b/contrib/macosx/Welcome.html --- a/contrib/macosx/Welcome.html +++ b/contrib/macosx/Welcome.html @@ -12,6 +12,6 @@

This is a prepackaged release of Mercurial for Mac OS X.


-

It is based on Mercurial 0.8.

+

It is based on Mercurial 0.9.

diff --git a/contrib/mercurial.el b/contrib/mercurial.el --- a/contrib/mercurial.el +++ b/contrib/mercurial.el @@ -653,7 +653,7 @@ The Mercurial mode user interface is bas you're already familiar with VC, the same keybindings and functions will generally work. -Below is a list of many common SCM tasks. In the list, `G/L' +Below is a list of many common SCM tasks. In the list, `G/L\' indicates whether a key binding is global (G) to a repository or local (L) to a file. Many commands take a prefix argument. @@ -682,6 +682,8 @@ Pull changes G Update working directory after pull G C-c h u hg-update See changes that can be pushed G C-c h . hg-outgoing Push changes G C-c h > hg-push" + (unless vc-make-backup-files + (set (make-local-variable 'backup-inhibited) t)) (run-hooks 'hg-mode-hook)) (defun hg-find-file-hook () @@ -729,6 +731,8 @@ With a prefix argument, prompt for the p (goto-char 0) (cd (hg-root path))) (when update + (unless vc-make-backup-files + (set (make-local-variable 'backup-inhibited) t)) (with-current-buffer buf (hg-mode-line))))) @@ -968,6 +972,7 @@ With a prefix argument, prompt for the p (cd (hg-root path))) (when update (with-current-buffer buf + (set (make-local-variable 'backup-inhibited) nil) (hg-mode-line))))) (defun hg-incoming (&optional repo) diff --git a/contrib/win32/mercurial.ini b/contrib/win32/mercurial.ini --- a/contrib/win32/mercurial.ini +++ b/contrib/win32/mercurial.ini @@ -18,7 +18,8 @@ hgext.win32text = [encode] ; Encode files that don't contain NUL characters. -** = cleverencode: + +; ** = cleverencode: ; Alternatively, you can explicitly specify each file extension that ; you want encoded (any you omit will be left untouched), like this: @@ -28,7 +29,8 @@ hgext.win32text = [decode] ; Decode files that don't contain NUL characters. -** = cleverdecode: + +; ** = cleverdecode: ; Alternatively, you can explicitly specify each file extension that ; you want decoded (any you omit will be left untouched), like this: diff --git a/hgext/hgk.py b/hgext/hgk.py --- a/hgext/hgk.py +++ b/hgext/hgk.py @@ -131,7 +131,7 @@ def catcommit(repo, n, prefix, changes=N date_ar = changes[2] date = int(float(date_ar[0])) lines = changes[4].splitlines() - if lines[-1].startswith('committer:'): + if lines and lines[-1].startswith('committer:'): committer = lines[-1].split(': ')[1].rstrip() else: committer = changes[1] diff --git a/hgext/mq.py b/hgext/mq.py --- a/hgext/mq.py +++ b/hgext/mq.py @@ -5,6 +5,30 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. +'''patch management and development + +This extension lets you work with a stack of patches in a Mercurial +repository. It manages two stacks of patches - all known patches, and +applied patches (subset of known patches). + +Known patches are represented as patch files in the .hg/patches +directory. Applied patches are both patch files and changesets. + +Common tasks (use "hg help command" for more details): + +prepare repository to work with patches qinit +create new patch qnew +import existing patch qimport + +print patch series qseries +print applied patches qapplied +print name of top applied patch qtop + +add known patch to applied stack qpush +remove patch from applied stack qpop +refresh contents of top applied patch qrefresh +''' + from mercurial.demandload import * demandload(globals(), "os sys re struct traceback errno bz2") from mercurial.i18n import gettext as _ @@ -214,7 +238,6 @@ class queue: return pp[0] if p1 in arevs: return pp[1] - return None return pp[0] def mergepatch(self, repo, mergeq, series, wlock): @@ -386,15 +409,21 @@ class queue: self.ui.write("Local changes found, refresh first\n") sys.exit(1) def new(self, repo, patch, msg=None, force=None): - if not force: - self.check_localchanges(repo) + commitfiles = [] + (c, a, r, d, u) = repo.changes(None, None) + if c or a or d or r: + if not force: + raise util.Abort(_("Local changes found, refresh first")) + else: + commitfiles = c + a + r self.check_toppatch(repo) wlock = repo.wlock() insert = self.series_end() if msg: - n = repo.commit([], "[mq]: %s" % msg, force=True, wlock=wlock) + n = repo.commit(commitfiles, "[mq]: %s" % msg, force=True, + wlock=wlock) else: - n = repo.commit([], + n = repo.commit(commitfiles, "New patch: %s" % patch, force=True, wlock=wlock) if n == None: self.ui.warn("repo commit failed\n") @@ -412,6 +441,8 @@ class queue: wlock = None r = self.qrepo() if r: r.add([patch]) + if commitfiles: + self.refresh(repo, short=True) def strip(self, repo, rev, update=True, backup="all", wlock=None): def limitheads(chlog, stop): @@ -1076,6 +1107,7 @@ def init(ui, repo, **opts): return 0 def commit(ui, repo, *pats, **opts): + """commit changes in the queue repository""" q = repomap[repo] r = q.qrepo() if not r: raise util.Abort('no queue repository') @@ -1257,7 +1289,7 @@ cmdtable = { 'hg qimport [-e] [-n NAME] [-f] FILE...'), "^qinit": (init, - [('c', 'create-repo', None, 'create patch repository')], + [('c', 'create-repo', None, 'create queue repository')], 'hg qinit [-c]'), "qnew": (new, diff --git a/hgweb.cgi b/hgweb.cgi --- a/hgweb.cgi +++ b/hgweb.cgi @@ -6,7 +6,11 @@ import cgitb, os, sys cgitb.enable() # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install -from mercurial import hgweb +from mercurial.hgweb.hgweb_mod import hgweb +from mercurial.hgweb.request import wsgiapplication +import mercurial.hgweb.wsgicgi as wsgicgi -h = hgweb.hgweb("/path/to/repo", "repository name") -h.run() +def make_web_app(): + return hgweb("/path/to/repo", "repository name") + +wsgicgi.launch(wsgiapplication(make_web_app)) diff --git a/hgwebdir.cgi b/hgwebdir.cgi --- a/hgwebdir.cgi +++ b/hgwebdir.cgi @@ -6,7 +6,9 @@ import cgitb, sys cgitb.enable() # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install -from mercurial import hgweb +from mercurial.hgweb.hgwebdir_mod import hgwebdir +from mercurial.hgweb.request import wsgiapplication +import mercurial.hgweb.wsgicgi as wsgicgi # The config file looks like this. You can have paths to individual # repos, collections of repos in a directory tree, or both. @@ -27,5 +29,7 @@ from mercurial import hgweb # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples # or use a dictionary with entries like 'virtual/path': '/real/path' -h = hgweb.hgwebdir("hgweb.config") -h.run() +def make_web_app(): + return hgwebdir("hgweb.config") + +wsgicgi.launch(wsgiapplication(make_web_app)) diff --git a/mercurial/bdiff.c b/mercurial/bdiff.c --- a/mercurial/bdiff.c +++ b/mercurial/bdiff.c @@ -38,7 +38,7 @@ static uint32_t htonl(uint32_t x) #else #include #include -#include +#include #endif struct line { diff --git a/mercurial/changelog.py b/mercurial/changelog.py --- a/mercurial/changelog.py +++ b/mercurial/changelog.py @@ -39,21 +39,10 @@ class changelog(revlog): def add(self, manifest, list, desc, transaction, p1=None, p2=None, user=None, date=None): if date: - # validate explicit (probably user-specified) date and - # time zone offset. values must fit in signed 32 bits for - # current 32-bit linux runtimes. timezones go from UTC-12 - # to UTC+14 - try: - when, offset = map(int, date.split(' ')) - except ValueError: - raise ValueError(_('invalid date: %r') % date) - if abs(when) > 0x7fffffff: - raise ValueError(_('date exceeds 32 bits: %d') % when) - if offset < -50400 or offset > 43200: - raise ValueError(_('impossible time zone offset: %d') % offset) + parseddate = "%d %d" % util.parsedate(date) else: - date = "%d %d" % util.makedate() + parseddate = "%d %d" % util.makedate() list.sort() - l = [hex(manifest), user, date] + list + ["", desc] + l = [hex(manifest), user, parseddate] + list + ["", desc] text = "\n".join(l) return self.addrevision(text, transaction, self.count(), p1, p2) diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -12,7 +12,7 @@ demandload(globals(), "os re sys signal demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo") demandload(globals(), "fnmatch mdiff random signal tempfile time") demandload(globals(), "traceback errno socket version struct atexit sets bz2") -demandload(globals(), "archival changegroup") +demandload(globals(), "archival cStringIO changegroup email.Parser") demandload(globals(), "hgweb.server sshserver") class UnknownCommand(Exception): @@ -534,14 +534,22 @@ def show_version(ui): "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" )) -def help_(ui, cmd=None, with_version=False): - """show help for a given command or all commands""" +def help_(ui, name=None, with_version=False): + """show help for a command, extension, or list of commands + + With no arguments, print a list of commands and short help. + + Given a command name, print help for that command. + + Given an extension name, print help for that extension, and the + commands it provides.""" option_lists = [] - if cmd and cmd != 'shortlist': + + def helpcmd(name): if with_version: show_version(ui) ui.write('\n') - aliases, i = find(cmd) + aliases, i = findcmd(name) # synopsis ui.write("%s\n\n" % i[2]) @@ -561,30 +569,15 @@ def help_(ui, cmd=None, with_version=Fal # options if i[1]: option_lists.append(("options", i[1])) - - else: - # program name - if ui.verbose or with_version: - show_version(ui) - else: - ui.status(_("Mercurial Distributed SCM\n")) - ui.status('\n') - - # list of commands - if cmd == "shortlist": - ui.status(_('basic commands (use "hg help" ' - 'for the full list or option "-v" for details):\n\n')) - elif ui.verbose: - ui.status(_('list of commands:\n\n')) - else: - ui.status(_('list of commands (use "hg help -v" ' - 'to show aliases and global options):\n\n')) - + + def helplist(select=None): h = {} cmds = {} for c, e in table.items(): - f = c.split("|")[0] - if cmd == "shortlist" and not f.startswith("^"): + f = c.split("|", 1)[0] + if select and not select(f): + continue + if name == "shortlist" and not f.startswith("^"): continue f = f.lstrip("^") if not ui.debugflag and f.startswith("debug"): @@ -605,6 +598,53 @@ def help_(ui, cmd=None, with_version=Fal else: ui.write(' %-*s %s\n' % (m, f, h[f])) + def helpext(name): + try: + mod = findext(name) + except KeyError: + raise UnknownCommand(name) + + doc = (mod.__doc__ or _('No help text available')).splitlines(0) + ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0])) + for d in doc[1:]: + ui.write(d, '\n') + + ui.status('\n') + if ui.verbose: + ui.status(_('list of commands:\n\n')) + else: + ui.status(_('list of commands (use "hg help -v %s" ' + 'to show aliases and global options):\n\n') % name) + + modcmds = dict.fromkeys([c.split('|', 1)[0] for c in mod.cmdtable]) + helplist(modcmds.has_key) + + if name and name != 'shortlist': + try: + helpcmd(name) + except UnknownCommand: + helpext(name) + + else: + # program name + if ui.verbose or with_version: + show_version(ui) + else: + ui.status(_("Mercurial Distributed SCM\n")) + ui.status('\n') + + # list of commands + if name == "shortlist": + ui.status(_('basic commands (use "hg help" ' + 'for the full list or option "-v" for details):\n\n')) + elif ui.verbose: + ui.status(_('list of commands:\n\n')) + else: + ui.status(_('list of commands (use "hg help -v" ' + 'to show aliases and global options):\n\n')) + + helplist() + # global options if ui.verbose: option_lists.append(("global options", globalopts)) @@ -650,7 +690,7 @@ def add(ui, repo, *pats, **opts): elif repo.dirstate.state(abs) == '?': ui.status(_('adding %s\n') % rel) names.append(abs) - if not opts['dry_run']: + if not opts.get('dry_run'): repo.add(names) def addremove(ui, repo, *pats, **opts): @@ -908,13 +948,10 @@ def clone(ui, source, dest=None, **opts) if os.path.exists(dest): raise util.Abort(_("destination '%s' already exists"), dest) - dest = os.path.realpath(dest) - class Dircleanup(object): def __init__(self, dir_): self.rmtree = shutil.rmtree self.dir_ = dir_ - os.mkdir(dir_) def close(self): self.dir_ = None def __del__(self): @@ -927,13 +964,24 @@ def clone(ui, source, dest=None, **opts) ui.setconfig("ui", "remotecmd", opts['remotecmd']) source = ui.expandpath(source) - - d = Dircleanup(dest) + src_repo = hg.repository(ui, source) + + dest_repo = None + try: + dest_repo = hg.repository(ui, dest) + raise util.Abort(_("destination '%s' already exists." % dest)) + except hg.RepoError: + dest_repo = hg.repository(ui, dest, create=1) + + dest_path = None + d = None + if dest_repo.local(): + dest_path = os.path.realpath(dest) + d = Dircleanup(dest_path) + abspath = source - other = hg.repository(ui, source) - copy = False - if other.dev() != -1: + if src_repo.local() and dest_repo.local(): abspath = os.path.abspath(source) if not opts['pull'] and not opts['rev']: copy = True @@ -944,47 +992,57 @@ def clone(ui, source, dest=None, **opts) # can end up with extra data in the cloned revlogs that's # not pointed to by changesets, thus causing verify to # fail - l1 = other.lock() + l1 = src_repo.lock() except lock.LockException: copy = False if copy: # we lock here to avoid premature writing to the target - os.mkdir(os.path.join(dest, ".hg")) - l2 = lock.lock(os.path.join(dest, ".hg", "lock")) - + l2 = lock.lock(os.path.join(dest_path, ".hg", "lock")) + + # we need to remove the (empty) data dir in dest so copyfiles can do it's work + os.rmdir( os.path.join(dest_path, ".hg", "data") ) files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i" for f in files.split(): src = os.path.join(source, ".hg", f) - dst = os.path.join(dest, ".hg", f) + dst = os.path.join(dest_path, ".hg", f) try: util.copyfiles(src, dst) except OSError, inst: if inst.errno != errno.ENOENT: raise - repo = hg.repository(ui, dest) + # we need to re-init the repo after manually copying the data into it + dest_repo = hg.repository(ui, dest) else: revs = None if opts['rev']: - if not other.local(): + if not src_repo.local(): error = _("clone -r not supported yet for remote repositories.") raise util.Abort(error) else: - revs = [other.lookup(rev) for rev in opts['rev']] - repo = hg.repository(ui, dest, create=1) - repo.pull(other, heads = revs) - - f = repo.opener("hgrc", "w", text=True) - f.write("[paths]\n") - f.write("default = %s\n" % abspath) - f.close() - - if not opts['noupdate']: - doupdate(repo.ui, repo) - - d.close() + revs = [src_repo.lookup(rev) for rev in opts['rev']] + + if dest_repo.local(): + dest_repo.pull(src_repo, heads = revs) + elif src_repo.local(): + src_repo.push(dest_repo, revs = revs) + else: + error = _("clone from remote to remote not supported.") + raise util.Abort(error) + + if dest_repo.local(): + f = dest_repo.opener("hgrc", "w", text=True) + f.write("[paths]\n") + f.write("default = %s\n" % abspath) + f.close() + + if not opts['noupdate']: + doupdate(dest_repo.ui, dest_repo) + + if d: + d.close() def commit(ui, repo, *pats, **opts): """commit the specified files or all outstanding changes @@ -1065,21 +1123,21 @@ def docopy(ui, repo, pats, opts, wlock): ui.warn(_('%s: not overwriting - file exists\n') % reltarget) return - if not opts['after'] and not opts['dry_run']: + if not opts['after'] and not opts.get('dry_run'): os.unlink(reltarget) if opts['after']: if not os.path.exists(reltarget): return else: targetdir = os.path.dirname(reltarget) or '.' - if not os.path.isdir(targetdir) and not opts['dry_run']: + if not os.path.isdir(targetdir) and not opts.get('dry_run'): os.makedirs(targetdir) try: restore = repo.dirstate.state(abstarget) == 'r' - if restore and not opts['dry_run']: + if restore and not opts.get('dry_run'): repo.undelete([abstarget], wlock) try: - if not opts['dry_run']: + if not opts.get('dry_run'): shutil.copyfile(relsrc, reltarget) shutil.copymode(relsrc, reltarget) restore = False @@ -1099,7 +1157,7 @@ def docopy(ui, repo, pats, opts, wlock): if ui.verbose or not exact: ui.status(_('copying %s to %s\n') % (relsrc, reltarget)) targets[abstarget] = abssrc - if abstarget != origsrc and not opts['dry_run']: + if abstarget != origsrc and not opts.get('dry_run'): repo.copy(origsrc, abstarget, wlock) copied.append((abssrc, relsrc, exact)) @@ -1225,7 +1283,7 @@ def debugcomplete(ui, cmd='', **opts): options = [] otables = [globalopts] if cmd: - aliases, entry = find(cmd) + aliases, entry = findcmd(cmd) otables.append(entry[1]) for t in otables: for o in t: @@ -1707,11 +1765,16 @@ def import_(ui, repo, patch1, *patches, If there are outstanding changes in the working directory, import will abort unless given the -f flag. - If a patch looks like a mail message (its first line starts with - "From " or looks like an RFC822 header), it will not be applied - unless the -f option is used. The importer neither parses nor - discards mail headers, so use -f only to override the "mailness" - safety check, not to import a real mail message. + You can import a patch straight from a mail message. Even patches + as attachments work (body part must be type text/plain or + text/x-patch to be used). From and Subject headers of email + message are used as default committer and commit message. All + text/plain body parts before first diff are added to commit + message. + + If imported patch was generated by hg export, user and description + from patch override values from message headers and body. Values + given on command line with -m and -u override these. To read a patch from standard input, use patch name "-". """ @@ -1727,79 +1790,98 @@ def import_(ui, repo, patch1, *patches, # attempt to detect the start of a patch # (this heuristic is borrowed from quilt) - diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' + + diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' + 'retrieving revision [0-9]+(\.[0-9]+)*$|' + - '(---|\*\*\*)[ \t])') + '(---|\*\*\*)[ \t])', re.MULTILINE) for patch in patches: pf = os.path.join(d, patch) - message = [] + message = None user = None date = None hgpatch = False + + p = email.Parser.Parser() if pf == '-': - f = sys.stdin - fd, tmpname = tempfile.mkstemp(prefix='hg-patch-') - pf = tmpname - tmpfp = os.fdopen(fd, 'w') + msg = p.parse(sys.stdin) ui.status(_("applying patch from stdin\n")) else: - f = open(pf) - tmpfp, tmpname = None, None + msg = p.parse(file(pf)) ui.status(_("applying %s\n") % patch) + + fd, tmpname = tempfile.mkstemp(prefix='hg-patch-') + tmpfp = os.fdopen(fd, 'w') try: - while True: - line = f.readline() - if not line: break - if tmpfp: tmpfp.write(line) - line = line.rstrip() - if (not message and not hgpatch and - mailre.match(line) and not opts['force']): - if len(line) > 35: - line = line[:32] + '...' - raise util.Abort(_('first line looks like a ' - 'mail header: ') + line) - if diffre.match(line): + message = msg['Subject'] + if message: + message = message.replace('\n\t', ' ') + ui.debug('Subject: %s\n' % message) + user = msg['From'] + if user: + ui.debug('From: %s\n' % user) + diffs_seen = 0 + ok_types = ('text/plain', 'text/x-patch') + for part in msg.walk(): + content_type = part.get_content_type() + ui.debug('Content-Type: %s\n' % content_type) + if content_type not in ok_types: + continue + payload = part.get_payload(decode=True) + m = diffre.search(payload) + if m: + ui.debug(_('found patch at byte %d\n') % m.start(0)) + diffs_seen += 1 + hgpatch = False + fp = cStringIO.StringIO() + if message: + fp.write(message) + fp.write('\n') + for line in payload[:m.start(0)].splitlines(): + if line.startswith('# HG changeset patch'): + ui.debug(_('patch generated by hg export\n')) + hgpatch = True + # drop earlier commit message content + fp.seek(0) + fp.truncate() + elif hgpatch: + if line.startswith('# User '): + user = line[7:] + ui.debug('From: %s\n' % user) + elif line.startswith("# Date "): + date = line[7:] + if not line.startswith('# '): + fp.write(line) + fp.write('\n') + message = fp.getvalue() if tmpfp: - for chunk in util.filechunkiter(f): - tmpfp.write(chunk) - break - elif hgpatch: - # parse values when importing the result of an hg export - if line.startswith("# User "): - user = line[7:] - ui.debug(_('User: %s\n') % user) - elif line.startswith("# Date "): - date = line[7:] - elif not line.startswith("# ") and line: - message.append(line) - hgpatch = False - elif line == '# HG changeset patch': - hgpatch = True - message = [] # We may have collected garbage - elif message or line: - message.append(line) + tmpfp.write(payload) + if not payload.endswith('\n'): + tmpfp.write('\n') + elif not diffs_seen and message and content_type == 'text/plain': + message += '\n' + payload if opts['message']: # pickup the cmdline msg message = opts['message'] elif message: # pickup the patch msg - message = '\n'.join(message).rstrip() + message = message.strip() else: # launch the editor message = None ui.debug(_('message:\n%s\n') % message) - if tmpfp: tmpfp.close() - files = util.patch(strip, pf, ui) - + tmpfp.close() + if not diffs_seen: + raise util.Abort(_('no diffs found')) + + files = util.patch(strip, tmpname, ui) if len(files) > 0: addremove_lock(ui, repo, files, {}) repo.commit(files, message, user, date) finally: - if tmpname: os.unlink(tmpname) + os.unlink(tmpname) def incoming(ui, repo, source="default", **opts): """show new changesets found in source @@ -1839,7 +1921,10 @@ def incoming(ui, repo, source="default", # use the created uncompressed bundlerepo other = bundlerepo.bundlerepository(ui, repo.root, fname) - o = other.changelog.nodesbetween(incoming)[0] + revs = None + if opts['rev']: + revs = [other.lookup(rev) for rev in opts['rev']] + o = other.changelog.nodesbetween(incoming, revs)[0] if opts['newest_first']: o.reverse() displayer = show_changeset(ui, other, opts) @@ -1866,8 +1951,6 @@ def init(ui, dest="."): If no directory is given, the current directory is used. """ - if not os.path.exists(dest): - os.mkdir(dest) hg.repository(ui, dest, create=1) def locate(ui, repo, *pats, **opts): @@ -2049,13 +2132,16 @@ def outgoing(ui, repo, dest=None, **opts ui.setconfig("ui", "ssh", opts['ssh']) if opts['remotecmd']: ui.setconfig("ui", "remotecmd", opts['remotecmd']) + revs = None + if opts['rev']: + revs = [repo.lookup(rev) for rev in opts['rev']] other = hg.repository(ui, dest) o = repo.findoutgoing(other, force=opts['force']) if not o: ui.status(_("no changes found\n")) return - o = repo.changelog.nodesbetween(o)[0] + o = repo.changelog.nodesbetween(o, revs)[0] if opts['newest_first']: o.reverse() displayer = show_changeset(ui, repo, opts) @@ -2322,7 +2408,7 @@ def rename(ui, repo, *pats, **opts): if ui.verbose or not exact: ui.status(_('removing %s\n') % rel) names.append(abs) - if not opts['dry_run']: + if not opts.get('dry_run'): repo.remove(names, True, wlock) return errs @@ -2986,11 +3072,13 @@ table = { ('n', 'newest-first', None, _('show newest record first')), ('', 'bundle', '', _('file to store the bundles into')), ('p', 'patch', None, _('show patch')), + ('r', 'rev', [], _('a specific revision you would like to pull')), ('', 'template', '', _('display with template')), ('e', 'ssh', '', _('specify ssh command to use')), ('', 'remotecmd', '', _('specify hg command to run on the remote side'))], - _('hg incoming [-p] [-n] [-M] [--bundle FILENAME] [SOURCE]')), + _('hg incoming [-p] [-n] [-M] [-r REV]...' + ' [--bundle FILENAME] [SOURCE]')), "^init": (init, [], _('hg init [DEST]')), "locate": (locate, @@ -3028,12 +3116,13 @@ table = { _('run even when remote repository is unrelated')), ('p', 'patch', None, _('show patch')), ('', 'style', '', _('display using template map file')), + ('r', 'rev', [], _('a specific revision you would like to push')), ('n', 'newest-first', None, _('show newest record first')), ('', 'template', '', _('display with template')), ('e', 'ssh', '', _('specify ssh command to use')), ('', 'remotecmd', '', _('specify hg command to run on the remote side'))], - _('hg outgoing [-M] [-p] [-n] [DEST]')), + _('hg outgoing [-M] [-p] [-n] [-r REV]... [DEST]')), "^parents": (parents, [('b', 'branches', None, _('show branches')), @@ -3213,7 +3302,7 @@ def findpossible(cmd): return choice -def find(cmd): +def findcmd(cmd): """Return (aliases, command table entry) for command string.""" choice = findpossible(cmd) @@ -3250,7 +3339,7 @@ def parse(ui, args): if args: cmd, args = args[0], args[1:] - aliases, i = find(cmd) + aliases, i = findcmd(cmd) cmd = aliases[0] defaults = ui.config("defaults", cmd) if defaults: @@ -3277,6 +3366,19 @@ def parse(ui, args): return (cmd, cmd and i[0] or None, args, options, cmdoptions) +external = {} + +def findext(name): + '''return module with given extension name''' + try: + return external[name] + except KeyError: + dotname = '.' + name + for k, v in external.iteritems(): + if k.endswith('.' + name) or v.__name__ == name: + return v + raise KeyError(name) + def dispatch(args): for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': num = getattr(signal, name, None) @@ -3288,7 +3390,6 @@ def dispatch(args): sys.stderr.write(_("abort: %s\n") % inst) return -1 - external = [] for x in u.extensions(): try: if x[1]: @@ -3305,10 +3406,12 @@ def dispatch(args): mod = getattr(mod, comp) return mod try: - mod = importh("hgext." + x[0]) + name = 'hgext.' + x[0] + mod = importh(name) except ImportError: - mod = importh(x[0]) - external.append(mod) + name = x[0] + mod = importh(name) + external[name] = mod except (util.SignalInterrupt, KeyboardInterrupt): raise except Exception, inst: @@ -3316,7 +3419,7 @@ def dispatch(args): if u.print_exc(): return 1 - for x in external: + for x in external.itervalues(): uisetup = getattr(x, 'uisetup', None) if uisetup: uisetup(u) @@ -3372,7 +3475,7 @@ def dispatch(args): if not repo: repo = hg.repository(u, path=path) u = repo.ui - for x in external: + for x in external.itervalues(): if hasattr(x, 'reposetup'): x.reposetup(u, repo) except hg.RepoError: diff --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -33,6 +33,9 @@ def local_(ui, path, create=0): path = path[5:] return localrepo.localrepository(ui, path, create) +def ssh_(ui, path, create=0): + return sshrepo.sshrepository(ui, path, create) + def old_http(ui, path): ui.warn(_("old-http:// syntax is deprecated, " "please use static-http:// instead\n")) @@ -50,7 +53,7 @@ schemes = { 'http': lambda ui, path: httprepo.httprepository(ui, path), 'https': lambda ui, path: httprepo.httpsrepository(ui, path), 'old-http': old_http, - 'ssh': lambda ui, path: sshrepo.sshrepository(ui, path), + 'ssh': ssh_, 'static-http': static_http, } @@ -60,11 +63,11 @@ def repository(ui, path=None, create=0): if scheme: c = scheme.find(':') scheme = c >= 0 and scheme[:c] - try: - ctor = schemes.get(scheme) or schemes['file'] - if create: + ctor = schemes.get(scheme) or schemes['file'] + if create: + try: return ctor(ui, path, create) - return ctor(ui, path) - except TypeError: - raise util.Abort(_('cannot create new repository over "%s" protocol') % - scheme) + except TypeError: + raise util.Abort(_('cannot create new repository over "%s" protocol') % + scheme) + return ctor(ui, path) diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py --- a/mercurial/hgweb/common.py +++ b/mercurial/hgweb/common.py @@ -17,7 +17,7 @@ def get_mtime(repo_path): else: return os.stat(hg_path).st_mtime -def staticfile(directory, fname): +def staticfile(directory, fname, req): """return a file inside directory with guessed content-type header fname always uses '/' as directory separator and isn't allowed to @@ -36,7 +36,9 @@ def staticfile(directory, fname): try: os.stat(path) ct = mimetypes.guess_type(path)[0] or "text/plain" - return "Content-type: %s\n\n%s" % (ct, file(path).read()) + req.header([('Content-type', ct), + ('Content-length', os.path.getsize(path))]) + return file(path).read() except (TypeError, OSError): # illegal fname or unreadable file return "" diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py +++ b/mercurial/hgweb/hgweb_mod.py @@ -10,9 +10,8 @@ import os import os.path import mimetypes from mercurial.demandload import demandload -demandload(globals(), "re zlib ConfigParser cStringIO sys tempfile") +demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile") demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater") -demandload(globals(), "mercurial.hgweb.request:hgrequest") demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile") from mercurial.node import * from mercurial.i18n import gettext as _ @@ -651,9 +650,27 @@ class hgweb(object): raise Exception("suspicious path") return p - def run(self, req=hgrequest()): + def run(self): + if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."): + raise RuntimeError("This function is only intended to be called while running as a CGI script.") + import mercurial.hgweb.wsgicgi as wsgicgi + from request import wsgiapplication + def make_web_app(): + return self + wsgicgi.launch(wsgiapplication(make_web_app)) + + def run_wsgi(self, req): def header(**map): - yield self.t("header", **map) + header_file = cStringIO.StringIO(''.join(self.t("header", **map))) + msg = mimetools.Message(header_file, 0) + req.header(msg.items()) + yield header_file.read() + + def rawfileheader(**map): + req.header([('Content-type', map['mimetype']), + ('Content-disposition', 'filename=%s' % map['file']), + ('Content-length', str(len(map['raw'])))]) + yield '' def footer(**map): yield self.t("footer", @@ -712,6 +729,7 @@ class hgweb(object): "repo": self.reponame, "header": header, "footer": footer, + "rawfileheader": rawfileheader, }) if not req.form.has_key('cmd'): @@ -724,7 +742,6 @@ class hgweb(object): method(req) else: req.write(self.t("error")) - req.done() def do_changelog(self, req): hi = self.repo.changelog.count() - 1 @@ -830,7 +847,7 @@ class hgweb(object): static = self.repo.ui.config("web", "static", os.path.join(self.templatepath, "static")) - req.write(staticfile(static, fname) + req.write(staticfile(static, fname, req) or self.t("error", error="%r not found" % fname)) def do_capabilities(self, req): @@ -915,10 +932,11 @@ class hgweb(object): try: ret = self.repo.addchangegroup(fp, 'serve') - req.write('%d\n' % ret) - req.write(sys.stdout.getvalue()) finally: + val = sys.stdout.getvalue() sys.stdout = old_stdout + req.write('%d\n' % ret) + req.write(val) finally: lock.release() finally: diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -8,10 +8,9 @@ import os from mercurial.demandload import demandload -demandload(globals(), "ConfigParser") +demandload(globals(), "ConfigParser mimetools cStringIO") demandload(globals(), "mercurial:ui,hg,util,templater") demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb") -demandload(globals(), "mercurial.hgweb.request:hgrequest") demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile") from mercurial.i18n import gettext as _ @@ -47,9 +46,21 @@ class hgwebdir(object): self.repos.append((name.lstrip(os.sep), repo)) self.repos.sort() - def run(self, req=hgrequest()): + def run(self): + if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."): + raise RuntimeError("This function is only intended to be called while running as a CGI script.") + import mercurial.hgweb.wsgicgi as wsgicgi + from request import wsgiapplication + def make_web_app(): + return self + wsgicgi.launch(wsgiapplication(make_web_app)) + + def run_wsgi(self, req): def header(**map): - yield tmpl("header", **map) + header_file = cStringIO.StringIO(''.join(tmpl("header", **map))) + msg = mimetools.Message(header_file, 0) + req.header(msg.items()) + yield header_file.read() def footer(**map): yield tmpl("footer", motd=self.motd, **map) @@ -122,7 +133,7 @@ class hgwebdir(object): real = dict(self.repos).get(virtual) if real: try: - hgweb(real).run(req) + hgweb(real).run_wsgi(req) except IOError, inst: req.write(tmpl("error", error=inst.strerror)) except hg.RepoError, inst: @@ -133,7 +144,7 @@ class hgwebdir(object): if req.form.has_key('static'): static = os.path.join(templater.templatepath(), "static") fname = req.form['static'][0] - req.write(staticfile(static, fname) + req.write(staticfile(static, fname, req) or tmpl("error", error="%r not found" % fname)) else: sortable = ["name", "description", "contact", "lastchange"] diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py +++ b/mercurial/hgweb/request.py @@ -10,13 +10,48 @@ from mercurial.demandload import demandl demandload(globals(), "socket sys cgi os errno") from mercurial.i18n import gettext as _ -class hgrequest(object): - def __init__(self, inp=None, out=None, env=None): - self.inp = inp or sys.stdin - self.out = out or sys.stdout - self.env = env or os.environ +class wsgiapplication(object): + def __init__(self, destmaker): + self.destmaker = destmaker + + def __call__(self, wsgienv, start_response): + return _wsgirequest(self.destmaker(), wsgienv, start_response) + +class _wsgioutputfile(object): + def __init__(self, request): + self.request = request + + def write(self, data): + self.request.write(data) + def writelines(self, lines): + for line in lines: + self.write(line) + def flush(self): + return None + def close(self): + return None + +class _wsgirequest(object): + def __init__(self, destination, wsgienv, start_response): + version = wsgienv['wsgi.version'] + if (version < (1,0)) or (version >= (2, 0)): + raise RuntimeError("Unknown and unsupported WSGI version %d.%d" \ + % version) + self.inp = wsgienv['wsgi.input'] + self.out = _wsgioutputfile(self) + self.server_write = None + self.err = wsgienv['wsgi.errors'] + self.threaded = wsgienv['wsgi.multithread'] + self.multiprocess = wsgienv['wsgi.multiprocess'] + self.run_once = wsgienv['wsgi.run_once'] + self.env = wsgienv self.form = cgi.parse(self.inp, self.env, keep_blank_values=1) - self.will_close = True + self.start_response = start_response + self.headers = [] + destination.run_wsgi(self) + + def __iter__(self): + return iter([]) def read(self, count=-1): return self.inp.read(count) @@ -27,23 +62,22 @@ class hgrequest(object): for part in thing: self.write(part) else: + thing = str(thing) + if self.server_write is None: + if not self.headers: + raise RuntimeError("request.write called before headers sent (%s)." % thing) + self.server_write = self.start_response('200 Script output follows', + self.headers) + self.start_response = None + self.headers = None try: - self.out.write(str(thing)) + self.server_write(thing) except socket.error, inst: if inst[0] != errno.ECONNRESET: raise - def done(self): - if self.will_close: - self.inp.close() - self.out.close() - else: - self.out.flush() - def header(self, headers=[('Content-type','text/html')]): - for header in headers: - self.out.write("%s: %s\r\n" % header) - self.out.write("\r\n") + self.headers.extend(headers) def httphdr(self, type, filename=None, length=0, headers={}): headers = headers.items() @@ -51,12 +85,6 @@ class hgrequest(object): if filename: headers.append(('Content-disposition', 'attachment; filename=%s' % filename)) - # we do not yet support http 1.1 chunked transfer, so we have - # to force connection to close if content-length not known if length: headers.append(('Content-length', str(length))) - self.will_close = False - else: - headers.append(('Connection', 'close')) - self.will_close = True self.header(headers) diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py --- a/mercurial/hgweb/server.py +++ b/mercurial/hgweb/server.py @@ -10,7 +10,7 @@ from mercurial.demandload import demandl import os, sys, errno demandload(globals(), "urllib BaseHTTPServer socket SocketServer") demandload(globals(), "mercurial:ui,hg,util,templater") -demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:hgrequest") +demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:wsgiapplication") from mercurial.i18n import gettext as _ def _splitURI(uri): @@ -25,6 +25,17 @@ def _splitURI(uri): path, query = uri, '' return urllib.unquote(path), query +class _error_logger(object): + def __init__(self, handler): + self.handler = handler + def flush(self): + pass + def write(str): + self.writelines(str.split('\n')) + def writelines(seq): + for msg in seq: + self.handler.log_error("HG error: %s", msg) + class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler): def __init__(self, *args, **kargs): self.protocol_version = 'HTTP/1.1' @@ -76,17 +87,72 @@ class _hgwebhandler(object, BaseHTTPServ length = self.headers.getheader('content-length') if length: env['CONTENT_LENGTH'] = length - accept = [] - for line in self.headers.getallmatchingheaders('accept'): - if line[:1] in "\t\n\r ": - accept.append(line.strip()) - else: - accept = accept + line[7:].split(',') - env['HTTP_ACCEPT'] = ','.join(accept) + for header in [h for h in self.headers.keys() \ + if h not in ('content-type', 'content-length')]: + hkey = 'HTTP_' + header.replace('-', '_').upper() + hval = self.headers.getheader(header) + hval = hval.replace('\n', '').strip() + if hval: + env[hkey] = hval + env['SERVER_PROTOCOL'] = self.request_version + env['wsgi.version'] = (1, 0) + env['wsgi.url_scheme'] = 'http' + env['wsgi.input'] = self.rfile + env['wsgi.errors'] = _error_logger(self) + env['wsgi.multithread'] = isinstance(self.server, + SocketServer.ThreadingMixIn) + env['wsgi.multiprocess'] = isinstance(self.server, + SocketServer.ForkingMixIn) + env['wsgi.run_once'] = 0 + + self.close_connection = True + self.saved_status = None + self.saved_headers = [] + self.sent_headers = False + self.length = None + req = self.server.reqmaker(env, self._start_response) + for data in req: + if data: + self._write(data) - req = hgrequest(self.rfile, self.wfile, env) - self.send_response(200, "Script output follows") - self.close_connection = self.server.make_and_run_handler(req) + def send_headers(self): + if not self.saved_status: + raise AssertionError("Sending headers before start_response() called") + saved_status = self.saved_status.split(None, 1) + saved_status[0] = int(saved_status[0]) + self.send_response(*saved_status) + should_close = True + for h in self.saved_headers: + self.send_header(*h) + if h[0].lower() == 'content-length': + should_close = False + self.length = int(h[1]) + if should_close: + self.send_header('Connection', 'close') + self.close_connection = should_close + self.end_headers() + self.sent_headers = True + + def _start_response(self, http_status, headers, exc_info=None): + code, msg = http_status.split(None, 1) + code = int(code) + self.saved_status = http_status + bad_headers = ('connection', 'transfer-encoding') + self.saved_headers = [ h for h in headers \ + if h[0].lower() not in bad_headers ] + return self._write + + def _write(self, data): + if not self.saved_status: + raise AssertionError("data written before start_response() called") + elif not self.sent_headers: + self.send_headers() + if self.length is not None: + if len(data) > self.length: + raise AssertionError("Content-length header sent, but more bytes than specified are being written.") + self.length = self.length - len(data) + self.wfile.write(data) + self.wfile.flush() def create_server(ui, repo): use_threads = True @@ -126,8 +192,9 @@ def create_server(ui, repo): self.webdir_conf = webdir_conf self.webdirmaker = hgwebdir self.repoviewmaker = hgweb + self.reqmaker = wsgiapplication(self.make_handler) - def make_and_run_handler(self, req): + def make_handler(self): if self.webdir_conf: hgwebobj = self.webdirmaker(self.webdir_conf) elif self.repo is not None: @@ -135,8 +202,7 @@ def create_server(ui, repo): repo.origroot)) else: raise hg.RepoError(_('no repo found')) - hgwebobj.run(req) - return req.will_close + return hgwebobj class IPv6HTTPServer(MercurialHTTPServer): address_family = getattr(socket, 'AF_INET6', None) @@ -144,7 +210,7 @@ def create_server(ui, repo): def __init__(self, *args, **kwargs): if self.address_family is None: raise hg.RepoError(_('IPv6 not available on this system')) - super(IPv6HTTPServer, self).__init__(*args, **kargs) + super(IPv6HTTPServer, self).__init__(*args, **kwargs) if use_ipv6: return IPv6HTTPServer((address, port), _hgwebhandler) diff --git a/mercurial/hgweb/wsgicgi.py b/mercurial/hgweb/wsgicgi.py new file mode 100644 --- /dev/null +++ b/mercurial/hgweb/wsgicgi.py @@ -0,0 +1,70 @@ +# hgweb/wsgicgi.py - CGI->WSGI translator +# +# Copyright 2006 Eric Hopper +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. +# +# This was originally copied from the public domain code at +# http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side + +import os, sys + +def launch(application): + + environ = dict(os.environ.items()) + environ['wsgi.input'] = sys.stdin + environ['wsgi.errors'] = sys.stderr + environ['wsgi.version'] = (1,0) + environ['wsgi.multithread'] = False + environ['wsgi.multiprocess'] = True + environ['wsgi.run_once'] = True + + if environ.get('HTTPS','off') in ('on','1'): + environ['wsgi.url_scheme'] = 'https' + else: + environ['wsgi.url_scheme'] = 'http' + + headers_set = [] + headers_sent = [] + out = sys.stdout + + def write(data): + if not headers_set: + raise AssertionError("write() before start_response()") + + elif not headers_sent: + # Before the first output, send the stored headers + status, response_headers = headers_sent[:] = headers_set + out.write('Status: %s\r\n' % status) + for header in response_headers: + out.write('%s: %s\r\n' % header) + out.write('\r\n') + + out.write(data) + out.flush() + + def start_response(status,response_headers,exc_info=None): + if exc_info: + try: + if headers_sent: + # Re-raise original exception if headers sent + raise exc_info[0], exc_info[1], exc_info[2] + finally: + exc_info = None # avoid dangling circular ref + elif headers_set: + raise AssertionError("Headers already set!") + + headers_set[:] = [status,response_headers] + return write + + result = application(environ, start_response) + try: + for data in result: + if data: # don't send headers until body appears + write(data) + if not headers_sent: + write('') # send headers now if body was empty + finally: + if hasattr(result,'close'): + result.close() diff --git a/mercurial/httprepo.py b/mercurial/httprepo.py --- a/mercurial/httprepo.py +++ b/mercurial/httprepo.py @@ -20,16 +20,22 @@ class passwordmgr(urllib2.HTTPPasswordMg def find_user_password(self, realm, authuri): authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password( self, realm, authuri) - if authinfo != (None, None): - return authinfo + user, passwd = authinfo + if user and passwd: + return (user, passwd) if not self.ui.interactive: raise util.Abort(_('http authorization required')) self.ui.write(_("http authorization required\n")) self.ui.status(_("realm: %s\n") % realm) - user = self.ui.prompt(_("user:"), default=None) - passwd = self.ui.getpass() + if user: + self.ui.status(_("user: %s\n") % user) + else: + user = self.ui.prompt(_("user:"), default=None) + + if not passwd: + passwd = self.ui.getpass() self.add_password(realm, authuri, user, passwd) return (user, passwd) @@ -85,6 +91,22 @@ class httphandler(keepalive.HTTPHandler) def http_open(self, req): return self.do_open(httpconnection, req) +class httpsconnection(httplib.HTTPSConnection): + # must be able to send big bundle as stream. + + def send(self, data): + if isinstance(data, str): + httplib.HTTPSConnection.send(self, data) + else: + # if auth required, some data sent twice, so rewind here + data.seek(0) + for chunk in util.filechunkiter(data): + httplib.HTTPSConnection.send(self, chunk) + +class httpshandler(urllib2.HTTPSHandler): + def https_open(self, req): + return self.do_open(httpsconnection, req) + class httprepository(remoterepository): def __init__(self, ui, path): self.caps = None @@ -148,12 +170,13 @@ class httprepository(remoterepository): passmgr = passwordmgr(ui) if user: - ui.debug(_('will use user %s, password %s for http auth\n') % - (user, '*' * len(passwd))) + ui.debug(_('http auth: user %s, password %s\n') % + (user, passwd and '*' * len(passwd) or 'not set')) passmgr.add_password(None, host, user, passwd or '') opener = urllib2.build_opener( handler, + httpshandler(), urllib2.HTTPBasicAuthHandler(passmgr), urllib2.HTTPDigestAuthHandler(passmgr)) diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -74,6 +74,8 @@ class localrepository(object): self.transhandle = None if create: + if not os.path.exists(path): + os.mkdir(path) os.mkdir(self.path) os.mkdir(self.join("data")) @@ -1651,8 +1653,8 @@ class localrepository(object): linear_path = (pa == p1 or pa == p2) if allow and linear_path: - raise util.Abort(_("there is nothing to merge, " - "just use 'hg update'")) + raise util.Abort(_("there is nothing to merge, just use " + "'hg update' or look at 'hg heads'")) if allow and not forcemerge: if modified or added or removed: raise util.Abort(_("outstanding uncommitted changes")) diff --git a/mercurial/mpatch.c b/mercurial/mpatch.c --- a/mercurial/mpatch.c +++ b/mercurial/mpatch.c @@ -43,7 +43,7 @@ static uint32_t ntohl(uint32_t x) /* not windows */ # include # include -# include +# include #endif static char mpatch_doc[] = "Efficient binary patching."; diff --git a/mercurial/sshrepo.py b/mercurial/sshrepo.py --- a/mercurial/sshrepo.py +++ b/mercurial/sshrepo.py @@ -12,7 +12,7 @@ from demandload import * demandload(globals(), "hg os re stat util") class sshrepository(remoterepository): - def __init__(self, ui, path): + def __init__(self, ui, path, create=0): self.url = path self.ui = ui @@ -30,6 +30,25 @@ class sshrepository(remoterepository): sshcmd = self.ui.config("ui", "ssh", "ssh") remotecmd = self.ui.config("ui", "remotecmd", "hg") + + if create: + try: + self.validate_repo(ui, sshcmd, args, remotecmd) + return # the repo is good, nothing more to do + except hg.RepoError: + pass + + cmd = '%s %s "%s init %s"' + cmd = cmd % (sshcmd, args, remotecmd, self.path) + + ui.note('running %s\n' % cmd) + res = os.system(cmd) + if res != 0: + raise hg.RepoError(_("could not create remote repo")) + + self.validate_repo(ui, sshcmd, args, remotecmd) + + def validate_repo(self, ui, sshcmd, args, remotecmd): cmd = '%s %s "%s -R %s serve --stdio"' cmd = cmd % (sshcmd, args, remotecmd, self.path) diff --git a/mercurial/templater.py b/mercurial/templater.py --- a/mercurial/templater.py +++ b/mercurial/templater.py @@ -202,7 +202,7 @@ def fill(text, width): if para_re is None: para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M) space_re = re.compile(r' +') - + def findparas(): start = 0 while True: @@ -221,10 +221,21 @@ def fill(text, width): fp.write(rest) return fp.getvalue() +def firstline(text): + '''return the first line of text''' + try: + return text.splitlines(1)[0].rstrip('\r\n') + except IndexError: + return '' + def isodate(date): '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.''' return util.datestr(date, format='%Y-%m-%d %H:%M') +def hgdate(date): + '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.''' + return "%d %d" % date + def nl2br(text): '''replace raw newlines with xhtml line breaks.''' return text.replace('\n', '
\n') @@ -280,8 +291,9 @@ common_filters = { "escape": lambda x: cgi.escape(x, True), "fill68": lambda x: fill(x, width=68), "fill76": lambda x: fill(x, width=76), - "firstline": lambda x: x.splitlines(1)[0].rstrip('\r\n'), + "firstline": firstline, "tabindent": lambda x: indent(x, '\t'), + "hgdate": hgdate, "isodate": isodate, "obfuscate": obfuscate, "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--", diff --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -99,10 +99,10 @@ class ui(object): """Return a list of comma/space separated strings""" result = self.config(section, name) if result is None: - return [] - else: - return result.replace(",", " ").split() - + result = default or [] + if isinstance(result, basestring): + result = result.replace(",", " ").split() + return result def configbool(self, section, name, default=False): if self.overlay.has_key((section, name)): diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -859,6 +859,49 @@ def datestr(date=None, format='%a %b %d s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60)) return s +def strdate(string, format='%a %b %d %H:%M:%S %Y'): + """parse a localized time string and return a (unixtime, offset) tuple. + if the string cannot be parsed, ValueError is raised.""" + def hastimezone(string): + return (string[-4:].isdigit() and + (string[-5] == '+' or string[-5] == '-') and + string[-6].isspace()) + + if hastimezone(string): + date, tz = string[:-6], string[-5:] + tz = int(tz) + offset = - 3600 * (tz / 100) - 60 * (tz % 100) + else: + date, offset = string, 0 + when = int(time.mktime(time.strptime(date, format))) + offset + return when, offset + +def parsedate(string, formats=('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M')): + """parse a localized time string and return a (unixtime, offset) tuple. + The date may be a "unixtime offset" string or in one of the specified + formats.""" + try: + when, offset = map(int, string.split(' ')) + except ValueError: + for format in formats: + try: + when, offset = strdate(string, format) + except ValueError: + pass + else: + break + else: + raise ValueError(_('invalid date: %r') % string) + # validate explicit (probably user-specified) date and + # time zone offset. values must fit in signed 32 bits for + # current 32-bit linux runtimes. timezones go from UTC-12 + # to UTC+14 + if abs(when) > 0x7fffffff: + raise ValueError(_('date exceeds 32 bits: %d') % when) + if offset < -50400 or offset > 43200: + raise ValueError(_('impossible time zone offset: %d') % offset) + return when, offset + def shortuser(user): """Return a short representation of a user name or email address.""" f = user.find('@') diff --git a/templates/changeset-raw.tmpl b/templates/changeset-raw.tmpl --- a/templates/changeset-raw.tmpl +++ b/templates/changeset-raw.tmpl @@ -1,7 +1,7 @@ #header# # HG changeset patch # User #author# -# Date #date|date# +# Date #date|hgdate# # Node ID #node# #parent%changesetparent# #desc# diff --git a/templates/map-raw b/templates/map-raw --- a/templates/map-raw +++ b/templates/map-raw @@ -5,10 +5,10 @@ difflineplus = '#line#' difflineminus = '#line#' difflineat = '#line#' diffline = '#line#' -changesetparent = '# parent: #node#' -changesetchild = '# child: #node#' +changesetparent = '# Parent #node#' +changesetchild = '# Child #node#' filenodelink = '' -filerevision = 'Content-Type: #mimetype#\nContent-Disposition: filename=#file#\n\n#raw#' +filerevision = '#rawfileheader##raw#' fileline = '#line#' diffblock = '#lines#' filediff = filediff-raw.tmpl diff --git a/tests/get-with-headers.py b/tests/get-with-headers.py new file mode 100755 --- /dev/null +++ b/tests/get-with-headers.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +__doc__ = """This does HTTP get requests given a host:port and path and returns +a subset of the headers plus the body of the result.""" + +import httplib, sys +headers = [h.lower() for h in sys.argv[3:]] +conn = httplib.HTTPConnection(sys.argv[1]) +conn.request("GET", sys.argv[2]) +response = conn.getresponse() +print response.status, response.reason +for h in headers: + if response.getheader(h, None) is not None: + print "%s: %s" % (h, response.getheader(h)) +print +sys.stdout.write(response.read()) diff --git a/tests/test-globalopts b/tests/test-globalopts --- a/tests/test-globalopts +++ b/tests/test-globalopts @@ -62,7 +62,7 @@ echo %% --time hg --cwd a --time tip 2>&1 | grep '^Time:' | sed 's/[0-9][0-9]*/x/g' echo %% --version -hg --version -q | sed 's/version \([a-f0-9+]*\|unknown\)/version xxx/' +hg --version -q | sed 's/version [^)]*/version xxx/' echo %% -h/--help hg -h diff --git a/tests/test-globalopts.out b/tests/test-globalopts.out --- a/tests/test-globalopts.out +++ b/tests/test-globalopts.out @@ -127,7 +127,7 @@ list of commands (use "hg help -v" to sh export dump the header and diffs for one or more changesets grep search for a pattern in specified files and revisions heads show current repository heads - help show help for a given command or all commands + help show help for a command, extension, or list of commands identify print information about the working copy import import an ordered set of patches incoming show new changesets found in source @@ -173,7 +173,7 @@ list of commands (use "hg help -v" to sh export dump the header and diffs for one or more changesets grep search for a pattern in specified files and revisions heads show current repository heads - help show help for a given command or all commands + help show help for a command, extension, or list of commands identify print information about the working copy import import an ordered set of patches incoming show new changesets found in source diff --git a/tests/test-help.out b/tests/test-help.out --- a/tests/test-help.out +++ b/tests/test-help.out @@ -51,7 +51,7 @@ list of commands (use "hg help -v" to sh export dump the header and diffs for one or more changesets grep search for a pattern in specified files and revisions heads show current repository heads - help show help for a given command or all commands + help show help for a command, extension, or list of commands identify print information about the working copy import import an ordered set of patches incoming show new changesets found in source @@ -93,7 +93,7 @@ list of commands (use "hg help -v" to sh export dump the header and diffs for one or more changesets grep search for a pattern in specified files and revisions heads show current repository heads - help show help for a given command or all commands + help show help for a command, extension, or list of commands identify print information about the working copy import import an ordered set of patches incoming show new changesets found in source diff --git a/tests/test-http-proxy b/tests/test-http-proxy --- a/tests/test-http-proxy +++ b/tests/test-http-proxy @@ -26,5 +26,5 @@ http_proxy=http://user:passwd@localhost: echo %% bad host:port for proxy http_proxy=localhost:20061 hg clone --config http_proxy.always=True http://localhost:20059/ f -kill $(cat proxy.pid a/hg.pid) +kill `cat proxy.pid a/hg.pid` exit 0 diff --git a/tests/test-hup b/tests/test-hup --- a/tests/test-hup +++ b/tests/test-hup @@ -7,7 +7,7 @@ hg serve --stdio < p & P=$! (echo lock; echo addchangegroup; sleep 5) > p & Q=$! -sleep 1 +sleep 3 kill -HUP $P wait ls .hg diff --git a/tests/test-import b/tests/test-import new file mode 100755 --- /dev/null +++ b/tests/test-import @@ -0,0 +1,81 @@ +#!/bin/sh + +hg init a +echo line 1 > a/a +hg --cwd a ci -d '0 0' -Ama + +echo line 2 >> a/a +hg --cwd a ci -u someone -d '1 0' -m'second change' + +echo % import exported patch +hg clone -r0 a b +hg --cwd a export tip > tip.patch +hg --cwd b import ../tip.patch +echo % message should be same +hg --cwd b tip | grep 'second change' +echo % committer should be same +hg --cwd b tip | grep someone +rm -rf b + +echo % import of plain diff should fail without message +hg clone -r0 a b +hg --cwd a diff -r0:1 > tip.patch +hg --cwd b import ../tip.patch +rm -rf b + +echo % import of plain diff should be ok with message +hg clone -r0 a b +hg --cwd a diff -r0:1 > tip.patch +hg --cwd b import -mpatch ../tip.patch +rm -rf b + +echo % import from stdin +hg clone -r0 a b +hg --cwd a export tip | hg --cwd b import - +rm -rf b + +echo % override commit message +hg clone -r0 a b +hg --cwd a export tip | hg --cwd b import -m 'override' - +hg --cwd b tip | grep override +rm -rf b + +cat > mkmsg.py < tip.patch +python mkmsg.py > msg.patch +hg --cwd b import ../msg.patch +hg --cwd b tip | grep email +rm -rf b + +echo % plain diff in email, no subject, message body +hg clone -r0 a b +grep -v '^Subject:' msg.patch | hg --cwd b import - +rm -rf b + +echo % plain diff in email, subject, no message body +hg clone -r0 a b +grep -v '^email ' msg.patch | hg --cwd b import - +rm -rf b + +echo % plain diff in email, no subject, no message body, should fail +hg clone -r0 a b +grep -v '^\(Subject\|email\)' msg.patch | hg --cwd b import - +rm -rf b + +echo % hg export in email, should use patch header +hg clone -r0 a b +hg --cwd a export tip > tip.patch +python mkmsg.py | hg --cwd b import - +hg --cwd b tip | grep second +rm -rf b + diff --git a/tests/test-import.out b/tests/test-import.out new file mode 100644 --- /dev/null +++ b/tests/test-import.out @@ -0,0 +1,103 @@ +adding a +% import exported patch +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +applying ../tip.patch +patching file a +% message should be same +summary: second change +% committer should be same +user: someone +% import of plain diff should fail without message +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +applying ../tip.patch +patching file a +transaction abort! +rollback completed +% import of plain diff should be ok with message +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +applying ../tip.patch +patching file a +% import from stdin +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +applying patch from stdin +patching file a +% override commit message +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +applying patch from stdin +patching file a +summary: override +% plain diff in email, subject, message body +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +applying ../msg.patch +patching file a +user: email patcher +summary: email patch +% plain diff in email, no subject, message body +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +applying patch from stdin +patching file a +% plain diff in email, subject, no message body +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +applying patch from stdin +patching file a +% plain diff in email, no subject, no message body, should fail +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +applying patch from stdin +patching file a +transaction abort! +rollback completed +% hg export in email, should use patch header +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +applying patch from stdin +patching file a +summary: second change diff --git a/tests/test-incoming-outgoing b/tests/test-incoming-outgoing --- a/tests/test-incoming-outgoing +++ b/tests/test-incoming-outgoing @@ -14,8 +14,10 @@ cd .. hg init new # http incoming http_proxy= hg -R new incoming http://localhost:20059/ +http_proxy= hg -R new incoming -r 4 http://localhost:20059/ # local incoming hg -R new incoming test +hg -R new incoming -r 4 test # test with --bundle http_proxy= hg -R new incoming --bundle test.hg http://localhost:20059/ @@ -42,5 +44,6 @@ hg verify cd .. hg -R test-dev outgoing test http_proxy= hg -R test-dev outgoing http://localhost:20059/ +http_proxy= hg -R test-dev outgoing -r 11 http://localhost:20059/ kill `cat test/hg.pid` diff --git a/tests/test-incoming-outgoing.out b/tests/test-incoming-outgoing.out --- a/tests/test-incoming-outgoing.out +++ b/tests/test-incoming-outgoing.out @@ -75,6 +75,31 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: 4 +changeset: 0:9cb21d99fe27 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0 + +changeset: 1:d717f5dfad6a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1 + +changeset: 2:c0d6b86da426 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 + +changeset: 3:dfacbd43b3fe +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 3 + +changeset: 4:1f3a964b6022 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 4 + changeset: 5:c028bcc7a28a user: test date: Mon Jan 12 13:46:40 1970 +0000 @@ -121,6 +146,31 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: 4 +changeset: 0:9cb21d99fe27 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0 + +changeset: 1:d717f5dfad6a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1 + +changeset: 2:c0d6b86da426 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 + +changeset: 3:dfacbd43b3fe +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 3 + +changeset: 4:1f3a964b6022 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 4 + changeset: 5:c028bcc7a28a user: test date: Mon Jan 12 13:46:40 1970 +0000 @@ -270,3 +320,19 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: 13 +searching for changes +changeset: 9:3741c3ad1096 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 9 + +changeset: 10:de4143c8d9a5 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 10 + +changeset: 11:0e1c188b9a7a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 11 + diff --git a/tests/test-oldcgi b/tests/test-oldcgi new file mode 100755 --- /dev/null +++ b/tests/test-oldcgi @@ -0,0 +1,100 @@ +#!/bin/sh + +hg init test + +cat >hgweb.cgi <hgweb.config <hgwebdir.cgi <page1 2>&1 ; echo $? +./hgwebdir.cgi >page2 2>&1 ; echo $? +PATH_INFO="/test/" +PATH_TRANSLATED="/var/something/test.cgi" +REQUEST_URI="/test/test/" +SCRIPT_URI="http://hg.omnifarious.org/test/test/" +SCRIPT_URL="/test/test/" +./hgwebdir.cgi >page3 2>&1 ; echo $? +fgrep -i error page1 page2 page3 && exit 1 +exit 0 diff --git a/tests/test-oldcgi.out b/tests/test-oldcgi.out new file mode 100644 --- /dev/null +++ b/tests/test-oldcgi.out @@ -0,0 +1,3 @@ +0 +0 +0 diff --git a/tests/test-parse-date b/tests/test-parse-date new file mode 100755 --- /dev/null +++ b/tests/test-parse-date @@ -0,0 +1,16 @@ +#!/bin/sh + +hg init +echo "test-parse-date" > a +hg add a +hg ci -d "2006-02-01 13:00:30" -m "rev 0" +echo "hi!" >> a +hg ci -d "2006-02-01 13:00:30 -0500" -m "rev 1" +hg tag -d "2006-04-15 13:30" "Hi" +hg backout --merge -d "2006-04-15 13:30 +0200" -m "rev 3" 1 +hg ci -d "1150000000 14400" -m "rev 4 (merge)" +echo "fail" >> a +hg ci -d "should fail" -m "fail" +hg ci -d "100000000000000000 1400" -m "fail" +hg ci -d "100000 1400000" -m "fail" +hg log --template '{date|date}\n' diff --git a/tests/test-parse-date.out b/tests/test-parse-date.out new file mode 100644 --- /dev/null +++ b/tests/test-parse-date.out @@ -0,0 +1,19 @@ +reverting a +changeset 3:107ce1ee2b43 backs out changeset 1:25a1420a55f8 +merging with changeset 2:99a1acecff55 +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +abort: invalid date: 'should fail' +transaction abort! +rollback completed +abort: date exceeds 32 bits: 100000000000000000 +transaction abort! +rollback completed +abort: impossible time zone offset: 1400000 +transaction abort! +rollback completed +Sun Jun 11 00:26:40 2006 -0400 +Sat Apr 15 13:30:00 2006 +0200 +Sat Apr 15 13:30:00 2006 +0000 +Wed Feb 01 13:00:30 2006 -0500 +Wed Feb 01 13:00:30 2006 +0000 diff --git a/tests/test-pull-pull-corruption2 b/tests/test-pull-pull-corruption2 --- a/tests/test-pull-pull-corruption2 +++ b/tests/test-pull-pull-corruption2 @@ -20,5 +20,6 @@ hg -R version2 pull source1 & sleep 1 hg clone --pull -U version2 corrupted +wait hg -R corrupted verify hg -R version2 verify diff --git a/tests/test-ui-config b/tests/test-ui-config new file mode 100755 --- /dev/null +++ b/tests/test-ui-config @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +from mercurial import ui + +testui = ui.ui() +testui.updateopts(config=[ + 'values.string=string value', + 'values.bool1=true', + 'values.bool2=false', + 'lists.list1=foo', + 'lists.list2=foo bar baz', + 'lists.list3=alice, bob', + 'lists.list4=foo bar baz alice, bob', +]) + +print repr(testui.configitems('values')) +print repr(testui.configitems('lists')) +print "---" +print repr(testui.config('values', 'string')) +print repr(testui.config('values', 'bool1')) +print repr(testui.config('values', 'bool2')) +print repr(testui.config('values', 'unknown')) +print "---" +try: + print repr(testui.configbool('values', 'string')) +except ValueError, why: + print why +print repr(testui.configbool('values', 'bool1')) +print repr(testui.configbool('values', 'bool2')) +print repr(testui.configbool('values', 'bool2', True)) +print repr(testui.configbool('values', 'unknown')) +print repr(testui.configbool('values', 'unknown', True)) +print "---" +print repr(testui.configlist('lists', 'list1')) +print repr(testui.configlist('lists', 'list2')) +print repr(testui.configlist('lists', 'list3')) +print repr(testui.configlist('lists', 'list4')) +print repr(testui.configlist('lists', 'list4', ['foo'])) +print repr(testui.configlist('lists', 'unknown')) +print repr(testui.configlist('lists', 'unknown', '')) +print repr(testui.configlist('lists', 'unknown', 'foo')) +print repr(testui.configlist('lists', 'unknown', ['foo'])) +print repr(testui.configlist('lists', 'unknown', 'foo bar')) +print repr(testui.configlist('lists', 'unknown', 'foo, bar')) +print repr(testui.configlist('lists', 'unknown', ['foo bar'])) +print repr(testui.configlist('lists', 'unknown', ['foo', 'bar'])) +print "---" diff --git a/tests/test-ui-config.out b/tests/test-ui-config.out new file mode 100644 --- /dev/null +++ b/tests/test-ui-config.out @@ -0,0 +1,29 @@ +[('bool1', 'true'), ('bool2', 'false'), ('string', 'string value')] +[('list1', 'foo'), ('list2', 'foo bar baz'), ('list3', 'alice, bob'), ('list4', 'foo bar baz alice, bob')] +--- +'string value' +'true' +'false' +None +--- +Not a boolean: string value +True +False +False +False +True +--- +['foo'] +['foo', 'bar', 'baz'] +['alice', 'bob'] +['foo', 'bar', 'baz', 'alice', 'bob'] +['foo', 'bar', 'baz', 'alice', 'bob'] +[] +[] +['foo'] +['foo'] +['foo', 'bar'] +['foo', 'bar'] +['foo bar'] +['foo', 'bar'] +--- diff --git a/tests/test-up-local-change.out b/tests/test-up-local-change.out --- a/tests/test-up-local-change.out +++ b/tests/test-up-local-change.out @@ -43,7 +43,7 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: 1 -abort: there is nothing to merge, just use 'hg update' +abort: there is nothing to merge, just use 'hg update' or look at 'hg heads' failed changeset: 0:33aaa84a386b user: test diff --git a/tests/test-webraw b/tests/test-webraw new file mode 100755 --- /dev/null +++ b/tests/test-webraw @@ -0,0 +1,21 @@ +#!/bin/sh + +hg init test +cd test +cat >sometext.txt <getoutput.txt & + +sleep 5 +kill `cat hg.pid` +sleep 1 # wait for server to scream and die +cat getoutput.txt +cat access.log error.log | \ + sed 's/^[^ ]*\( [^[]*\[\)[^]]*\(\].*\)$/host\1date\2/' diff --git a/tests/test-webraw.out b/tests/test-webraw.out new file mode 100644 --- /dev/null +++ b/tests/test-webraw.out @@ -0,0 +1,10 @@ +200 Script output follows +content-type: text/plain +content-length: 157 +content-disposition: filename=sometext.txt + +This is just some random text +that will go inside the file and take a few lines. +It is very boring to read, but computers don't +care about things like that. +host - - [date] "GET /?f=f165dc289438;file=sometext.txt;style=raw HTTP/1.1" 200 -