# HG changeset patch # User Benoit Boissinot # Date 1138817895 -3600 # Node ID c21b54f7f7b8871601ba4078ae5d2858940de59d # Parent 675ca845c2f891b549ef96b12aa30cd7e21f2505# Parent 0690d0f202e1ae27a94ff81c862fd973408481b7 Merge with crew diff -r 675ca845c2f8 -r c21b54f7f7b8 .hgtags --- a/.hgtags Thu Dec 15 18:04:39 2005 +0100 +++ b/.hgtags Wed Feb 01 19:18:15 2006 +0100 @@ -8,3 +8,4 @@ 4ccf3de52989b14c3d84e1097f59e39a992e00bd 0.6b eac9c8efcd9bd8244e72fb6821f769f450457a32 0.6c 979c049974485125e1f9357f6bbe9c1b548a64c3 0.7 +3a56574f329a368d645853e0f9e09472aee62349 0.8 diff -r 675ca845c2f8 -r c21b54f7f7b8 contrib/bash_completion --- a/contrib/bash_completion Thu Dec 15 18:04:39 2005 +0100 +++ b/contrib/bash_completion Wed Feb 01 19:18:15 2006 +0100 @@ -1,23 +1,54 @@ shopt -s extglob +_hg_command_list() +{ + hg --debug help 2>/dev/null | \ + awk 'function command_line(line) { + gsub(/,/, "", line) + gsub(/:.*/, "", line) + split(line, aliases) + command = aliases[1] + delete aliases[1] + print command + for (i in aliases) + if (index(command, aliases[i]) != 1) + print aliases[i] + } + /^list of commands:/ {commands=1} + commands && /^ debug/ {a[i++] = $0; next;} + commands && /^ [^ ]/ {command_line($0)} + /^global options:/ {exit 0} + END {for (i in a) command_line(a[i])}' + +} + +_hg_option_list() +{ + hg -v help $1 2> /dev/null | \ + awk '/^ *-/ { + for (i = 1; i <= NF; i ++) { + if (index($i, "-") != 1) + break; + print $i; + } + }' +} + + _hg_commands() { local all commands result - all=($(hg --debug help | sed -e '1,/^list of commands:/d' \ - -e '/^global options:/,$d' \ - -e '/^ [^ ]/!d; s/^ //; s/[,:]//g;')) - - commands="${all[*]##debug*}" - result=$(compgen -W "${commands[*]}" -- "$cur") + all=$(_hg_command_list) + commands=${all%%$'\n'debug*} + result=$(compgen -W '$commands' -- "$cur") # hide debug commands from users, but complete them if # there is no other possible command if [ "$result" = "" ]; then local debug - debug=(${all[*]##!(debug*)}) - debug="${debug[*]/g/debug}" - result=$(compgen -W "$debug" -- "$cur") + debug=debug${all#*$'\n'debug} + result=$(compgen -W '$debug' -- "$cur") fi COMPREPLY=(${COMPREPLY[@]:-} $result) @@ -25,8 +56,8 @@ _hg_paths() { - local paths="$(hg paths | sed -e 's/ = .*$//')" - COMPREPLY=(${COMPREPLY[@]:-} $( compgen -W "$paths" -- "$cur" )) + local paths="$(hg paths 2> /dev/null | sed -e 's/ = .*$//')" + COMPREPLY=(${COMPREPLY[@]:-} $( compgen -W '$paths' -- "$cur" )) } _hg_repos() @@ -39,14 +70,15 @@ _hg_status() { - local files="$( hg status -$1 | cut -b 3- )" - COMPREPLY=(${COMPREPLY[@]:-} $( compgen -W "$files" -- "$cur" )) + local files="$( hg status -n$1 . 2> /dev/null)" + COMPREPLY=(${COMPREPLY[@]:-} $( compgen -W '$files' -- "$cur" )) } _hg_tags() { - local tags="$(hg tags | sed -e 's/[0-9]*:[a-f0-9]\{40\}$//; s/ *$//')" - COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$tags" -- "$cur") ) + local tags="$(hg tags 2> /dev/null | + sed -e 's/[0-9]*:[a-f0-9]\{40\}$//; s/ *$//')" + COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W '$tags' -- "$cur") ) } # this is "kind of" ugly... @@ -90,10 +122,9 @@ done if [[ "$cur" == -* ]]; then - # this assumes that there are no commands with spaces in the name - opts=$(hg -v help $cmd | sed -e '/^ *-/!d; s/ [^- ].*//') + opts=$(_hg_option_list $cmd) - COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$opts" -- "$cur") ) + COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W '$opts' -- "$cur") ) return fi @@ -115,7 +146,7 @@ fi # canonicalize command name - cmd=$(hg -q help "$cmd" | sed -e 's/^hg //; s/ .*//; 1q') + cmd=$(hg -q help "$cmd" 2> /dev/null | sed -e 's/^hg //; s/ .*//; 1q') if [ "$cmd" != status ] && [ "$prev" = -r ] || [ "$prev" = --rev ]; then _hg_tags @@ -140,19 +171,19 @@ _hg_status "u" ;; commit) - _hg_status "mra" + _hg_status "mar" ;; remove) - _hg_status "r" + _hg_status "d" ;; forget) _hg_status "a" ;; diff) - _hg_status "mra" + _hg_status "mar" ;; revert) - _hg_status "mra" + _hg_status "mard" ;; clone) local count=$(_hg_count_non_option) @@ -167,17 +198,6 @@ debugdata) COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -X "!*.d" -- "$cur" )) ;; - cat) - local count=$(_hg_count_non_option '-o|--output') - if [ $count = 2 ]; then - _hg_tags - else - COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -- "$cur" )) - fi - ;; - *) - COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -- "$cur" )) - ;; esac } diff -r 675ca845c2f8 -r c21b54f7f7b8 contrib/convert-repo --- a/contrib/convert-repo Thu Dec 15 18:04:39 2005 +0100 +++ b/contrib/convert-repo Wed Feb 01 19:18:15 2006 +0100 @@ -21,7 +21,7 @@ # interrupted and can be run repeatedly to copy new commits. import sys, os, zlib, sha, time -from mercurial import hg, ui, util +from mercurial import hg, ui, util, commands class convert_git: def __init__(self, path): @@ -113,7 +113,7 @@ except: pass - def putcommit(self, files, parents, author, dest, text): + def putcommit(self, files, parents, author, date, text): seen = {} pl = [] for p in parents: @@ -129,8 +129,13 @@ while parents: p1 = p2 p2 = parents.pop(0) - self.repo.rawcommit(files, text, author, dest, - hg.bin(p1), hg.bin(p2)) + self.repo.dirstate.setparents(hg.bin(p1), hg.bin(p2)) + if len(files) > 0: + olddir = os.getcwd() + os.chdir(self.path) + commands.addremove(self.repo.ui, self.repo, *files) + os.chdir(olddir) + self.repo.commit(files, text, author, date) text = "(octopus merge fixup)\n" p2 = hg.hex(self.repo.changelog.tip()) @@ -171,9 +176,12 @@ self.commitcache = {} self.map = {} - for l in file(self.mapfile): - sv, dv = l[:-1].split() - self.map[sv] = dv + try: + for l in file(self.mapfile): + sv, dv = l[:-1].split() + self.map[sv] = dv + except IOError: + pass def walktree(self, heads): visit = heads diff -r 675ca845c2f8 -r c21b54f7f7b8 contrib/hbisect.py --- a/contrib/hbisect.py Thu Dec 15 18:04:39 2005 +0100 +++ b/contrib/hbisect.py Wed Feb 01 19:18:15 2006 +0100 @@ -21,8 +21,8 @@ return parents.pop() def check_clean(ui, repo): - c, a, d, u = repo.changes() - if c or a or d: + modified, added, removed, deleted, unknown = repo.changes() + if modified or added or removed: ui.warn("Repository is not clean, please commit or revert\n") sys.exit(1) diff -r 675ca845c2f8 -r c21b54f7f7b8 contrib/hg-ssh --- a/contrib/hg-ssh Thu Dec 15 18:04:39 2005 +0100 +++ b/contrib/hg-ssh Wed Feb 01 19:18:15 2006 +0100 @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright 2005 by Intevation GmbH +# Copyright 2005, 2006 by Intevation GmbH # Author(s): # Thomas Arendsen Hein # @@ -20,6 +20,9 @@ If all your repositories are subdirectories of a common directory, you can allow shorter paths with: command="cd path/to/my/repositories && hg-ssh repo1 subdir/repo2" + +You can use pattern matching of your normal shell, e.g.: +command="cd repos && hg-ssh user/thomas/* projects/{mercurial,foo}" """ from mercurial import commands diff -r 675ca845c2f8 -r c21b54f7f7b8 contrib/hgdiff --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/hgdiff Wed Feb 01 19:18:15 2006 +0100 @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +import os, sys, struct, stat +import difflib +import re +from optparse import OptionParser +from mercurial.bdiff import bdiff, blocks +from mercurial.mdiff import bunidiff + +VERSION="0.2" +usage = "usage: %prog [options] file1 file2" +parser = OptionParser(usage=usage) + +parser.add_option("-d", "--difflib", action="store_true", default=False) +parser.add_option('-x', '--count', default=1) +parser.add_option('-c', '--context', type="int", default=3) +parser.add_option('-p', '--show-c-function', action="store_true", default=False) +parser.add_option('-w', '--ignore-all-space', action="store_true", + default=False) + +(options, args) = parser.parse_args() + +if not args: + parser.print_help() + sys.exit(1) + +# simple utility function to put all the +# files from a directory tree into a dict +def buildlist(names, top): + tlen = len(top) + for root, dirs, files in os.walk(top): + l = root[tlen + 1:] + for x in files: + p = os.path.join(root, x) + st = os.lstat(p) + if stat.S_ISREG(st.st_mode): + names[os.path.join(l, x)] = (st.st_dev, st.st_ino) + +def diff_files(file1, file2): + if file1 == None: + b = file(file2).read().splitlines(1) + l1 = "--- %s\n" % (file2) + l2 = "+++ %s\n" % (file2) + l3 = "@@ -0,0 +1,%d @@\n" % len(b) + l = [l1, l2, l3] + ["+" + e for e in b] + elif file2 == None: + a = file(file1).read().splitlines(1) + l1 = "--- %s\n" % (file1) + l2 = "+++ %s\n" % (file1) + l3 = "@@ -1,%d +0,0 @@\n" % len(a) + l = [l1, l2, l3] + ["-" + e for e in a] + else: + t1 = file(file1).read() + t2 = file(file2).read() + l1 = t1.splitlines(1) + l2 = t2.splitlines(1) + if options.difflib: + l = difflib.unified_diff(l1, l2, file1, file2) + else: + l = bunidiff(t1, t2, l1, l2, file1, file2, context=options.context, + showfunc=options.show_c_function, + ignorews=options.ignore_all_space) + for x in l: + if x[-1] != '\n': + x += "\n\ No newline at end of file\n" + print x, + +file1 = args[0] +file2 = args[1] + +if os.path.isfile(file1) and os.path.isfile(file2): + diff_files(file1, file2) +elif os.path.isdir(file1): + if not os.path.isdir(file2): + sys.stderr.write("file types don't match\n") + sys.exit(1) + + d1 = {} + d2 = {} + + buildlist(d1, file1) + buildlist(d2, file2) + keys = d1.keys() + keys.sort() + for x in keys: + if x not in d2: + f2 = None + else: + f2 = os.path.join(file2, x) + st1 = d1[x] + st2 = d2[x] + del d2[x] + if st1[0] == st2[0] and st1[1] == st2[1]: + sys.stderr.write("%s is a hard link\n" % x) + continue + x = os.path.join(file1, x) + diff_files(x, f2) + keys = d2.keys() + keys.sort() + for x in keys: + f1 = None + x = os.path.join(file2, x) + diff_files(f1, x) + diff -r 675ca845c2f8 -r c21b54f7f7b8 contrib/hgk.py --- a/contrib/hgk.py Thu Dec 15 18:04:39 2005 +0100 +++ b/contrib/hgk.py Wed Feb 01 19:18:15 2006 +0100 @@ -1,5 +1,3 @@ -#!/usr/bin/env python -# # Minimal support for git commands on an hg repository # # Copyright 2005 Chris Mason @@ -16,13 +14,13 @@ return time.asctime(time.gmtime(c[2][0])) if not changes: - (c, a, d, u) = repo.changes(node1, node2, files, match=match) - else: - (c, a, d, u) = changes + changes = repo.changes(node1, node2, files, match=match) + modified, added, removed, deleted, unknown = changes if files: - c, a, d = map(lambda x: filterfiles(files, x), (c, a, d)) + modified, added, removed = map(lambda x: filterfiles(files, x), + (modified, added, removed)) - if not c and not a and not d: + if not modified and not added and not removed: return if node2: @@ -42,19 +40,19 @@ mmap = repo.manifest.read(change[0]) date1 = date(change) - for f in c: + for f in modified: to = None if f in mmap: to = repo.file(f).read(mmap[f]) tn = read(f) fp.write("diff --git a/%s b/%s\n" % (f, f)) fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text)) - for f in a: + for f in added: to = None tn = read(f) fp.write("diff --git /dev/null b/%s\n" % (f)) fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text)) - for f in d: + for f in removed: to = repo.file(f).read(mmap[f]) tn = None fp.write("diff --git a/%s /dev/null\n" % (f)) @@ -69,12 +67,12 @@ if node2: change = repo.changelog.read(node2) mmap2 = repo.manifest.read(change[0]) - (c, a, d, u) = repo.changes(node1, node2) + modified, added, removed, deleted, unknown = repo.changes(node1, node2) def read(f): return repo.file(f).read(mmap2[f]) date2 = date(change) else: date2 = time.asctime() - (c, a, d, u) = repo.changes(node1, None) + modified, added, removed, deleted, unknown = repo.changes(node1) if not node1: node1 = repo.dirstate.parents()[0] def read(f): return file(os.path.join(repo.root, f)).read() @@ -84,13 +82,13 @@ date1 = date(change) empty = "0" * 40; - for f in c: + for f in modified: # TODO get file permissions print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]), hg.hex(mmap2[f]), f, f) - for f in a: + for f in added: print ":000000 100664 %s %s N\t%s\t%s" % (empty, hg.hex(mmap2[f]), f, f) - for f in d: + for f in removed: print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f) ## diff -r 675ca845c2f8 -r c21b54f7f7b8 contrib/patchbomb --- a/contrib/patchbomb Thu Dec 15 18:04:39 2005 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,270 +0,0 @@ -#!/usr/bin/python -# -# Interactive script for sending a collection of Mercurial changesets -# as a series of patch emails. -# -# The series is started off with a "[PATCH 0 of N]" introduction, -# which describes the series as a whole. -# -# Each patch email has a Subject line of "[PATCH M of N] ...", using -# the first line of the changeset description as the subject text. -# The message contains two or three body parts: -# -# The remainder of the changeset description. -# -# [Optional] If the diffstat program is installed, the result of -# running diffstat on the patch. -# -# The patch itself, as generated by "hg export". -# -# Each message refers to all of its predecessors using the In-Reply-To -# and References headers, so they will show up as a sequence in -# threaded mail and news readers, and in mail archives. -# -# For each changeset, you will be prompted with a diffstat summary and -# the changeset summary, so you can be sure you are sending the right -# changes. -# -# It is best to run this script with the "-n" (test only) flag before -# firing it up "for real", in which case it will use your pager to -# display each of the messages that it would send. -# -# To configure a default mail host, add a section like this to your -# hgrc file: -# -# [smtp] -# host = my_mail_host -# port = 1025 -# tls = yes # or omit if not needed -# username = user # if SMTP authentication required -# password = password # if SMTP authentication required - PLAINTEXT -# -# To configure other defaults, add a section like this to your hgrc -# file: -# -# [patchbomb] -# from = My Name -# to = recipient1, recipient2, ... -# cc = cc1, cc2, ... - -from email.MIMEMultipart import MIMEMultipart -from email.MIMEText import MIMEText -from mercurial import commands -from mercurial import fancyopts -from mercurial import hg -from mercurial import ui -import os -import popen2 -import smtplib -import socket -import sys -import tempfile -import time - -try: - # readline gives raw_input editing capabilities, but is not - # present on windows - import readline -except ImportError: pass - -def diffstat(patch): - fd, name = tempfile.mkstemp() - try: - p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name) - try: - for line in patch: print >> p.tochild, line - p.tochild.close() - if p.wait(): return - fp = os.fdopen(fd, 'r') - stat = [] - for line in fp: stat.append(line.lstrip()) - last = stat.pop() - stat.insert(0, last) - stat = ''.join(stat) - if stat.startswith('0 files'): raise ValueError - return stat - except: raise - finally: - try: os.unlink(name) - except: pass - -def patchbomb(ui, repo, *revs, **opts): - def prompt(prompt, default = None, rest = ': ', empty_ok = False): - if default: prompt += ' [%s]' % default - prompt += rest - while True: - r = raw_input(prompt) - if r: return r - if default is not None: return default - if empty_ok: return r - ui.warn('Please enter a valid value.\n') - - def confirm(s): - if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'): - raise ValueError - - def cdiffstat(summary, patch): - s = diffstat(patch) - if s: - if summary: - ui.write(summary, '\n') - ui.write(s, '\n') - confirm('Does the diffstat above look okay') - return s - - def makepatch(patch, idx, total): - desc = [] - node = None - body = '' - for line in patch: - if line.startswith('#'): - if line.startswith('# Node ID'): node = line.split()[-1] - continue - if line.startswith('diff -r'): break - desc.append(line) - if not node: raise ValueError - - #body = ('\n'.join(desc[1:]).strip() or - # 'Patch subject is complete summary.') - #body += '\n\n\n' - - if opts['diffstat']: - body += cdiffstat('\n'.join(desc), patch) + '\n\n' - body += '\n'.join(patch) - msg = MIMEText(body) - subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip()) - if subj.endswith('.'): subj = subj[:-1] - msg['Subject'] = subj - msg['X-Mercurial-Node'] = node - return msg - - start_time = int(time.time()) - - def genmsgid(id): - return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn()) - - patches = [] - - class exportee: - def __init__(self, container): - self.lines = [] - self.container = container - self.name = 'email' - - def write(self, data): - self.lines.append(data) - - def close(self): - self.container.append(''.join(self.lines).split('\n')) - self.lines = [] - - commands.export(ui, repo, *args, **{'output': exportee(patches), - 'text': None}) - - jumbo = [] - msgs = [] - - ui.write('This patch series consists of %d patches.\n\n' % len(patches)) - - for p, i in zip(patches, range(len(patches))): - jumbo.extend(p) - msgs.append(makepatch(p, i + 1, len(patches))) - - ui.write('\nWrite the introductory message for the patch series.\n\n') - - sender = (opts['from'] or ui.config('patchbomb', 'from') or - prompt('From', ui.username())) - - msg = MIMEMultipart() - msg['Subject'] = '[PATCH 0 of %d] %s' % ( - len(patches), - opts['subject'] or - prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches))) - - def getaddrs(opt, prpt, default = None): - addrs = opts[opt] or (ui.config('patchbomb', opt) or - prompt(prpt, default = default)).split(',') - return [a.strip() for a in addrs if a.strip()] - to = getaddrs('to', 'To') - cc = getaddrs('cc', 'Cc', '') - - ui.write('Finish with ^D or a dot on a line by itself.\n\n') - - body = [] - - while True: - try: l = raw_input() - except EOFError: break - if l == '.': break - body.append(l) - - msg.attach(MIMEText('\n'.join(body) + '\n')) - - ui.write('\n') - - if opts['diffstat']: - d = cdiffstat('Final summary:\n', jumbo) - if d: msg.attach(MIMEText(d)) - - msgs.insert(0, msg) - - if not opts['test']: - s = smtplib.SMTP() - s.connect(host = ui.config('smtp', 'host', 'mail'), - port = int(ui.config('smtp', 'port', 25))) - if ui.configbool('smtp', 'tls'): - s.ehlo() - s.starttls() - s.ehlo() - username = ui.config('smtp', 'username') - password = ui.config('smtp', 'password') - if username and password: - s.login(username, password) - parent = None - tz = time.strftime('%z') - for m in msgs: - try: - m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) - except TypeError: - m['Message-Id'] = genmsgid('patchbomb') - if parent: - m['In-Reply-To'] = parent - else: - parent = m['Message-Id'] - m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz - start_time += 1 - m['From'] = sender - m['To'] = ', '.join(to) - if cc: m['Cc'] = ', '.join(cc) - ui.status('Sending ', m['Subject'], ' ...\n') - if opts['test']: - fp = os.popen(os.getenv('PAGER', 'more'), 'w') - fp.write(m.as_string(0)) - fp.write('\n') - fp.close() - else: - s.sendmail(sender, to + cc, m.as_string(0)) - if not opts['test']: - s.close() - -if __name__ == '__main__': - optspec = [('c', 'cc', [], 'email addresses of copy recipients'), - ('d', 'diffstat', None, 'add diffstat output to messages'), - ('f', 'from', '', 'email address of sender'), - ('n', 'test', None, 'print messages that would be sent'), - ('s', 'subject', '', 'subject of introductory message'), - ('t', 'to', [], 'email addresses of recipients')] - options = {} - try: - args = fancyopts.fancyopts(sys.argv[1:], commands.globalopts + optspec, - options) - except fancyopts.getopt.GetoptError, inst: - u = ui.ui() - u.warn('error: %s' % inst) - sys.exit(1) - - u = ui.ui(options["verbose"], options["debug"], options["quiet"], - not options["noninteractive"]) - repo = hg.repository(ui = u) - - patchbomb(u, repo, *args, **options) diff -r 675ca845c2f8 -r c21b54f7f7b8 doc/hg.1.txt --- a/doc/hg.1.txt Thu Dec 15 18:04:39 2005 +0100 +++ b/doc/hg.1.txt Wed Feb 01 19:18:15 2006 +0100 @@ -155,6 +155,8 @@ errors. In these cases, use the --pull option to avoid hardlinking. + See pull for valid source format details. + options: -U, --noupdate do not update the new working directory --pull use pull protocol to copy metadata @@ -388,6 +390,8 @@ default push repo. These are the changesets that would be pushed if a push was requested. + See pull for valid source format details. + options: -p, --patch show patch @@ -454,11 +458,14 @@ --remotecmd specify hg command to run on the remote side rawcommit [-p -d -u -F -m -l]:: - Lowlevel commit, for use in helper scripts. + Lowlevel commit, for use in helper scripts. (DEPRECATED) This command is not intended to be used by normal users, as it is primarily useful for importing from other SCMs. + This command is now deprecated and will be removed in a future + release, please use debugsetparents and commit instead. + recover:: Recover from an interrupted commit or pull. @@ -497,9 +504,18 @@ aliases: mv revert [names ...]:: - Revert any uncommitted modifications made to the named files or - directories. This restores the contents of the affected files to - an unmodified state. + The revert command has two modes of operation. + + In its default mode, it reverts any uncommitted modifications made + to the named files or directories. This restores the contents of + the affected files to an unmodified state. + + Using the -r option, it reverts the given files or directories to + their state as of an earlier revision. This can be helpful to "roll + back" some or all of a change that should not have been committed. + + Revert modifies the working directory. It does not commit any + changes, or change the parent of the current working directory. If a file has been deleted, it is recreated. If the executable mode of a file was changed, it is reset. diff -r 675ca845c2f8 -r c21b54f7f7b8 hgeditor --- a/hgeditor Thu Dec 15 18:04:39 2005 +0100 +++ b/hgeditor Wed Feb 01 19:18:15 2006 +0100 @@ -1,10 +1,7 @@ #!/bin/sh # -# This is an example of using HGEDITOR to automate the signing of -# commits and so on. - -# change this to one to turn on GPG support -SIGN=0 +# This is an example of using HGEDITOR to create of diff to review the +# changes while commiting. # If you want to pass your favourite editor some other parameters # only for Mercurial, modify this: @@ -43,12 +40,7 @@ done ) -echo > "$HGTMP/msg" -if [ "$SIGN" == "1" ]; then - MANIFEST=`grep '^HG: manifest hash' "$1" | cut -b 19-` - echo -e "\nmanifest hash: $MANIFEST" >> "$HGTMP/msg" -fi -grep -vE '^(HG: manifest hash .*)?$' "$1" >> "$HGTMP/msg" +cat "$1" > "$HGTMP/msg" CHECKSUM=`md5sum "$HGTMP/msg"` if [ -s "$HGTMP/diff" ]; then @@ -58,14 +50,6 @@ fi echo "$CHECKSUM" | md5sum -c >/dev/null 2>&1 && exit 13 -if [ "$SIGN" == "1" ]; then - { - head -n 1 "$HGTMP/msg" - echo - grep -v "^HG:" "$HGTMP/msg" | gpg -t -a -u "${HGUSER}" --clearsign - } > "$HGTMP/msg.gpg" && mv "$HGTMP/msg.gpg" "$1" -else - mv "$HGTMP/msg" "$1" -fi +mv "$HGTMP/msg" "$1" exit $? diff -r 675ca845c2f8 -r c21b54f7f7b8 hgext/gpg.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/gpg.py Wed Feb 01 19:18:15 2006 +0100 @@ -0,0 +1,206 @@ +import os, tempfile, binascii, errno +from mercurial import util +from mercurial import node as hgnode + +class gpg: + def __init__(self, path, key=None): + self.path = path + self.key = (key and " --local-user \"%s\"" % key) or "" + + def sign(self, data): + gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key) + return util.filter(data, gpgcmd) + + def verify(self, data, sig): + """ returns of the good and bad signatures""" + try: + fd, sigfile = tempfile.mkstemp(prefix="hggpgsig") + fp = os.fdopen(fd, 'wb') + fp.write(sig) + fp.close() + fd, datafile = tempfile.mkstemp(prefix="hggpgdata") + fp = os.fdopen(fd, 'wb') + fp.write(data) + fp.close() + gpgcmd = "%s --logger-fd 1 --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile) + #gpgcmd = "%s --status-fd 1 --verify \"%s\" \"%s\"" % (self.path, sigfile, datafile) + ret = util.filter("", gpgcmd) + except: + for f in (sigfile, datafile): + try: + if f: os.unlink(f) + except: pass + raise + keys = [] + key, fingerprint = None, None + err = "" + for l in ret.splitlines(): + # see DETAILS in the gnupg documentation + # filter the logger output + if not l.startswith("[GNUPG:]"): + continue + l = l[9:] + if l.startswith("ERRSIG"): + err = "error while verifying signature" + break + elif l.startswith("VALIDSIG"): + # fingerprint of the primary key + fingerprint = l.split()[10] + elif (l.startswith("GOODSIG") or + l.startswith("EXPSIG") or + l.startswith("EXPKEYSIG") or + l.startswith("BADSIG")): + if key is not None: + keys.append(key + [fingerprint]) + key = l.split(" ", 2) + fingerprint = None + if err: + return err, [] + if key is not None: + keys.append(key + [fingerprint]) + return err, keys + +def newgpg(ui, **opts): + gpgpath = ui.config("gpg", "cmd", "gpg") + gpgkey = opts.get('key') + if not gpgkey: + gpgkey = ui.config("gpg", "key", None) + return gpg(gpgpath, gpgkey) + +def check(ui, repo, rev): + """verify all the signatures there may be for a particular revision""" + mygpg = newgpg(ui) + rev = repo.lookup(rev) + hexrev = hgnode.hex(rev) + keys = [] + + def addsig(fn, ln, l): + if not l: return + n, v, sig = l.split(" ", 2) + if n == hexrev: + data = node2txt(repo, rev, v) + sig = binascii.a2b_base64(sig) + err, k = mygpg.verify(data, sig) + if not err: + keys.append((k, fn, ln)) + else: + ui.warn("%s:%d %s\n" % (fn, ln , err)) + + fl = repo.file(".hgsigs") + h = fl.heads() + h.reverse() + # read the heads + for r in h: + ln = 1 + for l in fl.read(r).splitlines(): + addsig(".hgsigs|%s" % hgnode.short(r), ln, l) + ln +=1 + try: + # read local signatures + ln = 1 + f = repo.opener("localsigs") + for l in f: + addsig("localsigs", ln, l) + ln +=1 + except IOError: + pass + + if not keys: + ui.write("%s not signed\n" % hgnode.short(rev)) + return + valid = [] + # warn for expired key and/or sigs + for k, fn, ln in keys: + prefix = "%s:%d" % (fn, ln) + for key in k: + if key[0] == "BADSIG": + ui.write("%s Bad signature from \"%s\"\n" % (prefix, key[2])) + continue + if key[0] == "EXPSIG": + ui.write("%s Note: Signature has expired" + " (signed by: \"%s\")\n" % (prefix, key[2])) + elif key[0] == "EXPKEYSIG": + ui.write("%s Note: This key has expired" + " (signed by: \"%s\")\n" % (prefix, key[2])) + valid.append((key[1], key[2], key[3])) + # print summary + ui.write("%s is signed by:\n" % hgnode.short(rev)) + for keyid, user, fingerprint in valid: + role = getrole(ui, fingerprint) + ui.write(" %s (%s)\n" % (user, role)) + +def getrole(ui, fingerprint): + return ui.config("gpg", fingerprint, "no role defined") + +def sign(ui, repo, *revs, **opts): + """add a signature for the current tip or a given revision""" + mygpg = newgpg(ui, **opts) + sigver = "0" + sigmessage = "" + if revs: + nodes = [repo.lookup(n) for n in revs] + else: + nodes = [repo.changelog.tip()] + + for n in nodes: + hexnode = hgnode.hex(n) + ui.write("Signing %d:%s\n" % (repo.changelog.rev(n), + hgnode.short(n))) + # build data + data = node2txt(repo, n, sigver) + sig = mygpg.sign(data) + if not sig: + raise util.Abort("Error while signing") + sig = binascii.b2a_base64(sig) + sig = sig.replace("\n", "") + sigmessage += "%s %s %s\n" % (hexnode, sigver, sig) + + # write it + if opts['local']: + repo.opener("localsigs", "ab").write(sigmessage) + return + + for x in repo.changes(): + if ".hgsigs" in x and not opts["force"]: + raise util.Abort("working copy of .hgsigs is changed " + "(please commit .hgsigs manually " + "or use --force)") + + repo.wfile(".hgsigs", "ab").write(sigmessage) + + if repo.dirstate.state(".hgsigs") == '?': + repo.add([".hgsigs"]) + + if opts["no_commit"]: + return + + message = opts['message'] + if not message: + message = "\n".join(["Added signature for changeset %s" % hgnode.hex(n) + for n in nodes]) + try: + repo.commit([".hgsigs"], message, opts['user'], opts['date']) + except ValueError, inst: + raise util.Abort(str(inst)) + +def node2txt(repo, node, ver): + """map a manifest into some text""" + if ver == "0": + return "%s\n" % hgnode.hex(node) + else: + util.Abort("unknown signature version") + +cmdtable = { + "sign": + (sign, + [('l', 'local', None, "make the signature local"), + ('f', 'force', None, "sign even if the sigfile is modified"), + ('', 'no-commit', None, "do not commit the sigfile after signing"), + ('m', 'message', "", "commit message"), + ('d', 'date', "", "date code"), + ('u', 'user', "", "user"), + ('k', 'key', "", "the key id to sign with")], + "hg sign [OPTION]... REVISIONS"), + "sigcheck": (check, [], 'hg sigcheck REVISION') +} + diff -r 675ca845c2f8 -r c21b54f7f7b8 hgext/patchbomb.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/patchbomb.py Wed Feb 01 19:18:15 2006 +0100 @@ -0,0 +1,275 @@ +# Command for sending a collection of Mercurial changesets as a series +# of patch emails. +# +# The series is started off with a "[PATCH 0 of N]" introduction, +# which describes the series as a whole. +# +# Each patch email has a Subject line of "[PATCH M of N] ...", using +# the first line of the changeset description as the subject text. +# The message contains two or three body parts: +# +# The remainder of the changeset description. +# +# [Optional] If the diffstat program is installed, the result of +# running diffstat on the patch. +# +# The patch itself, as generated by "hg export". +# +# Each message refers to all of its predecessors using the In-Reply-To +# and References headers, so they will show up as a sequence in +# threaded mail and news readers, and in mail archives. +# +# For each changeset, you will be prompted with a diffstat summary and +# the changeset summary, so you can be sure you are sending the right +# changes. +# +# It is best to run this script with the "-n" (test only) flag before +# firing it up "for real", in which case it will use your pager to +# display each of the messages that it would send. +# +# To configure a default mail host, add a section like this to your +# hgrc file: +# +# [smtp] +# host = my_mail_host +# port = 1025 +# tls = yes # or omit if not needed +# username = user # if SMTP authentication required +# password = password # if SMTP authentication required - PLAINTEXT +# +# To configure other defaults, add a section like this to your hgrc +# file: +# +# [patchbomb] +# from = My Name +# to = recipient1, recipient2, ... +# cc = cc1, cc2, ... + +from email.MIMEMultipart import MIMEMultipart +from email.MIMEText import MIMEText +from mercurial import commands +from mercurial import hg +from mercurial import ui +from mercurial.i18n import gettext as _ +import os +import popen2 +import smtplib +import socket +import sys +import tempfile +import time + +try: + # readline gives raw_input editing capabilities, but is not + # present on windows + import readline +except ImportError: pass + +def diffstat(patch): + fd, name = tempfile.mkstemp() + try: + p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name) + try: + for line in patch: print >> p.tochild, line + p.tochild.close() + if p.wait(): return + fp = os.fdopen(fd, 'r') + stat = [] + for line in fp: stat.append(line.lstrip()) + last = stat.pop() + stat.insert(0, last) + stat = ''.join(stat) + if stat.startswith('0 files'): raise ValueError + return stat + except: raise + finally: + try: os.unlink(name) + except: pass + +def patchbomb(ui, repo, *revs, **opts): + '''send changesets as a series of patch emails + + The series starts with a "[PATCH 0 of N]" introduction, which + describes the series as a whole. + + Each patch email has a Subject line of "[PATCH M of N] ...", using + the first line of the changeset description as the subject text. + The message contains two or three body parts. First, the rest of + the changeset description. Next, (optionally) if the diffstat + program is installed, the result of running diffstat on the patch. + Finally, the patch itself, as generated by "hg export".''' + def prompt(prompt, default = None, rest = ': ', empty_ok = False): + if default: prompt += ' [%s]' % default + prompt += rest + while True: + r = raw_input(prompt) + if r: return r + if default is not None: return default + if empty_ok: return r + ui.warn(_('Please enter a valid value.\n')) + + def confirm(s): + if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'): + raise ValueError + + def cdiffstat(summary, patch): + s = diffstat(patch) + if s: + if summary: + ui.write(summary, '\n') + ui.write(s, '\n') + confirm(_('Does the diffstat above look okay')) + return s + + def makepatch(patch, idx, total): + desc = [] + node = None + body = '' + for line in patch: + if line.startswith('#'): + if line.startswith('# Node ID'): node = line.split()[-1] + continue + if line.startswith('diff -r'): break + desc.append(line) + if not node: raise ValueError + + #body = ('\n'.join(desc[1:]).strip() or + # 'Patch subject is complete summary.') + #body += '\n\n\n' + + if opts['plain']: + while patch and patch[0].startswith('# '): patch.pop(0) + if patch: patch.pop(0) + while patch and not patch[0].strip(): patch.pop(0) + if opts['diffstat']: + body += cdiffstat('\n'.join(desc), patch) + '\n\n' + body += '\n'.join(patch) + msg = MIMEText(body) + subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip()) + if subj.endswith('.'): subj = subj[:-1] + msg['Subject'] = subj + msg['X-Mercurial-Node'] = node + return msg + + start_time = int(time.time()) + + def genmsgid(id): + return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn()) + + patches = [] + + class exportee: + def __init__(self, container): + self.lines = [] + self.container = container + self.name = 'email' + + def write(self, data): + self.lines.append(data) + + def close(self): + self.container.append(''.join(self.lines).split('\n')) + self.lines = [] + + commands.export(ui, repo, *revs, **{'output': exportee(patches), + 'switch_parent': False, + 'text': None}) + + jumbo = [] + msgs = [] + + ui.write(_('This patch series consists of %d patches.\n\n') % len(patches)) + + for p, i in zip(patches, range(len(patches))): + jumbo.extend(p) + msgs.append(makepatch(p, i + 1, len(patches))) + + ui.write(_('\nWrite the introductory message for the patch series.\n\n')) + + sender = (opts['from'] or ui.config('patchbomb', 'from') or + prompt('From', ui.username())) + + msg = MIMEMultipart() + msg['Subject'] = '[PATCH 0 of %d] %s' % ( + len(patches), + opts['subject'] or + prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches))) + + def getaddrs(opt, prpt, default = None): + addrs = opts[opt] or (ui.config('patchbomb', opt) or + prompt(prpt, default = default)).split(',') + return [a.strip() for a in addrs if a.strip()] + to = getaddrs('to', 'To') + cc = getaddrs('cc', 'Cc', '') + + ui.write(_('Finish with ^D or a dot on a line by itself.\n\n')) + + body = [] + + while True: + try: l = raw_input() + except EOFError: break + if l == '.': break + body.append(l) + + msg.attach(MIMEText('\n'.join(body) + '\n')) + + ui.write('\n') + + if opts['diffstat']: + d = cdiffstat(_('Final summary:\n'), jumbo) + if d: msg.attach(MIMEText(d)) + + msgs.insert(0, msg) + + if not opts['test']: + s = smtplib.SMTP() + s.connect(host = ui.config('smtp', 'host', 'mail'), + port = int(ui.config('smtp', 'port', 25))) + if ui.configbool('smtp', 'tls'): + s.ehlo() + s.starttls() + s.ehlo() + username = ui.config('smtp', 'username') + password = ui.config('smtp', 'password') + if username and password: + s.login(username, password) + parent = None + tz = time.strftime('%z') + for m in msgs: + try: + m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) + except TypeError: + m['Message-Id'] = genmsgid('patchbomb') + if parent: + m['In-Reply-To'] = parent + else: + parent = m['Message-Id'] + m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz + start_time += 1 + m['From'] = sender + m['To'] = ', '.join(to) + if cc: m['Cc'] = ', '.join(cc) + ui.status('Sending ', m['Subject'], ' ...\n') + if opts['test']: + fp = os.popen(os.getenv('PAGER', 'more'), 'w') + fp.write(m.as_string(0)) + fp.write('\n') + fp.close() + else: + s.sendmail(sender, to + cc, m.as_string(0)) + if not opts['test']: + s.close() + +cmdtable = { + 'email': + (patchbomb, + [('c', 'cc', [], 'email addresses of copy recipients'), + ('d', 'diffstat', None, 'add diffstat output to messages'), + ('f', 'from', '', 'email address of sender'), + ('', 'plain', None, 'omit hg patch header'), + ('n', 'test', None, 'print messages that would be sent'), + ('s', 'subject', '', 'subject of introductory message'), + ('t', 'to', [], 'email addresses of recipients')], + "hg email [OPTION]... [REV]...") + } diff -r 675ca845c2f8 -r c21b54f7f7b8 hgmerge --- a/hgmerge Thu Dec 15 18:04:39 2005 +0100 +++ b/hgmerge Wed Feb 01 19:18:15 2006 +0100 @@ -44,6 +44,39 @@ cp "$LOCAL.orig" "$LOCAL" fi +# on MacOS X try FileMerge.app, shipped with Apple's developer tools +# TODO: make proper temp files. foo.orig and foo.link are dangerous +FILEMERGE='/Developer/Applications/Utilities/FileMerge.app/Contents/MacOS/FileMerge' +if type "$FILEMERGE" > /dev/null 2>&1; then + cp "$LOCAL.orig" "$LOCAL" + ln "$LOCAL" "$LOCAL.link" + # filemerge prefers the right by default + if ! "$FILEMERGE" -left "$OTHER" -right "$LOCAL" -ancestor "$BASE" -merge "$LOCAL" + then + echo "FileMerge failed to launch" + exit 1 + fi + if ! test "$LOCAL" -ef "$LOCAL.link" + then + rm "$LOCAL.orig" "$LOCAL.link" + exit 0 + else + rm "$LOCAL.link" + echo "$LOCAL is unchanged. Was the merge successful?" + select answer in yes no + do + if test "$answer" == "yes" + then + rm "$LOCAL.orig" + exit 0 + else + exit 1 + fi + done + exit 1 + fi +fi + if [ -n "$DISPLAY" ]; then # try using kdiff3, which is fairly nice if type kdiff3 > /dev/null 2>&1; then diff -r 675ca845c2f8 -r c21b54f7f7b8 mercurial/commands.py --- a/mercurial/commands.py Thu Dec 15 18:04:39 2005 +0100 +++ b/mercurial/commands.py Wed Feb 01 19:18:15 2006 +0100 @@ -40,14 +40,14 @@ opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] cwd = '' return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'), - opts.get('exclude'), head) + (cwd,) + opts.get('exclude'), head) def makewalk(repo, pats, opts, node=None, head=''): - files, matchfn, anypats, cwd = matchpats(repo, pats, opts, head) + files, matchfn, anypats = matchpats(repo, pats, opts, head) exact = dict(zip(files, files)) def walk(): for src, fn in repo.walk(node=node, files=files, match=matchfn): - yield src, fn, util.pathto(cwd, fn), fn in exact + yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact return files, matchfn, walk() def walk(repo, pats, opts, node=None, head=''): @@ -82,7 +82,7 @@ "iter", rev, None: in-order traversal of the revs earlier iterated over with "add" - use to display data''' - files, matchfn, anypats, cwd = matchpats(repo, pats, opts) + files, matchfn, anypats = matchpats(repo, pats, opts) if repo.changelog.count() == 0: return [], False, matchfn @@ -170,8 +170,10 @@ num = int(val) if str(num) != val: raise ValueError - if num < 0: num += revcount - if num < 0: num = 0 + if num < 0: + num += revcount + if num < 0: + num = 0 elif num >= revcount: raise ValueError except ValueError: @@ -191,12 +193,14 @@ end = fix(end, revcount - 1) step = start > end and -1 or 1 for rev in xrange(start, end+step, step): - if rev in seen: continue + if rev in seen: + continue seen[rev] = 1 yield str(rev) else: rev = fix(spec, None) - if rev in seen: continue + if rev in seen: + continue seen[rev] = 1 yield str(rev) @@ -259,13 +263,13 @@ def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always, changes=None, text=False): if not changes: - (c, a, d, u) = repo.changes(node1, node2, files, match=match) - else: - (c, a, d, u) = changes + changes = repo.changes(node1, node2, files, match=match) + modified, added, removed, deleted, unknown = changes if files: - c, a, d = map(lambda x: filterfiles(files, x), (c, a, d)) - - if not c and not a and not d: + modified, added, removed = map(lambda x: filterfiles(files, x), + (modified, added, removed)) + + if not modified and not added and not removed: return if node2: @@ -279,7 +283,7 @@ if not node1: node1 = repo.dirstate.parents()[0] def read(f): - return repo.wfile(f).read() + return repo.wread(f) if ui.quiet: r = None @@ -291,20 +295,26 @@ mmap = repo.manifest.read(change[0]) date1 = util.datestr(change[2]) - for f in c: + diffopts = ui.diffopts() + showfunc = diffopts['showfunc'] + ignorews = diffopts['ignorews'] + for f in modified: to = None if f in mmap: to = repo.file(f).read(mmap[f]) tn = read(f) - fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text)) - for f in a: + fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text, + showfunc=showfunc, ignorews=ignorews)) + for f in added: to = None tn = read(f) - fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text)) - for f in d: + fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text, + showfunc=showfunc, ignorews=ignorews)) + for f in removed: to = repo.file(f).read(mmap[f]) tn = None - fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text)) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text, + showfunc=showfunc, ignorews=ignorews)) def trimuser(ui, name, rev, revcache): """trim the name of the user who committed a change""" @@ -439,7 +449,7 @@ if e[0].__doc__: d = e[0].__doc__.splitlines(0)[0].rstrip() h[f] = d - cmds[f]=c.lstrip("^") + cmds[f] = c.lstrip("^") fns = h.keys() fns.sort() @@ -447,7 +457,7 @@ for f in fns: if ui.verbose: commands = cmds[f].replace("|",", ") - ui.write(" %s:\n %s\n"%(commands,h[f])) + ui.write(" %s:\n %s\n"%(commands, h[f])) else: ui.write(' %-*s %s\n' % (m, f, h[f])) @@ -463,7 +473,8 @@ opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt, longopt and " --%s" % longopt), "%s%s" % (desc, - default and _(" (default: %s)") % default + default + and _(" (default: %s)") % default or ""))) if opt_output: @@ -489,7 +500,8 @@ names = [] for src, abs, rel, exact in walk(repo, pats, opts): if exact: - if ui.verbose: ui.status(_('adding %s\n') % rel) + if ui.verbose: + ui.status(_('adding %s\n') % rel) names.append(abs) elif repo.dirstate.state(abs) == '?': ui.status(_('adding %s\n') % rel) @@ -509,11 +521,11 @@ if src == 'f' and repo.dirstate.state(abs) == '?': add.append(abs) if ui.verbose or not exact: - ui.status(_('adding %s\n') % rel) + ui.status(_('adding %s\n') % ((pats and rel) or abs)) if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel): remove.append(abs) if ui.verbose or not exact: - ui.status(_('removing %s\n') % rel) + ui.status(_('removing %s\n') % ((pats and rel) or abs)) repo.add(add) repo.remove(remove) @@ -539,11 +551,11 @@ dcache = {} def getdate(rev): - datestr = dcache.get(rev) + datestr = dcache.get(rev) if datestr is None: cl = repo.changelog.read(repo.changelog.node(rev)) datestr = dcache[rev] = util.datestr(cl[2]) - return datestr + return datestr if not pats: raise util.Abort(_('at least one file name or pattern required')) @@ -562,12 +574,13 @@ for src, abs, rel, exact in walk(repo, pats, opts): if abs not in mmap: - ui.warn(_("warning: %s is not in the repository!\n") % rel) + ui.warn(_("warning: %s is not in the repository!\n") % + ((pats and rel) or abs)) continue f = repo.file(abs) if not opts['text'] and util.binary(f.read(mmap[abs])): - ui.write(_("%s: binary file\n") % rel) + ui.write(_("%s: binary file\n") % ((pats and rel) or abs)) continue lines = f.annotate(mmap[abs]) @@ -722,7 +735,8 @@ try: util.copyfiles(src, dst) except OSError, inst: - if inst.errno != errno.ENOENT: raise + if inst.errno != errno.ENOENT: + raise repo = hg.repository(ui, dest) @@ -730,7 +744,8 @@ revs = None if opts['rev']: if not other.local(): - raise util.Abort("clone -r not supported yet for remote repositories.") + 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) @@ -775,10 +790,11 @@ if opts['addremove']: addremove(ui, repo, *pats, **opts) - fns, match, anypats, cwd = matchpats(repo, pats, opts) + fns, match, anypats = matchpats(repo, pats, opts) if pats: - c, a, d, u = repo.changes(files=fns, match=match) - files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r'] + modified, added, removed, deleted, unknown = ( + repo.changes(files=fns, match=match)) + files = modified + added + removed else: files = [] try: @@ -794,10 +810,12 @@ def okaytocopy(abs, rel, exact): reasons = {'?': _('is not managed'), - 'a': _('has been marked for add')} + 'a': _('has been marked for add'), + 'r': _('has been marked for remove')} reason = reasons.get(repo.dirstate.state(abs)) if reason: - if exact: ui.warn(_('%s: not copying - file %s\n') % (rel, reason)) + if exact: + ui.warn(_('%s: not copying - file %s\n') % (rel, reason)) else: return True @@ -845,12 +863,11 @@ def targetpathfn(pat, dest, srcs): if os.path.isdir(pat): - if pat.endswith(os.sep): - pat = pat[:-len(os.sep)] + abspfx = util.canonpath(repo.root, cwd, pat) if destdirexists: - striplen = len(os.path.split(pat)[0]) + striplen = len(os.path.split(abspfx)[0]) else: - striplen = len(pat) + striplen = len(abspfx) if striplen: striplen += len(os.sep) res = lambda p: os.path.join(dest, p[striplen:]) @@ -864,34 +881,36 @@ if util.patkind(pat, None)[0]: # a mercurial pattern res = lambda p: os.path.join(dest, os.path.basename(p)) - elif len(util.canonpath(repo.root, cwd, pat)) < len(srcs[0][0]): - # A directory. Either the target path contains the last - # component of the source path or it does not. - def evalpath(striplen): - score = 0 - for s in srcs: - t = os.path.join(dest, s[1][striplen:]) - if os.path.exists(t): - score += 1 - return score - - if pat.endswith(os.sep): - pat = pat[:-len(os.sep)] - striplen = len(pat) + len(os.sep) - if os.path.isdir(os.path.join(dest, os.path.split(pat)[1])): - score = evalpath(striplen) - striplen1 = len(os.path.split(pat)[0]) - if striplen1: - striplen1 += len(os.sep) - if evalpath(striplen1) > score: - striplen = striplen1 - res = lambda p: os.path.join(dest, p[striplen:]) else: - # a file - if destdirexists: - res = lambda p: os.path.join(dest, os.path.basename(p)) + abspfx = util.canonpath(repo.root, cwd, pat) + if len(abspfx) < len(srcs[0][0]): + # A directory. Either the target path contains the last + # component of the source path or it does not. + def evalpath(striplen): + score = 0 + for s in srcs: + t = os.path.join(dest, s[0][striplen:]) + if os.path.exists(t): + score += 1 + return score + + striplen = len(abspfx) + if striplen: + striplen += len(os.sep) + if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])): + score = evalpath(striplen) + striplen1 = len(os.path.split(abspfx)[0]) + if striplen1: + striplen1 += len(os.sep) + if evalpath(striplen1) > score: + striplen = striplen1 + res = lambda p: os.path.join(dest, p[striplen:]) else: - res = lambda p: dest + # a file + if destdirexists: + res = lambda p: os.path.join(dest, os.path.basename(p)) + else: + res = lambda p: dest return res @@ -923,7 +942,7 @@ for targetpath, srcs in copylist: for abssrc, relsrc, exact in srcs: - copy(abssrc, relsrc, targetpath(relsrc), exact) + copy(abssrc, relsrc, targetpath(abssrc), exact) if errors: ui.warn(_('(consider using --after)\n')) @@ -985,7 +1004,8 @@ ui.warn(_("%s in manifest1, but listed as state %s") % (f, state)) errors += 1 if errors: - raise util.Abort(_(".hg/dirstate inconsistent with current parent's manifest")) + error = _(".hg/dirstate inconsistent with current parent's manifest") + raise util.Abort(error) def debugconfig(ui): """show combined config settings from all hgrc files""" @@ -1111,7 +1131,7 @@ if len(revs) > 2: raise util.Abort(_("too many revisions to diff")) - fns, matchfn, anypats, cwd = matchpats(repo, pats, opts) + fns, matchfn, anypats = matchpats(repo, pats, opts) dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn, text=opts['text']) @@ -1119,11 +1139,11 @@ def doexport(ui, repo, changeset, seqno, total, revwidth, opts): node = repo.lookup(changeset) parents = [p for p in repo.changelog.parents(node) if p != nullid] + if opts['switch_parent']: + parents.reverse() prev = (parents and parents[0]) or nullid change = repo.changelog.read(node) - if opts['switch_parent']: - parents.reverse() fp = make_file(repo, repo.changelog, opts['output'], node=node, total=total, seqno=seqno, revwidth=revwidth) @@ -1176,7 +1196,8 @@ revs = list(revrange(ui, repo, changesets)) total = len(revs) revwidth = max(map(len, revs)) - ui.note(len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n")) + msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n") + ui.note(msg) for cset in revs: seqno += 1 doexport(ui, repo, cset, seqno, total, revwidth, opts) @@ -1191,7 +1212,7 @@ if repo.dirstate.state(abs) == 'a': forget.append(abs) if ui.verbose or not exact: - ui.status(_('forgetting %s\n') % rel) + ui.status(_('forgetting %s\n') % ((pats and rel) or abs)) repo.forget(forget) def grep(ui, repo, pattern, *pats, **opts): @@ -1272,13 +1293,17 @@ change = ((l in states) and '-') or '+' r = prev[fn] cols = [fn, str(rev)] - if opts['line_number']: cols.append(str(l.linenum)) - if opts['all']: cols.append(change) - if opts['user']: cols.append(trimuser(ui, getchange(rev)[1], rev, + if opts['line_number']: + cols.append(str(l.linenum)) + if opts['all']: + cols.append(change) + if opts['user']: + cols.append(trimuser(ui, getchange(rev)[1], rev, ucache)) if opts['files_with_matches']: c = (fn, rev) - if c in filerevmatches: continue + if c in filerevmatches: + continue filerevmatches[c] = 1 else: cols.append(l.line) @@ -1300,7 +1325,8 @@ mf = repo.manifest.read(change[0]) matches[rev] = {} for fn in fns: - if fn in skip: continue + if fn in skip: + continue fstate.setdefault(fn, {}) try: grepbody(fn, rev, getfile(fn).read(mf[fn])) @@ -1310,7 +1336,8 @@ states = matches[rev].items() states.sort() for fn, m in states: - if fn in skip: continue + if fn in skip: + continue if incrementing or not opts['all'] or fstate[fn]: pos, neg = display(fn, rev, m, fstate[fn]) count += pos + neg @@ -1323,7 +1350,8 @@ fstate = fstate.items() fstate.sort() for fn, state in fstate: - if fn in skip: continue + if fn in skip: + continue display(fn, rev, {}, state) return (count == 0 and 1) or 0 @@ -1361,9 +1389,10 @@ return hexfunc = ui.verbose and hex or short - (c, a, d, u) = repo.changes() - output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]), - (c or a or d) and "+" or "")] + modified, added, removed, deleted, unknown = repo.changes() + output = ["%s%s" % + ('+'.join([hexfunc(parent) for parent in parents]), + (modified or added or removed or deleted) and "+" or "")] if not ui.quiet: # multiple tags for a single parent separated by '/' @@ -1392,8 +1421,8 @@ patches = (patch1,) + patches if not opts['force']: - (c, a, d, u) = repo.changes() - if c or a or d: + modified, added, removed, deleted, unknown = repo.changes() + if modified or added or removed or deleted: raise util.Abort(_("outstanding uncommitted changes")) d = opts["base"] @@ -1418,7 +1447,8 @@ line = line.rstrip() if (not message and not hgpatch and mailre.match(line) and not opts['force']): - if len(line) > 35: line = line[:32] + '...' + if len(line) > 35: + line = line[:32] + '...' raise util.Abort(_('first line looks like a ' 'mail header: ') + line) if diffre.match(line): @@ -1510,14 +1540,20 @@ that contain white space as multiple filenames. """ end = opts['print0'] and '\0' or '\n' - - for src, abs, rel, exact in walk(repo, pats, opts, '(?:.*/|)'): - if repo.dirstate.state(abs) == '?': + rev = opts['rev'] + if rev: + node = repo.lookup(rev) + else: + node = None + + for src, abs, rel, exact in walk(repo, pats, opts, node=node, + head='(?:.*/|)'): + if not node and repo.dirstate.state(abs) == '?': continue if opts['fullpath']: ui.write(os.path.join(repo.root, abs), end) else: - ui.write(rel, end) + ui.write(((pats and rel) or abs), end) def log(ui, repo, *pats, **opts): """show revision history of entire repository or files @@ -1561,9 +1597,9 @@ parents = [p for p in repo.changelog.parents(changenode) if p != nullid] if opts['no_merges'] and len(parents) == 2: - continue + continue if opts['only_merges'] and len(parents) != 2: - continue + continue br = None if opts['keyword']: @@ -1710,7 +1746,7 @@ other = hg.repository(ui, source) revs = None if opts['rev'] and not other.local(): - raise util.Abort("pull -r doesn't work for remote repositories yet") + raise util.Abort(_("pull -r doesn't work for remote repositories yet")) elif opts['rev']: revs = [other.lookup(rev) for rev in opts['rev']] r = repo.pull(other, heads=revs) @@ -1757,13 +1793,19 @@ return r def rawcommit(ui, repo, *flist, **rc): - """raw commit interface + """raw commit interface (DEPRECATED) Lowlevel commit, for use in helper scripts. This command is not intended to be used by normal users, as it is primarily useful for importing from other SCMs. + + This command is now deprecated and will be removed in a future + release, please use debugsetparents and commit instead. """ + + ui.warn(_("(the rawcommit command is deprecated)\n")) + message = rc['message'] if not message and rc['logfile']: try: @@ -1808,18 +1850,23 @@ """ names = [] def okaytoremove(abs, rel, exact): - c, a, d, u = repo.changes(files = [abs]) + modified, added, removed, deleted, unknown = repo.changes(files=[abs]) reason = None - if c: reason = _('is modified') - elif a: reason = _('has been marked for add') - elif u: reason = _('is not managed') + if modified: + reason = _('is modified') + elif added: + reason = _('has been marked for add') + elif unknown: + reason = _('is not managed') if reason: - if exact: ui.warn(_('not removing %s: file %s\n') % (rel, reason)) + if exact: + ui.warn(_('not removing %s: file %s\n') % (rel, reason)) else: return True for src, abs, rel, exact in walk(repo, (pat,) + pats, opts): if okaytoremove(abs, rel, exact): - if ui.verbose or not exact: ui.status(_('removing %s\n') % rel) + if ui.verbose or not exact: + ui.status(_('removing %s\n') % rel) names.append(abs) repo.remove(names, unlink=True) @@ -1843,7 +1890,8 @@ errs, copied = docopy(ui, repo, pats, opts) names = [] for abs, rel, exact in copied: - if ui.verbose or not exact: ui.status(_('removing %s\n') % rel) + if ui.verbose or not exact: + ui.status(_('removing %s\n') % rel) names.append(abs) repo.remove(names, unlink=True) return errs @@ -1865,10 +1913,10 @@ node = opts['rev'] and repo.lookup(opts['rev']) or \ repo.dirstate.parents()[0] - files, choose, anypats, cwd = matchpats(repo, pats, opts) - (c, a, d, u) = repo.changes(match=choose) - repo.forget(a) - repo.undelete(d) + files, choose, anypats = matchpats(repo, pats, opts) + modified, added, removed, deleted, unknown = repo.changes(match=choose) + repo.forget(added) + repo.undelete(removed + deleted) return repo.update(node, False, True, choose, False) @@ -1968,7 +2016,7 @@ try: httpd = hgweb.create_server(repo) except socket.error, inst: - raise util.Abort('cannot start server: ' + inst.args[1]) + raise util.Abort(_('cannot start server: ') + inst.args[1]) if ui.verbose: addr, port = httpd.socket.getsockname() @@ -1995,17 +2043,21 @@ M = modified A = added R = removed + ! = deleted, but still tracked ? = not tracked """ - files, matchfn, anypats, cwd = matchpats(repo, pats, opts) - (c, a, d, u) = [[util.pathto(cwd, x) for x in n] - for n in repo.changes(files=files, match=matchfn)] - - changetypes = [(_('modified'), 'M', c), - (_('added'), 'A', a), - (_('removed'), 'R', d), - (_('unknown'), '?', u)] + files, matchfn, anypats = matchpats(repo, pats, opts) + cwd = (pats and repo.getcwd()) or '' + modified, added, removed, deleted, unknown = [ + [util.pathto(cwd, x) for x in n] + for n in repo.changes(files=files, match=matchfn)] + + changetypes = [(_('modified'), 'M', modified), + (_('added'), 'A', added), + (_('removed'), 'R', removed), + (_('deleted'), '!', deleted), + (_('unknown'), '?', unknown)] end = opts['print0'] and '\0' or '\n' @@ -2019,7 +2071,7 @@ for f in changes: ui.write(format % f) -def tag(ui, repo, name, rev=None, **opts): +def tag(ui, repo, name, rev_=None, **opts): """add a tag for the current tip or a given revision Name a particular revision using . @@ -2033,14 +2085,20 @@ To facilitate version control, distribution, and merging of tags, they are stored as a file named ".hgtags" which is managed similarly to other project files and can be hand-edited if - necessary. + necessary. The file '.hg/localtags' is used for local tags (not + shared among repositories). """ if name == "tip": raise util.Abort(_("the name 'tip' is reserved")) - if 'rev' in opts: - rev = opts['rev'] - if rev: - r = hex(repo.lookup(rev)) + if rev_ is not None: + ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, " + "please use 'hg tag [-r REV] NAME' instead\n")) + if opts['rev']: + raise util.Abort(_("use only one form to specify the revision")) + if opts['rev']: + rev_ = opts['rev'] + if rev_: + r = hex(repo.lookup(rev_)) else: r = hex(repo.changelog.tip()) @@ -2053,8 +2111,7 @@ repo.opener("localtags", "a").write("%s %s\n" % (r, name)) return - (c, a, d, u) = repo.changes() - for x in (c, a, d, u): + for x in repo.changes(): if ".hgtags" in x: raise util.Abort(_("working copy of .hgtags is changed " "(please commit .hgtags manually)")) @@ -2095,7 +2152,7 @@ n = repo.changelog.tip() show_changeset(ui, repo, changenode=n) -def unbundle(ui, repo, fname): +def unbundle(ui, repo, fname, **opts): """apply a changegroup file Apply a compressed changegroup file generated by the bundle @@ -2112,7 +2169,13 @@ yield zd.decompress(chunk) bzgen = bzgenerator(util.filechunkiter(f, 4096)) - repo.addchangegroup(util.chunkbuffer(bzgen)) + if repo.addchangegroup(util.chunkbuffer(bzgen)): + return 1 + + if opts['update']: + return update(ui, repo) + else: + ui.status(_("(run 'hg update' to get a working copy)\n")) def undo(ui, repo): """undo the last commit or pull @@ -2188,12 +2251,12 @@ (add, [('I', 'include', [], _('include names matching the given patterns')), ('X', 'exclude', [], _('exclude names matching the given patterns'))], - "hg add [OPTION]... [FILE]..."), + _('hg add [OPTION]... [FILE]...')), "addremove": (addremove, [('I', 'include', [], _('include names matching the given patterns')), ('X', 'exclude', [], _('exclude names matching the given patterns'))], - "hg addremove [OPTION]... [FILE]..."), + _('hg addremove [OPTION]... [FILE]...')), "^annotate": (annotate, [('r', 'rev', '', _('annotate the specified revision')), @@ -2213,33 +2276,37 @@ (cat, [('I', 'include', [], _('include names matching the given patterns')), ('X', 'exclude', [], _('exclude names matching the given patterns')), - ('o', 'output', "", _('print output to file with formatted name')), + ('o', 'output', '', _('print output to file with formatted name')), ('r', 'rev', '', _('print the given revision'))], _('hg cat [OPTION]... FILE...')), "^clone": (clone, [('U', 'noupdate', None, _('do not update the new working directory')), - ('e', 'ssh', "", _('specify ssh command to use')), + ('e', 'ssh', '', _('specify ssh command to use')), ('', 'pull', None, _('use pull protocol to copy metadata')), - ('r', 'rev', [], _('a changeset you would like to have after cloning')), - ('', 'remotecmd', "", _('specify hg command to run on the remote side'))], + ('r', 'rev', [], + _('a changeset you would like to have after cloning')), + ('', 'remotecmd', '', + _('specify hg command to run on the remote side'))], _('hg clone [OPTION]... SOURCE [DEST]')), "^commit|ci": (commit, [('A', 'addremove', None, _('run addremove during commit')), ('I', 'include', [], _('include names matching the given patterns')), ('X', 'exclude', [], _('exclude names matching the given patterns')), - ('m', 'message', "", _('use as commit message')), - ('l', 'logfile', "", _('read the commit message from ')), - ('d', 'date', "", _('record datecode as commit date')), - ('u', 'user', "", _('record user as commiter'))], + ('m', 'message', '', _('use as commit message')), + ('l', 'logfile', '', _('read the commit message from ')), + ('d', 'date', '', _('record datecode as commit date')), + ('u', 'user', '', _('record user as commiter'))], _('hg commit [OPTION]... [FILE]...')), - "copy|cp": (copy, - [('I', 'include', [], _('include names matching the given patterns')), - ('X', 'exclude', [], _('exclude names matching the given patterns')), - ('A', 'after', None, _('record a copy that has already occurred')), - ('f', 'force', None, _('forcibly copy over an existing managed file'))], - _('hg copy [OPTION]... [SOURCE]... DEST')), + "copy|cp": + (copy, + [('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns')), + ('A', 'after', None, _('record a copy that has already occurred')), + ('f', 'force', None, + _('forcibly copy over an existing managed file'))], + _('hg copy [OPTION]... [SOURCE]... DEST')), "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')), "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')), "debugconfig": (debugconfig, [], _('debugconfig')), @@ -2263,15 +2330,15 @@ _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')), "^export": (export, - [('o', 'output', "", _('print output to file with formatted name')), + [('o', 'output', '', _('print output to file with formatted name')), ('a', 'text', None, _('treat all files as text')), ('', 'switch-parent', None, _('diff against the second parent'))], - "hg export [-a] [-o OUTFILE] REV..."), + _('hg export [-a] [-o OUTFILE] REV...')), "forget": (forget, [('I', 'include', [], _('include names matching the given patterns')), ('X', 'exclude', [], _('exclude names matching the given patterns'))], - "hg forget [OPTION]... FILE..."), + _('hg forget [OPTION]... FILE...')), "grep": (grep, [('0', 'print0', None, _('end fields with NUL')), @@ -2279,27 +2346,30 @@ ('X', 'exclude', [], _('exclude names matching the given patterns')), ('', 'all', None, _('print all revisions that match')), ('i', 'ignore-case', None, _('ignore case when matching')), - ('l', 'files-with-matches', None, _('print only filenames and revs that match')), + ('l', 'files-with-matches', None, + _('print only filenames and revs that match')), ('n', 'line-number', None, _('print matching line numbers')), ('r', 'rev', [], _('search in given revision range')), ('u', 'user', None, _('print user who committed change'))], - "hg grep [OPTION]... PATTERN [FILE]..."), + _('hg grep [OPTION]... PATTERN [FILE]...')), "heads": (heads, [('b', 'branches', None, _('find branch info')), - ('r', 'rev', "", _('show only heads which are descendants of rev'))], + ('r', 'rev', '', _('show only heads which are descendants of rev'))], _('hg heads [-b] [-r ]')), "help": (help_, [], _('hg help [COMMAND]')), "identify|id": (identify, [], _('hg identify')), "import|patch": (import_, - [('p', 'strip', 1, _('directory strip option for patch. This has the same\n') + - _('meaning as the corresponding patch option')), - ('f', 'force', None, _('skip check for outstanding uncommitted changes')), - ('b', 'base', "", _('base path'))], - "hg import [-f] [-p NUM] [-b BASE] PATCH..."), + [('p', 'strip', 1, + _('directory strip option for patch. This has the same\n') + + _('meaning as the corresponding patch option')), + ('f', 'force', None, + _('skip check for outstanding uncommitted changes')), + ('b', 'base', '', _('base path'))], + _('hg import [-f] [-p NUM] [-b BASE] PATCH...')), "incoming|in": (incoming, - [('M', 'no-merges', None, _("do not show merges")), + [('M', 'no-merges', None, _('do not show merges')), ('p', 'patch', None, _('show patch')), ('n', 'newest-first', None, _('show newest record first'))], _('hg incoming [-p] [-n] [-M] [SOURCE]')), @@ -2307,8 +2377,10 @@ "locate": (locate, [('r', 'rev', '', _('search the repository as it stood at rev')), - ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), - ('f', 'fullpath', None, _('print complete paths from the filesystem root')), + ('0', 'print0', None, + _('end filenames with NUL, for use with xargs')), + ('f', 'fullpath', None, + _('print complete paths from the filesystem root')), ('I', 'include', [], _('include names matching the given patterns')), ('X', 'exclude', [], _('exclude names matching the given patterns'))], _('hg locate [OPTION]... [PATTERN]...')), @@ -2319,13 +2391,13 @@ ('b', 'branch', None, _('show branches')), ('k', 'keyword', [], _('search for a keyword')), ('r', 'rev', [], _('show the specified revision or range')), - ('M', 'no-merges', None, _("do not show merges")), - ('m', 'only-merges', None, _("show only merges")), + ('M', 'no-merges', None, _('do not show merges')), + ('m', 'only-merges', None, _('show only merges')), ('p', 'patch', None, _('show patch'))], _('hg log [-I] [-X] [-r REV]... [-p] [FILE]')), "manifest": (manifest, [], _('hg manifest [REV]')), "outgoing|out": (outgoing, - [('M', 'no-merges', None, _("do not show merges")), + [('M', 'no-merges', None, _('do not show merges')), ('p', 'patch', None, _('show patch')), ('n', 'newest-first', None, _('show newest record first'))], _('hg outgoing [-p] [-n] [-M] [DEST]')), @@ -2333,85 +2405,95 @@ "paths": (paths, [], _('hg paths [NAME]')), "^pull": (pull, - [('u', 'update', None, _('update the working directory to tip after pull')), - ('e', 'ssh', "", _('specify ssh command to use')), + [('u', 'update', None, + _('update the working directory to tip after pull')), + ('e', 'ssh', '', _('specify ssh command to use')), ('r', 'rev', [], _('a specific revision you would like to pull')), - ('', 'remotecmd', "", _('specify hg command to run on the remote side'))], + ('', 'remotecmd', '', + _('specify hg command to run on the remote side'))], _('hg pull [-u] [-e FILE] [-r rev] [--remotecmd FILE] [SOURCE]')), "^push": (push, [('f', 'force', None, _('force push')), - ('e', 'ssh', "", _('specify ssh command to use')), - ('', 'remotecmd', "", _('specify hg command to run on the remote side'))], + ('e', 'ssh', '', _('specify ssh command to use')), + ('', 'remotecmd', '', + _('specify hg command to run on the remote side'))], _('hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]')), "rawcommit": (rawcommit, [('p', 'parent', [], _('parent')), - ('d', 'date', "", _('date code')), - ('u', 'user', "", _('user')), - ('F', 'files', "", _('file list')), - ('m', 'message', "", _('commit message')), - ('l', 'logfile', "", _('commit message file'))], + ('d', 'date', '', _('date code')), + ('u', 'user', '', _('user')), + ('F', 'files', '', _('file list')), + ('m', 'message', '', _('commit message')), + ('l', 'logfile', '', _('commit message file'))], _('hg rawcommit [OPTION]... [FILE]...')), - "recover": (recover, [], _("hg recover")), - "^remove|rm": (remove, - [('I', 'include', [], _('include names matching the given patterns')), - ('X', 'exclude', [], _('exclude names matching the given patterns'))], - _("hg remove [OPTION]... FILE...")), - "rename|mv": (rename, - [('I', 'include', [], _('include names matching the given patterns')), - ('X', 'exclude', [], _('exclude names matching the given patterns')), - ('A', 'after', None, _('record a rename that has already occurred')), - ('f', 'force', None, _('forcibly copy over an existing managed file'))], - _('hg rename [OPTION]... [SOURCE]... DEST')), + "recover": (recover, [], _('hg recover')), + "^remove|rm": + (remove, + [('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg remove [OPTION]... FILE...')), + "rename|mv": + (rename, + [('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns')), + ('A', 'after', None, _('record a rename that has already occurred')), + ('f', 'force', None, + _('forcibly copy over an existing managed file'))], + _('hg rename [OPTION]... [SOURCE]... DEST')), "^revert": (revert, [('I', 'include', [], _('include names matching the given patterns')), ('X', 'exclude', [], _('exclude names matching the given patterns')), - ("r", "rev", "", _("revision to revert to"))], - _("hg revert [-n] [-r REV] [NAME]...")), - "root": (root, [], _("hg root")), + ('r', 'rev', '', _('revision to revert to'))], + _('hg revert [-n] [-r REV] [NAME]...')), + "root": (root, [], _('hg root')), "^serve": (serve, [('A', 'accesslog', '', _('name of access log file to write to')), ('E', 'errorlog', '', _('name of error log file to write to')), ('p', 'port', 0, _('port to use (default: 8000)')), ('a', 'address', '', _('address to use')), - ('n', 'name', "", _('name to show in web pages (default: working dir)')), + ('n', 'name', '', + _('name to show in web pages (default: working dir)')), ('', 'stdio', None, _('for remote clients')), - ('t', 'templates', "", _('web templates to use')), - ('', 'style', "", _('template style to use')), + ('t', 'templates', '', _('web templates to use')), + ('', 'style', '', _('template style to use')), ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))], - _("hg serve [OPTION]...")), + _('hg serve [OPTION]...')), "^status|st": (status, [('m', 'modified', None, _('show only modified files')), ('a', 'added', None, _('show only added files')), ('r', 'removed', None, _('show only removed files')), + ('d', 'deleted', None, _('show only deleted (but tracked) files')), ('u', 'unknown', None, _('show only unknown (not tracked) files')), ('n', 'no-status', None, _('hide status prefix')), - ('0', 'print0', None, _('end filenames with NUL, for use with xargs')), + ('0', 'print0', None, + _('end filenames with NUL, for use with xargs')), ('I', 'include', [], _('include names matching the given patterns')), ('X', 'exclude', [], _('exclude names matching the given patterns'))], - _("hg status [OPTION]... [FILE]...")), + _('hg status [OPTION]... [FILE]...')), "tag": (tag, [('l', 'local', None, _('make the tag local')), - ('m', 'message', "", _('message for tag commit log entry')), - ('d', 'date', "", _('record datecode as commit date')), - ('u', 'user', "", _('record user as commiter')), - ('r', 'rev', "", _('revision to tag'))], - _('hg tag [OPTION]... NAME [REV]')), + ('m', 'message', '', _('message for tag commit log entry')), + ('d', 'date', '', _('record datecode as commit date')), + ('u', 'user', '', _('record user as commiter')), + ('r', 'rev', '', _('revision to tag'))], + _('hg tag [-r REV] [OPTION]... NAME')), "tags": (tags, [], _('hg tags')), "tip": (tip, [], _('hg tip')), "unbundle": (unbundle, - [], - _('hg unbundle FILE')), + [('u', 'update', None, + _('update the working directory to tip after unbundle'))], + _('hg unbundle [-u] FILE')), "undo": (undo, [], _('hg undo')), "^update|up|checkout|co": (update, - [('b', 'branch', "", _('checkout the head of a specific branch')), + [('b', 'branch', '', _('checkout the head of a specific branch')), ('m', 'merge', None, _('allow merging of branches')), ('C', 'clean', None, _('overwrite locally modified files')), ('f', 'force', None, _('force a merge with outstanding changes'))], @@ -2421,18 +2503,19 @@ } globalopts = [ - ('R', 'repository', "", _("repository root directory")), - ('', 'cwd', '', _("change working directory")), - ('y', 'noninteractive', None, _("do not prompt, assume 'yes' for any required answers")), - ('q', 'quiet', None, _("suppress output")), - ('v', 'verbose', None, _("enable additional output")), - ('', 'debug', None, _("enable debugging output")), - ('', 'debugger', None, _("start debugger")), - ('', 'traceback', None, _("print traceback on exception")), - ('', 'time', None, _("time how long the command takes")), - ('', 'profile', None, _("print command execution profile")), - ('', 'version', None, _("output version information and exit")), - ('h', 'help', None, _("display help and exit")), + ('R', 'repository', '', _('repository root directory')), + ('', 'cwd', '', _('change working directory')), + ('y', 'noninteractive', None, + _('do not prompt, assume \'yes\' for any required answers')), + ('q', 'quiet', None, _('suppress output')), + ('v', 'verbose', None, _('enable additional output')), + ('', 'debug', None, _('enable debugging output')), + ('', 'debugger', None, _('start debugger')), + ('', 'traceback', None, _('print traceback on exception')), + ('', 'time', None, _('time how long the command takes')), + ('', 'profile', None, _('print command execution profile')), + ('', 'version', None, _('output version information and exit')), + ('h', 'help', None, _('display help and exit')), ] norepo = ("clone init version help debugancestor debugconfig debugdata" @@ -2615,7 +2698,8 @@ path = options["repository"] or "" repo = hg.repository(ui=u, path=path) for x in external: - if hasattr(x, 'reposetup'): x.reposetup(u, repo) + if hasattr(x, 'reposetup'): + x.reposetup(u, repo) d = lambda: func(u, repo, *args, **cmdoptions) else: d = lambda: func(u, *args, **cmdoptions) diff -r 675ca845c2f8 -r c21b54f7f7b8 mercurial/dirstate.py --- a/mercurial/dirstate.py Thu Dec 15 18:04:39 2005 +0100 +++ b/mercurial/dirstate.py Wed Feb 01 19:18:15 2006 +0100 @@ -68,7 +68,8 @@ try: syntax = syntaxes[s] except KeyError: - self.ui.warn(_("ignoring invalid syntax '%s'\n") % s) + self.ui.warn(_(".hgignore: ignoring invalid " + "syntax '%s'\n") % s) continue pat = syntax + line for s in syntaxes.values(): @@ -88,7 +89,8 @@ ignore = self.hgignore() if ignore: files, self.ignorefunc, anypats = util.matcher(self.root, - inc=ignore) + inc=ignore, + src='.hgignore') else: self.ignorefunc = util.never return self.ignorefunc(fn) @@ -415,4 +417,4 @@ elif type == 'r': removed.append(fn) - return (lookup, modified, added, removed + deleted, unknown) + return (lookup, modified, added, removed, deleted, unknown) diff -r 675ca845c2f8 -r c21b54f7f7b8 mercurial/filelog.py --- a/mercurial/filelog.py Thu Dec 15 18:04:39 2005 +0100 +++ b/mercurial/filelog.py Wed Feb 01 19:18:15 2006 +0100 @@ -58,7 +58,7 @@ return self.addrevision(text, transaction, link, p1, p2) def renamed(self, node): - if 0 and self.parents(node)[0] != nullid: # XXX + if self.parents(node)[0] != nullid: return False m = self.readmeta(node) if m and m.has_key("copy"): diff -r 675ca845c2f8 -r c21b54f7f7b8 mercurial/hgweb.py --- a/mercurial/hgweb.py Thu Dec 15 18:04:39 2005 +0100 +++ b/mercurial/hgweb.py Wed Feb 01 19:18:15 2006 +0100 @@ -6,7 +6,7 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -import os, cgi, sys +import os, cgi, sys, urllib from demandload import demandload demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser") demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util") @@ -163,7 +163,8 @@ return common_filters = { - "escape": cgi.escape, + "escape": lambda x: cgi.escape(x, True), + "urlescape": urllib.quote, "strip": lambda x: x.strip(), "age": age, "date": lambda x: util.datestr(x), @@ -212,27 +213,33 @@ if len(files) > self.maxfiles: yield self.t("fileellipses") - def parents(self, node, parents=[], rev=None, hide=False, **args): + def siblings(self, siblings=[], rev=None, hiderev=None, **args): if not rev: rev = lambda x: "" - parents = [p for p in parents if p != nullid] - if hide and len(parents) == 1 and rev(parents[0]) == rev(node) - 1: + siblings = [s for s in siblings if s != nullid] + if len(siblings) == 1 and rev(siblings[0]) == hiderev: return - for p in parents: - yield dict(node=hex(p), rev=rev(p), **args) + for s in siblings: + yield dict(node=hex(s), rev=rev(s), **args) + + def renamelink(self, fl, node): + r = fl.renamed(node) + if r: + return [dict(file=r[0], node=hex(r[1]))] + return [] def showtag(self, t1, node=nullid, **args): for t in self.repo.nodetags(node): yield self.t(t1, tag=t, **args) def diff(self, node1, node2, files): - def filterfiles(list, files): - l = [x for x in list if x in files] + def filterfiles(filters, files): + l = [x for x in files if x in filters] - for f in files: - if f[-1] != os.sep: - f += os.sep - l += [x for x in list if x.startswith(f)] + for t in filters: + if t and t[-1] != os.sep: + t += os.sep + l += [x for x in files if x.startswith(t)] return l parity = [0] @@ -265,22 +272,29 @@ date1 = util.datestr(change1[2]) date2 = util.datestr(change2[2]) - c, a, d, u = r.changes(node1, node2) + modified, added, removed, deleted, unknown = r.changes(node1, node2) if files: - c, a, d = map(lambda x: filterfiles(x, files), (c, a, d)) + modified, added, removed = map(lambda x: filterfiles(files, x), + (modified, added, removed)) - for f in c: + diffopts = self.repo.ui.diffopts() + showfunc = diffopts['showfunc'] + ignorews = diffopts['ignorews'] + for f in modified: to = r.file(f).read(mmap1[f]) tn = r.file(f).read(mmap2[f]) - yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn) - for f in a: + yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, + showfunc=showfunc, ignorews=ignorews), f, tn) + for f in added: to = None tn = r.file(f).read(mmap2[f]) - yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn) - for f in d: + yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, + showfunc=showfunc, ignorews=ignorews), f, tn) + for f in removed: to = r.file(f).read(mmap1[f]) tn = None - yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn) + yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, + showfunc=showfunc, ignorews=ignorews), f, tn) def changelog(self, pos): def changenav(**map): @@ -321,8 +335,10 @@ l.insert(0, {"parity": parity, "author": changes[1], - "parent": self.parents(n, cl.parents(n), cl.rev, - hide=True), + "parent": self.siblings(cl.parents(n), cl.rev, + cl.rev(n) - 1), + "child": self.siblings(cl.children(n), cl.rev, + cl.rev(n) + 1), "changelogtag": self.showtag("changelogtag",n), "manifest": hex(changes[0]), "desc": changes[4], @@ -382,7 +398,8 @@ yield self.t('searchentry', parity=count & 1, author=changes[1], - parent=self.parents(n, cl.parents(n), cl.rev), + parent=self.siblings(cl.parents(n), cl.rev), + child=self.siblings(cl.children(n), cl.rev), changelogtag=self.showtag("changelogtag",n), manifest=hex(changes[0]), desc=changes[4], @@ -422,7 +439,8 @@ diff=diff, rev=cl.rev(n), node=nodeid, - parent=self.parents(n, cl.parents(n), cl.rev), + parent=self.siblings(cl.parents(n), cl.rev), + child=self.siblings(cl.children(n), cl.rev), changesettag=self.showtag("changesettag",n), manifest=hex(changes[0]), author=changes[1], @@ -454,7 +472,10 @@ "node": hex(cn), "author": cs[1], "date": cs[2], - "parent": self.parents(n, fl.parents(n), + "rename": self.renamelink(fl, n), + "parent": self.siblings(fl.parents(n), + fl.rev, file=f), + "child": self.siblings(fl.children(n), fl.rev, file=f), "desc": cs[4]}) parity = 1 - parity @@ -498,7 +519,9 @@ manifest=hex(mfn), author=cs[1], date=cs[2], - parent=self.parents(n, fl.parents(n), fl.rev, file=f), + parent=self.siblings(fl.parents(n), fl.rev, file=f), + child=self.siblings(fl.children(n), fl.rev, file=f), + rename=self.renamelink(fl, n), permissions=self.repo.manifest.readflags(mfn)[f]) def fileannotate(self, f, node): @@ -550,7 +573,9 @@ manifest=hex(mfn), author=cs[1], date=cs[2], - parent=self.parents(n, fl.parents(n), fl.rev, file=f), + rename=self.renamelink(fl, n), + parent=self.siblings(fl.parents(n), fl.rev, file=f), + child=self.siblings(fl.children(n), fl.rev, file=f), permissions=self.repo.manifest.readflags(mfn)[f]) def manifest(self, mnode, path): @@ -565,6 +590,8 @@ files = {} p = path[1:] + if p and p[-1] != "/": + p += "/" l = len(p) for f,n in mf.items(): @@ -727,7 +754,8 @@ filenode=hex(mf.get(file, nullid)), node=changeset, rev=self.repo.changelog.rev(n), - parent=self.parents(n, cl.parents(n), cl.rev), + parent=self.siblings(cl.parents(n), cl.rev), + child=self.siblings(cl.children(n), cl.rev), diff=diff) def archive(self, req, cnode, type): @@ -785,6 +813,12 @@ # find tag, changeset, file def run(self, req=hgrequest()): + def clean(path): + p = os.path.normpath(path) + if p[:2] == "..": + raise "suspicious path" + return p + def header(**map): yield self.t("header", **map) @@ -865,7 +899,8 @@ req.write(self.changeset(req.form['node'][0])) elif req.form['cmd'][0] == 'manifest': - req.write(self.manifest(req.form['manifest'][0], req.form['path'][0])) + req.write(self.manifest(req.form['manifest'][0], + clean(req.form['path'][0]))) elif req.form['cmd'][0] == 'tags': req.write(self.tags()) @@ -874,16 +909,20 @@ req.write(self.summary()) elif req.form['cmd'][0] == 'filediff': - req.write(self.filediff(req.form['file'][0], req.form['node'][0])) + req.write(self.filediff(clean(req.form['file'][0]), + req.form['node'][0])) elif req.form['cmd'][0] == 'file': - req.write(self.filerevision(req.form['file'][0], req.form['filenode'][0])) + req.write(self.filerevision(clean(req.form['file'][0]), + req.form['filenode'][0])) elif req.form['cmd'][0] == 'annotate': - req.write(self.fileannotate(req.form['file'][0], req.form['filenode'][0])) + req.write(self.fileannotate(clean(req.form['file'][0]), + req.form['filenode'][0])) elif req.form['cmd'][0] == 'filelog': - req.write(self.filelog(req.form['file'][0], req.form['filenode'][0])) + req.write(self.filelog(clean(req.form['file'][0]), + req.form['filenode'][0])) elif req.form['cmd'][0] == 'heads': req.httphdr("application/mercurial-0.1") diff -r 675ca845c2f8 -r c21b54f7f7b8 mercurial/localrepo.py --- a/mercurial/localrepo.py Thu Dec 15 18:04:39 2005 +0100 +++ b/mercurial/localrepo.py Wed Feb 01 19:18:15 2006 +0100 @@ -19,7 +19,8 @@ while not os.path.isdir(os.path.join(p, ".hg")): oldp = p p = os.path.dirname(p) - if p == oldp: raise repo.RepoError(_("no repo found")) + if p == oldp: + raise repo.RepoError(_("no repo found")) path = p self.path = os.path.join(path, ".hg") @@ -44,7 +45,8 @@ self.dirstate = dirstate.dirstate(self.opener, ui, self.root) try: self.ui.readconfig(self.join("hgrc")) - except IOError: pass + except IOError: + pass def hook(self, name, **args): def runhook(name, cmd): @@ -126,16 +128,16 @@ r = self.changelog.rev(n) except: r = -2 # sort to the beginning of the list if unknown - l.append((r,t,n)) + l.append((r, t, n)) l.sort() - return [(t,n) for r,t,n in l] + return [(t, n) for r, t, n in l] def nodetags(self, node): '''return the tags associated with a node''' if not self.nodetagscache: self.nodetagscache = {} - for t,n in self.tags().items(): - self.nodetagscache.setdefault(n,[]).append(t) + for t, n in self.tags().items(): + self.nodetagscache.setdefault(n, []).append(t) return self.nodetagscache.get(node, []) def lookup(self, key): @@ -160,7 +162,8 @@ return os.path.join(self.root, f) def file(self, f): - if f[0] == '/': f = f[1:] + if f[0] == '/': + f = f[1:] return filelog.filelog(self.opener, f) def getcwd(self): @@ -226,6 +229,8 @@ if os.path.exists(self.join("journal")): self.ui.status(_("rolling back interrupted transaction\n")) transaction.rollback(self.opener, self.join("journal")) + self.manifest = manifest.manifest(self.opener) + self.changelog = changelog.changelog(self.opener) return True else: self.ui.warn(_("no interrupted transaction available\n")) @@ -334,8 +339,8 @@ if update_dirstate: self.dirstate.setparents(n, nullid) - def commit(self, files = None, text = "", user = None, date = None, - match = util.always, force=False): + def commit(self, files=None, text="", user=None, date=None, + match=util.always, force=False): commit = [] remove = [] changed = [] @@ -350,9 +355,9 @@ else: self.ui.warn(_("%s not tracked!\n") % f) else: - (c, a, d, u) = self.changes(match=match) - commit = c + a - remove = d + modified, added, removed, deleted, unknown = self.changes(match=match) + commit = modified + added + remove = removed p1, p2 = self.dirstate.parents() c1 = self.changelog.read(p1) @@ -398,10 +403,6 @@ fp1 = m1.get(f, nullid) fp2 = m2.get(f, nullid) - # is the same revision on two branches of a merge? - if fp2 == fp1: - fp2 = nullid - if fp2 != nullid: # is one parent an ancestor of the other? fpa = r.ancestor(fp1, fp2) @@ -411,7 +412,7 @@ fp2 = nullid # is the file unmodified from the parent? - if not meta and t == r.read(fp1): + if not meta and t == r.read(fp1) and fp2 == nullid: # record the proper existing parent in manifest # no need to add a revision new[f] = fp1 @@ -423,6 +424,7 @@ changed.append(f) # update manifest + m1 = m1.copy() m1.update(new) for f in remove: if f in m1: @@ -449,7 +451,7 @@ text = edittext user = user or self.ui.username() - n = self.changelog.add(mn, changed, text, tr, p1, p2, user, date) + n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date) tr.close() self.dirstate.setparents(n) @@ -474,9 +476,12 @@ for src, fn in self.dirstate.walk(files, match): yield src, fn - def changes(self, node1 = None, node2 = None, files = [], - match = util.always): - mf2, u = None, [] + def changes(self, node1=None, node2=None, files=[], match=util.always): + """return changes between two nodes or node and working directory + + If node1 is None, use the first dirstate parent instead. + If node2 is None, compare node1 with working directory. + """ def fcmp(fn, mf): t1 = self.wread(fn) @@ -484,7 +489,8 @@ return cmp(t1, t2) def mfmatches(node): - mf = dict(self.manifest.read(node)) + change = self.changelog.read(node) + mf = dict(self.manifest.read(change[0])) for fn in mf.keys(): if not match(fn): del mf[fn] @@ -496,60 +502,53 @@ wlock = self.wlock(wait=0) except lock.LockHeld: wlock = None - l, c, a, d, u = self.dirstate.changes(files, match) + lookup, modified, added, removed, deleted, unknown = ( + self.dirstate.changes(files, match)) # are we comparing working dir against its parent? if not node1: - if l: + if lookup: # do a full compare of any files that might have changed - change = self.changelog.read(self.dirstate.parents()[0]) - mf2 = mfmatches(change[0]) - for f in l: + mf2 = mfmatches(self.dirstate.parents()[0]) + for f in lookup: if fcmp(f, mf2): - c.append(f) + modified.append(f) elif wlock is not None: self.dirstate.update([f], "n") - - for l in c, a, d, u: - l.sort() - - return (c, a, d, u) - - # are we comparing working dir against non-tip? - # generate a pseudo-manifest for the working dir - if not node2: - if not mf2: - change = self.changelog.read(self.dirstate.parents()[0]) - mf2 = mfmatches(change[0]) - for f in a + c + l: - mf2[f] = "" - for f in d: - if f in mf2: del mf2[f] + else: + # we are comparing working dir against non-parent + # generate a pseudo-manifest for the working dir + mf2 = mfmatches(self.dirstate.parents()[0]) + for f in lookup + modified + added: + mf2[f] = "" + for f in removed: + if f in mf2: + del mf2[f] else: - change = self.changelog.read(node2) - mf2 = mfmatches(change[0]) + # we are comparing two revisions + deleted, unknown = [], [] + mf2 = mfmatches(node2) - # flush lists from dirstate before comparing manifests - c, a = [], [] + if node1: + # flush lists from dirstate before comparing manifests + modified, added = [], [] - change = self.changelog.read(node1) - mf1 = mfmatches(change[0]) + mf1 = mfmatches(node1) - for fn in mf2: - if mf1.has_key(fn): - if mf1[fn] != mf2[fn]: - if mf2[fn] != "" or fcmp(fn, mf1): - c.append(fn) - del mf1[fn] - else: - a.append(fn) + for fn in mf2: + if mf1.has_key(fn): + if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)): + modified.append(fn) + del mf1[fn] + else: + added.append(fn) - d = mf1.keys() + removed = mf1.keys() - for l in c, a, d, u: + # sort and return results: + for l in modified, added, removed, deleted, unknown: l.sort() - - return (c, a, d, u) + return (modified, added, removed, deleted, unknown) def add(self, list): wlock = self.wlock() @@ -558,7 +557,8 @@ if not os.path.exists(p): self.ui.warn(_("%s does not exist!\n") % f) elif not os.path.isfile(p): - self.ui.warn(_("%s not added: only files supported currently\n") % f) + self.ui.warn(_("%s not added: only files supported currently\n") + % f) elif self.dirstate.state(f) in 'an': self.ui.warn(_("%s already tracked!\n") % f) else: @@ -578,7 +578,8 @@ try: util.unlink(self.wjoin(f)) except OSError, inst: - if inst.errno != errno.ENOENT: raise + if inst.errno != errno.ENOENT: + raise wlock = self.wlock() for f in list: p = self.wjoin(f) @@ -733,7 +734,8 @@ return out def branches(self, nodes): - if not nodes: nodes = [self.changelog.tip()] + if not nodes: + nodes = [self.changelog.tip()] b = [] for n in nodes: t = n @@ -805,7 +807,8 @@ if n[0] in seen: continue - self.ui.debug(_("examining %s:%s\n") % (short(n[0]), short(n[1]))) + self.ui.debug(_("examining %s:%s\n") + % (short(n[0]), short(n[1]))) if n[0] == nullid: break if n in seenbranch: @@ -841,7 +844,8 @@ self.ui.debug(_("received %s:%s\n") % (short(b[0]), short(b[1]))) if b[0] in m: - self.ui.debug(_("found base node %s\n") % short(b[0])) + self.ui.debug(_("found base node %s\n") + % short(b[0])) base[b[0]] = 1 elif b[0] not in seen: unknown.append(b) @@ -914,7 +918,7 @@ # this is the set of all roots we have to push return subset - def pull(self, remote, heads = None): + def pull(self, remote, heads=None): lock = self.lock() # if we have an empty repo, fetch everything @@ -1199,8 +1203,11 @@ filerevlog = self.file(fname) # Toss out the filenodes that the recipient isn't really # missing. - prune_filenodes(fname, filerevlog) - msng_filenode_lst = msng_filenode_set[fname].keys() + if msng_filenode_set.has_key(fname): + prune_filenodes(fname, filerevlog) + msng_filenode_lst = msng_filenode_set[fname].keys() + else: + msng_filenode_lst = [] # If any filenodes are left, generate the group for them, # otherwise don't bother. if len(msng_filenode_lst) > 0: @@ -1214,8 +1221,9 @@ lookup_filenode_link_func(fname)) for chnk in group: yield chnk - # Don't need this anymore, toss it to free memory. - del msng_filenode_set[fname] + if msng_filenode_set.has_key(fname): + # Don't need this anymore, toss it to free memory. + del msng_filenode_set[fname] # Signal that no more groups are left. yield struct.pack(">l", 0) @@ -1285,9 +1293,11 @@ def getchunk(): d = source.read(4) - if not d: return "" + if not d: + return "" l = struct.unpack(">l", d)[0] - if l <= 4: return "" + if l <= 4: + return "" d = source.read(l - 4) if len(d) < l - 4: raise repo.RepoError(_("premature EOF reading chunk" @@ -1298,7 +1308,8 @@ def getgroup(): while 1: c = getchunk() - if not c: break + if not c: + break yield c def csmap(x): @@ -1308,7 +1319,8 @@ def revmap(x): return self.changelog.rev(x) - if not source: return + if not source: + return changesets = files = revisions = 0 tr = self.transaction() @@ -1333,7 +1345,8 @@ self.ui.status(_("adding file changes\n")) while 1: f = getchunk() - if not f: break + if not f: + break self.ui.debug(_("adding %s revisions\n") % f) fl = self.file(f) o = fl.count() @@ -1354,7 +1367,7 @@ if changesets > 0: if not self.hook("changegroup", - node=hex(self.changelog.node(cor+1))): + node=hex(self.changelog.node(cor+1))): self.ui.warn(_("abort: changegroup hook returned failure!\n")) return 1 @@ -1370,6 +1383,8 @@ self.ui.warn(_("aborting: outstanding uncommitted merges\n")) return 1 + err = False + p1, p2 = pl[0], node pa = self.changelog.ancestor(p1, p2) m1n = self.changelog.read(p1)[0] @@ -1377,29 +1392,32 @@ man = self.manifest.ancestor(m1n, m2n) m1 = self.manifest.read(m1n) mf1 = self.manifest.readflags(m1n) - m2 = self.manifest.read(m2n) + m2 = self.manifest.read(m2n).copy() mf2 = self.manifest.readflags(m2n) ma = self.manifest.read(man) mfa = self.manifest.readflags(man) - (c, a, d, u) = self.changes() - - if allow and not forcemerge: - if c or a or d: - raise util.Abort(_("outstanding uncommited changes")) - if not forcemerge and not force: - for f in u: - if f in m2: - t1 = self.wread(f) - t2 = self.file(f).read(m2[f]) - if cmp(t1, t2) != 0: - raise util.Abort(_("'%s' already exists in the working" - " dir and differs from remote") % f) + modified, added, removed, deleted, unknown = self.changes() # is this a jump, or a merge? i.e. is there a linear path # from p1 to p2? linear_path = (pa == p1 or pa == p2) + if allow and linear_path: + raise util.Abort(_("there is nothing to merge, " + "just use 'hg update'")) + if allow and not forcemerge: + if modified or added or removed: + raise util.Abort(_("outstanding uncommited changes")) + if not forcemerge and not force: + for f in unknown: + if f in m2: + t1 = self.wread(f) + t2 = self.file(f).read(m2[f]) + if cmp(t1, t2) != 0: + raise util.Abort(_("'%s' already exists in the working" + " dir and differs from remote") % f) + # resolve the manifest to determine which files # we care about merging self.ui.note(_("resolving manifests\n")) @@ -1415,17 +1433,18 @@ # construct a working dir manifest mw = m1.copy() mfw = mf1.copy() - umap = dict.fromkeys(u) + umap = dict.fromkeys(unknown) - for f in a + c + u: + for f in added + modified + unknown: mw[f] = "" mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False)) if moddirstate: wlock = self.wlock() - for f in d: - if f in mw: del mw[f] + for f in deleted + removed: + if f in mw: + del mw[f] # If we're jumping between revisions (as opposed to merging), # and if neither the working directory nor the target rev has @@ -1437,7 +1456,8 @@ # Compare manifests for f, n in mw.iteritems(): - if choose and not choose(f): continue + if choose and not choose(f): + continue if f in m2: s = 0 @@ -1480,7 +1500,8 @@ a, b, c = mfa.get(f, 0), mfw[f], mf2[f] mode = ((a^b) | (a^c)) ^ a if mode != b: - self.ui.debug(_(" updating permissions for %s\n") % f) + self.ui.debug(_(" updating permissions for %s\n") + % f) util.set_exec(self.wjoin(f), mode) del m2[f] elif f in ma: @@ -1510,15 +1531,18 @@ self.ui.debug(_("working dir created %s, keeping\n") % f) for f, n in m2.iteritems(): - if choose and not choose(f): continue - if f[0] == "/": continue + if choose and not choose(f): + continue + if f[0] == "/": + continue if f in ma and n != ma[f]: r = _("k") if not force and (linear_path or allow): r = self.ui.prompt( (_("remote changed %s which local deleted\n") % f) + _("(k)eep or (d)elete?"), _("[kd]"), _("k")) - if r == _("k"): get[f] = n + if r == _("k"): + get[f] = n elif f not in ma: self.ui.debug(_("remote created %s\n") % f) get[f] = n @@ -1548,7 +1572,8 @@ fl.sort() for f in fl: cf = "" - if f in merge: cf = _(" (resolve)") + if f in merge: + cf = _(" (resolve)") self.ui.status(" %s%s\n" % (f, cf)) self.ui.warn(_("aborting update spanning branches!\n")) self.ui.status(_("(use update -m to merge across branches" @@ -1560,7 +1585,8 @@ files = get.keys() files.sort() for f in files: - if f[0] == "/": continue + if f[0] == "/": + continue self.ui.note(_("getting %s\n") % f) t = self.file(f).read(get[f]) self.wwrite(f, t) @@ -1577,7 +1603,9 @@ for f in files: self.ui.status(_("merging %s\n") % f) my, other, flag = merge[f] - self.merge3(f, my, other) + ret = self.merge3(f, my, other) + if ret: + err = True util.set_exec(self.wjoin(f), flag) if moddirstate: if branch_merge: @@ -1610,6 +1638,7 @@ if moddirstate: self.dirstate.setparents(p1, p2) + return err def merge3(self, fn, my, other): """perform a 3-way merge in the working directory""" @@ -1640,6 +1669,7 @@ os.unlink(b) os.unlink(c) + return r def verify(self): filelinkrevs = {} @@ -1652,11 +1682,17 @@ self.ui.warn(msg + "\n") errors[0] += 1 + def checksize(obj, name): + d = obj.checksize() + if d[0]: + err(_("%s data length off by %d bytes") % (name, d[0])) + if d[1]: + err(_("%s index contains %d extra bytes") % (name, d[1])) + seen = {} self.ui.status(_("checking changesets\n")) - d = self.changelog.checksize() - if d: - err(_("changeset data short %d bytes") % d) + checksize(self.changelog, "changelog") + for i in range(self.changelog.count()): changesets += 1 n = self.changelog.node(i) @@ -1686,9 +1722,8 @@ seen = {} self.ui.status(_("checking manifests\n")) - d = self.manifest.checksize() - if d: - err(_("manifest data short %d bytes") % d) + checksize(self.manifest, "manifest") + for i in range(self.manifest.count()): n = self.manifest.node(i) l = self.manifest.linkrev(n) @@ -1723,7 +1758,7 @@ self.ui.status(_("crosschecking files in changesets and manifests\n")) - for m,c in neededmanifests.items(): + for m, c in neededmanifests.items(): err(_("Changeset %s refers to unknown manifest %s") % (short(m), short(c))) del neededmanifests @@ -1740,14 +1775,13 @@ ff = filenodes.keys() ff.sort() for f in ff: - if f == "/dev/null": continue + if f == "/dev/null": + continue files += 1 fl = self.file(f) - d = fl.checksize() - if d: - err(_("%s file data short %d bytes") % (f, d)) + checksize(fl, f) - nodes = { nullid: 1 } + nodes = {nullid: 1} seen = {} for i in range(fl.count()): revisions += 1 diff -r 675ca845c2f8 -r c21b54f7f7b8 mercurial/manifest.py --- a/mercurial/manifest.py Thu Dec 15 18:04:39 2005 +0100 +++ b/mercurial/manifest.py Wed Feb 01 19:18:15 2006 +0100 @@ -108,6 +108,8 @@ files = map.keys() files.sort() + # if this is changed to support newlines in filenames, + # be sure to check the templates/ dir again (especially *-raw.tmpl) text = ["%s\000%s%s\n" % (f, hex(map[f]), flags[f] and "x" or '') for f in files] diff -r 675ca845c2f8 -r c21b54f7f7b8 mercurial/mdiff.py --- a/mercurial/mdiff.py Thu Dec 15 18:04:39 2005 +0100 +++ b/mercurial/mdiff.py Wed Feb 01 19:18:15 2006 +0100 @@ -5,9 +5,13 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -import difflib, struct, bdiff, util, mpatch +from demandload import demandload +import struct, bdiff, util, mpatch +demandload(globals(), "re") -def unidiff(a, ad, b, bd, fn, r=None, text=False): + +def unidiff(a, ad, b, bd, fn, r=None, text=False, + showfunc=False, ignorews=False): if not a and not b: return "" epoch = util.datestr((0, 0)) @@ -27,9 +31,10 @@ l3 = "@@ -1,%d +0,0 @@\n" % len(a) l = [l1, l2, l3] + ["-" + e for e in a] else: - a = a.splitlines(1) - b = b.splitlines(1) - l = list(difflib.unified_diff(a, b, "a/" + fn, "b/" + fn)) + al = a.splitlines(1) + bl = b.splitlines(1) + l = list(bunidiff(a, b, al, bl, "a/" + fn, "b/" + fn, + showfunc=showfunc, ignorews=ignorews)) if not l: return "" # difflib uses a space, rather than a tab l[0] = "%s\t%s\n" % (l[0][:-2], ad) @@ -45,6 +50,128 @@ return "".join(l) +# somewhat self contained replacement for difflib.unified_diff +# t1 and t2 are the text to be diffed +# l1 and l2 are the text broken up into lines +# header1 and header2 are the filenames for the diff output +# context is the number of context lines +# showfunc enables diff -p output +# ignorews ignores all whitespace changes in the diff +def bunidiff(t1, t2, l1, l2, header1, header2, context=3, showfunc=False, + ignorews=False): + def contextend(l, len): + ret = l + context + if ret > len: + ret = len + return ret + + def contextstart(l): + ret = l - context + if ret < 0: + return 0 + return ret + + def yieldhunk(hunk, header): + if header: + for x in header: + yield x + (astart, a2, bstart, b2, delta) = hunk + aend = contextend(a2, len(l1)) + alen = aend - astart + blen = b2 - bstart + aend - a2 + + func = "" + if 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): + t = l1[x].rstrip() + if funcre.match(t): + func = ' ' + t[:40] + break + + yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen, + bstart + 1, blen, func) + for x in delta: + yield x + for x in xrange(a2, aend): + yield ' ' + l1[x] + + header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ] + + if showfunc: + funcre = re.compile('\w') + if ignorews: + wsre = re.compile('[ \t]') + + # 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. + # + diff = bdiff.blocks(t1, t2) + hunk = None + for i in xrange(len(diff)): + # The first match is special. + # we've either found a match starting at line 0 or a match later + # in the file. If it starts later, old and new below will both be + # empty and we'll continue to the next match. + if i > 0: + s = diff[i-1] + else: + s = [0, 0, 0, 0] + delta = [] + s1 = diff[i] + a1 = s[1] + a2 = s1[0] + b1 = s[3] + b2 = s1[2] + + old = l1[a1:a2] + new = l2[b1:b2] + + # bdiff sometimes gives huge matches past eof, this check eats them, + # and deals with the special first match case described above + if not old and not new: + continue + + if ignorews: + wsold = wsre.sub('', "".join(old)) + wsnew = wsre.sub('', "".join(new)) + if wsold == wsnew: + continue + + astart = contextstart(a1) + bstart = contextstart(b1) + prev = None + if hunk: + # join with the previous hunk if it falls inside the context + if astart < hunk[1] + context + 1: + prev = hunk + astart = hunk[1] + bstart = hunk[3] + else: + for x in yieldhunk(hunk, header): + yield x + # we only want to yield the header if the files differ, and + # we only want to yield it once. + header = None + if prev: + # we've joined the previous hunk, record the new ending points. + hunk[1] = a2 + hunk[3] = b2 + delta = hunk[4] + else: + # create a new hunk + hunk = [ astart, a2, bstart, b2, delta ] + + delta[len(delta):] = [ ' ' + x for x in l1[astart:a1] ] + delta[len(delta):] = [ '-' + x for x in old ] + delta[len(delta):] = [ '+' + x for x in new ] + + if hunk: + for x in yieldhunk(hunk, header): + yield x + def patchtext(bin): pos = 0 t = [] diff -r 675ca845c2f8 -r c21b54f7f7b8 mercurial/revlog.py --- a/mercurial/revlog.py Thu Dec 15 18:04:39 2005 +0100 +++ b/mercurial/revlog.py Wed Feb 01 19:18:15 2006 +0100 @@ -188,6 +188,7 @@ self.datafile = datafile self.opener = opener self.cache = None + self.chunkcache = None try: i = self.opener(self.indexfile).read() @@ -196,6 +197,10 @@ raise i = "" + if i and i[:4] != "\0\0\0\0": + raise RevlogError(_("incompatible revlog signature on %s") % + self.indexfile) + if len(i) > 10000: # big index, let's parse it on demand parser = lazyparser(i, self) @@ -208,7 +213,7 @@ m = [None] * l n = 0 - for f in xrange(0, len(i), s): + for f in xrange(0, l * s, s): # offset, size, base, linkrev, p1, p2, nodeid e = struct.unpack(indexformat, i[f:f + s]) m[n] = (e[6], n) @@ -473,6 +478,35 @@ """apply a list of patches to a string""" return mdiff.patches(t, pl) + def chunk(self, rev): + start, length = self.start(rev), self.length(rev) + end = start + length + + def loadcache(): + cache_length = max(4096 * 1024, length) # 4Mo + df = self.opener(self.datafile) + df.seek(start) + self.chunkcache = (start, df.read(cache_length)) + + if not self.chunkcache: + loadcache() + + cache_start = self.chunkcache[0] + cache_end = cache_start + len(self.chunkcache[1]) + if start >= cache_start and end <= cache_end: + # it is cached + offset = start - cache_start + else: + loadcache() + offset = 0 + + #def checkchunk(): + # df = self.opener(self.datafile) + # df.seek(start) + # return df.read(length) + #assert s == checkchunk() + return decompress(self.chunkcache[1][offset:offset + length]) + def delta(self, node): """return or calculate a delta between a node and its predecessor""" r = self.rev(node) @@ -481,10 +515,7 @@ return self.diff(self.revision(self.node(r - 1)), self.revision(node)) else: - f = self.opener(self.datafile) - f.seek(self.start(r)) - data = f.read(self.length(r)) - return decompress(data) + return self.chunk(r) def revision(self, node): """return an uncompressed revision of a given""" @@ -494,33 +525,22 @@ # look up what we need to read text = None rev = self.rev(node) - start, length, base, link, p1, p2, node = self.index[rev] - end = start + length - if base != rev: start = self.start(base) + base = self.base(rev) # do we have useful data cached? if self.cache and self.cache[1] >= base and self.cache[1] < rev: base = self.cache[1] - start = self.start(base + 1) text = self.cache[2] - last = 0 - - f = self.opener(self.datafile) - f.seek(start) - data = f.read(end - start) - - if text is None: - last = self.length(base) - text = decompress(data[:last]) + else: + text = self.chunk(base) bins = [] for r in xrange(base + 1, rev + 1): - s = self.length(r) - bins.append(decompress(data[last:last + s])) - last = last + s + bins.append(self.chunk(r)) text = mdiff.patches(text, bins) + p1, p2 = self.parents(node) if node != hash(text, p1, p2): raise RevlogError(_("integrity check failed on %s:%d") % (self.datafile, rev)) @@ -650,7 +670,7 @@ #print "next x" gx = x.next() - def group(self, nodelist, lookup, infocollect = None): + def group(self, nodelist, lookup, infocollect=None): """calculate a delta group Given a list of changeset revs, return a set of deltas and @@ -660,7 +680,6 @@ changesets. parent is parent[0] """ revs = [self.rev(n) for n in nodelist] - needed = dict.fromkeys(revs, 1) # if we don't have any revisions touched by these changesets, bail if not revs: @@ -671,88 +690,30 @@ p = self.parents(self.node(revs[0]))[0] revs.insert(0, self.rev(p)) - # for each delta that isn't contiguous in the log, we need to - # reconstruct the base, reconstruct the result, and then - # calculate the delta. We also need to do this where we've - # stored a full version and not a delta - for i in xrange(0, len(revs) - 1): - a, b = revs[i], revs[i + 1] - if a + 1 != b or self.base(b) == b: - for j in xrange(self.base(a), a + 1): - needed[j] = 1 - for j in xrange(self.base(b), b + 1): - needed[j] = 1 - - # calculate spans to retrieve from datafile - needed = needed.keys() - needed.sort() - spans = [] - oo = -1 - ol = 0 - for n in needed: - if n < 0: continue - o = self.start(n) - l = self.length(n) - if oo + ol == o: # can we merge with the previous? - nl = spans[-1][2] - nl.append((n, l)) - ol += l - spans[-1] = (oo, ol, nl) - else: - oo = o - ol = l - spans.append((oo, ol, [(n, l)])) - - # read spans in, divide up chunks - chunks = {} - for span in spans: - # we reopen the file for each span to make http happy for now - f = self.opener(self.datafile) - f.seek(span[0]) - data = f.read(span[1]) - - # divide up the span - pos = 0 - for r, l in span[2]: - chunks[r] = decompress(data[pos: pos + l]) - pos += l - # helper to reconstruct intermediate versions def construct(text, base, rev): - bins = [chunks[r] for r in xrange(base + 1, rev + 1)] + bins = [self.chunk(r) for r in xrange(base + 1, rev + 1)] return mdiff.patches(text, bins) # build deltas - deltas = [] for d in xrange(0, len(revs) - 1): a, b = revs[d], revs[d + 1] - n = self.node(b) + na = self.node(a) + nb = self.node(b) if infocollect is not None: - infocollect(n) + infocollect(nb) # do we need to construct a new delta? if a + 1 != b or self.base(b) == b: - if a >= 0: - base = self.base(a) - ta = chunks[self.base(a)] - ta = construct(ta, base, a) - else: - ta = "" - - base = self.base(b) - if a > base: - base = a - tb = ta - else: - tb = chunks[self.base(b)] - tb = construct(tb, base, b) + ta = self.revision(na) + tb = self.revision(nb) d = self.diff(ta, tb) else: - d = chunks[b] + d = self.chunk(b) - p = self.parents(n) - meta = n + p[0] + p[1] + lookup(n) + p = self.parents(nb) + meta = nb + p[0] + p[1] + lookup(nb) l = struct.pack(">l", len(meta) + len(d) + 4) yield l yield meta @@ -880,14 +841,29 @@ expected = 0 if self.count(): expected = self.end(self.count() - 1) + try: f = self.opener(self.datafile) f.seek(0, 2) actual = f.tell() - return expected - actual + dd = actual - expected except IOError, inst: - if inst.errno == errno.ENOENT: - return 0 - raise + if inst.errno != errno.ENOENT: + raise + dd = 0 + + try: + f = self.opener(self.indexfile) + f.seek(0, 2) + actual = f.tell() + s = struct.calcsize(indexformat) + i = actual / s + di = actual - (i * s) + except IOError, inst: + if inst.errno != errno.ENOENT: + raise + di = 0 + + return (dd, di) diff -r 675ca845c2f8 -r c21b54f7f7b8 mercurial/statichttprepo.py --- a/mercurial/statichttprepo.py Thu Dec 15 18:04:39 2005 +0100 +++ b/mercurial/statichttprepo.py Wed Feb 01 19:18:15 2006 +0100 @@ -35,6 +35,8 @@ self.changelog = changelog.changelog(self.opener) self.tagscache = None self.nodetagscache = None + self.encodepats = None + self.decodepats = None def dev(self): return -1 diff -r 675ca845c2f8 -r c21b54f7f7b8 mercurial/ui.py --- a/mercurial/ui.py Thu Dec 15 18:04:39 2005 +0100 +++ b/mercurial/ui.py Wed Feb 01 19:18:15 2006 +0100 @@ -23,6 +23,7 @@ self.interactive = self.configbool("ui", "interactive", True) self.updateopts(verbose, debug, quiet, interactive) + self.diffcache = None def updateopts(self, verbose=False, debug=False, quiet=False, interactive=True): @@ -76,6 +77,23 @@ def extensions(self): return self.configitems("extensions") + def diffopts(self): + if self.diffcache: + return self.diffcache + ret = { 'showfunc' : True, 'ignorews' : False} + for x in self.configitems("diff"): + k = x[0].lower() + v = x[1] + if v: + v = v.lower() + if v == 'true': + value = True + else: + value = False + ret[k] = value + self.diffcache = ret + return ret + def username(self): return (os.environ.get("HGUSER") or self.config("ui", "username") or @@ -110,7 +128,7 @@ sys.stdout.write(str(a)) def write_err(self, *args): - sys.stdout.flush() + if not sys.stdout.closed: sys.stdout.flush() for a in args: sys.stderr.write(str(a)) diff -r 675ca845c2f8 -r c21b54f7f7b8 mercurial/util.py --- a/mercurial/util.py Thu Dec 15 18:04:39 2005 +0100 +++ b/mercurial/util.py Wed Feb 01 19:18:15 2006 +0100 @@ -13,7 +13,8 @@ import os, errno from i18n import gettext as _ from demandload import * -demandload(globals(), "re cStringIO shutil popen2 sys tempfile threading time") +demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile") +demandload(globals(), "threading time") def pipefilter(s, cmd): '''filter string S through command CMD, returning its output''' @@ -190,17 +191,17 @@ else: raise Abort('%s not under root' % myname) -def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''): - return _matcher(canonroot, cwd, names, inc, exc, head, 'glob') +def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None): + return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src) -def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head=''): +def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None): if os.name == 'nt': dflt_pat = 'glob' else: dflt_pat = 'relpath' - return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat) + return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src) -def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat): +def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src): """build a function to match a set of file patterns arguments: @@ -261,7 +262,8 @@ pat = '(?:%s)' % regex(k, p, tail) matches.append(re.compile(pat).match) except re.error: - raise Abort("invalid pattern: %s:%s" % (k, p)) + if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p)) + else: raise Abort("invalid pattern (%s): %s" % (k, p)) def buildfn(text): for m in matches: @@ -357,9 +359,9 @@ os_link(src, dst) except: hardlink = False - shutil.copy2(src, dst) + shutil.copy(src, dst) else: - shutil.copy2(src, dst) + shutil.copy(src, dst) def opener(base): """ @@ -442,12 +444,36 @@ if os.name == 'nt': demandload(globals(), "msvcrt") nulldev = 'NUL:' - + + class winstdout: + '''stdout on windows misbehaves if sent through a pipe''' + + def __init__(self, fp): + self.fp = fp + + def __getattr__(self, key): + return getattr(self.fp, key) + + def close(self): + try: + self.fp.close() + except: pass + + def write(self, s): + try: + return self.fp.write(s) + except IOError, inst: + if inst.errno != 0: raise + self.close() + raise IOError(errno.EPIPE, 'Broken pipe') + + sys.stdout = winstdout(sys.stdout) + try: import win32api, win32process filename = win32process.GetModuleFileNameEx(win32api.GetCurrentProcess(), 0) systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini') - + except ImportError: systemrc = r'c:\mercurial\mercurial.ini' pass @@ -518,14 +544,19 @@ if f.endswith(".rc")]) except OSError, inst: pass return rcs - rcpath = rcfiles(os.path.dirname(sys.argv[0]) + '/../etc/mercurial') + rcpath = [] + if len(sys.argv) > 0: + rcpath.extend(rcfiles(os.path.dirname(sys.argv[0]) + '/../etc/mercurial')) rcpath.extend(rcfiles('/etc/mercurial')) rcpath.append(os.path.expanduser('~/.hgrc')) rcpath = [os.path.normpath(f) for f in rcpath] def parse_patch_output(output_line): """parses the output produced by patch and returns the file name""" - return output_line[14:] + pf = output_line[14:] + if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0: + pf = pf[1:-1] # Remove the quotes + return pf def is_exec(f, last): """check whether a file is executable""" diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/changelog-gitweb.tmpl --- a/templates/changelog-gitweb.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/changelog-gitweb.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -11,7 +11,7 @@
-#desc|addbreaks# +#desc|escape|addbreaks#

diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/changelogentry.tmpl --- a/templates/changelogentry.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/changelogentry.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -8,6 +8,7 @@ #node|short# #parent%changelogparent# + #child%changelogchild# #changelogtag# author: diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/changeset-gitweb.tmpl --- a/templates/changeset-gitweb.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/changeset-gitweb.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -10,7 +10,7 @@
@@ -23,11 +23,12 @@ changeset#node|short# manifest#manifest|short# #parent%changesetparent# +#child%changesetchild# #changesettag#
-#desc|addbreaks# +#desc|escape|addbreaks#
diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/changeset.tmpl --- a/templates/changeset.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/changeset.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -19,6 +19,7 @@ #node|short# #parent%changesetparent# +#child%changesetchild# #changesettag# author: diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/fileannotate-gitweb.tmpl --- a/templates/fileannotate-gitweb.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/fileannotate-gitweb.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -10,16 +10,17 @@
-
#file#
+
#file|escape#
#parent%fileannotateparent# +#child%fileannotatechild# diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/fileannotate.tmpl --- a/templates/fileannotate.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/fileannotate.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -1,5 +1,5 @@ #header# -#repo|escape#: #file# annotate +#repo|escape#: #file|escape# annotate @@ -7,18 +7,20 @@ changelogtagschangeset -manifest -file -revisions +manifest +file +revisions -

Annotate #file#

+

Annotate #file|escape#

changeset #rev#: #node|short#
manifest: #manifest|short#
+#rename%filerename# #parent%fileannotateparent# +#child%fileannotatechild# diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/filediff.tmpl --- a/templates/filediff.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/filediff.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -1,5 +1,5 @@ #header# -#repo|escape#: #file# diff +#repo|escape#: #file|escape# diff @@ -7,13 +7,13 @@ changelogtagschangeset -file -revisions -annotate -raw +file +revisions +annotate +raw -

#file#

+

#file|escape#

changeset #rev#: #node|short#
author: #author|obfuscate#
@@ -21,6 +21,7 @@ #parent%filediffparent# +#child%filediffchild#
#node|short#
diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/filelog-gitweb.tmpl --- a/templates/filelog-gitweb.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/filelog-gitweb.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -10,7 +10,7 @@
diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/filelog-rss.tmpl --- a/templates/filelog-rss.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/filelog-rss.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -1,6 +1,6 @@ #header# - #repo|escape#: #file# history - #file# revision history + #repo|escape#: #file|escape# history + #file|escape# revision history #entries%filelogentry# - \ No newline at end of file + diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/filelog.tmpl --- a/templates/filelog.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/filelog.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -1,7 +1,7 @@ #header# -#repo|escape#: #file# history +#repo|escape#: #file|escape# history + href="?fl=0;file=#file|urlescape#;style=rss" title="RSS feed for #repo|escape#:#file#"> @@ -9,12 +9,12 @@ -

#file# revision history

+

#file|escape# revision history

#entries%filelogentry# diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/filelogentry-rss.tmpl --- a/templates/filelogentry-rss.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/filelogentry-rss.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -1,6 +1,6 @@ #desc|strip|firstline|strip|escape# - #url#?f=#filenode|short#;file=#file# + #url#?f=#filenode|short#;file=#file|urlescape# #author|obfuscate# #date|rfc822date#> diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/filelogentry.tmpl --- a/templates/filelogentry.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/filelogentry.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -4,10 +4,11 @@ - +#rename%filelogrename# diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/filerevision-gitweb.tmpl --- a/templates/filerevision-gitweb.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/filerevision-gitweb.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -10,16 +10,17 @@ -
#file#
+
#file|escape#
#desc|strip|firstline|escape#
revision #filerev#: #filenode|short# -(diff) -(annotate) + #filenode|short# +(diff) +(annotate)
author:  #author|obfuscate#
#parent%fileannotateparent# +#child%fileannotatechild# diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/filerevision.tmpl --- a/templates/filerevision.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/filerevision.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -1,5 +1,5 @@ #header# -#repo|escape#:#file# +#repo|escape#:#file|escape# @@ -7,19 +7,21 @@ changelogtagschangeset -manifest -revisions -annotate -raw +manifest +revisions +annotate +raw -

#file#

+

#file|escape#

changeset #rev#: #node|short#
manifest: #manifest|short#
+#rename%filerename# #parent%filerevparent# +#child%filerevchild# diff -r 675ca845c2f8 -r c21b54f7f7b8 templates/header.tmpl --- a/templates/header.tmpl Thu Dec 15 18:04:39 2005 +0100 +++ b/templates/header.tmpl Wed Feb 01 19:18:15 2006 +0100 @@ -3,6 +3,7 @@ +
changeset #rev#: #node|short#
author: #author|obfuscate#