# HG changeset patch # User Dirkjan Ochtman # Date 1214292878 -7200 # Node ID d43707e09b02e441b4b32671db10a25a6b51ab84 # Parent 1fe6f365df2e0ed6dd56b5bd9993812378506174# Parent 2e58f1a3604682a4fdfe40817150ccc12e76b25e merge from crew-stable diff -r 2e58f1a36046 -r d43707e09b02 .hgignore --- a/.hgignore Tue Jun 24 09:33:13 2008 +0200 +++ b/.hgignore Tue Jun 24 09:34:38 2008 +0200 @@ -7,6 +7,7 @@ *.mergebackup *.o *.so +*.pyd *.pyc *.swp *.prof diff -r 2e58f1a36046 -r d43707e09b02 contrib/dumprevlog --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/dumprevlog Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# Dump revlogs as raw data stream +# $ find .hg/store/ -name "*.i" | xargs dumprevlog > repo.dump + +import sys +from mercurial import revlog, node, util + +for fp in (sys.stdin, sys.stdout, sys.stderr): + util.set_binary(fp) + +for f in sys.argv[1:]: + binopen = lambda fn: open(fn, 'rb') + r = revlog.revlog(binopen, f) + print "file:", f + for i in xrange(r.count()): + n = r.node(i) + p = r.parents(n) + d = r.revision(n) + print "node:", node.hex(n) + print "linkrev:", r.linkrev(n) + print "parents:", node.hex(p[0]), node.hex(p[1]) + print "length:", len(d) + print "-start-" + print d + print "-end-" diff -r 2e58f1a36046 -r d43707e09b02 contrib/mercurial.el --- a/contrib/mercurial.el Tue Jun 24 09:33:13 2008 +0200 +++ b/contrib/mercurial.el Tue Jun 24 09:34:38 2008 +0200 @@ -35,8 +35,10 @@ ;; This code has been developed under XEmacs 21.5, and may not work as ;; well under GNU Emacs (albeit tested under 21.4). Patches to ;; enhance the portability of this code, fix bugs, and add features -;; are most welcome. You can clone a Mercurial repository for this -;; package from http://www.serpentine.com/hg/hg-emacs +;; are most welcome. + +;; As of version 22.3, GNU Emacs's VC mode has direct support for +;; Mercurial, so this package may not prove as useful there. ;; Please send problem reports and suggestions to bos@serpentine.com. diff -r 2e58f1a36046 -r d43707e09b02 contrib/undumprevlog --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/undumprevlog Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# Undump a dump from dumprevlog +# $ hg init +# $ undumprevlog < repo.dump + +import sys +from mercurial import revlog, node, util, transaction + +for fp in (sys.stdin, sys.stdout, sys.stderr): + util.set_binary(fp) + +opener = util.opener('.', False) +tr = transaction.transaction(sys.stderr.write, opener, "undump.journal") +while 1: + l = sys.stdin.readline() + if not l: + break + if l.startswith("file:"): + f = l[6:-1] + r = revlog.revlog(opener, f) + print f + elif l.startswith("node:"): + n = node.bin(l[6:-1]) + elif l.startswith("linkrev:"): + lr = int(l[9:-1]) + elif l.startswith("parents:"): + p = l[9:-1].split() + p1 = node.bin(p[0]) + p2 = node.bin(p[1]) + elif l.startswith("length:"): + length = int(l[8:-1]) + sys.stdin.readline() # start marker + d = sys.stdin.read(length) + sys.stdin.readline() # end marker + r.addrevision(d, tr, lr, p1, p2) + +tr.close() diff -r 2e58f1a36046 -r d43707e09b02 contrib/win32/hg.bat --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/win32/hg.bat Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,12 @@ +@echo off +rem Windows Driver script for Mercurial + +setlocal +set HG=%~f0 + +rem Use a full path to Python (relative to this script) as the standard Python +rem install does not put python.exe on the PATH... +rem %~dp0 is the directory of this script + +%~dp0..\python "%~dp0hg" %* +endlocal diff -r 2e58f1a36046 -r d43707e09b02 doc/gendoc.py --- a/doc/gendoc.py Tue Jun 24 09:33:13 2008 +0200 +++ b/doc/gendoc.py Tue Jun 24 09:34:38 2008 +0200 @@ -69,6 +69,7 @@ if f.startswith("debug"): continue d = get_cmd(h[f]) # synopsis + ui.write("[[%s]]\n" % d['cmd']) ui.write("%s::\n" % d['synopsis'].replace("hg ","", 1)) # description ui.write("%s\n\n" % d['desc'][1]) @@ -91,11 +92,10 @@ ui.write(_(" aliases: %s\n\n") % " ".join(d['aliases'])) # print topics - for t in helptable: + for t, doc in helptable: l = t.split("|") section = l[-1] underlined(_(section).upper()) - doc = helptable[t] if callable(doc): doc = doc() ui.write(_(doc)) diff -r 2e58f1a36046 -r d43707e09b02 doc/hg.1.txt --- a/doc/hg.1.txt Tue Jun 24 09:33:13 2008 +0200 +++ b/doc/hg.1.txt Tue Jun 24 09:34:38 2008 +0200 @@ -30,65 +30,13 @@ repository path:: either the pathname of a local repository or the URI of a remote - repository. There are two available URI protocols, http:// which is + repository. There are two available URI protocols, http:// which is fast and the static-http:// protocol which is much slower but does not require a special server on the web host. include::hg.1.gendoc.txt[] -SPECIFYING SINGLE REVISIONS ---------------------------- - - Mercurial accepts several notations for identifying individual - revisions. - - A plain integer is treated as a revision number. Negative - integers are treated as offsets from the tip, with -1 denoting the - tip. - - A 40-digit hexadecimal string is treated as a unique revision - identifier. - - A hexadecimal string less than 40 characters long is treated as a - unique revision identifier, and referred to as a short-form - identifier. A short-form identifier is only valid if it is the - prefix of one full-length identifier. - - Any other string is treated as a tag name, which is a symbolic - name associated with a revision identifier. Tag names may not - contain the ":" character. - - The reserved name "tip" is a special tag that always identifies - the most recent revision. - - The reserved name "null" indicates the null revision. This is the - revision of an empty repository, and the parent of revision 0. - - The reserved name "." indicates the working directory parent. If - no working directory is checked out, it is equivalent to null. - If an uncommitted merge is in progress, "." is the revision of - the first parent. - -SPECIFYING MULTIPLE REVISIONS ------------------------------ - - When Mercurial accepts more than one revision, they may be - specified individually, or provided as a continuous range, - separated by the ":" character. - - The syntax of range notation is [BEGIN]:[END], where BEGIN and END - are revision identifiers. Both BEGIN and END are optional. If - BEGIN is not specified, it defaults to revision number 0. If END - is not specified, it defaults to the tip. The range ":" thus - means "all revisions". - - If BEGIN is greater than END, revisions are treated in reverse - order. - - A range acts as a closed interval. This means that a range of 3:5 - gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2. - FILES ----- .hgignore:: @@ -103,7 +51,7 @@ /etc/mercurial/hgrc, $HOME/.hgrc, .hg/hgrc:: This file contains defaults and configuration. Values in .hg/hgrc override those in $HOME/.hgrc, and these override settings made in the - global /etc/mercurial/hgrc configuration. See hgrc(5) for details of + global /etc/mercurial/hgrc configuration. See hgrc(5) for details of the contents and format of these files. Some commands (e.g. revert) produce backup files ending in .orig, if diff -r 2e58f1a36046 -r d43707e09b02 doc/hgignore.5.txt --- a/doc/hgignore.5.txt Tue Jun 24 09:33:13 2008 +0200 +++ b/doc/hgignore.5.txt Tue Jun 24 09:34:38 2008 +0200 @@ -17,25 +17,25 @@ ----------- Mercurial ignores every unmanaged file that matches any pattern in an -ignore file. The patterns in an ignore file do not apply to files -managed by Mercurial. To control Mercurial's handling of files that -it manages, see the hg(1) man page. Look for the "-I" and "-X" +ignore file. The patterns in an ignore file do not apply to files +managed by Mercurial. To control Mercurial's handling of files that +it manages, see the hg(1) man page. Look for the "-I" and "-X" options. In addition, a Mercurial configuration file can point to a set of -per-user or global ignore files. See the hgrc(5) man page for details -of how to configure these files. Look for the "ignore" entry in the +per-user or global ignore files. See the hgrc(5) man page for details +of how to configure these files. Look for the "ignore" entry in the "ui" section. SYNTAX ------ An ignore file is a plain text file consisting of a list of patterns, -with one pattern per line. Empty lines are skipped. The "#" +with one pattern per line. Empty lines are skipped. The "#" character is treated as a comment character, and the "\" character is treated as an escape character. -Mercurial supports several pattern syntaxes. The default syntax used +Mercurial supports several pattern syntaxes. The default syntax used is Python/Perl-style regular expressions. To change the syntax used, use a line of the following form: @@ -52,9 +52,9 @@ The chosen syntax stays in effect when parsing all patterns that follow, until another syntax is selected. -Neither glob nor regexp patterns are rooted. A glob-syntax pattern of +Neither glob nor regexp patterns are rooted. A glob-syntax pattern of the form "*.c" will match a file ending in ".c" in any directory, and -a regexp pattern of the form "\.c$" will do the same. To root a +a regexp pattern of the form "\.c$" will do the same. To root a regexp pattern, start it with "^". EXAMPLE diff -r 2e58f1a36046 -r d43707e09b02 doc/hgrc.5.txt --- a/doc/hgrc.5.txt Tue Jun 24 09:33:13 2008 +0200 +++ b/doc/hgrc.5.txt Tue Jun 24 09:34:38 2008 +0200 @@ -17,26 +17,26 @@ Mercurial reads configuration data from several files, if they exist. The names of these files depend on the system on which Mercurial is -installed. *.rc files from a single directory are read in -alphabetical order, later ones overriding earlier ones. Where +installed. *.rc files from a single directory are read in +alphabetical order, later ones overriding earlier ones. Where multiple paths are given below, settings from later paths override earlier ones. (Unix) /etc/mercurial/hgrc.d/*.rc:: (Unix) /etc/mercurial/hgrc:: Per-installation configuration files, searched for in the - directory where Mercurial is installed. is the + directory where Mercurial is installed. is the parent directory of the hg executable (or symlink) being run. For example, if installed in /shared/tools/bin/hg, Mercurial will - look in /shared/tools/etc/mercurial/hgrc. Options in these files + look in /shared/tools/etc/mercurial/hgrc. Options in these files apply to all Mercurial commands executed by any user in any directory. (Unix) /etc/mercurial/hgrc.d/*.rc:: (Unix) /etc/mercurial/hgrc:: Per-system configuration files, for the system on which Mercurial - is running. Options in these files apply to all Mercurial - commands executed by any user in any directory. Options in these + is running. Options in these files apply to all Mercurial + commands executed by any user in any directory. Options in these files override per-installation options. (Windows) \Mercurial.ini:: @@ -45,7 +45,7 @@ or else:: (Windows) C:\Mercurial\Mercurial.ini:: Per-installation/system configuration files, for the system on - which Mercurial is running. Options in these files apply to all + which Mercurial is running. Options in these files apply to all Mercurial commands executed by any user in any directory. Registry keys contain PATH-like strings, every part of which must reference a Mercurial.ini file or be a directory where *.rc files @@ -59,16 +59,16 @@ Per-user configuration file(s), for the user running Mercurial. On Windows 9x, %HOME% is replaced by %APPDATA%. Options in these files apply to all Mercurial commands executed - by this user in any directory. Options in thes files override + by this user in any directory. Options in thes files override per-installation and per-system options. (Unix, Windows) /.hg/hgrc:: Per-repository configuration options that only apply in a - particular repository. This file is not version-controlled, and - will not get transferred during a "clone" operation. Options in + particular repository. This file is not version-controlled, and + will not get transferred during a "clone" operation. Options in this file override options in all other configuration files. On Unix, most of this file will be ignored if it doesn't belong - to a trusted user or to a trusted group. See the documentation + to a trusted user or to a trusted group. See the documentation for the trusted section below for more details. SYNTAX @@ -82,10 +82,10 @@ green= eggs -Each line contains one entry. If the lines that follow are indented, +Each line contains one entry. If the lines that follow are indented, they are treated as continuations of that entry. -Leading whitespace is removed from values. Empty lines are skipped. +Leading whitespace is removed from values. Empty lines are skipped. The optional values can contain format strings which refer to other values in the same section, or values in a special DEFAULT section. @@ -100,6 +100,7 @@ Mercurial "hgrc" file, the purpose of each section, its possible keys, and their possible values. +[[decode]] decode/encode:: Filters for transforming files on checkout/checkin. This would typically be used for newline processing or other @@ -107,12 +108,12 @@ Filters consist of a filter pattern followed by a filter command. Filter patterns are globs by default, rooted at the repository - root. For example, to match any file ending in ".txt" in the root - directory only, use the pattern "*.txt". To match any file ending + root. For example, to match any file ending in ".txt" in the root + directory only, use the pattern "*.txt". To match any file ending in ".c" anywhere in the repository, use the pattern "**.c". The filter command can start with a specifier, either "pipe:" or - "tempfile:". If no specifier is given, "pipe:" is used by default. + "tempfile:". If no specifier is given, "pipe:" is used by default. A "pipe:" command must accept data on stdin and return the transformed data on stdout. @@ -129,9 +130,9 @@ # can safely omit "pipe:", because it's the default) *.gz = gzip - A "tempfile:" command is a template. The string INFILE is replaced + A "tempfile:" command is a template. The string INFILE is replaced with the name of a temporary file that contains the data to be - filtered by the command. The string OUTFILE is replaced with the + filtered by the command. The string OUTFILE is replaced with the name of an empty temporary file, where the filtered data must be written by the command. @@ -158,6 +159,7 @@ [decode] **.txt = dumbdecode: +[[defaults]] defaults:: Use the [defaults] section to define command defaults, i.e. the default options/arguments to pass to the specified commands. @@ -173,6 +175,7 @@ defining command defaults. The command defaults will also be applied to the aliases of the commands defined. +[[diff]] diff:: Settings used when displaying diffs. They are all boolean and defaults to False. @@ -189,25 +192,26 @@ ignoreblanklines;; Ignore changes whose lines are all blank. +[[email]] email:: Settings for extensions that send email messages. from;; - Optional. Email address to use in "From" header and SMTP envelope + Optional. Email address to use in "From" header and SMTP envelope of outgoing messages. to;; - Optional. Comma-separated list of recipients' email addresses. + Optional. Comma-separated list of recipients' email addresses. cc;; - Optional. Comma-separated list of carbon copy recipients' + Optional. Comma-separated list of carbon copy recipients' email addresses. bcc;; - Optional. Comma-separated list of blind carbon copy - recipients' email addresses. Cannot be set interactively. + Optional. Comma-separated list of blind carbon copy + recipients' email addresses. Cannot be set interactively. method;; - Optional. Method to use to send email messages. If value is + Optional. Method to use to send email messages. If value is "smtp" (default), use SMTP (see section "[smtp]" for - configuration). Otherwise, use as name of program to run that + configuration). Otherwise, use as name of program to run that acts like sendmail (takes "-f" option for sender, list of - recipients on command line, message on stdin). Normally, setting + recipients on command line, message on stdin). Normally, setting this to "sendmail" or "/usr/sbin/sendmail" is enough to use sendmail to send messages. @@ -217,6 +221,7 @@ from = Joseph User method = /usr/sbin/sendmail +[[extensions]] extensions:: Mercurial has an extension mechanism for adding new features. To enable an extension, create an entry for it in this section. @@ -241,6 +246,7 @@ # (this extension will get loaded from the file specified) myfeature = ~/.hgext/myfeature.py +[[format]] format:: usestore;; @@ -250,6 +256,7 @@ you to store longer filenames in some situations at the expense of compatibility. +[[merge-patterns]] merge-patterns:: This section specifies merge tools to associate with particular file patterns. Tools matched here will take precedence over the default @@ -261,6 +268,7 @@ **.c = kdiff3 **.jpg = myimgmerge +[[merge-tools]] merge-tools:: This section configures external merge tools to use for file-level merges. @@ -281,6 +289,7 @@ myHtmlTool.priority = 1 Supported arguments: + priority;; The priority in which to evaluate this tool. Default: 0. @@ -297,10 +306,10 @@ launching external tool. Default: True binary;; - This tool can merge binary files. Defaults to False, unless tool + This tool can merge binary files. Defaults to False, unless tool was selected by file pattern match. symlink;; - This tool can merge symlinks. Defaults to False, even if tool was + This tool can merge symlinks. Defaults to False, even if tool was selected by file pattern match. checkconflicts;; Check whether there are conflicts even though the tool reported @@ -313,19 +322,20 @@ fixeol;; Attempt to fix up EOL changes caused by the merge tool. Default: False - gui:; + gui;; This tool requires a graphical interface to run. Default: False regkey;; Windows registry key which describes install location of this tool. Mercurial will search for this key first under HKEY_CURRENT_USER and - then under HKEY_LOCAL_MACHINE. Default: None + then under HKEY_LOCAL_MACHINE. Default: None regname;; - Name of value to read from specified registry key. Defaults to the + Name of value to read from specified registry key. Defaults to the unnamed (default) value. regappend;; String to append to the value read from the registry, typically the - executable name of the tool. Default: None + executable name of the tool. Default: None +[[hooks]] hooks:: Commands or Python functions that get automatically executed by various actions such as starting or finishing a commit. Multiple @@ -342,24 +352,24 @@ incoming.autobuild = /my/build/hook Most hooks are run with environment variables set that give added - useful information. For each hook below, the environment variables + useful information. For each hook below, the environment variables it is passed are listed with names of the form "$HG_foo". changegroup;; Run after a changegroup has been added via push, pull or - unbundle. ID of the first new changeset is in $HG_NODE. URL from + unbundle. ID of the first new changeset is in $HG_NODE. URL from which changes came is in $HG_URL. commit;; Run after a changeset has been created in the local repository. - ID of the newly created changeset is in $HG_NODE. Parent + ID of the newly created changeset is in $HG_NODE. Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2. incoming;; Run after a changeset has been pulled, pushed, or unbundled into - the local repository. The ID of the newly arrived changeset is in - $HG_NODE. URL that was source of changes came is in $HG_URL. + the local repository. The ID of the newly arrived changeset is in + $HG_NODE. URL that was source of changes came is in $HG_URL. outgoing;; - Run after sending changes from local repository to another. ID of - first changeset sent is in $HG_NODE. Source of operation is in + Run after sending changes from local repository to another. ID of + first changeset sent is in $HG_NODE. Source of operation is in $HG_SOURCE; see "preoutgoing" hook for description. post-;; Run after successful invocations of the associated command. The @@ -371,56 +381,56 @@ the command doesn't execute and Mercurial returns the failure code. prechangegroup;; Run before a changegroup is added via push, pull or unbundle. - Exit status 0 allows the changegroup to proceed. Non-zero status - will cause the push, pull or unbundle to fail. URL from which + Exit status 0 allows the changegroup to proceed. Non-zero status + will cause the push, pull or unbundle to fail. URL from which changes will come is in $HG_URL. precommit;; - Run before starting a local commit. Exit status 0 allows the - commit to proceed. Non-zero status will cause the commit to fail. + Run before starting a local commit. Exit status 0 allows the + commit to proceed. Non-zero status will cause the commit to fail. Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2. preoutgoing;; Run before collecting changes to send from the local repository to - another. Non-zero status will cause failure. This lets you - prevent pull over http or ssh. Also prevents against local pull, + another. Non-zero status will cause failure. This lets you + prevent pull over http or ssh. Also prevents against local pull, push (outbound) or bundle commands, but not effective, since you - can just copy files instead then. Source of operation is in - $HG_SOURCE. If "serve", operation is happening on behalf of - remote ssh or http repository. If "push", "pull" or "bundle", + can just copy files instead then. Source of operation is in + $HG_SOURCE. If "serve", operation is happening on behalf of + remote ssh or http repository. If "push", "pull" or "bundle", operation is happening on behalf of repository on same system. pretag;; - Run before creating a tag. Exit status 0 allows the tag to be - created. Non-zero status will cause the tag to fail. ID of - changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag + Run before creating a tag. Exit status 0 allows the tag to be + created. Non-zero status will cause the tag to fail. ID of + changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0. pretxnchangegroup;; Run after a changegroup has been added via push, pull or unbundle, - but before the transaction has been committed. Changegroup is - visible to hook program. This lets you validate incoming changes - before accepting them. Passed the ID of the first new changeset - in $HG_NODE. Exit status 0 allows the transaction to commit. + but before the transaction has been committed. Changegroup is + visible to hook program. This lets you validate incoming changes + before accepting them. Passed the ID of the first new changeset + in $HG_NODE. Exit status 0 allows the transaction to commit. Non-zero status will cause the transaction to be rolled back and - the push, pull or unbundle will fail. URL that was source of + the push, pull or unbundle will fail. URL that was source of changes is in $HG_URL. pretxncommit;; Run after a changeset has been created but the transaction not yet - committed. Changeset is visible to hook program. This lets you - validate commit message and changes. Exit status 0 allows the - commit to proceed. Non-zero status will cause the transaction to - be rolled back. ID of changeset is in $HG_NODE. Parent changeset + committed. Changeset is visible to hook program. This lets you + validate commit message and changes. Exit status 0 allows the + commit to proceed. Non-zero status will cause the transaction to + be rolled back. ID of changeset is in $HG_NODE. Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2. preupdate;; - Run before updating the working directory. Exit status 0 allows - the update to proceed. Non-zero status will prevent the update. - Changeset ID of first new parent is in $HG_PARENT1. If merge, ID + Run before updating the working directory. Exit status 0 allows + the update to proceed. Non-zero status will prevent the update. + Changeset ID of first new parent is in $HG_PARENT1. If merge, ID of second new parent is in $HG_PARENT2. tag;; - Run after a tag is created. ID of tagged changeset is in - $HG_NODE. Name of tag is in $HG_TAG. Tag is local if + Run after a tag is created. ID of tagged changeset is in + $HG_NODE. Name of tag is in $HG_TAG. Tag is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0. update;; - Run after updating the working directory. Changeset ID of first - new parent is in $HG_PARENT1. If merge, ID of second new parent - is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update + Run after updating the working directory. Changeset ID of first + new parent is in $HG_PARENT1. If merge, ID of second new parent + is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update failed (e.g. because conflicts not resolved), $HG_ERROR=1. Note: it is generally better to use standard hooks rather than the @@ -438,16 +448,17 @@ hookname = python:modulename.submodule.callable - Python hooks are run within the Mercurial process. Each hook is + Python hooks are run within the Mercurial process. Each hook is called with at least three keyword arguments: a ui object (keyword "ui"), a repository object (keyword "repo"), and a "hooktype" - keyword that tells what kind of hook is used. Arguments listed as + keyword that tells what kind of hook is used. Arguments listed as environment variables above are passed as keyword arguments, with no "HG_" prefix, and names in lower case. If a Python hook returns a "true" value or raises an exception, this is treated as failure of the hook. +[[http_proxy]] http_proxy:: Used to access web-based Mercurial repositories through a HTTP proxy. @@ -455,68 +466,72 @@ Host name and (optional) port of the proxy server, for example "myproxy:8000". no;; - Optional. Comma-separated list of host names that should bypass + Optional. Comma-separated list of host names that should bypass the proxy. passwd;; - Optional. Password to authenticate with at the proxy server. + Optional. Password to authenticate with at the proxy server. user;; - Optional. User name to authenticate with at the proxy server. + Optional. User name to authenticate with at the proxy server. +[[smtp]] smtp:: Configuration for extensions that need to send email messages. host;; Host name of mail server, e.g. "mail.example.com". port;; - Optional. Port to connect to on mail server. Default: 25. + Optional. Port to connect to on mail server. Default: 25. tls;; - Optional. Whether to connect to mail server using TLS. True or - False. Default: False. + Optional. Whether to connect to mail server using TLS. True or + False. Default: False. username;; - Optional. User name to authenticate to SMTP server with. + Optional. User name to authenticate to SMTP server with. If username is specified, password must also be specified. Default: none. password;; - Optional. Password to authenticate to SMTP server with. + Optional. Password to authenticate to SMTP server with. If username is specified, password must also be specified. Default: none. local_hostname;; - Optional. It's the hostname that the sender can use to identify itself + Optional. It's the hostname that the sender can use to identify itself to the MTA. +[[paths]] paths:: - Assigns symbolic names to repositories. The left side is the + Assigns symbolic names to repositories. The left side is the symbolic name, and the right gives the directory or URL that is the - location of the repository. Default paths can be declared by + location of the repository. Default paths can be declared by setting the following entries. default;; Directory or URL to use when pulling if no source is specified. Default is set to repository from which the current repository was cloned. default-push;; - Optional. Directory or URL to use when pushing if no destination + Optional. Directory or URL to use when pushing if no destination is specified. +[[server]] server:: Controls generic server settings. uncompressed;; Whether to allow clients to clone a repo using the uncompressed - streaming protocol. This transfers about 40% more data than a + streaming protocol. This transfers about 40% more data than a regular clone, but uses less memory and CPU on both server and - client. Over a LAN (100Mbps or better) or a very fast WAN, an + client. Over a LAN (100Mbps or better) or a very fast WAN, an uncompressed streaming clone is a lot faster (~10x) than a regular - clone. Over most WAN connections (anything slower than about + clone. Over most WAN connections (anything slower than about 6Mbps), uncompressed streaming is slower, because of the extra - data transfer overhead. Default is False. + data transfer overhead. Default is False. +[[trusted]] trusted:: For security reasons, Mercurial will not use the settings in the .hg/hgrc file from a repository if it doesn't belong to a - trusted user or to a trusted group. The main exception is the + trusted user or to a trusted group. The main exception is the web interface, which automatically uses some safe settings, since it's common to serve repositories from different users. - This section specifies what users and groups are trusted. The - current user is always trusted. To trust everybody, list a user + This section specifies what users and groups are trusted. The + current user is always trusted. To trust everybody, list a user or a group with name "*". users;; @@ -524,6 +539,7 @@ groups;; Comma-separated list of trusted groups. +[[ui]] ui:: User interface controls. archivemeta;; @@ -532,12 +548,12 @@ the hg archive command or downloaded via hgweb. Default is true. debug;; - Print debugging information. True or False. Default is False. + Print debugging information. True or False. Default is False. editor;; - The editor to use during a commit. Default is $EDITOR or "vi". + The editor to use during a commit. Default is $EDITOR or "vi". fallbackencoding;; Encoding to try if it's not possible to decode the changelog using - UTF-8. Default is ISO-8859-1. + UTF-8. Default is ISO-8859-1. ignore;; A file to read per-user ignore patterns from. This file should be in the same format as a repository-wide .hgignore file. This option @@ -546,7 +562,7 @@ "ignore.other = ~/.hgignore2". For details of the ignore file format, see the hgignore(5) man page. interactive;; - Allow to prompt the user. True or False. Default is True. + Allow to prompt the user. True or False. Default is True. logtemplate;; Template string for commands that print changesets. merge;; @@ -563,18 +579,19 @@ fail to merge See the merge-tools section for more information on configuring tools. + patch;; command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if unset. quiet;; - Reduce the amount of output printed. True or False. Default is False. + Reduce the amount of output printed. True or False. Default is False. remotecmd;; remote command to use for clone/push/pull operations. Default is 'hg'. report_untrusted;; Warn if a .hg/hgrc file is ignored due to not being owned by a - trusted user or group. True or False. Default is True. + trusted user or group. True or False. Default is True. slash;; - Display paths using a slash ("/") as the path separator. This only + Display paths using a slash ("/") as the path separator. This only makes a difference on systems where the default path separator is not the slash character (e.g. Windows uses the backslash character ("\")). Default is False. @@ -582,7 +599,7 @@ command to use for SSH connections. Default is 'ssh'. strict;; Require exact command names, instead of allowing unambiguous - abbreviations. True or False. Default is False. + abbreviations. True or False. Default is False. style;; Name of style to use for command output. timeout;; @@ -591,14 +608,15 @@ username;; The committer of a changeset created when running "commit". Typically a person's name and email address, e.g. "Fred Widget - ". Default is $EMAIL or username@hostname. + ". Default is $EMAIL or username@hostname. If the username in hgrc is empty, it has to be specified manually or in a different hgrc file (e.g. $HOME/.hgrc, if the admin set "username =" in the system hgrc). verbose;; - Increase the amount of output printed. True or False. Default is False. + Increase the amount of output printed. True or False. Default is False. +[[web]] web:: Web interface configuration. accesslog;; @@ -617,9 +635,9 @@ allowpull;; Whether to allow pulling from the repository. Default is true. allow_push;; - Whether to allow pushing to the repository. If empty or not set, - push is not allowed. If the special value "*", any remote user - can push, including unauthenticated users. Otherwise, the remote + Whether to allow pushing to the repository. If empty or not set, + push is not allowed. If the special value "*", any remote user + can push, including unauthenticated users. Otherwise, the remote user must have been authenticated, and the authenticated user name must be present in this list (separated by whitespace or ","). The contents of the allow_push list are examined after the @@ -635,11 +653,11 @@ Name or email address of the person in charge of the repository. Defaults to ui.username or $EMAIL or "unknown" if unset or empty. deny_push;; - Whether to deny pushing to the repository. If empty or not set, - push is not denied. If the special value "*", all remote users - are denied push. Otherwise, unauthenticated users are all denied, + Whether to deny pushing to the repository. If empty or not set, + push is not denied. If the special value "*", all remote users + are denied push. Otherwise, unauthenticated users are all denied, and any authenticated user name present in this list (separated by - whitespace or ",") is also denied. The contents of the deny_push + whitespace or ",") is also denied. The contents of the deny_push list are examined before the allow_push list. description;; Textual description of the repository's purpose or contents. @@ -666,7 +684,7 @@ Prefix path to serve from. Default is '' (server root). push_ssl;; Whether to require that inbound pushes be transported over SSL to - prevent password sniffing. Default is true. + prevent password sniffing. Default is true. staticurl;; Base URL to use for static files. If unset, static files (e.g. the hgicon.png favicon) will be served by the CGI script itself. diff -r 2e58f1a36046 -r d43707e09b02 hgext/bugzilla.py --- a/hgext/bugzilla.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/bugzilla.py Tue Jun 24 09:34:38 2008 +0200 @@ -55,7 +55,7 @@ from mercurial.i18n import _ from mercurial.node import short from mercurial import cmdutil, templater, util -import os, re, time +import re, time MySQLdb = None @@ -127,7 +127,7 @@ cmd = self.ui.config('bugzilla', 'notify', 'cd /var/www/html/bugzilla && ' './processmail %s nobody@nowhere.com') % id - fp = os.popen('(%s) 2>&1' % cmd) + fp = util.popen('(%s) 2>&1' % cmd) out = fp.read() ret = fp.close() if ret: diff -r 2e58f1a36046 -r d43707e09b02 hgext/churn.py --- a/hgext/churn.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/churn.py Tue Jun 24 09:34:38 2008 +0200 @@ -4,12 +4,7 @@ # # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -# -# -# Aliases map file format is simple one alias per line in the following -# format: -# -# +'''allow graphing the number of lines changed per contributor''' from mercurial.i18n import gettext as _ from mercurial import mdiff, cmdutil, util, node @@ -64,7 +59,7 @@ lines = 0 - changes = repo.status(node1, node2, None, util.always)[:5] + changes = repo.status(node1, node2)[:5] modified, added, removed, deleted, unknown = changes @@ -137,7 +132,11 @@ return stats def churn(ui, repo, **opts): - "Graphs the number of lines changed" + '''graphs the number of lines changed + + The map file format used to specify aliases is fairly simple: + + ''' def pad(s, l): if len(s) < l: diff -r 2e58f1a36046 -r d43707e09b02 hgext/convert/__init__.py --- a/hgext/convert/__init__.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/convert/__init__.py Tue Jun 24 09:34:38 2008 +0200 @@ -4,6 +4,7 @@ # # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. +'''converting foreign VCS repositories to Mercurial''' import convcmd from mercurial import commands diff -r 2e58f1a36046 -r d43707e09b02 hgext/convert/common.py --- a/hgext/convert/common.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/convert/common.py Tue Jun 24 09:34:38 2008 +0200 @@ -153,26 +153,18 @@ mapping equivalent authors identifiers for each system.""" return None - def putfile(self, f, e, data): - """Put file for next putcommit(). - f: path to file - e: '', 'x', or 'l' (regular file, executable, or symlink) - data: file contents""" - raise NotImplementedError() - - def delfile(self, f): - """Delete file for next putcommit(). - f: path to file""" - raise NotImplementedError() - - def putcommit(self, files, parents, commit): + def putcommit(self, files, copies, parents, commit, source): """Create a revision with all changed files listed in 'files' and having listed parents. 'commit' is a commit object containing at a minimum the author, date, and message for this changeset. - Called after putfile() and delfile() calls. Note that the sink - repository is not told to update itself to a particular revision - (or even what that revision would be) before it receives the - file data.""" + 'files' is a list of (path, version) tuples, 'copies'is a dictionary + mapping destinations to sources, and 'source' is the source repository. + Only getfile() and getmode() should be called on 'source'. + + Note that the sink repository is not told to update itself to + a particular revision (or even what that revision would be) + before it receives the file data. + """ raise NotImplementedError() def puttags(self, tags): @@ -181,7 +173,7 @@ raise NotImplementedError() def setbranch(self, branch, pbranches): - """Set the current branch name. Called before the first putfile + """Set the current branch name. Called before the first putcommit on the branch. branch: branch name for subsequent commits pbranches: (converted parent revision, parent branch) tuples""" diff -r 2e58f1a36046 -r d43707e09b02 hgext/convert/convcmd.py --- a/hgext/convert/convcmd.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/convert/convcmd.py Tue Jun 24 09:34:38 2008 +0200 @@ -221,8 +221,6 @@ def copy(self, rev): commit = self.commitcache[rev] - do_copies = hasattr(self.dest, 'copyfile') - filenames = [] changes = self.source.getchanges(rev) if isinstance(changes, basestring): @@ -241,21 +239,6 @@ pbranches.append((self.map[prev], self.commitcache[prev].branch)) self.dest.setbranch(commit.branch, pbranches) - for f, v in files: - filenames.append(f) - try: - data = self.source.getfile(f, v) - except IOError, inst: - self.dest.delfile(f) - else: - e = self.source.getmode(f, v) - self.dest.putfile(f, e, data) - if do_copies: - if f in copies: - copyf = copies[f] - # Merely marks that a copy happened. - self.dest.copyfile(copyf, f) - try: parents = self.splicemap[rev].replace(',', ' ').split() self.ui.status('spliced in %s as parents of %s\n' % @@ -263,7 +246,7 @@ parents = [self.map.get(p, p) for p in parents] except KeyError: parents = [b[0] for b in pbranches] - newnode = self.dest.putcommit(filenames, parents, commit) + newnode = self.dest.putcommit(files, copies, parents, commit, self.source) self.source.converted(rev, newnode) self.map[rev] = newnode diff -r 2e58f1a36046 -r d43707e09b02 hgext/convert/cvs.py --- a/hgext/convert/cvs.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/convert/cvs.py Tue Jun 24 09:34:38 2008 +0200 @@ -3,8 +3,10 @@ import os, locale, re, socket from cStringIO import StringIO from mercurial import util +from mercurial.i18n import _ from common import NoRepo, commit, converter_source, checktool +import cvsps class convert_cvs(converter_source): def __init__(self, ui, path, rev=None): @@ -14,10 +16,13 @@ if not os.path.exists(cvs): raise NoRepo("%s does not look like a CVS checkout" % path) + checktool('cvs') self.cmd = ui.config('convert', 'cvsps', 'cvsps -A -u --cvs-direct -q') cvspsexe = self.cmd.split(None, 1)[0] - for tool in (cvspsexe, 'cvs'): - checktool(tool) + self.builtin = cvspsexe == 'builtin' + + if not self.builtin: + checktool(cvspsexe) self.changeset = {} self.files = {} @@ -28,10 +33,11 @@ self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1] self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1] self.encoding = locale.getpreferredencoding() - self._parse() + + self._parse(ui) self._connect() - def _parse(self): + def _parse(self, ui): if self.changeset: return @@ -56,80 +62,114 @@ id = None state = 0 filerevids = {} - for l in util.popen(cmd): - if state == 0: # header - if l.startswith("PatchSet"): - id = l[9:-2] - if maxrev and int(id) > maxrev: - # ignore everything - state = 3 - elif l.startswith("Date"): - date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"]) - date = util.datestr(date) - elif l.startswith("Branch"): - branch = l[8:-1] - self.parent[id] = self.lastbranch.get(branch, 'bad') - self.lastbranch[branch] = id - elif l.startswith("Ancestor branch"): - ancestor = l[17:-1] - # figure out the parent later - self.parent[id] = self.lastbranch[ancestor] - elif l.startswith("Author"): - author = self.recode(l[8:-1]) - elif l.startswith("Tag:") or l.startswith("Tags:"): - t = l[l.index(':')+1:] - t = [ut.strip() for ut in t.split(',')] - if (len(t) > 1) or (t[0] and (t[0] != "(none)")): - self.tags.update(dict.fromkeys(t, id)) - elif l.startswith("Log:"): - # switch to gathering log - state = 1 - log = "" - elif state == 1: # log - if l == "Members: \n": - # switch to gathering members - files = {} - oldrevs = [] - log = self.recode(log[:-1]) - state = 2 - else: - # gather log - log += l - elif state == 2: # members - if l == "\n": # start of next entry - state = 0 - p = [self.parent[id]] - if id == "1": - p = [] - if branch == "HEAD": - branch = "" - if branch: - latest = None - # the last changeset that contains a base - # file is our parent - for r in oldrevs: - latest = max(filerevids.get(r, None), latest) - if latest: - p = [latest] + + if self.builtin: + # builtin cvsps code + ui.status(_('using builtin cvsps\n')) + + db = cvsps.createlog(ui, cache='update') + db = cvsps.createchangeset(ui, db, + fuzz=int(ui.config('convert', 'cvsps.fuzz', 60)), + mergeto=ui.config('convert', 'cvsps.mergeto', None), + mergefrom=ui.config('convert', 'cvsps.mergefrom', None)) + + for cs in db: + if maxrev and cs.id>maxrev: + break + id = str(cs.id) + cs.author = self.recode(cs.author) + self.lastbranch[cs.branch] = id + cs.comment = self.recode(cs.comment) + date = util.datestr(cs.date) + self.tags.update(dict.fromkeys(cs.tags, id)) + + files = {} + for f in cs.entries: + files[f.file] = "%s%s" % ('.'.join([str(x) for x in f.revision]), + ['', '(DEAD)'][f.dead]) - # add current commit to set - c = commit(author=author, date=date, parents=p, - desc=log, branch=branch) - self.changeset[id] = c - self.files[id] = files - else: - colon = l.rfind(':') - file = l[1:colon] - rev = l[colon+1:-2] - oldrev, rev = rev.split("->") - files[file] = rev + # add current commit to set + c = commit(author=cs.author, date=date, + parents=[str(p.id) for p in cs.parents], + desc=cs.comment, branch=cs.branch or '') + self.changeset[id] = c + self.files[id] = files + else: + # external cvsps + for l in util.popen(cmd): + if state == 0: # header + if l.startswith("PatchSet"): + id = l[9:-2] + if maxrev and int(id) > maxrev: + # ignore everything + state = 3 + elif l.startswith("Date"): + date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"]) + date = util.datestr(date) + elif l.startswith("Branch"): + branch = l[8:-1] + self.parent[id] = self.lastbranch.get(branch, 'bad') + self.lastbranch[branch] = id + elif l.startswith("Ancestor branch"): + ancestor = l[17:-1] + # figure out the parent later + self.parent[id] = self.lastbranch[ancestor] + elif l.startswith("Author"): + author = self.recode(l[8:-1]) + elif l.startswith("Tag:") or l.startswith("Tags:"): + t = l[l.index(':')+1:] + t = [ut.strip() for ut in t.split(',')] + if (len(t) > 1) or (t[0] and (t[0] != "(none)")): + self.tags.update(dict.fromkeys(t, id)) + elif l.startswith("Log:"): + # switch to gathering log + state = 1 + log = "" + elif state == 1: # log + if l == "Members: \n": + # switch to gathering members + files = {} + oldrevs = [] + log = self.recode(log[:-1]) + state = 2 + else: + # gather log + log += l + elif state == 2: # members + if l == "\n": # start of next entry + state = 0 + p = [self.parent[id]] + if id == "1": + p = [] + if branch == "HEAD": + branch = "" + if branch: + latest = None + # the last changeset that contains a base + # file is our parent + for r in oldrevs: + latest = max(filerevids.get(r, None), latest) + if latest: + p = [latest] - # save some information for identifying branch points - oldrevs.append("%s:%s" % (oldrev, file)) - filerevids["%s:%s" % (rev, file)] = id - elif state == 3: - # swallow all input - continue + # add current commit to set + c = commit(author=author, date=date, parents=p, + desc=log, branch=branch) + self.changeset[id] = c + self.files[id] = files + else: + colon = l.rfind(':') + file = l[1:colon] + rev = l[colon+1:-2] + oldrev, rev = rev.split("->") + files[file] = rev + + # save some information for identifying branch points + oldrevs.append("%s:%s" % (oldrev, file)) + filerevids["%s:%s" % (rev, file)] = id + elif state == 3: + # swallow all input + continue self.heads = self.lastbranch.values() finally: diff -r 2e58f1a36046 -r d43707e09b02 hgext/convert/cvsps --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/convert/cvsps Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,154 @@ +#!/usr/bin/env python +# +# Commandline front-end for cvsps.py +# +# Copyright 2008, Frank Kingswood +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import sys +from mercurial import util +from mercurial.i18n import _ +from optparse import OptionParser, SUPPRESS_HELP +from hgext.convert.cvsps import createlog, createchangeset, logerror + +def main(): + '''Main program to mimic cvsps.''' + + op = OptionParser(usage='%prog [-bpruvxz] path', + description='Read CVS rlog for current directory or named ' + 'path in repository, and convert the log to changesets ' + 'based on matching commit log entries and dates.') + + # Options that are ignored for compatibility with cvsps-2.1 + op.add_option('-A', dest='Ignore', action='store_true', help=SUPPRESS_HELP) + op.add_option('--cvs-direct', dest='Ignore', action='store_true', help=SUPPRESS_HELP) + op.add_option('-q', dest='Ignore', action='store_true', help=SUPPRESS_HELP) + + # Main options shared with cvsps-2.1 + op.add_option('-b', dest='Branches', action='append', default=[], + help='Only return changes on specified branches') + op.add_option('-p', dest='Prefix', action='store', default='', + help='Prefix to remove from file names') + op.add_option('-r', dest='Revisions', action='append', default=[], + help='Only return changes after or between specified tags') + op.add_option('-u', dest='Cache', action='store_const', const='update', + help="Update cvs log cache") + op.add_option('-v', dest='Verbose', action='count', default=0, + help='Be verbose') + op.add_option('-x', dest='Cache', action='store_const', const='write', + help="Create new cvs log cache") + op.add_option('-z', dest='Fuzz', action='store', type='int', default=60, + help='Set commit time fuzz', metavar='seconds') + op.add_option('--root', dest='Root', action='store', default='', + help='Specify cvsroot', metavar='cvsroot') + + # Options specific to this version + op.add_option('--parents', dest='Parents', action='store_true', + help='Show parent changesets') + op.add_option('--ancestors', dest='Ancestors', action='store_true', + help='Show current changeset in ancestor branches') + + options, args = op.parse_args() + + # Create a ui object for printing progress messages + class UI: + def __init__(self, verbose): + if verbose: + self.status = self.message + if verbose>1: + self.note = self.message + if verbose>2: + self.debug = self.message + def message(self, msg): + sys.stderr.write(msg) + def nomessage(self, msg): + pass + status = nomessage + note = nomessage + debug = nomessage + ui = UI(options.Verbose) + + try: + if args: + log = [] + for d in args: + log += createlog(ui, d, root=options.Root, cache=options.Cache) + else: + log = createlog(ui, root=options.Root, cache=options.Cache) + except logerror, e: + print e + return + + changesets = createchangeset(ui, log, options.Fuzz) + del log + + # Print changesets (optionally filtered) + + off = len(options.Revisions) + branches = {} # latest version number in each branch + ancestors = {} # parent branch + for cs in changesets: + + if options.Ancestors: + if cs.branch not in branches and cs.parents and cs.parents[0].id: + ancestors[cs.branch] = changesets[cs.parents[0].id-1].branch, cs.parents[0].id + branches[cs.branch] = cs.id + + # limit by branches + if options.Branches and (cs.branch or 'HEAD') not in options.Branches: + continue + + if not off: + # Note: trailing spaces on several lines here are needed to have + # bug-for-bug compatibility with cvsps. + print '---------------------' + print 'PatchSet %d ' % cs.id + print 'Date: %s' % util.datestr(cs.date, '%Y/%m/%d %H:%M:%S %1%2') + print 'Author: %s' % cs.author + print 'Branch: %s' % (cs.branch or 'HEAD') + print 'Tag%s: %s ' % (['', 's'][len(cs.tags)>1], + ','.join(cs.tags) or '(none)') + if options.Parents and cs.parents: + if len(cs.parents)>1: + print 'Parents: %s' % (','.join([str(p.id) for p in cs.parents])) + else: + print 'Parent: %d' % cs.parents[0].id + + if options.Ancestors: + b = cs.branch + r = [] + while b: + b, c = ancestors[b] + r.append('%s:%d:%d' % (b or "HEAD", c, branches[b])) + if r: + print 'Ancestors: %s' % (','.join(r)) + + print 'Log:' + print cs.comment + print + print 'Members: ' + for f in cs.entries: + fn = f.file + if fn.startswith(options.Prefix): + fn = fn[len(options.Prefix):] + print '\t%s:%s->%s%s ' % (fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL', + '.'.join([str(x) for x in f.revision]), ['', '(DEAD)'][f.dead]) + print + + # have we seen the start tag? + if options.Revisions and off: + if options.Revisions[0] == str(cs.id) or \ + options.Revisions[0] in cs.tags: + off = False + + # see if we reached the end tag + if len(options.Revisions)>1 and not off: + if options.Revisions[1] == str(cs.id) or \ + options.Revisions[1] in cs.tags: + break + + +if __name__ == '__main__': + main() diff -r 2e58f1a36046 -r d43707e09b02 hgext/convert/cvsps.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/convert/cvsps.py Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,551 @@ +# +# Mercurial built-in replacement for cvsps. +# +# Copyright 2008, Frank Kingswood +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import os +import re +import sys +import cPickle as pickle +from mercurial import util +from mercurial.i18n import _ + +def listsort(list, key): + "helper to sort by key in Python 2.3" + try: + list.sort(key=key) + except TypeError: + list.sort(lambda l, r: cmp(key(l), key(r))) + +class logentry(object): + '''Class logentry has the following attributes: + .author - author name as CVS knows it + .branch - name of branch this revision is on + .branches - revision tuple of branches starting at this revision + .comment - commit message + .date - the commit date as a (time, tz) tuple + .dead - true if file revision is dead + .file - Name of file + .lines - a tuple (+lines, -lines) or None + .parent - Previous revision of this entry + .rcs - name of file as returned from CVS + .revision - revision number as tuple + .tags - list of tags on the file + ''' + def __init__(self, **entries): + self.__dict__.update(entries) + +class logerror(Exception): + pass + +def createlog(ui, directory=None, root="", rlog=True, cache=None): + '''Collect the CVS rlog''' + + # Because we store many duplicate commit log messages, reusing strings + # saves a lot of memory and pickle storage space. + _scache = {} + def scache(s): + "return a shared version of a string" + return _scache.setdefault(s, s) + + ui.status(_('collecting CVS rlog\n')) + + log = [] # list of logentry objects containing the CVS state + + # patterns to match in CVS (r)log output, by state of use + re_00 = re.compile('RCS file: (.+)$') + re_01 = re.compile('cvs \\[r?log aborted\\]: (.+)$') + re_02 = re.compile('cvs (r?log|server): (.+)\n$') + re_03 = re.compile("(Cannot access.+CVSROOT)|(can't create temporary directory.+)$") + re_10 = re.compile('Working file: (.+)$') + re_20 = re.compile('symbolic names:') + re_30 = re.compile('\t(.+): ([\\d.]+)$') + re_31 = re.compile('----------------------------$') + re_32 = re.compile('=============================================================================$') + re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$') + re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?') + re_70 = re.compile('branches: (.+);$') + + prefix = '' # leading path to strip of what we get from CVS + + if directory is None: + # Current working directory + + # Get the real directory in the repository + try: + prefix = file(os.path.join('CVS','Repository')).read().strip() + if prefix == ".": + prefix = "" + directory = prefix + except IOError: + raise logerror('Not a CVS sandbox') + + if prefix and not prefix.endswith('/'): + prefix += '/' + + # Use the Root file in the sandbox, if it exists + try: + root = file(os.path.join('CVS','Root')).read().strip() + except IOError: + pass + + if not root: + root = os.environ.get('CVSROOT', '') + + # read log cache if one exists + oldlog = [] + date = None + + if cache: + cachedir = os.path.expanduser('~/.hg.cvsps') + if not os.path.exists(cachedir): + os.mkdir(cachedir) + + # The cvsps cache pickle needs a uniquified name, based on the + # repository location. The address may have all sort of nasties + # in it, slashes, colons and such. So here we take just the + # alphanumerics, concatenated in a way that does not mix up the + # various components, so that + # :pserver:user@server:/path + # and + # /pserver/user/server/path + # are mapped to different cache file names. + cachefile = root.split(":") + [directory, "cache"] + cachefile = ['-'.join(re.findall(r'\w+', s)) for s in cachefile if s] + cachefile = os.path.join(cachedir, + '.'.join([s for s in cachefile if s])) + + if cache == 'update': + try: + ui.note(_('reading cvs log cache %s\n') % cachefile) + oldlog = pickle.load(file(cachefile)) + ui.note(_('cache has %d log entries\n') % len(oldlog)) + except Exception, e: + ui.note(_('error reading cache: %r\n') % e) + + if oldlog: + date = oldlog[-1].date # last commit date as a (time,tz) tuple + date = util.datestr(date, '%Y/%m/%d %H:%M:%S %1%2') + + # build the CVS commandline + cmd = ['cvs', '-q'] + if root: + cmd.append('-d%s' % root) + p = root.split(':')[-1] + if not p.endswith('/'): + p += '/' + prefix = p + prefix + cmd.append(['log', 'rlog'][rlog]) + if date: + # no space between option and date string + cmd.append('-d>%s' % date) + cmd.append(directory) + + # state machine begins here + tags = {} # dictionary of revisions on current file with their tags + state = 0 + store = False # set when a new record can be appended + + cmd = [util.shellquote(arg) for arg in cmd] + ui.note("running %s\n" % (' '.join(cmd))) + ui.debug("prefix=%r directory=%r root=%r\n" % (prefix, directory, root)) + + for line in util.popen(' '.join(cmd)): + if line.endswith('\n'): + line = line[:-1] + #ui.debug('state=%d line=%r\n' % (state, line)) + + if state == 0: + # initial state, consume input until we see 'RCS file' + match = re_00.match(line) + if match: + rcs = match.group(1) + tags = {} + if rlog: + filename = rcs[:-2] + if filename.startswith(prefix): + filename = filename[len(prefix):] + if filename.startswith('/'): + filename = filename[1:] + if filename.startswith('Attic/'): + filename = filename[6:] + else: + filename = filename.replace('/Attic/', '/') + state = 2 + continue + state = 1 + continue + match = re_01.match(line) + if match: + raise Exception(match.group(1)) + match = re_02.match(line) + if match: + raise Exception(match.group(2)) + if re_03.match(line): + raise Exception(line) + + elif state == 1: + # expect 'Working file' (only when using log instead of rlog) + match = re_10.match(line) + assert match, _('RCS file must be followed by working file') + filename = match.group(1) + state = 2 + + elif state == 2: + # expect 'symbolic names' + if re_20.match(line): + state = 3 + + elif state == 3: + # read the symbolic names and store as tags + match = re_30.match(line) + if match: + rev = [int(x) for x in match.group(2).split('.')] + + # Convert magic branch number to an odd-numbered one + revn = len(rev) + if revn > 3 and (revn % 2) == 0 and rev[-2] == 0: + rev = rev[:-2] + rev[-1:] + rev = tuple(rev) + + if rev not in tags: + tags[rev] = [] + tags[rev].append(match.group(1)) + + elif re_31.match(line): + state = 5 + elif re_32.match(line): + state = 0 + + elif state == 4: + # expecting '------' separator before first revision + if re_31.match(line): + state = 5 + else: + assert not re_32.match(line), _('Must have at least some revisions') + + elif state == 5: + # expecting revision number and possibly (ignored) lock indication + # we create the logentry here from values stored in states 0 to 4, + # as this state is re-entered for subsequent revisions of a file. + match = re_50.match(line) + assert match, _('expected revision number') + e = logentry(rcs=scache(rcs), file=scache(filename), + revision=tuple([int(x) for x in match.group(1).split('.')]), + branches=[], parent=None) + state = 6 + + elif state == 6: + # expecting date, author, state, lines changed + match = re_60.match(line) + assert match, _('revision must be followed by date line') + d = match.group(1) + if d[2] == '/': + # Y2K + d = '19' + d + + if len(d.split()) != 3: + # cvs log dates always in GMT + d = d + ' UTC' + e.date = util.parsedate(d, ['%y/%m/%d %H:%M:%S', '%Y/%m/%d %H:%M:%S', '%Y-%m-%d %H:%M:%S']) + e.author = scache(match.group(2)) + e.dead = match.group(3).lower() == 'dead' + + if match.group(5): + if match.group(6): + e.lines = (int(match.group(5)), int(match.group(6))) + else: + e.lines = (int(match.group(5)), 0) + elif match.group(6): + e.lines = (0, int(match.group(6))) + else: + e.lines = None + e.comment = [] + state = 7 + + elif state == 7: + # read the revision numbers of branches that start at this revision + # or store the commit log message otherwise + m = re_70.match(line) + if m: + e.branches = [tuple([int(y) for y in x.strip().split('.')]) + for x in m.group(1).split(';')] + state = 8 + elif re_31.match(line): + state = 5 + store = True + elif re_32.match(line): + state = 0 + store = True + else: + e.comment.append(line) + + elif state == 8: + # store commit log message + if re_31.match(line): + state = 5 + store = True + elif re_32.match(line): + state = 0 + store = True + else: + e.comment.append(line) + + if store: + # clean up the results and save in the log. + store = False + e.tags = [scache(x) for x in tags.get(e.revision, [])] + e.tags.sort() + e.comment = scache('\n'.join(e.comment)) + + revn = len(e.revision) + if revn > 3 and (revn % 2) == 0: + e.branch = tags.get(e.revision[:-1], [None])[0] + else: + e.branch = None + + log.append(e) + + if len(log) % 100 == 0: + ui.status(util.ellipsis('%d %s' % (len(log), e.file), 80)+'\n') + + listsort(log, key=lambda x:(x.rcs, x.revision)) + + # find parent revisions of individual files + versions = {} + for e in log: + branch = e.revision[:-1] + p = versions.get((e.rcs, branch), None) + if p is None: + p = e.revision[:-2] + e.parent = p + versions[(e.rcs, branch)] = e.revision + + # update the log cache + if cache: + if log: + # join up the old and new logs + listsort(log, key=lambda x:x.date) + + if oldlog and oldlog[-1].date >= log[0].date: + raise logerror('Log cache overlaps with new log entries,' + ' re-run without cache.') + + log = oldlog + log + + # write the new cachefile + ui.note(_('writing cvs log cache %s\n') % cachefile) + pickle.dump(log, file(cachefile, 'w')) + else: + log = oldlog + + ui.status(_('%d log entries\n') % len(log)) + + return log + + +class changeset(object): + '''Class changeset has the following attributes: + .author - author name as CVS knows it + .branch - name of branch this changeset is on, or None + .comment - commit message + .date - the commit date as a (time,tz) tuple + .entries - list of logentry objects in this changeset + .parents - list of one or two parent changesets + .tags - list of tags on this changeset + ''' + def __init__(self, **entries): + self.__dict__.update(entries) + +def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None): + '''Convert log into changesets.''' + + ui.status(_('creating changesets\n')) + + # Merge changesets + + listsort(log, key=lambda x:(x.comment, x.author, x.branch, x.date)) + + changesets = [] + files = {} + c = None + for i, e in enumerate(log): + + # Check if log entry belongs to the current changeset or not. + if not (c and + e.comment == c.comment and + e.author == c.author and + e.branch == c.branch and + ((c.date[0] + c.date[1]) <= + (e.date[0] + e.date[1]) <= + (c.date[0] + c.date[1]) + fuzz) and + e.file not in files): + c = changeset(comment=e.comment, author=e.author, + branch=e.branch, date=e.date, entries=[]) + changesets.append(c) + files = {} + if len(changesets) % 100 == 0: + t = '%d %s' % (len(changesets), repr(e.comment)[1:-1]) + ui.status(util.ellipsis(t, 80) + '\n') + + c.entries.append(e) + files[e.file] = True + c.date = e.date # changeset date is date of latest commit in it + + # Sort files in each changeset + + for c in changesets: + def pathcompare(l, r): + 'Mimic cvsps sorting order' + l = l.split('/') + r = r.split('/') + nl = len(l) + nr = len(r) + n = min(nl, nr) + for i in range(n): + if i + 1 == nl and nl < nr: + return -1 + elif i + 1 == nr and nl > nr: + return +1 + elif l[i] < r[i]: + return -1 + elif l[i] > r[i]: + return +1 + return 0 + def entitycompare(l, r): + return pathcompare(l.file, r.file) + + c.entries.sort(entitycompare) + + # Sort changesets by date + + def cscmp(l, r): + d = sum(l.date) - sum(r.date) + if d: + return d + + # detect vendor branches and initial commits on a branch + le = {} + for e in l.entries: + le[e.rcs] = e.revision + re = {} + for e in r.entries: + re[e.rcs] = e.revision + + d = 0 + for e in l.entries: + if re.get(e.rcs, None) == e.parent: + assert not d + d = 1 + break + + for e in r.entries: + if le.get(e.rcs, None) == e.parent: + assert not d + d = -1 + break + + return d + + changesets.sort(cscmp) + + # Collect tags + + globaltags = {} + for c in changesets: + tags = {} + for e in c.entries: + for tag in e.tags: + # remember which is the latest changeset to have this tag + globaltags[tag] = c + + for c in changesets: + tags = {} + for e in c.entries: + for tag in e.tags: + tags[tag] = True + # remember tags only if this is the latest changeset to have it + tagnames = [tag for tag in tags if globaltags[tag] is c] + tagnames.sort() + c.tags = tagnames + + # Find parent changesets, handle {{mergetobranch BRANCHNAME}} + # by inserting dummy changesets with two parents, and handle + # {{mergefrombranch BRANCHNAME}} by setting two parents. + + if mergeto is None: + mergeto = r'{{mergetobranch ([-\w]+)}}' + if mergeto: + mergeto = re.compile(mergeto) + + if mergefrom is None: + mergefrom = r'{{mergefrombranch ([-\w]+)}}' + if mergefrom: + mergefrom = re.compile(mergefrom) + + versions = {} # changeset index where we saw any particular file version + branches = {} # changeset index where we saw a branch + n = len(changesets) + i = 0 + while i -The default is 'colorful'. If this is changed the corresponding CSS -file should be re-generated by running - -# pygmentize -f html -S - +The default is 'colorful'. -- Adam Hupp - - """ from mercurial import demandimport -demandimport.ignore.extend(['pkgutil', - 'pkg_resources', - '__main__',]) +demandimport.ignore.extend(['pkgutil', 'pkg_resources', '__main__',]) -from mercurial.hgweb.hgweb_mod import hgweb +from mercurial.hgweb import webcommands, webutil, common from mercurial import util from mercurial.templatefilters import filters @@ -40,10 +30,11 @@ from pygments.lexers import guess_lexer, guess_lexer_for_filename, TextLexer from pygments.formatters import HtmlFormatter -SYNTAX_CSS = ('\n') -def pygmentize(self, tmpl, fctx, field): +def pygmentize(field, fctx, style, tmpl): + # append a to the syntax highlighting css old_header = ''.join(tmpl('header')) if SYNTAX_CSS not in old_header: @@ -54,7 +45,6 @@ if util.binary(text): return - style = self.config("web", "pygments_style", "colorful") # To get multi-line strings right, we can't format line-by-line try: lexer = guess_lexer_for_filename(fctx.path(), text[:1024], @@ -79,20 +69,30 @@ newl = oldl.replace('line|escape', 'line|colorize') tmpl.cache[field] = newl -def filerevision_highlight(self, tmpl, fctx): - pygmentize(self, tmpl, fctx, 'fileline') +web_filerevision = webcommands._filerevision +web_annotate = webcommands.annotate - return realrevision(self, tmpl, fctx) +def filerevision_highlight(web, tmpl, fctx): + style = web.config('web', 'pygments_style', 'colorful') + pygmentize('fileline', fctx, style, tmpl) + return web_filerevision(web, tmpl, fctx) -def fileannotate_highlight(self, tmpl, fctx): - pygmentize(self, tmpl, fctx, 'annotateline') +def annotate_highlight(web, req, tmpl): + fctx = webutil.filectx(web.repo, req) + style = web.config('web', 'pygments_style', 'colorful') + pygmentize('annotateline', fctx, style, tmpl) + return web_annotate(web, req, tmpl) - return realannotate(self, tmpl, fctx) +def generate_css(web, req, tmpl): + pg_style = web.config('web', 'pygments_style', 'colorful') + fmter = HtmlFormatter(style = pg_style) + req.respond(common.HTTP_OK, 'text/css') + return ['/* pygments_style = %s */\n\n' % pg_style, fmter.get_style_defs('')] + # monkeypatch in the new version -# should be safer than overriding the method in a derived class -# and then patching the class -realrevision = hgweb.filerevision -hgweb.filerevision = filerevision_highlight -realannotate = hgweb.fileannotate -hgweb.fileannotate = fileannotate_highlight + +webcommands._filerevision = filerevision_highlight +webcommands.annotate = annotate_highlight +webcommands.highlightcss = generate_css +webcommands.__all__.append('highlightcss') diff -r 2e58f1a36046 -r d43707e09b02 hgext/inotify/__init__.py --- a/hgext/inotify/__init__.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/inotify/__init__.py Tue Jun 24 09:34:38 2008 +0200 @@ -47,8 +47,9 @@ # to recurse. inotifyserver = False - def status(self, files, match, list_ignored, list_clean, + def status(self, match, list_ignored, list_clean, list_unknown=True): + files = match.files() try: if not list_ignored and not self.inotifyserver: result = client.query(ui, repo, files, match, False, @@ -88,7 +89,7 @@ ui.print_exc() return super(inotifydirstate, self).status( - files, match or util.always, list_ignored, list_clean, + match, list_ignored, list_clean, list_unknown) repo.dirstate.__class__ = inotifydirstate diff -r 2e58f1a36046 -r d43707e09b02 hgext/keyword.py --- a/hgext/keyword.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/keyword.py Tue Jun 24 09:34:38 2008 +0200 @@ -88,7 +88,7 @@ commands.optionalrepo += ' kwdemo' # hg commands that do not act on keywords -nokwcommands = ('add addremove bundle copy export grep incoming init' +nokwcommands = ('add addremove annotate bundle copy export grep incoming init' ' log outgoing push rename rollback tip' ' convert email glog') @@ -100,52 +100,8 @@ '''Returns hgdate in cvs-like UTC format.''' return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0])) - # make keyword tools accessible -kwtools = {'templater': None, 'hgcmd': None} - -# store originals of monkeypatches -_patchfile_init = patch.patchfile.__init__ -_patch_diff = patch.diff -_dispatch_parse = dispatch._parse - -def _kwpatchfile_init(self, ui, fname, missing=False): - '''Monkeypatch/wrap patch.patchfile.__init__ to avoid - rejects or conflicts due to expanded keywords in working dir.''' - _patchfile_init(self, ui, fname, missing=missing) - # shrink keywords read from working dir - kwt = kwtools['templater'] - self.lines = kwt.shrinklines(self.fname, self.lines) - -def _kw_diff(repo, node1=None, node2=None, files=None, match=util.always, - fp=None, changes=None, opts=None): - '''Monkeypatch patch.diff to avoid expansion except when - comparing against working dir.''' - if node2 is not None: - kwtools['templater'].matcher = util.never - elif node1 is not None and node1 != repo.changectx().node(): - kwtools['templater'].restrict = True - _patch_diff(repo, node1=node1, node2=node2, files=files, match=match, - fp=fp, changes=changes, opts=opts) - -def _kwweb_changeset(web, req, tmpl): - '''Wraps webcommands.changeset turning off keyword expansion.''' - kwtools['templater'].matcher = util.never - return web.changeset(tmpl, web.changectx(req)) - -def _kwweb_filediff(web, req, tmpl): - '''Wraps webcommands.filediff turning off keyword expansion.''' - kwtools['templater'].matcher = util.never - return web.filediff(tmpl, web.filectx(req)) - -def _kwdispatch_parse(ui, args): - '''Monkeypatch dispatch._parse to obtain running hg command.''' - cmd, func, args, options, cmdoptions = _dispatch_parse(ui, args) - kwtools['hgcmd'] = cmd - return cmd, func, args, options, cmdoptions - -# dispatch._parse is run before reposetup, so wrap it here -dispatch._parse = _kwdispatch_parse +kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']} class kwtemplater(object): @@ -163,15 +119,16 @@ 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}', } - def __init__(self, ui, repo, inc, exc): + def __init__(self, ui, repo): self.ui = ui self.repo = repo - self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1] + self.matcher = util.matcher(repo.root, + inc=kwtools['inc'], exc=kwtools['exc'])[1] self.restrict = kwtools['hgcmd'] in restricted.split() kwmaps = self.ui.configitems('keywordmaps') if kwmaps: # override default templates - kwmaps = [(k, templater.parsestring(v, quoted=False)) + kwmaps = [(k, templater.parsestring(v, False)) for (k, v) in kwmaps] self.templates = dict(kwmaps) escaped = map(re.escape, self.templates.keys()) @@ -212,7 +169,7 @@ Caveat: localrepository._link fails on Windows.''' return self.matcher(path) and not islink(path) - def overwrite(self, node=None, expand=True, files=None): + def overwrite(self, node, expand, files): '''Overwrites selected files expanding/shrinking keywords.''' ctx = self.repo.changectx(node) mf = ctx.manifest() @@ -271,9 +228,9 @@ Subclass of filelog to hook into its read, add, cmp methods. Keywords are "stored" unexpanded, and processed on reading. ''' - def __init__(self, opener, path): + def __init__(self, opener, kwt, path): super(kwfilelog, self).__init__(opener, path) - self.kwt = kwtools['templater'] + self.kwt = kwt self.path = path def read(self, node): @@ -284,7 +241,7 @@ def add(self, text, meta, tr, link, p1=None, p2=None): '''Removes keyword substitutions when adding to filelog.''' text = self.kwt.shrink(self.path, text) - return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2) + return super(kwfilelog, self).add(text, meta, tr, link, p1, p2) def cmp(self, node, text): '''Removes keyword substitutions for comparison.''' @@ -298,24 +255,26 @@ '''Bails out if [keyword] configuration is not active. Returns status of working directory.''' if kwt: - files, match, anypats = cmdutil.matchpats(repo, pats, opts) - return repo.status(files=files, match=match, list_clean=True) + matcher = cmdutil.match(repo, pats, opts) + return repo.status(match=matcher, list_clean=True) if ui.configitems('keyword'): raise util.Abort(_('[keyword] patterns cannot match')) raise util.Abort(_('no [keyword] patterns configured')) def _kwfwrite(ui, repo, expand, *pats, **opts): '''Selects files and passes them to kwtemplater.overwrite.''' + if repo.dirstate.parents()[1] != nullid: + raise util.Abort(_('outstanding uncommitted merge')) kwt = kwtools['templater'] status = _status(ui, repo, kwt, *pats, **opts) modified, added, removed, deleted, unknown, ignored, clean = status if modified or added or removed or deleted: - raise util.Abort(_('outstanding uncommitted changes in given files')) + raise util.Abort(_('outstanding uncommitted changes')) wlock = lock = None try: wlock = repo.wlock() lock = repo.lock() - kwt.overwrite(expand=expand, files=clean) + kwt.overwrite(None, expand, clean) finally: del wlock, lock @@ -345,7 +304,7 @@ branchname = 'demobranch' tmpdir = tempfile.mkdtemp('', 'kwdemo.') ui.note(_('creating temporary repo at %s\n') % tmpdir) - repo = localrepo.localrepository(ui, path=tmpdir, create=True) + repo = localrepo.localrepository(ui, tmpdir, True) ui.setconfig('keyword', fn, '') if args or opts.get('rcfile'): kwstatus = 'custom' @@ -367,6 +326,7 @@ ui.readconfig(repo.join('hgrc')) if not opts.get('default'): kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates + uisetup(ui) reposetup(ui, repo) for k, v in ui.configitems('extensions'): if k.endswith('keyword'): @@ -448,46 +408,57 @@ _kwfwrite(ui, repo, False, *pats, **opts) +def uisetup(ui): + '''Collects [keyword] config in kwtools. + Monkeypatches dispatch._parse if needed.''' + + for pat, opt in ui.configitems('keyword'): + if opt != 'ignore': + kwtools['inc'].append(pat) + else: + kwtools['exc'].append(pat) + + if kwtools['inc']: + def kwdispatch_parse(ui, args): + '''Monkeypatch dispatch._parse to obtain running hg command.''' + cmd, func, args, options, cmdoptions = dispatch_parse(ui, args) + kwtools['hgcmd'] = cmd + return cmd, func, args, options, cmdoptions + + dispatch_parse = dispatch._parse + dispatch._parse = kwdispatch_parse + def reposetup(ui, repo): '''Sets up repo as kwrepo for keyword substitution. Overrides file method to return kwfilelog instead of filelog if file matches user configuration. Wraps commit to overwrite configured files with updated keyword substitutions. - This is done for local repos only, and only if there are - files configured at all for keyword substitution.''' + Monkeypatches patch and webcommands.''' try: - if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split() + if (not repo.local() or not kwtools['inc'] + or kwtools['hgcmd'] in nokwcommands.split() or '.hg' in util.splitpath(repo.root) or repo._url.startswith('bundle:')): return except AttributeError: pass - inc, exc = [], ['.hg*'] - for pat, opt in ui.configitems('keyword'): - if opt != 'ignore': - inc.append(pat) - else: - exc.append(pat) - if not inc: - return - - kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc) + kwtools['templater'] = kwt = kwtemplater(ui, repo) class kwrepo(repo.__class__): def file(self, f): if f[0] == '/': f = f[1:] - return kwfilelog(self.sopener, f) + return kwfilelog(self.sopener, kwt, f) def wread(self, filename): data = super(kwrepo, self).wread(filename) return kwt.wread(filename, data) def commit(self, files=None, text='', user=None, date=None, - match=util.always, force=False, force_editor=False, + match=None, force=False, force_editor=False, p1=None, p2=None, extra={}, empty_ok=False): wlock = lock = None _p1 = _p2 = None @@ -512,28 +483,66 @@ else: _p2 = hex(_p2) - node = super(kwrepo, - self).commit(files=files, text=text, user=user, - date=date, match=match, force=force, - force_editor=force_editor, - p1=p1, p2=p2, extra=extra, - empty_ok=empty_ok) + n = super(kwrepo, self).commit(files, text, user, date, match, + force, force_editor, p1, p2, + extra, empty_ok) # restore commit hooks for name, cmd in commithooks.iteritems(): ui.setconfig('hooks', name, cmd) - if node is not None: - kwt.overwrite(node=node) - repo.hook('commit', node=node, parent1=_p1, parent2=_p2) - return node + if n is not None: + kwt.overwrite(n, True, None) + repo.hook('commit', node=n, parent1=_p1, parent2=_p2) + return n finally: del wlock, lock + # monkeypatches + def kwpatchfile_init(self, ui, fname, missing=False): + '''Monkeypatch/wrap patch.patchfile.__init__ to avoid + rejects or conflicts due to expanded keywords in working dir.''' + patchfile_init(self, ui, fname, missing) + # shrink keywords read from working dir + self.lines = kwt.shrinklines(self.fname, self.lines) + + def kw_diff(repo, node1=None, node2=None, match=None, + fp=None, changes=None, opts=None): + '''Monkeypatch patch.diff to avoid expansion except when + comparing against working dir.''' + if node2 is not None: + kwt.matcher = util.never + elif node1 is not None and node1 != repo.changectx().node(): + kwt.restrict = True + patch_diff(repo, node1, node2, match, fp, changes, opts) + + def kwweb_annotate(web, req, tmpl): + '''Wraps webcommands.annotate turning off keyword expansion.''' + kwt.matcher = util.never + return webcommands_annotate(web, req, tmpl) + + def kwweb_changeset(web, req, tmpl): + '''Wraps webcommands.changeset turning off keyword expansion.''' + kwt.matcher = util.never + return webcommands_changeset(web, req, tmpl) + + def kwweb_filediff(web, req, tmpl): + '''Wraps webcommands.filediff turning off keyword expansion.''' + kwt.matcher = util.never + return webcommands_filediff(web, req, tmpl) + repo.__class__ = kwrepo - patch.patchfile.__init__ = _kwpatchfile_init - patch.diff = _kw_diff - webcommands.changeset = webcommands.rev = _kwweb_changeset - webcommands.filediff = webcommands.diff = _kwweb_filediff + + patchfile_init = patch.patchfile.__init__ + patch_diff = patch.diff + webcommands_annotate = webcommands.annotate + webcommands_changeset = webcommands.changeset + webcommands_filediff = webcommands.filediff + + patch.patchfile.__init__ = kwpatchfile_init + patch.diff = kw_diff + webcommands.annotate = kwweb_annotate + webcommands.changeset = webcommands.rev = kwweb_changeset + webcommands.filediff = webcommands.diff = kwweb_filediff cmdtable = { diff -r 2e58f1a36046 -r d43707e09b02 hgext/mq.py --- a/hgext/mq.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/mq.py Tue Jun 24 09:34:38 2008 +0200 @@ -322,10 +322,8 @@ def printdiff(self, repo, node1, node2=None, files=None, fp=None, changes=None, opts={}): - fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts) - - patch.diff(repo, node1, node2, fns, match=matchfn, - fp=fp, changes=changes, opts=self.diffopts()) + m = cmdutil.match(repo, files, opts) + patch.diff(repo, node1, node2, m, fp, changes, self.diffopts()) def mergeone(self, repo, mergeq, head, patch, rev): # first try just applying the patch @@ -510,8 +508,10 @@ repo.dirstate.merge(f) p1, p2 = repo.dirstate.parents() repo.dirstate.setparents(p1, merge) + files = patch.updatedir(self.ui, repo, files) - n = repo.commit(files, message, user, date, match=util.never, + match = cmdutil.matchfiles(repo, files or []) + n = repo.commit(files, message, user, date, match=match, force=True) if n == None: @@ -535,6 +535,41 @@ break return (err, n) + def _clean_series(self, patches): + indices = [self.find_series(p) for p in patches] + indices.sort() + for i in indices[-1::-1]: + del self.full_series[i] + self.parse_series() + self.series_dirty = 1 + + def finish(self, repo, revs): + revs.sort() + firstrev = repo.changelog.rev(revlog.bin(self.applied[0].rev)) + appliedbase = 0 + patches = [] + for rev in revs: + if rev < firstrev: + raise util.Abort(_('revision %d is not managed') % rev) + base = revlog.bin(self.applied[appliedbase].rev) + node = repo.changelog.node(rev) + if node != base: + raise util.Abort(_('cannot delete revision %d above ' + 'applied patches') % rev) + patches.append(self.applied[appliedbase].name) + appliedbase += 1 + + r = self.qrepo() + if r: + r.remove(patches, True) + else: + for p in patches: + os.unlink(self.join(p)) + + del self.applied[:appliedbase] + self.applied_dirty = 1 + self._clean_series(patches) + def delete(self, repo, patches, opts): if not patches and not opts.get('rev'): raise util.Abort(_('qdelete requires at least one revision or ' @@ -580,12 +615,7 @@ if appliedbase: del self.applied[:appliedbase] self.applied_dirty = 1 - indices = [self.find_series(p) for p in realpatches] - indices.sort() - for i in indices[-1::-1]: - del self.full_series[i] - self.parse_series() - self.series_dirty = 1 + self._clean_series(realpatches) def check_toppatch(self, repo): if len(self.applied) > 0: @@ -623,11 +653,11 @@ if os.path.exists(self.join(patch)): raise util.Abort(_('patch "%s" already exists') % patch) if opts.get('include') or opts.get('exclude') or pats: - fns, match, anypats = cmdutil.matchpats(repo, pats, opts) - m, a, r, d = repo.status(files=fns, match=match)[:4] + match = cmdutil.match(repo, pats, opts) + m, a, r, d = repo.status(match=match)[:4] else: m, a, r, d = self.check_localchanges(repo, force) - fns, match, anypats = cmdutil.matchpats(repo, m + a + r) + match = cmdutil.match(repo, m + a + r) commitfiles = m + a + r self.check_toppatch(repo) wlock = repo.wlock() @@ -665,14 +695,14 @@ finally: del wlock - def strip(self, repo, rev, update=True, backup="all"): + def strip(self, repo, rev, update=True, backup="all", force=None): wlock = lock = None try: wlock = repo.wlock() lock = repo.lock() if update: - self.check_localchanges(repo, refresh=False) + self.check_localchanges(repo, force=force, refresh=False) urev = self.qparents(repo, rev) hg.clean(repo, urev) repo.dirstate.write() @@ -937,10 +967,7 @@ self.ui.write("No patches applied\n") return qp = self.qparents(repo, top) - if opts.get('git'): - self.diffopts().git = True - if opts.get('unified') is not None: - self.diffopts().context = opts['unified'] + self._diffopts = patch.diffopts(self.ui, opts) self.printdiff(repo, qp, files=pats, opts=opts) def refresh(self, repo, pats=None, **opts): @@ -1026,7 +1053,7 @@ if opts.get('git'): self.diffopts().git = True - fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts) + matchfn = cmdutil.match(repo, pats, opts) tip = repo.changelog.tip() if top == tip: # if the top of our patch queue is also the tip, there is an @@ -1048,12 +1075,10 @@ man = repo.manifest.read(changes[0]) aaa = aa[:] if opts.get('short'): - filelist = mm + aa + dd - match = dict.fromkeys(filelist).__contains__ + match = cmdutil.matchfiles(repo, mm + aa + dd) else: - filelist = None - match = util.always - m, a, r, d, u = repo.status(files=filelist, match=match)[:5] + match = cmdutil.matchall(repo) + m, a, r, d, u = repo.status(match=match)[:5] # we might end up with files that were added between # tip and the dirstate parent, but then changed in the @@ -1087,8 +1112,8 @@ r = util.unique(dd) a = util.unique(aa) c = [filter(matchfn, l) for l in (m, a, r, [], u)] - filelist = util.unique(c[0] + c[1] + c[2]) - patch.diff(repo, patchparent, files=filelist, match=matchfn, + match = cmdutil.matchfiles(repo, util.unique(c[0] + c[1] + c[2])) + patch.diff(repo, patchparent, match=match, fp=patchf, changes=c, opts=self.diffopts()) patchf.close() @@ -1146,7 +1171,7 @@ self.applied_dirty = 1 self.strip(repo, top, update=False, backup='strip') - n = repo.commit(filelist, message, user, date, match=matchfn, + n = repo.commit(match.files(), message, user, date, match=match, force=1) self.applied.append(statusentry(revlog.hex(n), patchfn)) self.removeundo(repo) @@ -1499,9 +1524,8 @@ the --rev parameter. At least one patch or revision is required. With --rev, mq will stop managing the named revisions (converting - them to regular mercurial changesets). The patches must be applied - and at the base of the stack. This option is useful when the patches - have been applied upstream. + them to regular mercurial changesets). The qfinish command should be + used as an alternative for qdel -r, as the latter option is deprecated. With --keep, the patch files are preserved in the patch directory.""" q = repo.mq @@ -2086,7 +2110,7 @@ elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)): update = False - repo.mq.strip(repo, rev, backup=backup, update=update) + repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force']) return 0 def select(ui, repo, *args, **opts): @@ -2191,6 +2215,34 @@ finally: q.save_dirty() +def finish(ui, repo, *revrange, **opts): + """move applied patches into repository history + + Finishes the specified revisions (corresponding to applied patches) by + moving them out of mq control into regular repository history. + + Accepts a revision range or the --all option. If --all is specified, all + applied mq revisions are removed from mq control. Otherwise, the given + revisions must be at the base of the stack of applied patches. + + This can be especially useful if your changes have been applied to an + upstream repository, or if you are about to push your changes to upstream. + """ + if not opts['applied'] and not revrange: + raise util.Abort(_('no revisions specified')) + elif opts['applied']: + revrange = ('qbase:qtip',) + revrange + + q = repo.mq + if not q.applied: + ui.status(_('no patches applied\n')) + return 0 + + revs = cmdutil.revrange(repo, revrange) + q.finish(repo, revs) + q.save_dirty() + return 0 + def reposetup(ui, repo): class mqrepo(repo.__class__): def abort_if_wdir_patched(self, errmsg, force=False): @@ -2300,10 +2352,8 @@ _('hg qcommit [OPTION]... [FILE]...')), "^qdiff": (diff, - [('g', 'git', None, _('use git extended diff format')), - ('U', 'unified', 3, _('number of lines of context to show')), - ] + commands.walkopts, - _('hg qdiff [-I] [-X] [-U NUM] [-g] [FILE]...')), + commands.diffopts + commands.diffopts2 + commands.walkopts, + _('hg qdiff [OPTION]... [FILE]...')), "qdelete|qremove|qrm": (delete, [('k', 'keep', None, _('keep patch file')), @@ -2395,9 +2445,14 @@ _('hg qseries [-ms]')), "^strip": (strip, - [('b', 'backup', None, _('bundle unrelated changesets')), + [('f', 'force', None, _('force removal with local changes')), + ('b', 'backup', None, _('bundle unrelated changesets')), ('n', 'nobackup', None, _('no backups'))], _('hg strip [-f] [-b] [-n] REV')), "qtop": (top, [] + seriesopts, _('hg qtop [-s]')), "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')), + "qfinish": + (finish, + [('a', 'applied', None, _('finish all applied changesets'))], + _('hg qfinish [-a] [REV...]')), } diff -r 2e58f1a36046 -r d43707e09b02 hgext/pager.py --- a/hgext/pager.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/pager.py Tue Jun 24 09:34:38 2008 +0200 @@ -10,26 +10,56 @@ # [extension] # hgext.pager = # -# To set the pager that should be used, set the application variable: -# -# [pager] -# pager = LESS='FSRX' less -# -# If no pager is set, the pager extensions uses the environment -# variable $PAGER. If neither pager.pager, nor $PAGER is set, no pager -# is used. -# -# If you notice "BROKEN PIPE" error messages, you can disable them -# by setting: -# -# [pager] -# quiet = True +# Run "hg help pager" to get info on configuration. + +'''browse command output with external pager + +To set the pager that should be used, set the application variable: + + [pager] + pager = LESS='FSRX' less + +If no pager is set, the pager extensions uses the environment +variable $PAGER. If neither pager.pager, nor $PAGER is set, no pager +is used. + +If you notice "BROKEN PIPE" error messages, you can disable them +by setting: + + [pager] + quiet = True + +You can disable the pager for certain commands by adding them to the +pager.ignore list: + + [pager] + ignore = version, help, update + +You can also enable the pager only for certain commands using pager.attend: + + [pager] + attend = log + +If pager.attend is present, pager.ignore will be ignored. + +To ignore global commands like "hg version" or "hg help", you have to specify +them in the global .hgrc +''' import sys, os, signal +from mercurial import dispatch, util def uisetup(ui): - p = ui.config("pager", "pager", os.environ.get("PAGER")) - if p and sys.stdout.isatty() and '--debugger' not in sys.argv: - if ui.configbool('pager', 'quiet'): - signal.signal(signal.SIGPIPE, signal.SIG_DFL) - sys.stderr = sys.stdout = os.popen(p, "wb") + def pagecmd(ui, options, cmd, cmdfunc): + p = ui.config("pager", "pager", os.environ.get("PAGER")) + if p and sys.stdout.isatty() and '--debugger' not in sys.argv: + attend = ui.configlist('pager', 'attend') + if (cmd in attend or + (cmd not in ui.configlist('pager', 'ignore') and not attend)): + sys.stderr = sys.stdout = util.popen(p, "wb") + if ui.configbool('pager', 'quiet'): + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + return oldrun(ui, options, cmd, cmdfunc) + + oldrun = dispatch._runcommand + dispatch._runcommand = pagecmd diff -r 2e58f1a36046 -r d43707e09b02 hgext/patchbomb.py --- a/hgext/patchbomb.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/patchbomb.py Tue Jun 24 09:34:38 2008 +0200 @@ -1,72 +1,69 @@ -# 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. -# -# To enable this extension: -# -# [extensions] -# hgext.patchbomb = -# -# To configure other defaults, add a section like this to your hgrc -# file: -# -# [email] -# from = My Name -# to = recipient1, recipient2, ... -# cc = cc1, cc2, ... -# bcc = bcc1, bcc2, ... -# -# Then you can use the "hg email" command to mail a series of changesets -# as a patchbomb. -# -# To avoid sending patches prematurely, it is a good idea to first run -# the "email" command with the "-n" option (test only). You will be -# prompted for an email recipient address, a subject an an introductory -# message describing the patches of your patchbomb. Then when all is -# done, patchbomb messages are displayed. If PAGER environment variable -# is set, your pager will be fired up once for each patchbomb message, so -# you can verify everything is alright. -# -# The "-m" (mbox) option is also very useful. Instead of previewing -# each patchbomb message in a pager or sending the messages directly, -# it will create a UNIX mailbox file with the patch emails. This -# mailbox file can be previewed with any mail user agent which supports -# UNIX mbox files, i.e. with mutt: -# -# % mutt -R -f mbox -# -# When you are previewing the patchbomb messages, you can use `formail' -# (a utility that is commonly installed as part of the procmail package), -# to send each message out: -# -# % formail -s sendmail -bm -t < mbox -# -# That should be all. Now your patchbomb is on its way out. +'''sending 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. + +To enable this extension: + + [extensions] + hgext.patchbomb = + +To configure other defaults, add a section like this to your hgrc file: -import os, errno, socket, tempfile + [email] + from = My Name + to = recipient1, recipient2, ... + cc = cc1, cc2, ... + bcc = bcc1, bcc2, ... + +Then you can use the "hg email" command to mail a series of changesets +as a patchbomb. + +To avoid sending patches prematurely, it is a good idea to first run +the "email" command with the "-n" option (test only). You will be +prompted for an email recipient address, a subject an an introductory +message describing the patches of your patchbomb. Then when all is +done, patchbomb messages are displayed. If PAGER environment variable +is set, your pager will be fired up once for each patchbomb message, so +you can verify everything is alright. + +The "-m" (mbox) option is also very useful. Instead of previewing +each patchbomb message in a pager or sending the messages directly, +it will create a UNIX mailbox file with the patch emails. This +mailbox file can be previewed with any mail user agent which supports +UNIX mbox files, i.e. with mutt: + + % mutt -R -f mbox + +When you are previewing the patchbomb messages, you can use `formail' +(a utility that is commonly installed as part of the procmail package), +to send each message out: + + % formail -s sendmail -bm -t < mbox + +That should be all. Now your patchbomb is on its way out.''' + +import os, errno, socket, tempfile, cStringIO import email.MIMEMultipart, email.MIMEText, email.MIMEBase -import email.Utils, email.Encoders +import email.Utils, email.Encoders, email.Generator from mercurial import cmdutil, commands, hg, mail, patch, util from mercurial.i18n import _ from mercurial.node import bin @@ -404,11 +401,12 @@ ui.status('Displaying ', m['Subject'], ' ...\n') ui.flush() if 'PAGER' in os.environ: - fp = os.popen(os.environ['PAGER'], 'w') + fp = util.popen(os.environ['PAGER'], 'w') else: fp = ui + generator = email.Generator.Generator(fp, mangle_from_=False) try: - fp.write(m.as_string(0)) + generator.flatten(m, 0) fp.write('\n') except IOError, inst: if inst.errno != errno.EPIPE: @@ -418,9 +416,10 @@ elif opts.get('mbox'): ui.status('Writing ', m['Subject'], ' ...\n') fp = open(opts.get('mbox'), 'In-Reply-To' in m and 'ab+' or 'wb+') + generator = email.Generator.Generator(fp, mangle_from_=True) date = util.datestr(start_time, '%a %b %d %H:%M:%S %Y') fp.write('From %s %s\n' % (sender_addr, date)) - fp.write(m.as_string(0)) + generator.flatten(m, 0) fp.write('\n\n') fp.close() else: @@ -429,7 +428,10 @@ ui.status('Sending ', m['Subject'], ' ...\n') # Exim does not remove the Bcc field del m['Bcc'] - sendmail(sender, to + bcc + cc, m.as_string(0)) + fp = cStringIO.StringIO() + generator = email.Generator.Generator(fp, mangle_from_=False) + generator.flatten(m, 0) + sendmail(sender, to + bcc + cc, fp.getvalue()) cmdtable = { "email": diff -r 2e58f1a36046 -r d43707e09b02 hgext/purge.py --- a/hgext/purge.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/purge.py Tue Jun 24 09:34:38 2008 +0200 @@ -27,13 +27,43 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -from mercurial import util, commands +from mercurial import util, commands, cmdutil from mercurial.i18n import _ import os -def dopurge(ui, repo, dirs=None, act=True, ignored=False, - abort_on_err=False, eol='\n', - force=False, include=None, exclude=None): +def purge(ui, repo, *dirs, **opts): + '''removes files not tracked by mercurial + + Delete files not known to mercurial, this is useful to test local and + uncommitted changes in the otherwise clean source tree. + + This means that purge will delete: + - Unknown files: files marked with "?" by "hg status" + - Ignored files: files usually ignored by Mercurial because they match + a pattern in a ".hgignore" file + - Empty directories: in fact Mercurial ignores directories unless they + contain files under source control managment + But it will leave untouched: + - Unmodified tracked files + - Modified tracked files + - New files added to the repository (with "hg add") + + If directories are given on the command line, only files in these + directories are considered. + + Be careful with purge, you could irreversibly delete some files you + forgot to add to the repository. If you only want to print the list of + files that this program would delete use the --print option. + ''' + act = not opts['print'] + ignored = bool(opts['all']) + abort_on_err = bool(opts['abort_on_err']) + eol = opts['print0'] and '\0' or '\n' + if eol == '\0': + # --print0 implies --print + act = False + force = bool(opts['force']) + def error(msg): if abort_on_err: raise util.Abort(msg) @@ -54,16 +84,10 @@ directories = [] files = [] - missing = [] - roots, match, anypats = util.cmdmatcher(repo.root, repo.getcwd(), dirs, - include, exclude) - for src, f, st in repo.dirstate.statwalk(files=roots, match=match, - ignored=ignored, directories=True): - if src == 'd': - directories.append(f) - elif src == 'm': - missing.append(f) - elif src == 'f' and f not in repo.dirstate: + match = cmdutil.match(repo, dirs, opts) + match.dir = directories.append + for src, f, st in repo.dirstate.statwalk(match, ignored=ignored): + if src == 'f' and f not in repo.dirstate: files.append(f) directories.sort() @@ -99,45 +123,6 @@ "fully supported.\n")) raise util.Abort(_("outstanding uncommitted changes")) - -def purge(ui, repo, *dirs, **opts): - '''removes files not tracked by mercurial - - Delete files not known to mercurial, this is useful to test local and - uncommitted changes in the otherwise clean source tree. - - This means that purge will delete: - - Unknown files: files marked with "?" by "hg status" - - Ignored files: files usually ignored by Mercurial because they match - a pattern in a ".hgignore" file - - Empty directories: in fact Mercurial ignores directories unless they - contain files under source control managment - But it will leave untouched: - - Unmodified tracked files - - Modified tracked files - - New files added to the repository (with "hg add") - - If directories are given on the command line, only files in these - directories are considered. - - Be careful with purge, you could irreversibly delete some files you - forgot to add to the repository. If you only want to print the list of - files that this program would delete use the --print option. - ''' - act = not opts['print'] - ignored = bool(opts['all']) - abort_on_err = bool(opts['abort_on_err']) - eol = opts['print0'] and '\0' or '\n' - if eol == '\0': - # --print0 implies --print - act = False - force = bool(opts['force']) - include = opts['include'] - exclude = opts['exclude'] - dopurge(ui, repo, dirs, act, ignored, abort_on_err, - eol, force, include, exclude) - - cmdtable = { 'purge|clean': (purge, diff -r 2e58f1a36046 -r d43707e09b02 hgext/record.py --- a/hgext/record.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/record.py Tue Jun 24 09:34:38 2008 +0200 @@ -389,7 +389,7 @@ if not ui.interactive: raise util.Abort(_('running non-interactively, use commit instead')) - def recordfunc(ui, repo, files, message, match, opts): + def recordfunc(ui, repo, message, match, opts): """This is generic record driver. It's job is to interactively filter local changes, and accordingly @@ -402,16 +402,16 @@ In the end we'll record intresting changes, and everything else will be left in place, so the user can continue his work. """ - if files: + if match.files(): changes = None else: - changes = repo.status(files=files, match=match)[:5] + changes = repo.status(match=match)[:5] modified, added, removed = changes[:3] - files = modified + added + removed + match = cmdutil.matchfiles(repo, modified + added + removed) diffopts = mdiff.diffopts(git=True, nodates=True) fp = cStringIO.StringIO() - patch.diff(repo, repo.dirstate.parents()[0], files=files, - match=match, changes=changes, opts=diffopts, fp=fp) + patch.diff(repo, repo.dirstate.parents()[0], match=match, + changes=changes, opts=diffopts, fp=fp) fp.seek(0) # 1. filter patch, so we have intending-to apply subset of it @@ -423,14 +423,15 @@ try: contenders.update(dict.fromkeys(h.files())) except AttributeError: pass - newfiles = [f for f in files if f in contenders] + newfiles = [f for f in match.files() if f in contenders] if not newfiles: ui.status(_('no changes to record\n')) return 0 if changes is None: - changes = repo.status(files=newfiles, match=match)[:5] + match = cmdutil.matchfiles(repo, newfiles) + changes = repo.status(match=match)[:5] modified = dict.fromkeys(changes[0]) # 2. backup changed files, so we can restore them in the end diff -r 2e58f1a36046 -r d43707e09b02 hgext/win32text.py --- a/hgext/win32text.py Tue Jun 24 09:33:13 2008 +0200 +++ b/hgext/win32text.py Tue Jun 24 09:34:38 2008 +0200 @@ -1,4 +1,4 @@ -# win32text.py - LF <-> CRLF translation utilities for Windows users +# win32text.py - LF <-> CRLF/CR translation utilities for Windows/Mac users # # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. @@ -9,65 +9,94 @@ # hgext.win32text = # [encode] # ** = cleverencode: +# # or ** = macencode: # [decode] # ** = cleverdecode: +# # or ** = macdecode: # -# If not doing conversion, to make sure you do not commit CRLF by accident: +# If not doing conversion, to make sure you do not commit CRLF/CR by accident: # # [hooks] # pretxncommit.crlf = python:hgext.win32text.forbidcrlf +# # or pretxncommit.cr = python:hgext.win32text.forbidcr # -# To do the same check on a server to prevent CRLF from being pushed or pulled: +# To do the same check on a server to prevent CRLF/CR from being pushed or +# pulled: # # [hooks] # pretxnchangegroup.crlf = python:hgext.win32text.forbidcrlf +# # or pretxnchangegroup.cr = python:hgext.win32text.forbidcr from mercurial.i18n import gettext as _ from mercurial.node import bin, short +from mercurial import util import re # regexp for single LF without CR preceding. re_single_lf = re.compile('(^|[^\r])\n', re.MULTILINE) -def dumbdecode(s, cmd, ui=None, repo=None, filename=None, **kwargs): - # warn if already has CRLF in repository. +newlinestr = {'\r\n': 'CRLF', '\r': 'CR'} +filterstr = {'\r\n': 'clever', '\r': 'mac'} + +def checknewline(s, newline, ui=None, repo=None, filename=None): + # warn if already has 'newline' in repository. # it might cause unexpected eol conversion. # see issue 302: # http://www.selenic.com/mercurial/bts/issue302 - if '\r\n' in s and ui and filename and repo: - ui.warn(_('WARNING: %s already has CRLF line endings\n' + if newline in s and ui and filename and repo: + ui.warn(_('WARNING: %s already has %s line endings\n' 'and does not need EOL conversion by the win32text plugin.\n' 'Before your next commit, please reconsider your ' 'encode/decode settings in \nMercurial.ini or %s.\n') % - (filename, repo.join('hgrc'))) + (filename, newlinestr[newline], repo.join('hgrc'))) + +def dumbdecode(s, cmd, **kwargs): + checknewline(s, '\r\n', **kwargs) # replace single LF to CRLF return re_single_lf.sub('\\1\r\n', s) def dumbencode(s, cmd): return s.replace('\r\n', '\n') -def clevertest(s, cmd): - if '\0' in s: return False - return True +def macdumbdecode(s, cmd, **kwargs): + checknewline(s, '\r', **kwargs) + return s.replace('\n', '\r') + +def macdumbencode(s, cmd): + return s.replace('\r', '\n') def cleverdecode(s, cmd, **kwargs): - if clevertest(s, cmd): + if not util.binary(s): return dumbdecode(s, cmd, **kwargs) return s def cleverencode(s, cmd): - if clevertest(s, cmd): + if not util.binary(s): return dumbencode(s, cmd) return s +def macdecode(s, cmd, **kwargs): + if not util.binary(s): + return macdumbdecode(s, cmd, **kwargs) + return s + +def macencode(s, cmd): + if not util.binary(s): + return macdumbencode(s, cmd) + return s + _filters = { 'dumbdecode:': dumbdecode, 'dumbencode:': dumbencode, 'cleverdecode:': cleverdecode, 'cleverencode:': cleverencode, + 'macdumbdecode:': macdumbdecode, + 'macdumbencode:': macdumbencode, + 'macdecode:': macdecode, + 'macencode:': macencode, } -def forbidcrlf(ui, repo, hooktype, node, **kwargs): +def forbidnewline(ui, repo, hooktype, node, newline, **kwargs): halt = False for rev in xrange(repo.changelog.rev(bin(node)), repo.changelog.count()): c = repo.changectx(rev) @@ -75,29 +104,38 @@ if f not in c: continue data = c[f].data() - if '\0' not in data and '\r\n' in data: + if not util.binary(data) and newline in data: if not halt: ui.warn(_('Attempt to commit or push text file(s) ' - 'using CRLF line endings\n')) + 'using %s line endings\n') % + newlinestr[newline]) ui.warn(_('in %s: %s\n') % (short(c.node()), f)) halt = True if halt and hooktype == 'pretxnchangegroup': + crlf = newlinestr[newline].lower() + filter = filterstr[newline] ui.warn(_('\nTo prevent this mistake in your local repository,\n' 'add to Mercurial.ini or .hg/hgrc:\n' '\n' '[hooks]\n' - 'pretxncommit.crlf = python:hgext.win32text.forbidcrlf\n' + 'pretxncommit.%s = python:hgext.win32text.forbid%s\n' '\n' 'and also consider adding:\n' '\n' '[extensions]\n' 'hgext.win32text =\n' '[encode]\n' - '** = cleverencode:\n' + '** = %sencode:\n' '[decode]\n' - '** = cleverdecode:\n')) + '** = %sdecode:\n') % (crlf, crlf, filter, filter)) return halt +def forbidcrlf(ui, repo, hooktype, node, **kwargs): + return forbidnewline(ui, repo, hooktype, node, '\r\n', **kwargs) + +def forbidcr(ui, repo, hooktype, node, **kwargs): + return forbidnewline(ui, repo, hooktype, node, '\r', **kwargs) + def reposetup(ui, repo): if not repo.local(): return diff -r 2e58f1a36046 -r d43707e09b02 mercurial/archival.py --- a/mercurial/archival.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/archival.py Tue Jun 24 09:34:38 2008 +0200 @@ -52,7 +52,8 @@ def _write_gzip_header(self): self.fileobj.write('\037\213') # magic header self.fileobj.write('\010') # compression method - fname = self.filename[:-3] + # Python 2.6 deprecates self.filename + fname = getattr(self, 'name', None) or self.filename flags = 0 if fname: flags = gzip.FNAME diff -r 2e58f1a36046 -r d43707e09b02 mercurial/bundlerepo.py --- a/mercurial/bundlerepo.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/bundlerepo.py Tue Jun 24 09:34:38 2008 +0200 @@ -12,7 +12,7 @@ from node import hex, nullid, short from i18n import _ -import changegroup, util, os, struct, bz2, tempfile, shutil, mdiff +import changegroup, util, os, struct, bz2, zlib, tempfile, shutil, mdiff import repo, localrepo, changelog, manifest, filelog, revlog class bundlerevlog(revlog.revlog): @@ -127,7 +127,7 @@ def addrevision(self, text, transaction, link, p1=None, p2=None, d=None): raise NotImplementedError - def addgroup(self, revs, linkmapper, transaction, unique=0): + def addgroup(self, revs, linkmapper, transaction): raise NotImplementedError def strip(self, rev, minlink): raise NotImplementedError @@ -173,14 +173,17 @@ raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename) elif not header.startswith("HG10"): raise util.Abort(_("%s: unknown bundle version") % bundlename) - elif header == "HG10BZ": + elif (header == "HG10BZ") or (header == "HG10GZ"): fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg10un", dir=self.path) self.tempfile = temp fptemp = os.fdopen(fdtemp, 'wb') def generator(f): - zd = bz2.BZ2Decompressor() - zd.decompress("BZ") + if header == "HG10BZ": + zd = bz2.BZ2Decompressor() + zd.decompress("BZ") + elif header == "HG10GZ": + zd = zlib.decompressobj() for chunk in f: yield zd.decompress(chunk) gen = generator(util.filechunkiter(self.bundlefile, 4096)) diff -r 2e58f1a36046 -r d43707e09b02 mercurial/cmdutil.py --- a/mercurial/cmdutil.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/cmdutil.py Tue Jun 24 09:34:38 2008 +0200 @@ -9,6 +9,7 @@ from i18n import _ import os, sys, bisect, stat import mdiff, bdiff, util, templater, templatefilters, patch, errno +import match as _match revrangesep = ':' @@ -223,21 +224,22 @@ pathname), mode) -def matchpats(repo, pats=[], opts={}, globbed=False, default=None): - cwd = repo.getcwd() - return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'), - opts.get('exclude'), globbed=globbed, - default=default) +def match(repo, pats=[], opts={}, globbed=False, default='relpath'): + if not globbed and default == 'relpath': + pats = util.expand_glob(pats or []) + m = _match.match(repo.root, repo.getcwd(), pats, + opts.get('include'), opts.get('exclude'), default) + def badfn(f, msg): + repo.ui.warn("%s: %s\n" % (m.rel(f), msg)) + return False + m.bad = badfn + return m -def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False, - default=None): - files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed, - default=default) - exact = dict.fromkeys(files) - cwd = repo.getcwd() - for src, fn in repo.walk(node=node, files=files, match=matchfn, - badmatch=badmatch): - yield src, fn, repo.pathto(fn, cwd), fn in exact +def matchall(repo): + return _match.always(repo.root, repo.getcwd()) + +def matchfiles(repo, files): + return _match.exact(repo.root, repo.getcwd(), files) def findrenames(repo, added=None, removed=None, threshold=0.5): '''find renamed files -- yields (before, after, score) tuples''' @@ -275,16 +277,19 @@ add, remove = [], [] mapping = {} audit_path = util.path_auditor(repo.root) - for src, abs, rel, exact in walk(repo, pats, opts): + m = match(repo, pats, opts) + for abs in repo.walk(m): target = repo.wjoin(abs) good = True try: audit_path(abs) except: good = False - if src == 'f' and good and abs not in repo.dirstate: + rel = m.rel(abs) + exact = m.exact(abs) + if good and abs not in repo.dirstate: add.append(abs) - mapping[abs] = rel, exact + mapping[abs] = rel, m.exact(abs) if repo.ui.verbose or not exact: repo.ui.status(_('adding %s\n') % ((pats and rel) or abs)) if repo.dirstate[abs] != 'r' and (not good or not util.lexists(target) @@ -319,8 +324,11 @@ def walkpat(pat): srcs = [] - for tag, abs, rel, exact in walk(repo, [pat], opts, globbed=True): + m = match(repo, [pat], opts, globbed=True) + for abs in repo.walk(m): state = repo.dirstate[abs] + rel = m.rel(abs) + exact = m.exact(abs) if state in '?r': if exact and state == '?': ui.warn(_('%s: not copying - file is not managed\n') % rel) @@ -889,7 +897,7 @@ # options patch = False if opts.get('patch'): - patch = matchfn or util.always + patch = matchfn or matchall(repo) tmpl = opts.get('template') mapfile = None @@ -977,11 +985,11 @@ if windowsize < sizelimit: windowsize *= 2 - files, matchfn, anypats = matchpats(repo, pats, opts) + m = match(repo, pats, opts) follow = opts.get('follow') or opts.get('follow_first') if repo.changelog.count() == 0: - return [], matchfn + return [], m if follow: defrange = '%s:0' % repo.changectx().rev() @@ -989,10 +997,10 @@ defrange = '-1:0' revs = revrange(repo, opts['rev'] or [defrange]) wanted = {} - slowpath = anypats or opts.get('removed') + slowpath = m.anypats() or opts.get('removed') fncache = {} - if not slowpath and not files: + if not slowpath and not m.files(): # No files, no patterns. Display all revs. wanted = dict.fromkeys(revs) copies = [] @@ -1017,7 +1025,7 @@ if rev[0] < cl_count: yield rev def iterfiles(): - for filename in files: + for filename in m.files(): yield filename, None for filename_node in copies: yield filename_node @@ -1056,7 +1064,7 @@ yield j, change(j)[3] for rev, changefiles in changerevgen(): - matches = filter(matchfn, changefiles) + matches = filter(m, changefiles) if matches: fncache[rev] = matches wanted[rev] = 1 @@ -1109,7 +1117,7 @@ del wanted[x] def iterate(): - if follow and not files: + if follow and not m.files(): ff = followfilter(onlyfirst=opts.get('follow_first')) def want(rev): if ff.match(rev) and rev in wanted: @@ -1129,13 +1137,13 @@ if not fns: def fns_generator(): for f in change(rev)[3]: - if matchfn(f): + if m(f): yield f fns = fns_generator() yield 'add', rev, fns for rev in nrevs: yield 'iter', rev, None - return iterate(), matchfn + return iterate(), m def commit(ui, repo, commitfunc, pats, opts): '''commit the specified files or all outstanding changes''' @@ -1149,13 +1157,13 @@ if opts.get('addremove'): addremove(repo, pats, opts) - fns, match, anypats = matchpats(repo, pats, opts) + m = match(repo, pats, opts) if pats: - status = repo.status(files=fns, match=match) + status = repo.status(match=m) modified, added, removed, deleted, unknown = status[:5] files = modified + added + removed slist = None - for f in fns: + for f in m.files(): if f == '.': continue if f not in files: @@ -1179,9 +1187,8 @@ "unsupported file type!") % rel) elif f not in repo.dirstate: raise util.Abort(_("file %s not tracked!") % rel) - else: - files = [] + m = matchfiles(repo, files) try: - return commitfunc(ui, repo, files, message, match, opts) + return commitfunc(ui, repo, message, m, opts) except ValueError, inst: raise util.Abort(str(inst)) diff -r 2e58f1a36046 -r d43707e09b02 mercurial/commands.py --- a/mercurial/commands.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/commands.py Tue Jun 24 09:34:38 2008 +0200 @@ -13,6 +13,7 @@ import difflib, patch, time, help, mdiff, tempfile import version, socket import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect +import merge as merge_ # Commands start here, listed alphabetically @@ -30,15 +31,16 @@ rejected = None exacts = {} names = [] - for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, - badmatch=util.always): - if exact: + m = cmdutil.match(repo, pats, opts) + m.bad = lambda x,y: True + for abs in repo.walk(m): + if m.exact(abs): if ui.verbose: - ui.status(_('adding %s\n') % rel) + ui.status(_('adding %s\n') % m.rel(abs)) names.append(abs) exacts[abs] = 1 elif abs not in repo.dirstate: - ui.status(_('adding %s\n') % rel) + ui.status(_('adding %s\n') % m.rel(abs)) names.append(abs) if not opts.get('dry_run'): rejected = repo.add(names) @@ -53,11 +55,11 @@ New files are ignored if they match any of the patterns in .hgignore. As with add, these changes take effect at the next commit. - Use the -s option to detect renamed files. With a parameter > 0, + Use the -s option to detect renamed files. With a parameter > 0, this compares every removed file with every added file and records - those similar enough as renames. This option takes a percentage + those similar enough as renames. This option takes a percentage between 0 (disabled) and 100 (files must be identical) as its - parameter. Detecting renamed files this way can be expensive. + parameter. Detecting renamed files this way can be expensive. """ try: sim = float(opts.get('similarity') or 0) @@ -107,11 +109,11 @@ ctx = repo.changectx(opts['rev']) - for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, - node=ctx.node()): + m = cmdutil.match(repo, pats, opts) + for abs in repo.walk(m, ctx.node()): fctx = ctx.filectx(abs) if not opts['text'] and util.binary(fctx.data()): - ui.write(_("%s: binary file\n") % ((pats and rel) or abs)) + ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs)) continue lines = fctx.annotate(follow=opts.get('follow'), @@ -134,7 +136,7 @@ By default, the revision used is the parent of the working directory; use "-r" to specify a different revision. - To specify the type of archive to create, use "-t". Valid + To specify the type of archive to create, use "-t". Valid types are: "files" (default): a directory full of files @@ -148,7 +150,7 @@ using a format string; see "hg help export" for details. Each member added to an archive file has a directory prefix - prepended. Use "-p" to specify a format string for the prefix. + prepended. Use "-p" to specify a format string for the prefix. The default is the basename of the archive, with suffixes removed. ''' @@ -159,7 +161,7 @@ dest = cmdutil.make_filename(repo, dest, node) if os.path.realpath(dest) == repo.root: raise util.Abort(_('repository root cannot be destination')) - dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts) + matchfn = cmdutil.match(repo, [], opts) kind = opts.get('type') or 'files' prefix = opts['prefix'] if dest == '-': @@ -174,17 +176,17 @@ def backout(ui, repo, node=None, rev=None, **opts): '''reverse effect of earlier changeset - Commit the backed out changes as a new changeset. The new + Commit the backed out changes as a new changeset. The new changeset is a child of the backed out changeset. If you back out a changeset other than the tip, a new head is - created. This head will be the new tip and you should merge this + created. This head will be the new tip and you should merge this backout changeset with another head (current one by default). The --merge option remembers the parent of the working directory before starting the backout, then merges the new head with that - changeset afterwards. This saves you from doing the merge by - hand. The result of this merge is not committed, as for a normal + changeset afterwards. This saves you from doing the merge by + hand. The result of this merge is not committed, as for a normal merge. See 'hg help dates' for a list of formats valid for -d/--date. @@ -369,7 +371,7 @@ """list repository named branches List the repository's named branches, indicating which ones are - inactive. If active is specified, only show active branches. + inactive. If active is specified, only show active branches. A branch is considered active if it contains repository heads. @@ -401,8 +403,9 @@ If no destination repository is specified the destination is assumed to have all the nodes specified by one or more --base - parameters. To create a bundle containing all changesets, use - --all (or --base null). + parameters. To create a bundle containing all changesets, use + --all (or --base null). To change the compression method applied, + use the -t option (by default, bundles are compressed using bz2). The bundle file can then be transferred using conventional means and applied to another repository with the unbundle or pull command. @@ -456,7 +459,14 @@ cg = repo.changegroupsubset(o, revs, 'bundle') else: cg = repo.changegroup(o, 'bundle') - changegroup.writebundle(cg, fname, "HG10BZ") + + bundletype = opts.get('type', 'bzip2').lower() + btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'} + bundletype = btypes.get(bundletype) + if bundletype not in changegroup.bundletypes: + raise util.Abort(_('unknown bundle type specified with --type')) + + changegroup.writebundle(cg, fname, bundletype) def cat(ui, repo, file1, *pats, **opts): """output the current or given revision of files @@ -466,7 +476,7 @@ or tip if no revision is checked out. Output may be to a file, in which case the name of the file is - given using a format string. The formatting rules are the same as + given using a format string. The formatting rules are the same as for the export command, with the following additions: %s basename of file being printed @@ -475,8 +485,8 @@ """ ctx = repo.changectx(opts['rev']) err = 1 - for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts, - ctx.node()): + m = cmdutil.match(repo, (file1,) + pats, opts) + for abs in repo.walk(m, ctx.node()): fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs) data = ctx.filectx(abs).data() if opts.get('decode'): @@ -498,20 +508,22 @@ For efficiency, hardlinks are used for cloning whenever the source and destination are on the same filesystem (note this applies only - to the repository data, not to the checked out files). Some + to the repository data, not to the checked out files). Some filesystems, such as AFS, implement hardlinking incorrectly, but - do not report errors. In these cases, use the --pull option to + do not report errors. In these cases, use the --pull option to avoid hardlinking. - You can safely clone repositories and checked out files using full - hardlinks with + In some cases, you can clone repositories and checked out files + using full hardlinks with $ cp -al REPO REPOCLONE - which is the fastest way to clone. However, the operation is not - atomic (making sure REPO is not modified during the operation is - up to you) and you have to make sure your editor breaks hardlinks - (Emacs and most Linux Kernel tools do so). + This is the fastest way to clone, but it is not always safe. The + operation is not atomic (making sure REPO is not modified during + the operation is up to you) and you have to make sure your editor + breaks hardlinks (Emacs and most Linux Kernel tools do so). Also, + this is not compatible with certain extensions that place their + metadata under the .hg directory, such as mq. If you use the -r option to clone up to a specific revision, no subsequent revisions will be present in the cloned repository. @@ -550,9 +562,9 @@ See 'hg help dates' for a list of formats valid for -d/--date. """ - def commitfunc(ui, repo, files, message, match, opts): - return repo.commit(files, message, opts['user'], opts['date'], match, - force_editor=opts.get('force_editor')) + def commitfunc(ui, repo, message, match, opts): + return repo.commit(match.files(), message, opts['user'], opts['date'], + match, force_editor=opts.get('force_editor')) node = cmdutil.commit(ui, repo, commitfunc, pats, opts) if not node: @@ -571,12 +583,12 @@ def copy(ui, repo, *pats, **opts): """mark files as copied for the next commit - Mark dest as having copies of source files. If dest is a - directory, copies are put in that directory. If dest is a file, + Mark dest as having copies of source files. If dest is a + directory, copies are put in that directory. If dest is a file, there can only be one source. By default, this command copies the contents of files as they - stand in the working directory. If invoked with --after, the + stand in the working directory. If invoked with --after, the operation is recorded, but no copying is performed. This command takes effect in the next commit. To undo a copy @@ -902,25 +914,27 @@ """dump rename information""" ctx = repo.changectx(opts.get('rev', 'tip')) - for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts, - ctx.node()): + m = cmdutil.match(repo, (file1,) + pats, opts) + for abs in repo.walk(m, ctx.node()): fctx = ctx.filectx(abs) - m = fctx.filelog().renamed(fctx.filenode()) - if m: - ui.write(_("%s renamed from %s:%s\n") % (rel, m[0], hex(m[1]))) + o = fctx.filelog().renamed(fctx.filenode()) + rel = m.rel(abs) + if o: + ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1]))) else: ui.write(_("%s not renamed\n") % rel) def debugwalk(ui, repo, *pats, **opts): """show how files match on given patterns""" - items = list(cmdutil.walk(repo, pats, opts)) + m = cmdutil.match(repo, pats, opts) + items = list(repo.walk(m)) if not items: return - fmt = '%%s %%-%ds %%-%ds %%s' % ( - max([len(abs) for (src, abs, rel, exact) in items]), - max([len(rel) for (src, abs, rel, exact) in items])) - for src, abs, rel, exact in items: - line = fmt % (src, abs, rel, exact and 'exact' or '') + fmt = 'f %%-%ds %%-%ds %%s' % ( + max([len(abs) for abs in items]), + max([len(m.rel(abs)) for abs in items])) + for abs in items: + line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '') ui.write("%s\n" % line.rstrip()) def diff(ui, repo, *pats, **opts): @@ -946,10 +960,8 @@ """ node1, node2 = cmdutil.revpair(repo, opts['rev']) - fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts) - - patch.diff(repo, node1, node2, fns, match=matchfn, - opts=patch.diffopts(ui, opts)) + m = cmdutil.match(repo, pats, opts) + patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts)) def export(ui, repo, *changesets, **opts): """dump the header and diffs for one or more changesets @@ -963,7 +975,7 @@ as it will compare the merge changeset against its first parent only. Output may be to a file, in which case the name of the file is - given using a format string. The formatting rules are as follows: + given using a format string. The formatting rules are as follows: %% literal "%" character %H changeset hash (40 bytes of hexadecimal) @@ -997,13 +1009,13 @@ Search revisions of files for a regular expression. - This command behaves differently than Unix grep. It only accepts - Python/Perl regexps. It searches repository history, not the - working directory. It always prints the revision number in which + This command behaves differently than Unix grep. It only accepts + Python/Perl regexps. It searches repository history, not the + working directory. It always prints the revision number in which a match appears. By default, grep only prints output for the first revision of a - file in which it finds a match. To get it to print every revision + file in which it finds a match. To get it to print every revision that contains a change in match status ("-" for a match that becomes a non-match, or "+" for a non-match that becomes a match), use the --all flag. @@ -1047,6 +1059,9 @@ self.colstart = colstart self.colend = colend + def __hash__(self): + return hash((self.linenum, self.line)) + def __eq__(self, other): return self.line == other.line @@ -1173,7 +1188,7 @@ are the usual targets for update and merge operations. Branch heads are changesets that have a given branch tag, but have - no child changesets with that tag. They are usually where + no child changesets with that tag. They are usually where development on the given branch takes place. """ if opts['rev']: @@ -1239,7 +1254,14 @@ if with_version: version_(ui) ui.write('\n') - aliases, i = cmdutil.findcmd(ui, name, table) + + try: + aliases, i = cmdutil.findcmd(ui, name, table) + except cmdutil.AmbiguousCommand, inst: + select = lambda c: c.lstrip('^').startswith(inst.args[0]) + helplist(_('list of commands:\n\n'), select) + return + # synopsis ui.write("%s\n" % i[2]) @@ -1300,16 +1322,16 @@ def helptopic(name): v = None - for i in help.helptable: + for i, d in help.helptable: l = i.split('|') if name in l: v = i header = l[-1] + doc = d if not v: raise cmdutil.UnknownCommand(name) # description - doc = help.helptable[v] if not doc: doc = _("(No help text available)") if callable(doc): @@ -1380,6 +1402,16 @@ and _(" (default: %s)") % default or ""))) + if ui.verbose: + ui.write(_("\nspecial help topics:\n")) + topics = [] + for i, d in help.helptable: + l = i.split('|') + topics.append((", ".join(l[:-1]), l[-1])) + topics_len = max([len(s[0]) for s in topics]) + for t, desc in topics: + ui.write(" %-*s %s\n" % (topics_len, t, desc)) + if opt_output: opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0]) for first, second in opt_output: @@ -1466,15 +1498,15 @@ If there are outstanding changes in the working directory, import will abort unless given the -f flag. - You can import a patch straight from a mail message. Even patches + You can import a patch straight from a mail message. Even patches as attachments work (body part must be type text/plain or - text/x-patch to be used). From and Subject headers of email - message are used as default committer and commit message. All + text/x-patch to be used). From and Subject headers of email + message are used as default committer and commit message. All text/plain body parts before first diff are added to commit message. If the imported patch was generated by hg export, user and description - from patch override values from message headers and body. Values + from patch override values from message headers and body. Values given on command line with -m and -u override these. If --exact is specified, import will set the working directory @@ -1643,7 +1675,7 @@ def init(ui, dest=".", **opts): """create a new repository in the given directory - Initialize a new repository in the given directory. If the given + Initialize a new repository in the given directory. If the given directory does not exist, it is created. If no directory is given, the current directory is used. @@ -1661,7 +1693,7 @@ Print all files under Mercurial control whose names match the given patterns. - This command searches the entire repository by default. To search + This command searches the entire repository by default. To search just the current directory and its subdirectories, use "--include .". @@ -1681,17 +1713,15 @@ node = None ret = 1 - for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node, - badmatch=util.always, - default='relglob'): - if src == 'b': - continue + m = cmdutil.match(repo, pats, opts, default='relglob') + m.bad = lambda x,y: False + for abs in repo.walk(m, node): if not node and abs not in repo.dirstate: continue if opts['fullpath']: ui.write(os.path.join(repo.root, abs), end) else: - ui.write(((pats and rel) or abs), end) + ui.write(((pats and m.rel(abs)) or abs), end) ret = 0 return ret @@ -1703,7 +1733,7 @@ project. File history is shown without following rename or copy history of - files. Use -f/--follow with a file name to follow history across + files. Use -f/--follow with a file name to follow history across renames and copies. --follow without a file name will only show ancestors or descendants of the starting revision. --follow-first only follows the first parent of merge revisions. @@ -1861,8 +1891,8 @@ performed before any further updates are allowed. If no revision is specified, the working directory's parent is a - head revision, and the repository contains exactly one other head, - the other head is merged with by default. Otherwise, an explicit + head revision, and the current branch contains exactly one other head, + the other head is merged with by default. Otherwise, an explicit revision to merge with must be provided. """ @@ -1872,22 +1902,28 @@ node = rev if not node: - heads = repo.heads() - if len(heads) > 2: - raise util.Abort(_('repo has %d heads - ' - 'please merge with an explicit rev') % - len(heads)) + branch = repo.workingctx().branch() + bheads = repo.branchheads() + if len(bheads) > 2: + raise util.Abort(_("branch '%s' has %d heads - " + "please merge with an explicit rev") % + (branch, len(bheads))) + parent = repo.dirstate.parents()[0] - if len(heads) == 1: + if len(bheads) == 1: + if len(repo.heads()) > 1: + raise util.Abort(_("branch '%s' has one head - " + "please merge with an explicit rev") % + branch) msg = _('there is nothing to merge') if parent != repo.lookup(repo.workingctx().branch()): msg = _('%s - use "hg update" instead') % msg raise util.Abort(msg) - if parent not in heads: + if parent not in bheads: raise util.Abort(_('working dir not at a head rev - ' 'use "hg update" or merge with an explicit rev')) - node = parent == heads[0] and heads[-1] or heads[0] + node = parent == bheads[0] and bheads[-1] or bheads[0] return hg.merge(repo, node, force=force) def outgoing(ui, repo, dest=None, **opts): @@ -1942,10 +1978,10 @@ ctx = repo.workingctx() if file_: - files, match, anypats = cmdutil.matchpats(repo, (file_,), opts) - if anypats or len(files) != 1: + m = cmdutil.match(repo, (file_,), opts) + if m.anypats() or len(m.files()) != 1: raise util.Abort(_('can only specify an explicit file name')) - file_ = files[0] + file_ = m.files()[0] filenodes = [] for cp in ctx.parents(): if not cp: @@ -1973,7 +2009,7 @@ definition of available names. Path names are defined in the [paths] section of /etc/mercurial/hgrc - and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too. + and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too. """ if search: for name, path in ui.configitems("paths"): @@ -2115,7 +2151,7 @@ message = cmdutil.logmessage(opts) - files, match, anypats = cmdutil.matchpats(repo, pats, opts) + files = cmdutil.match(repo, pats, opts).files() if opts['files']: files += open(opts['files']).read().splitlines() @@ -2167,12 +2203,12 @@ if not pats and not after: raise util.Abort(_('no files specified')) - files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts) - mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5] + m = cmdutil.match(repo, pats, opts) + mardu = map(dict.fromkeys, repo.status(match=m))[:5] modified, added, removed, deleted, unknown = mardu remove, forget = [], [] - for src, abs, rel, exact in cmdutil.walk(repo, pats, opts): + for abs in repo.walk(m): reason = None if abs in removed or abs in unknown: @@ -2205,9 +2241,9 @@ remove.append(abs) if reason: - ui.warn(_('not removing %s: file %s\n') % (rel, reason)) - elif ui.verbose or not exact: - ui.status(_('removing %s\n') % rel) + ui.warn(_('not removing %s: file %s\n') % (m.rel(abs), reason)) + elif ui.verbose or not m.exact(abs): + ui.status(_('removing %s\n') % m.rel(abs)) repo.forget(forget) repo.remove(remove, unlink=not after) @@ -2215,12 +2251,12 @@ def rename(ui, repo, *pats, **opts): """rename files; equivalent of copy + remove - Mark dest as copies of sources; mark sources for deletion. If - dest is a directory, copies are put in that directory. If dest is + Mark dest as copies of sources; mark sources for deletion. If + dest is a directory, copies are put in that directory. If dest is a file, there can only be one source. By default, this command copies the contents of files as they - stand in the working directory. If invoked with --after, the + stand in the working directory. If invoked with --after, the operation is recorded, but no copying is performed. This command takes effect in the next commit. To undo a rename @@ -2232,6 +2268,39 @@ finally: del wlock +def resolve(ui, repo, *pats, **opts): + """resolve file merges from a branch merge or update + + This command will attempt to resolve unresolved merges from the + last update or merge command. This will use the local file + revision preserved at the last update or merge to cleanly retry + the file merge attempt. With no file or options specified, this + command will attempt to resolve all unresolved files. + + The codes used to show the status of files are: + U = unresolved + R = resolved + """ + + if len([x for x in opts if opts[x]]) > 1: + raise util.Abort(_("too many options specified")) + + ms = merge_.mergestate(repo) + m = cmdutil.match(repo, pats, opts) + + for f in ms: + if m(f): + if opts.get("list"): + ui.write("%s %s\n" % (ms[f].upper(), f)) + elif opts.get("mark"): + ms.mark(f, "r") + elif opts.get("unmark"): + ms.mark(f, "u") + else: + wctx = repo.workingctx() + mctx = wctx.parents()[-1] + ms.resolve(f, wctx, mctx) + def revert(ui, repo, *pats, **opts): """restore individual files or dirs to an earlier state @@ -2250,13 +2319,13 @@ back" some or all of an earlier change. See 'hg help dates' for a list of formats valid for -d/--date. - Revert modifies the working directory. It does not commit any - changes, or change the parent of the working directory. If you + Revert modifies the working directory. It does not commit any + changes, or change the parent of the working directory. If you revert to a revision other than the parent of the working directory, the reverted files will thus appear modified afterwards. - If a file has been deleted, it is restored. If the executable + If a file has been deleted, it is restored. If the executable mode of a file was changed, it is reset. If names are given, all files matching the names are reverted. @@ -2297,30 +2366,32 @@ try: # walk dirstate. files = [] - for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, - badmatch=mf.has_key): - names[abs] = (rel, exact) - if src != 'b': - files.append(abs) + + m = cmdutil.match(repo, pats, opts) + m.bad = lambda x,y: False + for abs in repo.walk(m): + names[abs] = m.rel(abs), m.exact(abs) # walk target manifest. - def badmatch(path): + def badfn(path, msg): if path in names: - return True + return False path_ = path + '/' for f in names: if f.startswith(path_): - return True + return False + repo.ui.warn("%s: %s\n" % (m.rel(path), msg)) return False - for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node, - badmatch=badmatch): - if abs in names or src == 'b': - continue - names[abs] = (rel, exact) - - changes = repo.status(files=files, match=names.has_key)[:4] + m = cmdutil.match(repo, pats, opts) + m.bad = badfn + for abs in repo.walk(m, node=node): + if abs not in names: + names[abs] = m.rel(abs), m.exact(abs) + + m = cmdutil.matchfiles(repo, names) + changes = repo.status(match=m)[:4] modified, added, removed, deleted = map(dict.fromkeys, changes) # if f is a rename, also revert the source @@ -2492,7 +2563,7 @@ Start a local HTTP repository browser and pull server. By default, the server logs accesses to stdout and errors to - stderr. Use the "-A" and "-E" options to log to files. + stderr. Use the "-A" and "-E" options to log to files. """ if opts["stdio"]: @@ -2531,8 +2602,17 @@ if port == ':80': port = '' - ui.status(_('listening at http://%s%s/%s (%s:%d)\n') % - (self.httpd.fqaddr, port, prefix, self.httpd.addr, self.httpd.port)) + bindaddr = self.httpd.addr + if bindaddr == '0.0.0.0': + bindaddr = '*' + elif ':' in bindaddr: # IPv6 + bindaddr = '[%s]' % bindaddr + + fqaddr = self.httpd.fqaddr + if ':' in fqaddr: + fqaddr = '[%s]' % fqaddr + ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') % + (fqaddr, port, prefix, bindaddr, self.httpd.port)) def run(self): self.httpd.serve_forever() @@ -2544,10 +2624,10 @@ def status(ui, repo, *pats, **opts): """show changed files in the working directory - Show status of files in the repository. If names are given, only - files that match are shown. Files that are clean or ignored or + Show status of files in the repository. If names are given, only + files that match are shown. Files that are clean or ignored or source of a copy/move operation, are not listed unless -c (clean), - -i (ignored), -C (copies) or -A is given. Unless options described + -i (ignored), -C (copies) or -A is given. Unless options described with "show only ..." are given, the options -mardu are used. Option -q/--quiet hides untracked (unknown and ignored) files @@ -2572,65 +2652,45 @@ = the previous added file was copied from here """ - all = opts['all'] node1, node2 = cmdutil.revpair(repo, opts.get('rev')) - - files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts) cwd = (pats and repo.getcwd()) or '' - modified, added, removed, deleted, unknown, ignored, clean = [ - n for n in repo.status(node1=node1, node2=node2, files=files, - match=matchfn, - list_ignored=opts['ignored'] - or all and not ui.quiet, - list_clean=opts['clean'] or all, - list_unknown=opts['unknown'] - or not (ui.quiet or - opts['modified'] or - opts['added'] or - opts['removed'] or - opts['deleted'] or - opts['ignored']))] - - changetypes = (('modified', 'M', modified), - ('added', 'A', added), - ('removed', 'R', removed), - ('deleted', '!', deleted), - ('unknown', '?', unknown), - ('ignored', 'I', ignored)) - - explicit_changetypes = changetypes + (('clean', 'C', clean),) - + end = opts['print0'] and '\0' or '\n' copy = {} - showcopy = {} - if ((all or opts.get('copies')) and not opts.get('no_status')): - if opts.get('rev') == []: - # fast path, more correct with merge parents - showcopy = copy = repo.dirstate.copies().copy() - else: - ctxn = repo.changectx(nullid) - ctx1 = repo.changectx(node1) - ctx2 = repo.changectx(node2) - if node2 is None: - ctx2 = repo.workingctx() - copy, diverge = copies.copies(repo, ctx1, ctx2, ctxn) - for k, v in copy.items(): + states = 'modified added removed deleted unknown ignored clean'.split() + show = [k for k in states if opts[k]] + if opts['all']: + show += ui.quiet and (states[:4] + ['clean']) or states + if not show: + show = ui.quiet and states[:4] or states[:5] + + stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts), + 'ignored' in show, 'clean' in show, 'unknown' in show) + changestates = zip(states, 'MAR!?IC', stat) + + if (opts['all'] or opts['copies']) and not opts['no_status']: + ctxn = repo.changectx(nullid) + ctx1 = repo.changectx(node1) + ctx2 = repo.changectx(node2) + added = stat[1] + if node2 is None: + added = stat[0] + stat[1] # merged? + ctx2 = repo.workingctx() + for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].items(): + if k in added: + copy[k] = v + elif v in added: copy[v] = k - end = opts['print0'] and '\0' or '\n' - - for opt, char, changes in ([ct for ct in explicit_changetypes - if all or opts[ct[0]]] - or changetypes): - - if opts['no_status']: - format = "%%s%s" % end - else: + for state, char, files in changestates: + if state in show: format = "%s %%s%s" % (char, end) - - for f in changes: - ui.write(format % repo.pathto(f, cwd)) - if f in copy and (f in added or f in showcopy): - ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end)) + if opts['no_status']: + format = "%%s%s" % end + + for f in files: + ui.write(format % repo.pathto(f, cwd)) + if f in copy: + ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end)) def tag(ui, repo, name1, *names, **opts): """add one or more tags for the current or given revision @@ -2647,7 +2707,7 @@ 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. The file '.hg/localtags' is used for local tags (not + necessary. The file '.hg/localtags' is used for local tags (not shared among repositories). See 'hg help dates' for a list of formats valid for -d/--date. @@ -2769,8 +2829,8 @@ def update(ui, repo, node=None, rev=None, clean=False, date=None): """update working directory - Update the working directory to the specified revision, or the - tip of the current branch if none is specified. + Update the repository's working directory to the specified revision, + or the tip of the current branch if none is specified. If the requested revision is a descendant of the working directory, any outstanding changes in the working directory will @@ -2879,6 +2939,23 @@ ('M', 'no-merges', None, _('do not show merges')), ] + templateopts +diffopts = [ + ('a', 'text', None, _('treat all files as text')), + ('g', 'git', None, _('use git extended diff format')), + ('', 'nodates', None, _("don't include dates in diff headers")) +] + +diffopts2 = [ + ('p', 'show-function', None, _('show which function each change is in')), + ('w', 'ignore-all-space', None, + _('ignore white space when comparing lines')), + ('b', 'ignore-space-change', None, + _('ignore changes in the amount of white space')), + ('B', 'ignore-blank-lines', None, + _('ignore changes whose lines are all blank')), + ('U', 'unified', '', _('number of lines of context to show')) +] + table = { "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')), "addremove": @@ -2942,8 +3019,8 @@ _('a changeset up to which you would like to bundle')), ('', 'base', [], _('a base changeset to specify instead of a destination')), - ('a', 'all', None, - _('bundle all changesets in the repository')), + ('a', 'all', None, _('bundle all changesets in the repository')), + ('t', 'type', 'bzip2', _('bundle compression type to use')), ] + remoteopts, _('hg bundle [-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')), "cat": @@ -3018,29 +3095,14 @@ "debugwalk": (debugwalk, walkopts, _('hg debugwalk [OPTION]... [FILE]...')), "^diff": (diff, - [('r', 'rev', [], _('revision')), - ('a', 'text', None, _('treat all files as text')), - ('p', 'show-function', None, - _('show which function each change is in')), - ('g', 'git', None, _('use git extended diff format')), - ('', 'nodates', None, _("don't include dates in diff headers")), - ('w', 'ignore-all-space', None, - _('ignore white space when comparing lines')), - ('b', 'ignore-space-change', None, - _('ignore changes in the amount of white space')), - ('B', 'ignore-blank-lines', None, - _('ignore changes whose lines are all blank')), - ('U', 'unified', '', - _('number of lines of context to show')) - ] + walkopts, + [('r', 'rev', [], _('revision')) + ] + diffopts + diffopts2 + walkopts, _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')), "^export": (export, [('o', 'output', '', _('print output to file with formatted name')), - ('a', 'text', None, _('treat all files as text')), - ('g', 'git', None, _('use git extended diff format')), - ('', 'nodates', None, _("don't include dates in diff headers")), - ('', 'switch-parent', None, _('diff against the second parent'))], + ('', 'switch-parent', None, _('diff against the second parent')) + ] + diffopts, _('hg export [OPTION]... [-o OUTFILESPEC] REV...')), "grep": (grep, @@ -3184,6 +3246,12 @@ _('forcibly copy over an existing managed file')), ] + walkopts + dryrunopts, _('hg rename [OPTION]... SOURCE... DEST')), + "resolve": + (resolve, + [('l', 'list', None, _('list state of files needing merge')), + ('m', 'mark', None, _('mark files as resolved')), + ('u', 'unmark', None, _('unmark files as resolved'))], + ('hg resolve [OPTION] [FILES...]')), "revert": (revert, [('a', 'all', None, _('revert all changes when no arguments given')), @@ -3258,7 +3326,7 @@ _('hg unbundle [-u] FILE...')), "^update|up|checkout|co": (update, - [('C', 'clean', None, _('overwrite locally modified files')), + [('C', 'clean', None, _('overwrite locally modified files (no backup)')), ('d', 'date', '', _('tipmost revision matching date')), ('r', 'rev', '', _('revision'))], _('hg update [-C] [-d DATE] [[-r] REV]')), diff -r 2e58f1a36046 -r d43707e09b02 mercurial/context.py --- a/mercurial/context.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/context.py Tue Jun 24 09:34:38 2008 +0200 @@ -34,6 +34,12 @@ def __repr__(self): return "" % str(self) + def __hash__(self): + try: + return hash(self._rev) + except AttributeError: + return id(self) + def __eq__(self, other): try: return self._rev == other._rev @@ -210,6 +216,12 @@ def __repr__(self): return "" % str(self) + def __hash__(self): + try: + return hash((self._path, self._fileid)) + except AttributeError: + return id(self) + def __eq__(self, other): try: return (self._path == other._path @@ -432,11 +444,46 @@ class workingctx(changectx): """A workingctx object makes access to data related to - the current working directory convenient.""" - def __init__(self, repo): + the current working directory convenient. + parents - a pair of parent nodeids, or None to use the dirstate. + date - any valid date string or (unixtime, offset), or None. + user - username string, or None. + extra - a dictionary of extra values, or None. + changes - a list of file lists as returned by localrepo.status() + or None to use the repository status. + """ + def __init__(self, repo, parents=None, text="", user=None, date=None, + extra=None, changes=None): self._repo = repo self._rev = None self._node = None + self._text = text + if date: + self._date = util.parsedate(date) + else: + self._date = util.makedate() + if user: + self._user = user + else: + self._user = self._repo.ui.username() + if parents: + p1, p2 = parents + self._parents = [self._repo.changectx(p) for p in (p1, p2)] + if changes: + self._status = list(changes) + + self._extra = {} + if extra: + self._extra = extra.copy() + if 'branch' not in self._extra: + branch = self._repo.dirstate.branch() + try: + branch = branch.decode('UTF-8').encode('UTF-8') + except UnicodeDecodeError: + raise util.Abort(_('branch name not in UTF-8!')) + self._extra['branch'] = branch + if self._extra['branch'] == '': + self._extra['branch'] = 'default' def __str__(self): return str(self._parents[0]) + "+" @@ -483,9 +530,9 @@ def manifest(self): return self._manifest - def user(self): return self._repo.ui.username() - def date(self): return util.makedate() - def description(self): return "" + def user(self): return self._user + def date(self): return self._date + def description(self): return self._text def files(self): f = self.modified() + self.added() + self.removed() f.sort() @@ -497,7 +544,8 @@ def deleted(self): return self._status[3] def unknown(self): return self._status[4] def clean(self): return self._status[5] - def branch(self): return self._repo.dirstate.branch() + def branch(self): return self._extra['branch'] + def extra(self): return self._extra def tags(self): t = [] @@ -625,3 +673,89 @@ return (t, tz) def cmp(self, text): return self._repo.wread(self._path) == text + +class memctx(object): + """A memctx is a subset of changectx supposed to be built on memory + and passed to commit functions. + + NOTE: this interface and the related memfilectx are experimental and + may change without notice. + + parents - a pair of parent nodeids. + filectxfn - a callable taking (repo, memctx, path) arguments and + returning a memctx object. + date - any valid date string or (unixtime, offset), or None. + user - username string, or None. + extra - a dictionary of extra values, or None. + """ + def __init__(self, repo, parents, text, files, filectxfn, user=None, + date=None, extra=None): + self._repo = repo + self._rev = None + self._node = None + self._text = text + self._date = date and util.parsedate(date) or util.makedate() + self._user = user or self._repo.ui.username() + parents = [(p or nullid) for p in parents] + p1, p2 = parents + self._parents = [self._repo.changectx(p) for p in (p1, p2)] + files = list(files) + files.sort() + self._status = [files, [], [], [], []] + self._filectxfn = filectxfn + + self._extra = extra and extra.copy() or {} + if 'branch' not in self._extra: + self._extra['branch'] = 'default' + elif self._extra.get('branch') == '': + self._extra['branch'] = 'default' + + def __str__(self): + return str(self._parents[0]) + "+" + + def __nonzero__(self): + return True + + def user(self): return self._user + def date(self): return self._date + def description(self): return self._text + def files(self): return self.modified() + def modified(self): return self._status[0] + def added(self): return self._status[1] + def removed(self): return self._status[2] + def deleted(self): return self._status[3] + def unknown(self): return self._status[4] + def clean(self): return self._status[5] + def branch(self): return self._extra['branch'] + def extra(self): return self._extra + + def parents(self): + """return contexts for each parent changeset""" + return self._parents + + def filectx(self, path, filelog=None): + """get a file context from the working directory""" + return self._filectxfn(self._repo, self, path) + +class memfilectx(object): + """A memfilectx is a subset of filectx supposed to be built by client + code and passed to commit functions. + """ + def __init__(self, path, data, islink, isexec, copied): + """copied is the source file path, or None.""" + self._path = path + self._data = data + self._flags = (islink and 'l' or '') + (isexec and 'x' or '') + self._copied = None + if copied: + self._copied = (copied, nullid) + + def __nonzero__(self): return True + def __str__(self): return "%s@%s" % (self.path(), self._changectx) + def path(self): return self._path + def data(self): return self._data + def fileflags(self): return self._flags + def isexec(self): return 'x' in self._flags + def islink(self): return 'l' in self._flags + def renamed(self): return self._copied + diff -r 2e58f1a36046 -r d43707e09b02 mercurial/copies.py --- a/mercurial/copies.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/copies.py Tue Jun 24 09:34:38 2008 +0200 @@ -109,6 +109,10 @@ if not c1 or not c2 or c1 == c2: return {}, {} + # avoid silly behavior for parent -> working dir + if c2.node() == None and c1.node() == repo.dirstate.parents()[0]: + return repo.dirstate.copies(), {} + limit = _findlimit(repo, c1.rev(), c2.rev()) m1 = c1.manifest() m2 = c2.manifest() diff -r 2e58f1a36046 -r d43707e09b02 mercurial/dirstate.py --- a/mercurial/dirstate.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/dirstate.py Tue Jun 24 09:34:38 2008 +0200 @@ -31,6 +31,13 @@ elif name == '_copymap': self._read() return self._copymap + elif name == '_foldmap': + _foldmap = {} + for name in self._map: + norm = os.path.normcase(os.path.normpath(name)) + _foldmap[norm] = name + self._foldmap = _foldmap + return self._foldmap elif name == '_branch': try: self._branch = (self._opener("branch").read().strip() @@ -66,12 +73,24 @@ elif name == '_checkexec': self._checkexec = util.checkexec(self._root) return self._checkexec + elif name == '_folding': + self._folding = not util.checkfolding(self._join('.hg')) + return self._folding + elif name == 'normalize': + if self._folding: + self.normalize = self._normalize + else: + self.normalize = lambda x: x + return self.normalize else: raise AttributeError, name def _join(self, f): return os.path.join(self._root, f) + def folding(self): + return self._folding + def getcwd(self): cwd = os.getcwd() if cwd == self._root: return '' @@ -161,7 +180,7 @@ dmap[f] = e # we hold onto e[4] because making a subtuple is slow def invalidate(self): - for a in "_map _copymap _branch _pl _dirs _ignore".split(): + for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split(): if a in self.__dict__: delattr(self, a) self._dirty = False @@ -316,6 +335,16 @@ except KeyError: self._ui.warn(_("not in dirstate: %s\n") % f) + def _normalize(self, path): + normpath = os.path.normcase(os.path.normpath(path)) + if normpath in self._foldmap: + return self._foldmap[normpath] + elif os.path.exists(path): + self._foldmap[normpath] = util.fspath(path, self._root) + return self._foldmap[normpath] + else: + return path + def clear(self): self._map = {} if "_dirs" in self.__dict__: @@ -418,13 +447,12 @@ return True return False - def walk(self, files=None, match=util.always, badmatch=None): - # filter out the stat - for src, f, st in self.statwalk(files, match, badmatch=badmatch): - yield src, f + def walk(self, match): + # filter out the src and stat + for src, f, st in self.statwalk(match): + yield f - def statwalk(self, files=None, match=util.always, unknown=True, - ignored=False, badmatch=None, directories=False): + def statwalk(self, match, unknown=True, ignored=False): ''' walk recursively through the directory tree, finding all files matched by the match function @@ -432,14 +460,20 @@ results are yielded in a tuple (src, filename, st), where src is one of: 'f' the file was found in the directory tree - 'd' the file is a directory of the tree 'm' the file was only in the dirstate and not in the tree - 'b' file was not found and matched badmatch and st is the stat result if the file was found in the directory. ''' + def fwarn(f, msg): + self._ui.warn('%s: %s\n' % (self.pathto(ff), msg)) + return False + badfn = fwarn + if hasattr(match, 'bad'): + badfn = match.bad + # walk all files by default + files = match.files() if not files: files = ['.'] dc = self._map.copy() @@ -483,8 +517,8 @@ wadd = work.append found = [] add = found.append - if directories: - add((normpath(s[common_prefix_len:]), 'd', lstat(s))) + if hasattr(match, 'dir'): + match.dir(normpath(s[common_prefix_len:])) while work: top = work.pop() entries = listdir(top, stat=True) @@ -511,8 +545,8 @@ if kind == stat.S_IFDIR: if not ignore(np): wadd(p) - if directories: - add((np, 'd', st)) + if hasattr(match, 'dir'): + match.dir(np) if np in dc and match(np): add((np, 'm', st)) elif imatch(np): @@ -537,11 +571,10 @@ found = True break if not found: - if inst.errno != errno.ENOENT or not badmatch: - self._ui.warn('%s: %s\n' % - (self.pathto(ff), inst.strerror)) - elif badmatch and badmatch(ff) and imatch(nf): - yield 'b', ff, None + if inst.errno != errno.ENOENT: + fwarn(ff, inst.strerror) + elif badfn(ff, inst.strerror) and imatch(nf): + yield 'f', ff, None continue if s_isdir(st.st_mode): if not dirignore(nf): @@ -553,7 +586,7 @@ known[nf] = 1 if match(nf): if supported(ff, st.st_mode, verbose=True): - yield 'f', nf, st + yield 'f', self.normalize(nf), st elif ff in dc: yield 'm', nf, st @@ -568,11 +601,10 @@ if imatch(k): yield 'm', k, None - def status(self, files, match, list_ignored, list_clean, list_unknown=True): + def status(self, match, list_ignored, list_clean, list_unknown): lookup, modified, added, unknown, ignored = [], [], [], [], [] removed, deleted, clean = [], [], [] - files = files or [] _join = self._join lstat = os.lstat cmap = self._copymap @@ -586,17 +618,18 @@ dadd = deleted.append cadd = clean.append - for src, fn, st in self.statwalk(files, match, unknown=list_unknown, + for src, fn, st in self.statwalk(match, unknown=list_unknown, ignored=list_ignored): - if fn in dmap: - type_, mode, size, time, foo = dmap[fn] - else: - if (list_ignored or fn in files) and self._dirignore(fn): + if fn not in dmap: + if (list_ignored or match.exact(fn)) and self._dirignore(fn): if list_ignored: iadd(fn) elif list_unknown: uadd(fn) continue + + state, mode, size, time, foo = dmap[fn] + if src == 'm': nonexistent = True if not st: @@ -609,13 +642,11 @@ # We need to re-check that it is a valid file if st and self._supported(fn, st.st_mode): nonexistent = False - # XXX: what to do with file no longer present in the fs - # who are not removed in the dirstate ? - if nonexistent and type_ in "nma": + if nonexistent and state in "nma": dadd(fn) continue # check the common case first - if type_ == 'n': + if state == 'n': if not st: st = lstat(_join(fn)) if (size >= 0 and @@ -628,11 +659,11 @@ ladd(fn) elif list_clean: cadd(fn) - elif type_ == 'm': + elif state == 'm': madd(fn) - elif type_ == 'a': + elif state == 'a': aadd(fn) - elif type_ == 'r': + elif state == 'r': radd(fn) return (lookup, modified, added, removed, deleted, unknown, ignored, diff -r 2e58f1a36046 -r d43707e09b02 mercurial/filemerge.py --- a/mercurial/filemerge.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/filemerge.py Tue Jun 24 09:34:38 2008 +0200 @@ -5,7 +5,7 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from node import nullrev +from node import nullrev, short from i18n import _ import util, os, tempfile, simplemerge, re, filecmp @@ -101,13 +101,14 @@ if newdata != data: open(file, "wb").write(newdata) -def filemerge(repo, fw, fd, fo, wctx, mctx): +def filemerge(repo, mynode, orig, fcd, fco, fca): """perform a 3-way merge in the working directory - fw = original filename in the working directory - fd = destination filename in the working directory - fo = filename in other parent - wctx, mctx = working and merge changecontexts + mynode = parent node before merge + orig = original local filename before merge + fco = other file context + fca = ancestor file context + fcd = local file context for current/destination file """ def temp(prefix, ctx): @@ -125,23 +126,21 @@ except IOError: return False - fco = mctx.filectx(fo) - if not fco.cmp(wctx.filectx(fd).data()): # files identical? + if not fco.cmp(fcd.data()): # files identical? return None ui = repo.ui - fcm = wctx.filectx(fw) - fca = fcm.ancestor(fco) or repo.filectx(fw, fileid=nullrev) - binary = isbin(fcm) or isbin(fco) or isbin(fca) - symlink = fcm.islink() or fco.islink() - tool, toolpath = _picktool(repo, ui, fw, binary, symlink) + fd = fcd.path() + binary = isbin(fcd) or isbin(fco) or isbin(fca) + symlink = fcd.islink() or fco.islink() + tool, toolpath = _picktool(repo, ui, fd, binary, symlink) ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") % - (tool, fw, binary, symlink)) + (tool, fd, binary, symlink)) if not tool: tool = "internal:local" if ui.prompt(_(" no tool found to merge %s\n" - "keep (l)ocal or take (o)ther?") % fw, + "keep (l)ocal or take (o)ther?") % fd, _("[lo]"), _("l")) != _("l"): tool = "internal:other" if tool == "internal:local": @@ -160,11 +159,12 @@ back = a + ".orig" util.copyfile(a, back) - if fw != fo: - repo.ui.status(_("merging %s and %s\n") % (fw, fo)) + if orig != fco.path(): + repo.ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd)) else: - repo.ui.status(_("merging %s\n") % fw) - repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca)) + repo.ui.status(_("merging %s\n") % fd) + + repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcd, fco, fca)) # do we attempt to simplemerge first? if _toolbool(ui, tool, "premerge", not (binary or symlink)): @@ -178,9 +178,9 @@ util.copyfile(back, a) # restore from backup and try again env = dict(HG_FILE=fd, - HG_MY_NODE=str(wctx.parents()[0]), - HG_OTHER_NODE=str(mctx), - HG_MY_ISLINK=fcm.islink(), + HG_MY_NODE=short(mynode), + HG_OTHER_NODE=str(fco.changectx()), + HG_MY_ISLINK=fcd.islink(), HG_OTHER_ISLINK=fco.islink(), HG_BASE_ISLINK=fca.islink()) @@ -196,7 +196,7 @@ r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env) if not r and _toolbool(ui, tool, "checkconflicts"): - if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcm.data()): + if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data()): r = 1 if not r and _toolbool(ui, tool, "checkchanged"): diff -r 2e58f1a36046 -r d43707e09b02 mercurial/graphmod.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/graphmod.py Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,74 @@ +# Revision graph generator for Mercurial +# +# Copyright 2008 Dirkjan Ochtman +# Copyright 2007 Joel Rosdahl +# +# This software may be used and distributed according to the terms of +# the GNU General Public License, incorporated herein by reference. + +from node import nullrev, short +import ui, hg, util, templatefilters + +def graph(repo, start_rev, stop_rev): + """incremental revision grapher + + This generator function walks through the revision history from + revision start_rev to revision stop_rev (which must be less than + or equal to start_rev) and for each revision emits tuples with the + following elements: + + - Current node + - Column and color for the current node + - Edges; a list of (col, next_col, color) indicating the edges between + the current node and its parents. + - First line of the changeset description + - The changeset author + - The changeset date/time + """ + + assert start_rev >= stop_rev + curr_rev = start_rev + revs = [] + cl = repo.changelog + colors = {} + new_color = 1 + + while curr_rev >= stop_rev: + node = cl.node(curr_rev) + + # Compute revs and next_revs + if curr_rev not in revs: + revs.append(curr_rev) # new head + colors[curr_rev] = new_color + new_color += 1 + + idx = revs.index(curr_rev) + color = colors.pop(curr_rev) + next = revs[:] + + # Add parents to next_revs + parents = [x for x in cl.parentrevs(curr_rev) if x != nullrev] + addparents = [p for p in parents if p not in next] + next[idx:idx + 1] = addparents + + # Set colors for the parents + for i, p in enumerate(addparents): + if not i: + colors[p] = color + else: + colors[p] = new_color + new_color += 1 + + # Add edges to the graph + edges = [] + for col, r in enumerate(revs): + if r in next: + edges.append((col, next.index(r), colors[r])) + elif r == curr_rev: + for p in parents: + edges.append((col, next.index(p), colors[p])) + + # Yield and move on + yield (repo.changectx(curr_rev), (idx, color), edges) + revs = next + curr_rev -= 1 diff -r 2e58f1a36046 -r d43707e09b02 mercurial/hbisect.py diff -r 2e58f1a36046 -r d43707e09b02 mercurial/help.py --- a/mercurial/help.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/help.py Tue Jun 24 09:34:38 2008 +0200 @@ -5,8 +5,8 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -helptable = { - "dates|Date Formats": +helptable = ( + ("dates|Date Formats", r''' Some commands allow the user to specify a date: backout, commit, import, tag: Specify the commit date. @@ -43,9 +43,55 @@ ">{date}" - on or after a given date "{date} to {date}" - a date range, inclusive "-{days}" - within a given number of days of today - ''', + '''), + + ("patterns|File Name Patterns", + r''' + Mercurial accepts several notations for identifying one or more + files at a time. + + By default, Mercurial treats filenames as shell-style extended + glob patterns. + + Alternate pattern notations must be specified explicitly. + + To use a plain path name without any pattern matching, start a + name with "path:". These path names must match completely, from + the root of the current repository. + + To use an extended glob, start a name with "glob:". Globs are + rooted at the current directory; a glob such as "*.c" will match + files ending in ".c" in the current directory only. + + The supported glob syntax extensions are "**" to match any string + across path separators, and "{a,b}" to mean "a or b". - 'environment|env|Environment Variables': + To use a Perl/Python regular expression, start a name with "re:". + Regexp pattern matching is anchored at the root of the repository. + + Plain examples: + + path:foo/bar a name bar in a directory named foo in the root of + the repository + path:path:name a file or directory named "path:name" + + Glob examples: + + glob:*.c any name ending in ".c" in the current directory + *.c any name ending in ".c" in the current directory + **.c any name ending in ".c" in the current directory, or + any subdirectory + foo/*.c any name ending in ".c" in the directory foo + foo/**.c any name ending in ".c" in the directory foo, or any + subdirectory + + Regexp examples: + + re:.*\.c$ any name ending in ".c", anywhere in the repository + + '''), + + ('environment|env|Environment Variables', r''' HG:: Path to the 'hg' executable, automatically passed when running hooks, @@ -114,51 +160,57 @@ PYTHONPATH:: This is used by Python to find imported modules and may need to be set appropriately if Mercurial is not installed system-wide. - ''', + '''), - "patterns|File Name Patterns": r''' - Mercurial accepts several notations for identifying one or more - files at a time. + ('revs|revisions|Specifying Single Revisions', + r''' + Mercurial accepts several notations for identifying individual + revisions. - By default, Mercurial treats filenames as shell-style extended - glob patterns. - - Alternate pattern notations must be specified explicitly. + A plain integer is treated as a revision number. Negative + integers are treated as offsets from the tip, with -1 denoting the + tip. - To use a plain path name without any pattern matching, start a - name with "path:". These path names must match completely, from - the root of the current repository. + A 40-digit hexadecimal string is treated as a unique revision + identifier. - To use an extended glob, start a name with "glob:". Globs are - rooted at the current directory; a glob such as "*.c" will match - files ending in ".c" in the current directory only. + A hexadecimal string less than 40 characters long is treated as a + unique revision identifier, and referred to as a short-form + identifier. A short-form identifier is only valid if it is the + prefix of one full-length identifier. - The supported glob syntax extensions are "**" to match any string - across path separators, and "{a,b}" to mean "a or b". + Any other string is treated as a tag name, which is a symbolic + name associated with a revision identifier. Tag names may not + contain the ":" character. + + The reserved name "tip" is a special tag that always identifies + the most recent revision. - To use a Perl/Python regular expression, start a name with "re:". - Regexp pattern matching is anchored at the root of the repository. - - Plain examples: + The reserved name "null" indicates the null revision. This is the + revision of an empty repository, and the parent of revision 0. - path:foo/bar a name bar in a directory named foo in the root of - the repository - path:path:name a file or directory named "path:name" - - Glob examples: + The reserved name "." indicates the working directory parent. If + no working directory is checked out, it is equivalent to null. + If an uncommitted merge is in progress, "." is the revision of + the first parent. + '''), - glob:*.c any name ending in ".c" in the current directory - *.c any name ending in ".c" in the current directory - **.c any name ending in ".c" in the current directory, or - any subdirectory - foo/*.c any name ending in ".c" in the directory foo - foo/**.c any name ending in ".c" in the directory foo, or any - subdirectory + ('mrevs|multirevs|Specifying Multiple Revisions', + r''' + When Mercurial accepts more than one revision, they may be + specified individually, or provided as a continuous range, + separated by the ":" character. - Regexp examples: - - re:.*\.c$ any name ending in ".c", anywhere in the repository + The syntax of range notation is [BEGIN]:[END], where BEGIN and END + are revision identifiers. Both BEGIN and END are optional. If + BEGIN is not specified, it defaults to revision number 0. If END + is not specified, it defaults to the tip. The range ":" thus + means "all revisions". -''', -} + If BEGIN is greater than END, revisions are treated in reverse + order. + A range acts as a closed interval. This means that a range of 3:5 + gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2. + '''), +) diff -r 2e58f1a36046 -r d43707e09b02 mercurial/hg.py --- a/mercurial/hg.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/hg.py Tue Jun 24 09:34:38 2008 +0200 @@ -16,7 +16,7 @@ return (os.path.isfile(util.drop_scheme('file', path)) and bundlerepo or localrepo) -def parseurl(url, revs): +def parseurl(url, revs=[]): '''parse url#branch, returning url, branch + revs''' if '#' not in url: @@ -69,6 +69,15 @@ '''return default destination of clone if none is given''' return os.path.basename(os.path.normpath(source)) +def localpath(path): + if path.startswith('file://localhost/'): + return path[16:] + if path.startswith('file://'): + return path[7:] + if path.startswith('file:'): + return path[5:] + return path + def clone(ui, source, dest=None, pull=False, rev=None, update=True, stream=False): """Make a copy of an existing repository. @@ -100,7 +109,8 @@ rev: revision to clone up to (implies pull=True) update: update working directory after clone completes, if - destination is local repository + destination is local repository (True means update to default rev, + anything else is treated as a revision) """ if isinstance(source, str): @@ -116,15 +126,6 @@ dest = defaultdest(source) ui.status(_("destination directory: %s\n") % dest) - def localpath(path): - if path.startswith('file://localhost/'): - return path[16:] - if path.startswith('file://'): - return path[7:] - if path.startswith('file:'): - return path[5:] - return path - dest = localpath(dest) source = localpath(source) @@ -244,7 +245,9 @@ if update: dest_repo.ui.status(_("updating working directory\n")) - if not checkout: + if update is not True: + checkout = update + elif not checkout: try: checkout = dest_repo.lookup("default") except: @@ -271,15 +274,7 @@ stats = _merge.update(repo, node, False, False, None) _showstats(repo, stats) if stats[3]: - repo.ui.status(_("There are unresolved merges with" - " locally modified files.\n")) - if stats[1]: - repo.ui.status(_("You can finish the partial merge using:\n")) - else: - repo.ui.status(_("You can redo the full merge using:\n")) - # len(pl)==1, otherwise _merge.update() would have raised util.Abort: - repo.ui.status(_(" hg update %s\n hg update %s\n") - % (pl[0].rev(), repo.changectx(node).rev())) + repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n")) return stats[3] > 0 def clean(repo, node, show_stats=True): @@ -294,11 +289,7 @@ _showstats(repo, stats) if stats[3]: pl = repo.parents() - repo.ui.status(_("There are unresolved merges," - " you can redo the full merge using:\n" - " hg update -C %s\n" - " hg merge %s\n") - % (pl[0].rev(), pl[1].rev())) + repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n")) elif remind: repo.ui.status(_("(branch merge, don't forget to commit)\n")) return stats[3] > 0 diff -r 2e58f1a36046 -r d43707e09b02 mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/hgweb/hgweb_mod.py Tue Jun 24 09:34:38 2008 +0200 @@ -6,16 +6,15 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -import os, mimetypes, re, mimetools, cStringIO -from mercurial.node import hex, nullid, short +import os, mimetypes +from mercurial.node import hex, nullid from mercurial.repo import RepoError -from mercurial import mdiff, ui, hg, util, archival, patch, hook +from mercurial import mdiff, ui, hg, util, patch, hook from mercurial import revlog, templater, templatefilters, changegroup -from common import get_mtime, style_map, paritygen, countgen, get_contact -from common import ErrorResponse +from common import get_mtime, style_map, paritygen, countgen, ErrorResponse from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR from request import wsgirequest -import webcommands, protocol +import webcommands, protocol, webutil shortcuts = { 'cl': [('cmd', ['changelog']), ('rev', None)], @@ -32,54 +31,6 @@ 'static': [('cmd', ['static']), ('file', None)] } -def _up(p): - if p[0] != "/": - p = "/" + p - if p[-1] == "/": - p = p[:-1] - up = os.path.dirname(p) - if up == "/": - return "/" - return up + "/" - -def revnavgen(pos, pagelen, limit, nodefunc): - def seq(factor, limit=None): - if limit: - yield limit - if limit >= 20 and limit <= 40: - yield 50 - else: - yield 1 * factor - yield 3 * factor - for f in seq(factor * 10): - yield f - - def nav(**map): - l = [] - last = 0 - for f in seq(1, pagelen): - if f < pagelen or f <= last: - continue - if f > limit: - break - last = f - if pos + f < limit: - l.append(("+%d" % f, hex(nodefunc(pos + f).node()))) - if pos - f >= 0: - l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node()))) - - try: - yield {"label": "(0)", "node": hex(nodefunc('0').node())} - - for label, node in l: - yield {"label": label, "node": node} - - yield {"label": "tip", "node": "tip"} - except RepoError: - pass - - return nav - class hgweb(object): def __init__(self, repo, name=None): if isinstance(repo, str): @@ -226,17 +177,8 @@ try: tmpl = self.templater(req) - try: - ctype = tmpl('mimetype', encoding=self.encoding) - ctype = templater.stringify(ctype) - except KeyError: - # old templates with inline HTTP headers? - if 'mimetype' in tmpl: - raise - header = tmpl('header', encoding=self.encoding) - header_file = cStringIO.StringIO(templater.stringify(header)) - msg = mimetools.Message(header_file, 0) - ctype = msg['content-type'] + ctype = tmpl('mimetype', encoding=self.encoding) + ctype = templater.stringify(ctype) if cmd == '': req.form['cmd'] = [tmpl.cache['default']] @@ -291,13 +233,7 @@ # some functions for the templater def header(**map): - header = tmpl('header', encoding=self.encoding, **map) - if 'mimetype' not in tmpl: - # old template with inline HTTP headers - header_file = cStringIO.StringIO(templater.stringify(header)) - msg = mimetools.Message(header_file, 0) - header = header_file.read() - yield header + yield tmpl('header', encoding=self.encoding, **map) def footer(**map): yield tmpl("footer", **map) @@ -355,54 +291,6 @@ if len(files) > self.maxfiles: yield tmpl("fileellipses") - def siblings(self, siblings=[], hiderev=None, **args): - siblings = [s for s in siblings if s.node() != nullid] - if len(siblings) == 1 and siblings[0].rev() == hiderev: - return - for s in siblings: - d = {'node': hex(s.node()), 'rev': s.rev()} - if hasattr(s, 'path'): - d['file'] = s.path() - d.update(args) - yield d - - def renamelink(self, fl, node): - r = fl.renamed(node) - if r: - return [dict(file=r[0], node=hex(r[1]))] - return [] - - def nodetagsdict(self, node): - return [{"name": i} for i in self.repo.nodetags(node)] - - def nodebranchdict(self, ctx): - branches = [] - branch = ctx.branch() - # If this is an empty repo, ctx.node() == nullid, - # ctx.branch() == 'default', but branchtags() is - # an empty dict. Using dict.get avoids a traceback. - if self.repo.branchtags().get(branch) == ctx.node(): - branches.append({"name": branch}) - return branches - - def nodeinbranch(self, ctx): - branches = [] - branch = ctx.branch() - if branch != 'default' and self.repo.branchtags().get(branch) != ctx.node(): - branches.append({"name": branch}) - return branches - - def nodebranchnodefault(self, ctx): - branches = [] - branch = ctx.branch() - if branch != 'default': - branches.append({"name": branch}) - return branches - - def showtag(self, tmpl, t1, node=nullid, **args): - for t in self.repo.nodetags(node): - yield tmpl(t1, tag=t, **args) - def diff(self, tmpl, node1, node2, files): def filterfiles(filters, files): l = [x for x in files if x in filters] @@ -470,514 +358,12 @@ yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f, opts=diffopts), f, tn) - def changelog(self, tmpl, ctx, shortlog=False): - def changelist(limit=0,**map): - cl = self.repo.changelog - l = [] # build a list in forward order for efficiency - for i in xrange(start, end): - ctx = self.repo.changectx(i) - n = ctx.node() - showtags = self.showtag(tmpl, 'changelogtag', n) - - l.insert(0, {"parity": parity.next(), - "author": ctx.user(), - "parent": self.siblings(ctx.parents(), i - 1), - "child": self.siblings(ctx.children(), i + 1), - "changelogtag": showtags, - "desc": ctx.description(), - "date": ctx.date(), - "files": self.listfilediffs(tmpl, ctx.files(), n), - "rev": i, - "node": hex(n), - "tags": self.nodetagsdict(n), - "inbranch": self.nodeinbranch(ctx), - "branches": self.nodebranchdict(ctx)}) - - if limit > 0: - l = l[:limit] - - for e in l: - yield e - - maxchanges = shortlog and self.maxshortchanges or self.maxchanges - cl = self.repo.changelog - count = cl.count() - pos = ctx.rev() - start = max(0, pos - maxchanges + 1) - end = min(count, start + maxchanges) - pos = end - 1 - parity = paritygen(self.stripecount, offset=start-end) - - changenav = revnavgen(pos, maxchanges, count, self.repo.changectx) - - return tmpl(shortlog and 'shortlog' or 'changelog', - changenav=changenav, - node=hex(cl.tip()), - rev=pos, changesets=count, - entries=lambda **x: changelist(limit=0,**x), - latestentry=lambda **x: changelist(limit=1,**x), - archives=self.archivelist("tip")) - - def search(self, tmpl, query): - - def changelist(**map): - cl = self.repo.changelog - count = 0 - qw = query.lower().split() - - def revgen(): - for i in xrange(cl.count() - 1, 0, -100): - l = [] - for j in xrange(max(0, i - 100), i + 1): - ctx = self.repo.changectx(j) - l.append(ctx) - l.reverse() - for e in l: - yield e - - for ctx in revgen(): - miss = 0 - for q in qw: - if not (q in ctx.user().lower() or - q in ctx.description().lower() or - q in " ".join(ctx.files()).lower()): - miss = 1 - break - if miss: - continue - - count += 1 - n = ctx.node() - showtags = self.showtag(tmpl, 'changelogtag', n) - - yield tmpl('searchentry', - parity=parity.next(), - author=ctx.user(), - parent=self.siblings(ctx.parents()), - child=self.siblings(ctx.children()), - changelogtag=showtags, - desc=ctx.description(), - date=ctx.date(), - files=self.listfilediffs(tmpl, ctx.files(), n), - rev=ctx.rev(), - node=hex(n), - tags=self.nodetagsdict(n), - inbranch=self.nodeinbranch(ctx), - branches=self.nodebranchdict(ctx)) - - if count >= self.maxchanges: - break - - cl = self.repo.changelog - parity = paritygen(self.stripecount) - - return tmpl('search', - query=query, - node=hex(cl.tip()), - entries=changelist, - archives=self.archivelist("tip")) - - def changeset(self, tmpl, ctx): - n = ctx.node() - showtags = self.showtag(tmpl, 'changesettag', n) - parents = ctx.parents() - p1 = parents[0].node() - - files = [] - parity = paritygen(self.stripecount) - for f in ctx.files(): - files.append(tmpl("filenodelink", - node=hex(n), file=f, - parity=parity.next())) - - def diff(**map): - yield self.diff(tmpl, p1, n, None) - - return tmpl('changeset', - diff=diff, - rev=ctx.rev(), - node=hex(n), - parent=self.siblings(parents), - child=self.siblings(ctx.children()), - changesettag=showtags, - author=ctx.user(), - desc=ctx.description(), - date=ctx.date(), - files=files, - archives=self.archivelist(hex(n)), - tags=self.nodetagsdict(n), - branch=self.nodebranchnodefault(ctx), - inbranch=self.nodeinbranch(ctx), - branches=self.nodebranchdict(ctx)) - - def filelog(self, tmpl, fctx): - f = fctx.path() - fl = fctx.filelog() - count = fl.count() - pagelen = self.maxshortchanges - pos = fctx.filerev() - start = max(0, pos - pagelen + 1) - end = min(count, start + pagelen) - pos = end - 1 - parity = paritygen(self.stripecount, offset=start-end) - - def entries(limit=0, **map): - l = [] - - for i in xrange(start, end): - ctx = fctx.filectx(i) - n = fl.node(i) - - l.insert(0, {"parity": parity.next(), - "filerev": i, - "file": f, - "node": hex(ctx.node()), - "author": ctx.user(), - "date": ctx.date(), - "rename": self.renamelink(fl, n), - "parent": self.siblings(fctx.parents()), - "child": self.siblings(fctx.children()), - "desc": ctx.description()}) - - if limit > 0: - l = l[:limit] - - for e in l: - yield e - - nodefunc = lambda x: fctx.filectx(fileid=x) - nav = revnavgen(pos, pagelen, count, nodefunc) - return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav, - entries=lambda **x: entries(limit=0, **x), - latestentry=lambda **x: entries(limit=1, **x)) - - def filerevision(self, tmpl, fctx): - f = fctx.path() - text = fctx.data() - fl = fctx.filelog() - n = fctx.filenode() - parity = paritygen(self.stripecount) - - if util.binary(text): - mt = mimetypes.guess_type(f)[0] or 'application/octet-stream' - text = '(binary:%s)' % mt - - def lines(): - for lineno, t in enumerate(text.splitlines(1)): - yield {"line": t, - "lineid": "l%d" % (lineno + 1), - "linenumber": "% 6d" % (lineno + 1), - "parity": parity.next()} - - return tmpl("filerevision", - file=f, - path=_up(f), - text=lines(), - rev=fctx.rev(), - node=hex(fctx.node()), - author=fctx.user(), - date=fctx.date(), - desc=fctx.description(), - branch=self.nodebranchnodefault(fctx), - parent=self.siblings(fctx.parents()), - child=self.siblings(fctx.children()), - rename=self.renamelink(fl, n), - permissions=fctx.manifest().flags(f)) - - def fileannotate(self, tmpl, fctx): - f = fctx.path() - n = fctx.filenode() - fl = fctx.filelog() - parity = paritygen(self.stripecount) - - def annotate(**map): - last = None - if util.binary(fctx.data()): - mt = (mimetypes.guess_type(fctx.path())[0] - or 'application/octet-stream') - lines = enumerate([((fctx.filectx(fctx.filerev()), 1), - '(binary:%s)' % mt)]) - else: - lines = enumerate(fctx.annotate(follow=True, linenumber=True)) - for lineno, ((f, targetline), l) in lines: - fnode = f.filenode() - name = self.repo.ui.shortuser(f.user()) - - if last != fnode: - last = fnode - - yield {"parity": parity.next(), - "node": hex(f.node()), - "rev": f.rev(), - "author": name, - "file": f.path(), - "targetline": targetline, - "line": l, - "lineid": "l%d" % (lineno + 1), - "linenumber": "% 6d" % (lineno + 1)} - - return tmpl("fileannotate", - file=f, - annotate=annotate, - path=_up(f), - rev=fctx.rev(), - node=hex(fctx.node()), - author=fctx.user(), - date=fctx.date(), - desc=fctx.description(), - rename=self.renamelink(fl, n), - branch=self.nodebranchnodefault(fctx), - parent=self.siblings(fctx.parents()), - child=self.siblings(fctx.children()), - permissions=fctx.manifest().flags(f)) - - def manifest(self, tmpl, ctx, path): - mf = ctx.manifest() - node = ctx.node() - - files = {} - parity = paritygen(self.stripecount) - - if path and path[-1] != "/": - path += "/" - l = len(path) - abspath = "/" + path - - for f, n in mf.items(): - if f[:l] != path: - continue - remain = f[l:] - if "/" in remain: - short = remain[:remain.index("/") + 1] # bleah - files[short] = (f, None) - else: - short = os.path.basename(remain) - files[short] = (f, n) - - if not files: - raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path) - - def filelist(**map): - fl = files.keys() - fl.sort() - for f in fl: - full, fnode = files[f] - if not fnode: - continue - - fctx = ctx.filectx(full) - yield {"file": full, - "parity": parity.next(), - "basename": f, - "date": fctx.changectx().date(), - "size": fctx.size(), - "permissions": mf.flags(full)} - - def dirlist(**map): - fl = files.keys() - fl.sort() - for f in fl: - full, fnode = files[f] - if fnode: - continue - - yield {"parity": parity.next(), - "path": "%s%s" % (abspath, f), - "basename": f[:-1]} - - return tmpl("manifest", - rev=ctx.rev(), - node=hex(node), - path=abspath, - up=_up(abspath), - upparity=parity.next(), - fentries=filelist, - dentries=dirlist, - archives=self.archivelist(hex(node)), - tags=self.nodetagsdict(node), - inbranch=self.nodeinbranch(ctx), - branches=self.nodebranchdict(ctx)) - - def tags(self, tmpl): - i = self.repo.tagslist() - i.reverse() - parity = paritygen(self.stripecount) - - def entries(notip=False,limit=0, **map): - count = 0 - for k, n in i: - if notip and k == "tip": - continue - if limit > 0 and count >= limit: - continue - count = count + 1 - yield {"parity": parity.next(), - "tag": k, - "date": self.repo.changectx(n).date(), - "node": hex(n)} - - return tmpl("tags", - node=hex(self.repo.changelog.tip()), - entries=lambda **x: entries(False,0, **x), - entriesnotip=lambda **x: entries(True,0, **x), - latestentry=lambda **x: entries(True,1, **x)) - - def summary(self, tmpl): - i = self.repo.tagslist() - i.reverse() - - def tagentries(**map): - parity = paritygen(self.stripecount) - count = 0 - for k, n in i: - if k == "tip": # skip tip - continue; - - count += 1 - if count > 10: # limit to 10 tags - break; - - yield tmpl("tagentry", - parity=parity.next(), - tag=k, - node=hex(n), - date=self.repo.changectx(n).date()) - - - def branches(**map): - parity = paritygen(self.stripecount) - - b = self.repo.branchtags() - l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()] - l.sort() - - for r,n,t in l: - ctx = self.repo.changectx(n) - - yield {'parity': parity.next(), - 'branch': t, - 'node': hex(n), - 'date': ctx.date()} - - def changelist(**map): - parity = paritygen(self.stripecount, offset=start-end) - l = [] # build a list in forward order for efficiency - for i in xrange(start, end): - ctx = self.repo.changectx(i) - n = ctx.node() - hn = hex(n) - - l.insert(0, tmpl( - 'shortlogentry', - parity=parity.next(), - author=ctx.user(), - desc=ctx.description(), - date=ctx.date(), - rev=i, - node=hn, - tags=self.nodetagsdict(n), - inbranch=self.nodeinbranch(ctx), - branches=self.nodebranchdict(ctx))) - - yield l - - cl = self.repo.changelog - count = cl.count() - start = max(0, count - self.maxchanges) - end = min(count, start + self.maxchanges) - - return tmpl("summary", - desc=self.config("web", "description", "unknown"), - owner=get_contact(self.config) or "unknown", - lastchange=cl.read(cl.tip())[2], - tags=tagentries, - branches=branches, - shortlog=changelist, - node=hex(cl.tip()), - archives=self.archivelist("tip")) - - def filediff(self, tmpl, fctx): - n = fctx.node() - path = fctx.path() - parents = fctx.parents() - p1 = parents and parents[0].node() or nullid - - def diff(**map): - yield self.diff(tmpl, p1, n, [path]) - - return tmpl("filediff", - file=path, - node=hex(n), - rev=fctx.rev(), - branch=self.nodebranchnodefault(fctx), - parent=self.siblings(parents), - child=self.siblings(fctx.children()), - diff=diff) - archive_specs = { 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None), 'gz': ('application/x-tar', 'tgz', '.tar.gz', None), 'zip': ('application/zip', 'zip', '.zip', None), } - def archive(self, tmpl, req, key, type_): - reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame)) - cnode = self.repo.lookup(key) - arch_version = key - if cnode == key or key == 'tip': - arch_version = short(cnode) - name = "%s-%s" % (reponame, arch_version) - mimetype, artype, extension, encoding = self.archive_specs[type_] - headers = [ - ('Content-Type', mimetype), - ('Content-Disposition', 'attachment; filename=%s%s' % - (name, extension)) - ] - if encoding: - headers.append(('Content-Encoding', encoding)) - req.header(headers) - req.respond(HTTP_OK) - archival.archive(self.repo, req, cnode, artype, prefix=name) - - # add tags to things - # tags -> list of changesets corresponding to tags - # find tag, changeset, file - - def cleanpath(self, path): - path = path.lstrip('/') - return util.canonpath(self.repo.root, '', path) - - def changectx(self, req): - if 'node' in req.form: - changeid = req.form['node'][0] - elif 'manifest' in req.form: - changeid = req.form['manifest'][0] - else: - changeid = self.repo.changelog.count() - 1 - - try: - ctx = self.repo.changectx(changeid) - except RepoError: - man = self.repo.manifest - mn = man.lookup(changeid) - ctx = self.repo.changectx(man.linkrev(mn)) - - return ctx - - def filectx(self, req): - path = self.cleanpath(req.form['file'][0]) - if 'node' in req.form: - changeid = req.form['node'][0] - else: - changeid = req.form['filenode'][0] - try: - ctx = self.repo.changectx(changeid) - fctx = ctx.filectx(path) - except RepoError: - fctx = self.repo.filectx(path, fileid=changeid) - - return fctx - def check_perm(self, req, op, default): '''check permission for operation based on user auth. return true if op allowed, else false. diff -r 2e58f1a36046 -r d43707e09b02 mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/hgweb/hgwebdir_mod.py Tue Jun 24 09:34:38 2008 +0200 @@ -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, mimetools, cStringIO +import os from mercurial.i18n import gettext as _ from mercurial.repo import RepoError from mercurial import ui, hg, util, templater, templatefilters @@ -81,17 +81,8 @@ virtual = req.env.get("PATH_INFO", "").strip('/') tmpl = self.templater(req) - try: - ctype = tmpl('mimetype', encoding=util._encoding) - ctype = templater.stringify(ctype) - except KeyError: - # old templates with inline HTTP headers? - if 'mimetype' in tmpl: - raise - header = tmpl('header', encoding=util._encoding) - header_file = cStringIO.StringIO(templater.stringify(header)) - msg = mimetools.Message(header_file, 0) - ctype = msg['content-type'] + ctype = tmpl('mimetype', encoding=util._encoding) + ctype = templater.stringify(ctype) # a static file if virtual.startswith('static/') or 'static' in req.form: @@ -257,13 +248,7 @@ def templater(self, req): def header(**map): - header = tmpl('header', encoding=util._encoding, **map) - if 'mimetype' not in tmpl: - # old template with inline HTTP headers - header_file = cStringIO.StringIO(templater.stringify(header)) - msg = mimetools.Message(header_file, 0) - header = header_file.read() - yield header + yield tmpl('header', encoding=util._encoding, **map) def footer(**map): yield tmpl("footer", **map) diff -r 2e58f1a36046 -r d43707e09b02 mercurial/hgweb/server.py --- a/mercurial/hgweb/server.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/hgweb/server.py Tue Jun 24 09:34:38 2008 +0200 @@ -268,12 +268,7 @@ self.addr, self.port = self.socket.getsockname()[0:2] self.prefix = prefix - self.fqaddr = socket.getfqdn(address) - try: - socket.getaddrbyhost(self.fqaddr) - except: - fqaddr = address class IPv6HTTPServer(MercurialHTTPServer): address_family = getattr(socket, 'AF_INET6', None) diff -r 2e58f1a36046 -r d43707e09b02 mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/hgweb/webcommands.py Tue Jun 24 09:34:38 2008 +0200 @@ -5,10 +5,15 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -import os, mimetypes -from mercurial import revlog, util +import os, mimetypes, re, cgi +import webutil +from mercurial import revlog, archival, templatefilters +from mercurial.node import short, hex, nullid +from mercurial.util import binary, datestr from mercurial.repo import RepoError -from common import staticfile, ErrorResponse, HTTP_OK, HTTP_NOT_FOUND +from common import paritygen, staticfile, get_contact, ErrorResponse +from common import HTTP_OK, HTTP_NOT_FOUND +from mercurial import graphmod # __all__ is populated with the allowed commands. Be sure to add to it if # you're adding a new command, or the new command won't work. @@ -16,7 +21,7 @@ __all__ = [ 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev', 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog', - 'archive', 'static', + 'archive', 'static', 'graph', ] def log(web, req, tmpl): @@ -26,17 +31,17 @@ return changelog(web, req, tmpl) def rawfile(web, req, tmpl): - path = web.cleanpath(req.form.get('file', [''])[0]) + path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) if not path: - content = web.manifest(tmpl, web.changectx(req), path) + content = manifest(web, req, tmpl) req.respond(HTTP_OK, web.ctype) return content try: - fctx = web.filectx(req) + fctx = webutil.filectx(web.repo, req) except revlog.LookupError, inst: try: - content = web.manifest(tmpl, web.changectx(req), path) + content = manifest(web, req, tmpl) req.respond(HTTP_OK, web.ctype) return content except ErrorResponse: @@ -45,28 +50,120 @@ path = fctx.path() text = fctx.data() mt = mimetypes.guess_type(path)[0] - if mt is None or util.binary(text): + if mt is None or binary(text): mt = mt or 'application/octet-stream' req.respond(HTTP_OK, mt, path, len(text)) return [text] +def _filerevision(web, tmpl, fctx): + f = fctx.path() + text = fctx.data() + fl = fctx.filelog() + n = fctx.filenode() + parity = paritygen(web.stripecount) + + if binary(text): + mt = mimetypes.guess_type(f)[0] or 'application/octet-stream' + text = '(binary:%s)' % mt + + def lines(): + for lineno, t in enumerate(text.splitlines(1)): + yield {"line": t, + "lineid": "l%d" % (lineno + 1), + "linenumber": "% 6d" % (lineno + 1), + "parity": parity.next()} + + return tmpl("filerevision", + file=f, + path=webutil.up(f), + text=lines(), + rev=fctx.rev(), + node=hex(fctx.node()), + author=fctx.user(), + date=fctx.date(), + desc=fctx.description(), + branch=webutil.nodebranchnodefault(fctx), + parent=webutil.siblings(fctx.parents()), + child=webutil.siblings(fctx.children()), + rename=webutil.renamelink(fctx), + permissions=fctx.manifest().flags(f)) + def file(web, req, tmpl): - path = web.cleanpath(req.form.get('file', [''])[0]) + path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) if path: try: - return web.filerevision(tmpl, web.filectx(req)) + return _filerevision(web, tmpl, webutil.filectx(web.repo, req)) except revlog.LookupError, inst: pass try: - return web.manifest(tmpl, web.changectx(req), path) + return manifest(web, req, tmpl) except ErrorResponse: raise inst +def _search(web, tmpl, query): + + def changelist(**map): + cl = web.repo.changelog + count = 0 + qw = query.lower().split() + + def revgen(): + for i in xrange(cl.count() - 1, 0, -100): + l = [] + for j in xrange(max(0, i - 100), i + 1): + ctx = web.repo.changectx(j) + l.append(ctx) + l.reverse() + for e in l: + yield e + + for ctx in revgen(): + miss = 0 + for q in qw: + if not (q in ctx.user().lower() or + q in ctx.description().lower() or + q in " ".join(ctx.files()).lower()): + miss = 1 + break + if miss: + continue + + count += 1 + n = ctx.node() + showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n) + + yield tmpl('searchentry', + parity=parity.next(), + author=ctx.user(), + parent=webutil.siblings(ctx.parents()), + child=webutil.siblings(ctx.children()), + changelogtag=showtags, + desc=ctx.description(), + date=ctx.date(), + files=web.listfilediffs(tmpl, ctx.files(), n), + rev=ctx.rev(), + node=hex(n), + tags=webutil.nodetagsdict(web.repo, n), + inbranch=webutil.nodeinbranch(web.repo, ctx), + branches=webutil.nodebranchdict(web.repo, ctx)) + + if count >= web.maxchanges: + break + + cl = web.repo.changelog + parity = paritygen(web.stripecount) + + return tmpl('search', + query=query, + node=hex(cl.tip()), + entries=changelist, + archives=web.archivelist("tip")) + def changelog(web, req, tmpl, shortlog = False): if 'node' in req.form: - ctx = web.changectx(req) + ctx = webutil.changectx(web.repo, req) else: if 'rev' in req.form: hi = req.form['rev'][0] @@ -75,47 +172,400 @@ try: ctx = web.repo.changectx(hi) except RepoError: - return web.search(tmpl, hi) # XXX redirect to 404 page? + return _search(web, tmpl, hi) # XXX redirect to 404 page? + + def changelist(limit=0, **map): + cl = web.repo.changelog + l = [] # build a list in forward order for efficiency + for i in xrange(start, end): + ctx = web.repo.changectx(i) + n = ctx.node() + showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n) + + l.insert(0, {"parity": parity.next(), + "author": ctx.user(), + "parent": webutil.siblings(ctx.parents(), i - 1), + "child": webutil.siblings(ctx.children(), i + 1), + "changelogtag": showtags, + "desc": ctx.description(), + "date": ctx.date(), + "files": web.listfilediffs(tmpl, ctx.files(), n), + "rev": i, + "node": hex(n), + "tags": webutil.nodetagsdict(web.repo, n), + "inbranch": webutil.nodeinbranch(web.repo, ctx), + "branches": webutil.nodebranchdict(web.repo, ctx) + }) - return web.changelog(tmpl, ctx, shortlog = shortlog) + if limit > 0: + l = l[:limit] + + for e in l: + yield e + + maxchanges = shortlog and web.maxshortchanges or web.maxchanges + cl = web.repo.changelog + count = cl.count() + pos = ctx.rev() + start = max(0, pos - maxchanges + 1) + end = min(count, start + maxchanges) + pos = end - 1 + parity = paritygen(web.stripecount, offset=start-end) + + changenav = webutil.revnavgen(pos, maxchanges, count, web.repo.changectx) + + return tmpl(shortlog and 'shortlog' or 'changelog', + changenav=changenav, + node=hex(ctx.node()), + rev=pos, changesets=count, + entries=lambda **x: changelist(limit=0,**x), + latestentry=lambda **x: changelist(limit=1,**x), + archives=web.archivelist("tip")) def shortlog(web, req, tmpl): return changelog(web, req, tmpl, shortlog = True) def changeset(web, req, tmpl): - return web.changeset(tmpl, web.changectx(req)) + ctx = webutil.changectx(web.repo, req) + n = ctx.node() + showtags = webutil.showtag(web.repo, tmpl, 'changesettag', n) + parents = ctx.parents() + p1 = parents[0].node() + + files = [] + parity = paritygen(web.stripecount) + for f in ctx.files(): + files.append(tmpl("filenodelink", + node=hex(n), file=f, + parity=parity.next())) + + diffs = web.diff(tmpl, p1, n, None) + return tmpl('changeset', + diff=diffs, + rev=ctx.rev(), + node=hex(n), + parent=webutil.siblings(parents), + child=webutil.siblings(ctx.children()), + changesettag=showtags, + author=ctx.user(), + desc=ctx.description(), + date=ctx.date(), + files=files, + archives=web.archivelist(hex(n)), + tags=webutil.nodetagsdict(web.repo, n), + branch=webutil.nodebranchnodefault(ctx), + inbranch=webutil.nodeinbranch(web.repo, ctx), + branches=webutil.nodebranchdict(web.repo, ctx)) rev = changeset def manifest(web, req, tmpl): - return web.manifest(tmpl, web.changectx(req), - web.cleanpath(req.form['path'][0])) + ctx = webutil.changectx(web.repo, req) + path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) + mf = ctx.manifest() + node = ctx.node() + + files = {} + parity = paritygen(web.stripecount) + + if path and path[-1] != "/": + path += "/" + l = len(path) + abspath = "/" + path + + for f, n in mf.items(): + if f[:l] != path: + continue + remain = f[l:] + if "/" in remain: + short = remain[:remain.index("/") + 1] # bleah + files[short] = (f, None) + else: + short = os.path.basename(remain) + files[short] = (f, n) + + if not files: + raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path) + + def filelist(**map): + fl = files.keys() + fl.sort() + for f in fl: + full, fnode = files[f] + if not fnode: + continue + + fctx = ctx.filectx(full) + yield {"file": full, + "parity": parity.next(), + "basename": f, + "date": fctx.changectx().date(), + "size": fctx.size(), + "permissions": mf.flags(full)} + + def dirlist(**map): + fl = files.keys() + fl.sort() + for f in fl: + full, fnode = files[f] + if fnode: + continue + + yield {"parity": parity.next(), + "path": "%s%s" % (abspath, f), + "basename": f[:-1]} + + return tmpl("manifest", + rev=ctx.rev(), + node=hex(node), + path=abspath, + up=webutil.up(abspath), + upparity=parity.next(), + fentries=filelist, + dentries=dirlist, + archives=web.archivelist(hex(node)), + tags=webutil.nodetagsdict(web.repo, node), + inbranch=webutil.nodeinbranch(web.repo, ctx), + branches=webutil.nodebranchdict(web.repo, ctx)) def tags(web, req, tmpl): - return web.tags(tmpl) + i = web.repo.tagslist() + i.reverse() + parity = paritygen(web.stripecount) + + def entries(notip=False,limit=0, **map): + count = 0 + for k, n in i: + if notip and k == "tip": + continue + if limit > 0 and count >= limit: + continue + count = count + 1 + yield {"parity": parity.next(), + "tag": k, + "date": web.repo.changectx(n).date(), + "node": hex(n)} + + return tmpl("tags", + node=hex(web.repo.changelog.tip()), + entries=lambda **x: entries(False,0, **x), + entriesnotip=lambda **x: entries(True,0, **x), + latestentry=lambda **x: entries(True,1, **x)) def summary(web, req, tmpl): - return web.summary(tmpl) + i = web.repo.tagslist() + i.reverse() + + def tagentries(**map): + parity = paritygen(web.stripecount) + count = 0 + for k, n in i: + if k == "tip": # skip tip + continue + + count += 1 + if count > 10: # limit to 10 tags + break + + yield tmpl("tagentry", + parity=parity.next(), + tag=k, + node=hex(n), + date=web.repo.changectx(n).date()) + + def branches(**map): + parity = paritygen(web.stripecount) + + b = web.repo.branchtags() + l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.items()] + l.sort() + + for r,n,t in l: + ctx = web.repo.changectx(n) + yield {'parity': parity.next(), + 'branch': t, + 'node': hex(n), + 'date': ctx.date()} + + def changelist(**map): + parity = paritygen(web.stripecount, offset=start-end) + l = [] # build a list in forward order for efficiency + for i in xrange(start, end): + ctx = web.repo.changectx(i) + n = ctx.node() + hn = hex(n) + + l.insert(0, tmpl( + 'shortlogentry', + parity=parity.next(), + author=ctx.user(), + desc=ctx.description(), + date=ctx.date(), + rev=i, + node=hn, + tags=webutil.nodetagsdict(web.repo, n), + inbranch=webutil.nodeinbranch(web.repo, ctx), + branches=webutil.nodebranchdict(web.repo, ctx))) + + yield l + + cl = web.repo.changelog + count = cl.count() + start = max(0, count - web.maxchanges) + end = min(count, start + web.maxchanges) + + return tmpl("summary", + desc=web.config("web", "description", "unknown"), + owner=get_contact(web.config) or "unknown", + lastchange=cl.read(cl.tip())[2], + tags=tagentries, + branches=branches, + shortlog=changelist, + node=hex(cl.tip()), + archives=web.archivelist("tip")) def filediff(web, req, tmpl): - return web.filediff(tmpl, web.filectx(req)) + fctx = webutil.filectx(web.repo, req) + n = fctx.node() + path = fctx.path() + parents = fctx.parents() + p1 = parents and parents[0].node() or nullid + + diffs = web.diff(tmpl, p1, n, [path]) + return tmpl("filediff", + file=path, + node=hex(n), + rev=fctx.rev(), + date=fctx.date(), + desc=fctx.description(), + author=fctx.user(), + rename=webutil.renamelink(fctx), + branch=webutil.nodebranchnodefault(fctx), + parent=webutil.siblings(parents), + child=webutil.siblings(fctx.children()), + diff=diffs) diff = filediff def annotate(web, req, tmpl): - return web.fileannotate(tmpl, web.filectx(req)) + fctx = webutil.filectx(web.repo, req) + f = fctx.path() + n = fctx.filenode() + fl = fctx.filelog() + parity = paritygen(web.stripecount) + + def annotate(**map): + last = None + if binary(fctx.data()): + mt = (mimetypes.guess_type(fctx.path())[0] + or 'application/octet-stream') + lines = enumerate([((fctx.filectx(fctx.filerev()), 1), + '(binary:%s)' % mt)]) + else: + lines = enumerate(fctx.annotate(follow=True, linenumber=True)) + for lineno, ((f, targetline), l) in lines: + fnode = f.filenode() + + if last != fnode: + last = fnode + + yield {"parity": parity.next(), + "node": hex(f.node()), + "rev": f.rev(), + "author": f.user(), + "desc": f.description(), + "file": f.path(), + "targetline": targetline, + "line": l, + "lineid": "l%d" % (lineno + 1), + "linenumber": "% 6d" % (lineno + 1)} + + return tmpl("fileannotate", + file=f, + annotate=annotate, + path=webutil.up(f), + rev=fctx.rev(), + node=hex(fctx.node()), + author=fctx.user(), + date=fctx.date(), + desc=fctx.description(), + rename=webutil.renamelink(fctx), + branch=webutil.nodebranchnodefault(fctx), + parent=webutil.siblings(fctx.parents()), + child=webutil.siblings(fctx.children()), + permissions=fctx.manifest().flags(f)) def filelog(web, req, tmpl): - return web.filelog(tmpl, web.filectx(req)) + fctx = webutil.filectx(web.repo, req) + f = fctx.path() + fl = fctx.filelog() + count = fl.count() + pagelen = web.maxshortchanges + pos = fctx.filerev() + start = max(0, pos - pagelen + 1) + end = min(count, start + pagelen) + pos = end - 1 + parity = paritygen(web.stripecount, offset=start-end) + + def entries(limit=0, **map): + l = [] + + for i in xrange(start, end): + ctx = fctx.filectx(i) + n = fl.node(i) + + l.insert(0, {"parity": parity.next(), + "filerev": i, + "file": f, + "node": hex(ctx.node()), + "author": ctx.user(), + "date": ctx.date(), + "rename": webutil.renamelink(fctx), + "parent": webutil.siblings(fctx.parents()), + "child": webutil.siblings(fctx.children()), + "desc": ctx.description()}) + + if limit > 0: + l = l[:limit] + + for e in l: + yield e + + nodefunc = lambda x: fctx.filectx(fileid=x) + nav = webutil.revnavgen(pos, pagelen, count, nodefunc) + return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav, + entries=lambda **x: entries(limit=0, **x), + latestentry=lambda **x: entries(limit=1, **x)) + def archive(web, req, tmpl): type_ = req.form.get('type', [None])[0] allowed = web.configlist("web", "allow_archive") - if (type_ in web.archives and (type_ in allowed or + key = req.form['node'][0] + + if not (type_ in web.archives and (type_ in allowed or web.configbool("web", "allow" + type_, False))): - web.archive(tmpl, req, req.form['node'][0], type_) - return [] - raise ErrorResponse(HTTP_NOT_FOUND, 'unsupported archive type: %s' % type_) + msg = 'Unsupported archive type: %s' % type_ + raise ErrorResponse(HTTP_NOT_FOUND, msg) + + reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame)) + cnode = web.repo.lookup(key) + arch_version = key + if cnode == key or key == 'tip': + arch_version = short(cnode) + name = "%s-%s" % (reponame, arch_version) + mimetype, artype, extension, encoding = web.archive_specs[type_] + headers = [ + ('Content-Type', mimetype), + ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension)) + ] + if encoding: + headers.append(('Content-Encoding', encoding)) + req.header(headers) + req.respond(HTTP_OK) + archival.archive(web.repo, req, cnode, artype, prefix=name) + return [] + def static(web, req, tmpl): fname = req.form['file'][0] @@ -125,3 +575,39 @@ os.path.join(web.templatepath, "static"), untrusted=False) return [staticfile(static, fname, req)] + +def graph(web, req, tmpl): + rev = webutil.changectx(web.repo, req).rev() + bg_height = 39 + + max_rev = web.repo.changelog.count() - 1 + revcount = min(max_rev, int(req.form.get('revcount', [25])[0])) + revnode = web.repo.changelog.node(rev) + revnode_hex = hex(revnode) + uprev = min(max_rev, rev + revcount) + downrev = max(0, rev - revcount) + lessrev = max(0, rev - revcount / 2) + + maxchanges = web.maxshortchanges or web.maxchanges + count = web.repo.changelog.count() + changenav = webutil.revnavgen(rev, maxchanges, count, web.repo.changectx) + + tree = list(graphmod.graph(web.repo, rev, rev - revcount)) + canvasheight = (len(tree) + 1) * bg_height - 27; + + data = [] + for i, (ctx, vtx, edges) in enumerate(tree): + node = short(ctx.node()) + age = templatefilters.age(ctx.date()) + desc = templatefilters.firstline(ctx.description()) + desc = cgi.escape(desc) + user = cgi.escape(templatefilters.person(ctx.user())) + branch = ctx.branch() + branch = branch, web.repo.branchtags().get(branch) == ctx.node() + data.append((node, vtx, edges, desc, user, age, branch, ctx.tags())) + + return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev, + lessrev=lessrev, revcountmore=revcount and 2 * revcount or 1, + revcountless=revcount / 2, downrev=downrev, + canvasheight=canvasheight, bg_height=bg_height, + jsdata=data, node=revnode_hex, changenav=changenav) diff -r 2e58f1a36046 -r d43707e09b02 mercurial/hgweb/webutil.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/hgweb/webutil.py Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,143 @@ +# hgweb/webutil.py - utility library for the web interface. +# +# Copyright 21 May 2005 - (c) 2005 Jake Edge +# Copyright 2005-2007 Matt Mackall +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import os +from mercurial.node import hex, nullid +from mercurial.repo import RepoError +from mercurial import util + +def up(p): + if p[0] != "/": + p = "/" + p + if p[-1] == "/": + p = p[:-1] + up = os.path.dirname(p) + if up == "/": + return "/" + return up + "/" + +def revnavgen(pos, pagelen, limit, nodefunc): + def seq(factor, limit=None): + if limit: + yield limit + if limit >= 20 and limit <= 40: + yield 50 + else: + yield 1 * factor + yield 3 * factor + for f in seq(factor * 10): + yield f + + def nav(**map): + l = [] + last = 0 + for f in seq(1, pagelen): + if f < pagelen or f <= last: + continue + if f > limit: + break + last = f + if pos + f < limit: + l.append(("+%d" % f, hex(nodefunc(pos + f).node()))) + if pos - f >= 0: + l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node()))) + + try: + yield {"label": "(0)", "node": hex(nodefunc('0').node())} + + for label, node in l: + yield {"label": label, "node": node} + + yield {"label": "tip", "node": "tip"} + except RepoError: + pass + + return nav + +def siblings(siblings=[], hiderev=None, **args): + siblings = [s for s in siblings if s.node() != nullid] + if len(siblings) == 1 and siblings[0].rev() == hiderev: + return + for s in siblings: + d = {'node': hex(s.node()), 'rev': s.rev()} + if hasattr(s, 'path'): + d['file'] = s.path() + d.update(args) + yield d + +def renamelink(fctx): + r = fctx.renamed() + if r: + return [dict(file=r[0], node=hex(r[1]))] + return [] + +def nodetagsdict(repo, node): + return [{"name": i} for i in repo.nodetags(node)] + +def nodebranchdict(repo, ctx): + branches = [] + branch = ctx.branch() + # If this is an empty repo, ctx.node() == nullid, + # ctx.branch() == 'default', but branchtags() is + # an empty dict. Using dict.get avoids a traceback. + if repo.branchtags().get(branch) == ctx.node(): + branches.append({"name": branch}) + return branches + +def nodeinbranch(repo, ctx): + branches = [] + branch = ctx.branch() + if branch != 'default' and repo.branchtags().get(branch) != ctx.node(): + branches.append({"name": branch}) + return branches + +def nodebranchnodefault(ctx): + branches = [] + branch = ctx.branch() + if branch != 'default': + branches.append({"name": branch}) + return branches + +def showtag(repo, tmpl, t1, node=nullid, **args): + for t in repo.nodetags(node): + yield tmpl(t1, tag=t, **args) + +def cleanpath(repo, path): + path = path.lstrip('/') + return util.canonpath(repo.root, '', path) + +def changectx(repo, req): + if 'node' in req.form: + changeid = req.form['node'][0] + elif 'manifest' in req.form: + changeid = req.form['manifest'][0] + else: + changeid = repo.changelog.count() - 1 + + try: + ctx = repo.changectx(changeid) + except RepoError: + man = repo.manifest + mn = man.lookup(changeid) + ctx = repo.changectx(man.linkrev(mn)) + + return ctx + +def filectx(repo, req): + path = cleanpath(repo, req.form['file'][0]) + if 'node' in req.form: + changeid = req.form['node'][0] + else: + changeid = req.form['filenode'][0] + try: + ctx = repo.changectx(changeid) + fctx = ctx.filectx(path) + except RepoError: + fctx = repo.filectx(path, fileid=changeid) + + return fctx diff -r 2e58f1a36046 -r d43707e09b02 mercurial/keepalive.py --- a/mercurial/keepalive.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/keepalive.py Tue Jun 24 09:34:38 2008 +0200 @@ -19,6 +19,8 @@ # Modified by Benoit Boissinot: # - fix for digest auth (inspired from urllib2.py @ Python v2.4) +# Modified by Dirkjan Ochtman: +# - import md5 function from a local util module """An HTTP handler for urllib2 that supports HTTP 1.1 and keepalive. @@ -450,7 +452,7 @@ keepalive_handler.close_all() def continuity(url): - import md5 + from util import md5 format = '%25s: %s' # first fetch the file with the normal http handler diff -r 2e58f1a36046 -r d43707e09b02 mercurial/localrepo.py --- a/mercurial/localrepo.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/localrepo.py Tue Jun 24 09:34:38 2008 +0200 @@ -11,6 +11,7 @@ import changelog, dirstate, filelog, manifest, context, weakref import lock, transaction, stat, errno, ui import os, revlog, time, util, extensions, hook, inspect +import match as match_ class localrepository(repo.repository): capabilities = util.set(('lookup', 'changegroupsubset')) @@ -146,7 +147,11 @@ if prevtags and prevtags[-1] != '\n': fp.write('\n') for name in names: - fp.write('%s %s\n' % (hex(node), munge and munge(name) or name)) + m = munge and munge(name) or name + if self._tagstypecache and name in self._tagstypecache: + old = self.tagscache.get(name, nullid) + fp.write('%s %s\n' % (hex(old), m)) + fp.write('%s %s\n' % (hex(node), m)) fp.close() prevtags = '' @@ -302,9 +307,8 @@ n = nh[0] if n != nullid: self.tagscache[k] = n - self._tagstypecache[k] = tagtypes[k] + self._tagstypecache[k] = tagtypes[k] self.tagscache['tip'] = self.changelog.tip() - return self.tagscache def tagtype(self, tagname): @@ -476,6 +480,9 @@ def wjoin(self, f): return os.path.join(self.root, f) + def rjoin(self, f): + return os.path.join(self.root, util.pconvert(f)) + def file(self, f): if f[0] == '/': f = f[1:] @@ -676,19 +683,21 @@ self._wlockref = weakref.ref(l) return l - def filecommit(self, fn, manifest1, manifest2, linkrev, tr, changelist): + def filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist): """ commit an individual file as part of a larger transaction """ - t = self.wread(fn) + fn = fctx.path() + t = fctx.data() fl = self.file(fn) fp1 = manifest1.get(fn, nullid) fp2 = manifest2.get(fn, nullid) meta = {} - cp = self.dirstate.copied(fn) - if cp and cp != fn: + cp = fctx.renamed() + if cp and cp[0] != fn: + cp = cp[0] # Mark the new revision of this file as a copy of another # file. This copy data will effectively act as a parent # of this new revision. If this is a merge, the first @@ -745,66 +754,80 @@ p1=p1, p2=p2, extra=extra, empty_ok=True) def commit(self, files=None, text="", user=None, date=None, - match=util.always, force=False, force_editor=False, + match=None, force=False, force_editor=False, p1=None, p2=None, extra={}, empty_ok=False): - wlock = lock = tr = None - valid = 0 # don't save the dirstate if this isn't set + wlock = lock = None if files: files = util.unique(files) try: wlock = self.wlock() lock = self.lock() - commit = [] - remove = [] - changed = [] use_dirstate = (p1 is None) # not rawcommit - extra = extra.copy() - - if use_dirstate: - if files: - for f in files: - s = self.dirstate[f] - if s in 'nma': - commit.append(f) - elif s == 'r': - remove.append(f) - else: - self.ui.warn(_("%s not tracked!\n") % f) - else: - changes = self.status(match=match)[:5] - modified, added, removed, deleted, unknown = changes - commit = modified + added - remove = removed - else: - commit = files if use_dirstate: p1, p2 = self.dirstate.parents() update_dirstate = True if (not force and p2 != nullid and - (files or match != util.always)): + (match and (match.files() or match.anypats()))): raise util.Abort(_('cannot partially commit a merge ' '(do not specify files or patterns)')) + + if files: + modified, removed = [], [] + for f in files: + s = self.dirstate[f] + if s in 'nma': + modified.append(f) + elif s == 'r': + removed.append(f) + else: + self.ui.warn(_("%s not tracked!\n") % f) + changes = [modified, [], removed, [], []] + else: + changes = self.status(match=match) else: p1, p2 = p1, p2 or nullid update_dirstate = (self.dirstate.parents()[0] == p1) + changes = [files, [], [], [], []] + wctx = context.workingctx(self, (p1, p2), text, user, date, + extra, changes) + return self._commitctx(wctx, force, force_editor, empty_ok, + use_dirstate, update_dirstate) + finally: + del lock, wlock + + def commitctx(self, ctx): + wlock = lock = None + try: + wlock = self.wlock() + lock = self.lock() + return self._commitctx(ctx, force=True, force_editor=False, + empty_ok=True, use_dirstate=False, + update_dirstate=False) + finally: + del lock, wlock + + def _commitctx(self, wctx, force=False, force_editor=False, empty_ok=False, + use_dirstate=True, update_dirstate=True): + tr = None + valid = 0 # don't save the dirstate if this isn't set + try: + commit = wctx.modified() + wctx.added() + remove = wctx.removed() + extra = wctx.extra().copy() + branchname = extra['branch'] + user = wctx.user() + text = wctx.description() + + p1, p2 = [p.node() for p in wctx.parents()] c1 = self.changelog.read(p1) c2 = self.changelog.read(p2) m1 = self.manifest.read(c1[0]).copy() m2 = self.manifest.read(c2[0]) if use_dirstate: - branchname = self.workingctx().branch() - try: - branchname = branchname.decode('UTF-8').encode('UTF-8') - except UnicodeDecodeError: - raise util.Abort(_('branch name not in UTF-8!')) - else: - branchname = "" - - if use_dirstate: oldname = c1[5].get("branch") # stored in UTF-8 if (not commit and not remove and not force and p2 == nullid and branchname == oldname): @@ -822,16 +845,16 @@ # check in files new = {} + changed = [] linkrev = self.changelog.count() commit.sort() - is_exec = util.execfunc(self.root, m1.execf) - is_link = util.linkfunc(self.root, m1.linkf) for f in commit: self.ui.note(f + "\n") try: - new[f] = self.filecommit(f, m1, m2, linkrev, trp, changed) - new_exec = is_exec(f) - new_link = is_link(f) + fctx = wctx.filectx(f) + new[f] = self.filecommit(fctx, m1, m2, linkrev, trp, changed) + new_exec = fctx.isexec() + new_link = fctx.islink() if ((not changed or changed[-1] != f) and m2.get(f) != new[f]): # mention the file in the changelog if some @@ -867,10 +890,6 @@ (new, removed)) # add changeset - new = new.keys() - new.sort() - - user = user or self.ui.username() if (not empty_ok and not text) or force_editor: edittext = [] if text: @@ -895,9 +914,6 @@ text = self.ui.edit("\n".join(edittext), user) os.chdir(olddir) - if branchname: - extra["branch"] = branchname - lines = [line.rstrip() for line in text.rstrip().splitlines()] while lines and not lines[0]: del lines[0] @@ -906,7 +922,7 @@ text = '\n'.join(lines) n = self.changelog.add(mn, changed + removed, text, trp, p1, p2, - user, date, extra) + user, wctx.date(), extra) self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1, parent2=xp2) tr.close() @@ -926,23 +942,17 @@ finally: if not valid: # don't save our updated dirstate self.dirstate.invalidate() - del tr, lock, wlock + del tr - def walk(self, node=None, files=[], match=util.always, badmatch=None): + def walk(self, match, node=None): ''' walk recursively through the directory tree or a given changeset, finding all files matched by the match function - - results are yielded in a tuple (src, filename), where src - is one of: - 'f' the file was found in the directory tree - 'm' the file was only in the dirstate and not in the tree - 'b' file was not found and matched badmatch ''' if node: - fdict = dict.fromkeys(files) + fdict = dict.fromkeys(match.files()) # for dirstate.walk, files=['.'] means "walk the whole tree". # follow that here, too fdict.pop('.', None) @@ -956,21 +966,18 @@ del fdict[ffn] break if match(fn): - yield 'm', fn + yield fn ffiles = fdict.keys() ffiles.sort() for fn in ffiles: - if badmatch and badmatch(fn): - if match(fn): - yield 'b', fn - else: - self.ui.warn(_('%s: No such file in rev %s\n') - % (self.pathto(fn), short(node))) + if match.bad(fn, 'No such file in rev ' + short(node)) \ + and match(fn): + yield fn else: - for src, fn in self.dirstate.walk(files, match, badmatch=badmatch): - yield src, fn + for fn in self.dirstate.walk(match): + yield fn - def status(self, node1=None, node2=None, files=[], match=util.always, + def status(self, node1=None, node2=None, match=None, list_ignored=False, list_clean=False, list_unknown=True): """return status of files between two nodes or node and working directory @@ -990,6 +997,9 @@ del mf[fn] return mf + if not match: + match = match_.always(self.root, self.getcwd()) + modified, added, removed, deleted, unknown = [], [], [], [], [] ignored, clean = [], [] @@ -1006,10 +1016,8 @@ # are we comparing the working directory? if not node2: (lookup, modified, added, removed, deleted, unknown, - ignored, clean) = self.dirstate.status(files, match, - list_ignored, list_clean, - list_unknown) - + ignored, clean) = self.dirstate.status(match, list_ignored, + list_clean, list_unknown) # are we comparing working dir against its parent? if compareworking: if lookup: @@ -1196,7 +1204,8 @@ heads.sort() return [n for (r, n) in heads] - def branchheads(self, branch, start=None): + def branchheads(self, branch=None, start=None): + branch = branch or self.workingctx().branch() branches = self.branchtags() if branch not in branches: return [] @@ -1989,7 +1998,7 @@ self.ui.status(_("adding changesets\n")) cor = cl.count() - 1 chunkiter = changegroup.chunkiter(source) - if cl.addgroup(chunkiter, csmap, trp, 1) is None and not emptyok: + if cl.addgroup(chunkiter, csmap, trp) is None and not emptyok: raise util.Abort(_("received changelog group is empty")) cnr = cl.count() - 1 changesets = cnr - cor diff -r 2e58f1a36046 -r d43707e09b02 mercurial/mail.py --- a/mercurial/mail.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/mail.py Tue Jun 24 09:34:38 2008 +0200 @@ -6,7 +6,8 @@ # of the GNU General Public License, incorporated herein by reference. from i18n import _ -import os, smtplib, util, socket +import os, smtplib, socket +import util def _smtp(ui): '''build an smtp connection and return a function to send mail''' @@ -53,7 +54,7 @@ cmdline = '%s -f %s %s' % (program, util.email(sender), ' '.join(map(util.email, recipients))) ui.note(_('sending mail: %s\n') % cmdline) - fp = os.popen(cmdline, 'w') + fp = util.popen(cmdline, 'w') fp.write(msg) ret = fp.close() if ret: diff -r 2e58f1a36046 -r d43707e09b02 mercurial/manifest.py --- a/mercurial/manifest.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/manifest.py Tue Jun 24 09:34:38 2008 +0200 @@ -8,7 +8,7 @@ from node import bin, hex, nullid from revlog import revlog, RevlogError from i18n import _ -import array, struct, mdiff +import array, struct, mdiff, parsers class manifestdict(dict): def __init__(self, mapping=None, flags=None): @@ -39,14 +39,7 @@ def parse(self, lines): mfdict = manifestdict() - fdict = mfdict._flags - for l in lines.splitlines(): - f, n = l.split('\0') - if len(n) > 40: - fdict[f] = n[40:] - mfdict[f] = bin(n[:40]) - else: - mfdict[f] = bin(n) + parsers.parse_manifest(mfdict, mfdict._flags, lines) return mfdict def readdelta(self, node): diff -r 2e58f1a36046 -r d43707e09b02 mercurial/match.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/match.py Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,47 @@ +import util + +class _match(object): + def __init__(self, root, cwd, files, mf, ap): + self._root = root + self._cwd = cwd + self._files = files + self._fmap = dict.fromkeys(files) + self._matchfn = mf + self._anypats = ap + def __call__(self, fn): + return self._matchfn(fn) + def __iter__(self): + for f in self._files: + yield f + def bad(self, f, msg): + return True + def dir(self, f): + pass + def missing(self, f): + pass + def exact(self, f): + return f in self._fmap + def rel(self, f): + return util.pathto(self._root, self._cwd, f) + def files(self): + return self._files + def anypats(self): + return self._anypats + +class always(_match): + def __init__(self, root, cwd): + _match.__init__(self, root, cwd, [], lambda f: True, False) + +class never(_match): + def __init__(self, root, cwd): + _match.__init__(self, root, cwd, [], lambda f: False, False) + +class exact(_match): + def __init__(self, root, cwd, files): + _match.__init__(self, root, cwd, files, lambda f: f in files, False) + +class match(_match): + def __init__(self, root, cwd, patterns, include, exclude, default): + f, mf, ap = util.matcher(root, cwd, patterns, include, exclude, + None, default) + _match.__init__(self, root, cwd, f, mf, ap) diff -r 2e58f1a36046 -r d43707e09b02 mercurial/mdiff.py --- a/mercurial/mdiff.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/mdiff.py Tue Jun 24 09:34:38 2008 +0200 @@ -6,7 +6,7 @@ # of the GNU General Public License, incorporated herein by reference. from i18n import _ -import bdiff, mpatch, re, struct, util, md5 +import bdiff, mpatch, re, struct, util def splitnewlines(text): '''like str.splitlines, but only split on newlines.''' @@ -80,7 +80,7 @@ if not opts.text and (util.binary(a) or util.binary(b)): def h(v): # md5 is used instead of sha1 because md5 is supposedly faster - return md5.new(v).digest() + return util.md5(v).digest() if a and b and len(a) == len(b) and h(a) == h(b): return "" l = ['Binary file %s has changed\n' % fn1] diff -r 2e58f1a36046 -r d43707e09b02 mercurial/merge.py --- a/mercurial/merge.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/merge.py Tue Jun 24 09:34:38 2008 +0200 @@ -5,9 +5,70 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from node import nullid, nullrev +from node import nullid, nullrev, hex, bin from i18n import _ -import errno, util, os, filemerge, copies +import errno, util, os, filemerge, copies, shutil + +class mergestate(object): + '''track 3-way merge state of individual files''' + def __init__(self, repo): + self._repo = repo + self._read() + def reset(self, node): + self._state = {} + self._local = node + shutil.rmtree(self._repo.join("merge"), True) + def _read(self): + self._state = {} + try: + localnode = None + f = self._repo.opener("merge/state") + for i, l in enumerate(f): + if i == 0: + localnode = l[:-1] + else: + bits = l[:-1].split("\0") + self._state[bits[0]] = bits[1:] + self._local = bin(localnode) + except IOError, err: + if err.errno != errno.ENOENT: + raise + def _write(self): + f = self._repo.opener("merge/state", "w") + f.write(hex(self._local) + "\n") + for d, v in self._state.items(): + f.write("\0".join([d] + v) + "\n") + def add(self, fcl, fco, fca, fd, flags): + hash = util.sha1(fcl.path()).hexdigest() + self._repo.opener("merge/" + hash, "w").write(fcl.data()) + self._state[fd] = ['u', hash, fcl.path(), fca.path(), + hex(fca.filenode()), fco.path(), flags] + self._write() + def __contains__(self, dfile): + return dfile in self._state + def __getitem__(self, dfile): + return self._state[dfile][0] + def __iter__(self): + l = self._state.keys() + l.sort() + for f in l: + yield f + def mark(self, dfile, state): + self._state[dfile][0] = state + self._write() + def resolve(self, dfile, wctx, octx): + if self[dfile] == 'r': + return 0 + state, hash, lfile, afile, anode, ofile, flags = self._state[dfile] + f = self._repo.opener("merge/" + hash) + self._repo.wwrite(dfile, f.read(), flags) + fcd = wctx[dfile] + fco = octx[ofile] + fca = self._repo.filectx(afile, fileid=anode) + r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca) + if not r: + self.mark(dfile, 'r') + return r def _checkunknown(wctx, mctx): "check for collisions between unknown files and files in mctx" @@ -202,14 +263,29 @@ updated, merged, removed, unresolved = 0, 0, 0, 0 action.sort() - # prescan for copy/renames + + ms = mergestate(repo) + ms.reset(wctx.parents()[0].node()) + moves = [] + + # prescan for merges for a in action: f, m = a[:2] if m == 'm': # merge f2, fd, flags, move = a[2:] - if f != fd: - repo.ui.debug(_("copying %s to %s\n") % (f, fd)) - repo.wwrite(fd, repo.wread(f), flags) + repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd)) + fcl = wctx[f] + fco = mctx[f2] + fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev) + ms.add(fcl, fco, fca, fd, flags) + if f != fd and move: + moves.append(f) + + # remove renamed files after safely stored + for f in moves: + if util.lexists(repo.wjoin(f)): + repo.ui.debug(_("removing %s\n") % f) + os.unlink(repo.wjoin(f)) audit_path = util.path_auditor(repo.root) @@ -229,7 +305,7 @@ removed += 1 elif m == "m": # merge f2, fd, flags, move = a[2:] - r = filemerge.filemerge(repo, f, fd, f2, wctx, mctx) + r = ms.resolve(fd, wctx, mctx) if r > 0: unresolved += 1 else: @@ -237,10 +313,6 @@ updated += 1 else: merged += 1 - util.set_flags(repo.wjoin(fd), flags) - if f != fd and move and util.lexists(repo.wjoin(f)): - repo.ui.debug(_("removing %s\n") % f) - os.unlink(repo.wjoin(f)) elif m == "g": # get flags = a[2] repo.ui.note(_("getting %s\n") % f) diff -r 2e58f1a36046 -r d43707e09b02 mercurial/parsers.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/parsers.c Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,169 @@ +/* + parsers.c - efficient content parsing + + Copyright 2008 Matt Mackall and others + + This software may be used and distributed according to the terms of + the GNU General Public License, incorporated herein by reference. +*/ + +#include +#include +#include + +static int hexdigit(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + return -1; +} + +/* + * Turn a hex-encoded string into binary. + */ +static PyObject *unhexlify(const char *str, int len) +{ + PyObject *ret = NULL; + const char *c; + char *d; + + if (len % 2) { + PyErr_SetString(PyExc_ValueError, + "input is not even in length"); + goto bail; + } + + ret = PyString_FromStringAndSize(NULL, len / 2); + if (!ret) + goto bail; + + d = PyString_AsString(ret); + if (!d) + goto bail; + + for (c = str; c < str + len;) { + int hi = hexdigit(*c++); + int lo = hexdigit(*c++); + + if (hi == -1 || lo == -1) { + PyErr_SetString(PyExc_ValueError, + "input contains non-hex character"); + goto bail; + } + + *d++ = (hi << 4) | lo; + } + + goto done; + +bail: + Py_XDECREF(ret); + ret = NULL; +done: + return ret; +} + +/* + * This code assumes that a manifest is stitched together with newline + * ('\n') characters. + */ +static PyObject *parse_manifest(PyObject *self, PyObject *args) +{ + PyObject *mfdict, *fdict; + char *str, *cur, *start, *zero; + int len; + + if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest", + &PyDict_Type, &mfdict, + &PyDict_Type, &fdict, + &str, &len)) + goto quit; + + for (start = cur = str, zero = NULL; cur < str + len; cur++) { + PyObject *file = NULL, *node = NULL; + PyObject *flags = NULL; + int nlen; + + if (!*cur) { + zero = cur; + continue; + } + else if (*cur != '\n') + continue; + + if (!zero) { + PyErr_SetString(PyExc_ValueError, + "manifest entry has no separator"); + goto quit; + } + + file = PyString_FromStringAndSize(start, zero - start); + if (!file) + goto bail; + + nlen = cur - zero - 1; + + node = unhexlify(zero + 1, nlen > 40 ? 40 : nlen); + if (!node) + goto bail; + + if (nlen > 40) { + PyObject *flags; + + flags = PyString_FromStringAndSize(zero + 41, + nlen - 40); + if (!flags) + goto bail; + + if (PyDict_SetItem(fdict, file, flags) == -1) + goto bail; + } + + if (PyDict_SetItem(mfdict, file, node) == -1) + goto bail; + + start = cur + 1; + zero = NULL; + + Py_XDECREF(flags); + Py_XDECREF(node); + Py_XDECREF(file); + continue; + bail: + Py_XDECREF(flags); + Py_XDECREF(node); + Py_XDECREF(file); + goto quit; + } + + if (len > 0 && *(cur - 1) != '\n') { + PyErr_SetString(PyExc_ValueError, + "manifest contains trailing garbage"); + goto quit; + } + + Py_INCREF(Py_None); + return Py_None; + +quit: + return NULL; +} + +static char parsers_doc[] = "Efficient content parsing."; + +static PyMethodDef methods[] = { + {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"}, + {NULL, NULL} +}; + +PyMODINIT_FUNC initparsers(void) +{ + Py_InitModule3("parsers", methods, parsers_doc); +} diff -r 2e58f1a36046 -r d43707e09b02 mercurial/patch.py --- a/mercurial/patch.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/patch.py Tue Jun 24 09:34:38 2008 +0200 @@ -9,7 +9,7 @@ from i18n import _ from node import hex, nullid, short import base85, cmdutil, mdiff, util, context, revlog, diffhelpers, copies -import cStringIO, email.Parser, os, popen2, re, sha, errno +import cStringIO, email.Parser, os, popen2, re, errno import sys, tempfile, zlib class PatchError(Exception): @@ -1120,7 +1120,7 @@ if not text: return '0' * 40 l = len(text) - s = sha.new('blob %d\0' % l) + s = util.sha1('blob %d\0' % l) s.update(text) return s.hexdigest() @@ -1152,7 +1152,7 @@ ret.append('\n') return ''.join(ret) -def diff(repo, node1=None, node2=None, files=None, match=util.always, +def diff(repo, node1=None, node2=None, match=None, fp=None, changes=None, opts=None): '''print diff of changes to files between two nodes, or node and working directory. @@ -1160,6 +1160,9 @@ if node1 is None, use first dirstate parent instead. if node2 is None, compare node1 with working directory.''' + if not match: + match = cmdutil.matchall(repo) + if opts is None: opts = mdiff.defaultopts if fp is None: @@ -1168,12 +1171,6 @@ if not node1: node1 = repo.dirstate.parents()[0] - ccache = {} - def getctx(r): - if r not in ccache: - ccache[r] = context.changectx(repo, r) - return ccache[r] - flcache = {} def getfilectx(f, ctx): flctx = ctx.filectx(f, filelog=flcache.get(f)) @@ -1189,7 +1186,7 @@ date1 = util.datestr(ctx1.date()) if not changes: - changes = repo.status(node1, node2, files, match=match)[:5] + changes = repo.status(node1, node2, match=match)[:5] modified, added, removed, deleted, unknown = changes if not modified and not added and not removed: diff -r 2e58f1a36046 -r d43707e09b02 mercurial/repair.py --- a/mercurial/repair.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/repair.py Tue Jun 24 09:34:38 2008 +0200 @@ -72,7 +72,6 @@ def strip(ui, repo, node, backup="all"): cl = repo.changelog # TODO delete the undo files, and handle undo of merge sets - pp = cl.parents(node) striprev = cl.rev(node) # Some revisions with rev > striprev may not be descendants of striprev. diff -r 2e58f1a36046 -r d43707e09b02 mercurial/repo.py --- a/mercurial/repo.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/repo.py Tue Jun 24 09:34:38 2008 +0200 @@ -40,3 +40,9 @@ def cancopy(self): return self.local() + + def rjoin(self, path): + url = self.url() + if url.endswith('/'): + return url + path + return url + '/' + path diff -r 2e58f1a36046 -r d43707e09b02 mercurial/revlog.py --- a/mercurial/revlog.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/revlog.py Tue Jun 24 09:34:38 2008 +0200 @@ -13,13 +13,13 @@ from node import bin, hex, nullid, nullrev, short from i18n import _ import changegroup, errno, ancestor, mdiff -import sha, struct, util, zlib +import struct, util, zlib _pack = struct.pack _unpack = struct.unpack _compress = zlib.compress _decompress = zlib.decompress -_sha = sha.new +_sha = util.sha1 # revlog flags REVLOGV0 = 0 @@ -32,13 +32,16 @@ class RevlogError(Exception): pass -class LookupError(RevlogError): +class LookupError(RevlogError, KeyError): def __init__(self, name, index, message): self.name = name if isinstance(name, str) and len(name) == 20: name = short(name) RevlogError.__init__(self, _('%s@%s: %s') % (index, name, message)) + def __str__(self): + return RevlogError.__str__(self) + def getoffset(q): return int(q >> 16) @@ -1133,7 +1136,7 @@ yield changegroup.closechunk() - def addgroup(self, revs, linkmapper, transaction, unique=0): + def addgroup(self, revs, linkmapper, transaction): """ add a delta group @@ -1170,8 +1173,6 @@ link = linkmapper(cs) if node in self.nodemap: # this can happen if two branches make the same change - # if unique: - # raise RevlogError(_("already have %s") % hex(node[:4])) chain = node continue delta = buffer(chunk, 80) diff -r 2e58f1a36046 -r d43707e09b02 mercurial/templatefilters.py --- a/mercurial/templatefilters.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/templatefilters.py Tue Jun 24 09:34:38 2008 +0200 @@ -122,6 +122,36 @@ .replace("'", ''')) # ' invalid in HTML return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text) +_escapes = [ + ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'), + ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'), +] + +def json(obj): + if obj is None or obj is False or obj is True: + return {None: 'null', False: 'false', True: 'true'}[obj] + elif isinstance(obj, int) or isinstance(obj, float): + return str(obj) + elif isinstance(obj, str): + for k, v in _escapes: + obj = obj.replace(k, v) + return '"%s"' % obj + elif isinstance(obj, unicode): + return json(obj.encode('utf-8')) + elif hasattr(obj, 'keys'): + out = [] + for k, v in obj.iteritems(): + s = '%s: %s' % (json(k), json(v)) + out.append(s) + return '{' + ', '.join(out) + '}' + elif hasattr(obj, '__iter__'): + out = [] + for i in obj: + out.append(json(i)) + return '[' + ', '.join(out) + ']' + else: + raise TypeError('cannot encode type %s' % obj.__class__.__name__) + filters = { "addbreaks": nl2br, "basename": os.path.basename, @@ -150,5 +180,5 @@ "user": lambda x: util.shortuser(x), "stringescape": lambda x: x.encode('string_escape'), "xmlescape": xmlescape, - } - + "json": json, +} diff -r 2e58f1a36046 -r d43707e09b02 mercurial/templater.py --- a/mercurial/templater.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/templater.py Tue Jun 24 09:34:38 2008 +0200 @@ -114,7 +114,7 @@ v = v(**map) if format: if not hasattr(v, '__iter__'): - raise SyntaxError(_("Error expanding '%s%s'") + raise SyntaxError(_("Error expanding '%s%%%s'") % (key, format)) lm = map.copy() for i in v: diff -r 2e58f1a36046 -r d43707e09b02 mercurial/transaction.py --- a/mercurial/transaction.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/transaction.py Tue Jun 24 09:34:38 2008 +0200 @@ -96,9 +96,13 @@ files = {} for l in open(file).readlines(): f, o = l.split('\0') - files[f] = o + files[f] = int(o) for f in files: o = files[f] - opener(f, "a").truncate(int(o)) + if o: + opener(f, "a").truncate(int(o)) + else: + fn = opener(f).name + os.unlink(fn) os.unlink(file) diff -r 2e58f1a36046 -r d43707e09b02 mercurial/util.py --- a/mercurial/util.py Tue Jun 24 09:33:13 2008 +0200 +++ b/mercurial/util.py Tue Jun 24 09:34:38 2008 +0200 @@ -15,7 +15,9 @@ from i18n import _ import cStringIO, errno, getpass, re, shutil, sys, tempfile import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil -import urlparse +import imp, urlparse + +# Python compatibility try: set = set @@ -23,6 +25,30 @@ except NameError: from sets import Set as set, ImmutableSet as frozenset +_md5 = None +def md5(s): + global _md5 + if _md5 is None: + try: + import hashlib + _md5 = hashlib.md5 + except ImportError: + import md5 + _md5 = md5.md5 + return _md5(s) + +_sha1 = None +def sha1(s): + global _sha1 + if _sha1 is None: + try: + import hashlib + _sha1 = hashlib.sha1 + except ImportError: + import sha + _sha1 = sha.sha + return _sha1(s) + try: _encoding = os.environ.get("HGENCODING") if sys.platform == 'darwin' and not _encoding: @@ -217,8 +243,8 @@ return pipefilter(s, cmd) def binary(s): - """return true if a string is binary data using diff's heuristic""" - if s and '\0' in s[:4096]: + """return true if a string is binary data""" + if s and '\0' in s: return True return False @@ -251,12 +277,12 @@ ret.append(p) return ret -def patkind(name, dflt_pat='glob'): +def patkind(name, default): """Split a string into an optional pattern kind prefix and the actual pattern.""" for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre': if name.startswith(prefix + ':'): return name.split(':', 1) - return dflt_pat, name + return default, name def globre(pat, head='^', tail='$'): "convert a glob pattern into a regexp" @@ -386,17 +412,7 @@ raise Abort('%s not under root' % myname) -def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None): - return _matcher(canonroot, cwd, names, inc, exc, 'glob', src) - -def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, - globbed=False, default=None): - default = default or 'relpath' - if default == 'relpath' and not globbed: - names = expand_glob(names) - return _matcher(canonroot, cwd, names, inc, exc, default, src) - -def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src): +def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'): """build a function to match a set of file patterns arguments: @@ -537,13 +553,29 @@ _hgexecutable = None +def main_is_frozen(): + """return True if we are a frozen executable. + + The code supports py2exe (most common, Windows only) and tools/freeze + (portable, not much used). + """ + return (hasattr(sys, "frozen") or # new py2exe + hasattr(sys, "importers") or # old py2exe + imp.is_frozen("__main__")) # tools/freeze + def hgexecutable(): """return location of the 'hg' executable. Defaults to $HG or 'hg' in the search path. """ if _hgexecutable is None: - set_hgexecutable(os.environ.get('HG') or find_exe('hg', 'hg')) + hg = os.environ.get('HG') + if hg: + set_hgexecutable(hg) + elif main_is_frozen(): + set_hgexecutable(sys.executable) + else: + set_hgexecutable(find_exe('hg', 'hg')) return _hgexecutable def set_hgexecutable(path): @@ -827,6 +859,53 @@ except: return True +_fspathcache = {} +def fspath(name, root): + '''Get name in the case stored in the filesystem + + The name is either relative to root, or it is an absolute path starting + with root. Note that this function is unnecessary, and should not be + called, for case-sensitive filesystems (simply because it's expensive). + ''' + # If name is absolute, make it relative + if name.lower().startswith(root.lower()): + l = len(root) + if name[l] == os.sep or name[l] == os.altsep: + l = l + 1 + name = name[l:] + + if not os.path.exists(os.path.join(root, name)): + return None + + seps = os.sep + if os.altsep: + seps = seps + os.altsep + # Protect backslashes. This gets silly very quickly. + seps.replace('\\','\\\\') + pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps)) + dir = os.path.normcase(os.path.normpath(root)) + result = [] + for part, sep in pattern.findall(name): + if sep: + result.append(sep) + continue + + if dir not in _fspathcache: + _fspathcache[dir] = os.listdir(dir) + contents = _fspathcache[dir] + + lpart = part.lower() + for n in contents: + if n.lower() == lpart: + result.append(n) + break + else: + # Cannot happen, as the file exists! + result.append(part) + dir = os.path.join(dir, lpart) + + return ''.join(result) + def checkexec(path): """ Check whether the given path is on a filesystem with UNIX-like exec flags @@ -1044,12 +1123,12 @@ # through the current COMSPEC. cmd.exe suppress enclosing quotes. return '"' + cmd + '"' - def popen(command): + def popen(command, mode='r'): # Work around "popen spawned process may not write to stdout # under windows" # http://bugs.python.org/issue1366 command += " 2> %s" % nulldev - return os.popen(quotecommand(command)) + return os.popen(quotecommand(command), mode) def explain_exit(code): return _("exited with status %d") % code, code @@ -1210,8 +1289,8 @@ def quotecommand(cmd): return cmd - def popen(command): - return os.popen(command) + def popen(command, mode='r'): + return os.popen(command, mode) def testpid(pid): '''return False if pid dead, True if running or not sure''' diff -r 2e58f1a36046 -r d43707e09b02 mercurial/version.py diff -r 2e58f1a36046 -r d43707e09b02 setup.py --- a/setup.py Tue Jun 24 09:33:13 2008 +0200 +++ b/setup.py Tue Jun 24 09:34:38 2008 +0200 @@ -19,6 +19,9 @@ import mercurial.version extra = {} +scripts = ['hg'] +if os.name == 'nt': + scripts.append('contrib/win32/hg.bat') # simplified version of distutils.ccompiler.CCompiler.has_function # that actually removes its temporary files. @@ -88,10 +91,11 @@ cmdclass = {'install_data': install_package_data} ext_modules=[ - Extension('mercurial.mpatch', ['mercurial/mpatch.c']), + Extension('mercurial.base85', ['mercurial/base85.c']), Extension('mercurial.bdiff', ['mercurial/bdiff.c']), - Extension('mercurial.base85', ['mercurial/base85.c']), - Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']) + Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']), + Extension('mercurial.mpatch', ['mercurial/mpatch.c']), + Extension('mercurial.parsers', ['mercurial/parsers.c']), ] packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert'] @@ -118,7 +122,7 @@ url='http://selenic.com/mercurial', description='Scalable distributed SCM', license='GNU GPL', - scripts=['hg'], + scripts=scripts, packages=packages, ext_modules=ext_modules, data_files=[(os.path.join('mercurial', root), diff -r 2e58f1a36046 -r d43707e09b02 templates/changelog.tmpl --- a/templates/changelog.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/changelog.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -9,6 +9,7 @@
shortlog +graph tags files #archives%archiveentry# diff -r 2e58f1a36046 -r d43707e09b02 templates/changeset.tmpl --- a/templates/changeset.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/changeset.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -6,6 +6,7 @@
changelog shortlog +graph tags files raw diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/changeset.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/changeset.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,72 @@ +{header} +{repo|escape}: {node|short} + + +
+ + +
+ +

{repo|escape}

+

changeset {rev}:{node|short} {changesettag}

+ + + +
{desc|strip|escape|addbreaks}
+ + + + + + + + + + + + + + + + + + + + + +
author{author|obfuscate}
date{date|date} ({date|age} ago)
parents{parent%changesetparent}
children{child%changesetchild}
files{files}
+ + +
+ + + + + +
linediff
+{diff} +
+
+{footer} + + diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/error.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/error.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,40 @@ +{header} +{repo|escape}: error + + + +
+ + +
+ +

{repo|escape}

+

error

+ + + +
+

+An error occurred while processing your request: +

+

+{error|escape} +

+
+
+
+ +{footer} diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/fileannotate.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/fileannotate.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,77 @@ +{header} +{repo|escape}: {file|escape} annotate + + + +
+ + +
+

{repo|escape}

+

annotate {file|escape} @ {rev}:{node|short}

+ + + +
{desc|strip|escape|addbreaks}
+ + + + + + + + + + + + + + + + + + +{changesettag} +
author{author|obfuscate}
date{date|date} ({date|age} ago)
parents{parent%filerevparent}
children{child%filerevchild}
+ +
+ +
+ + + + + + +{annotate%annotateline} +
revlinesource
+
+
+
+ +{footer} diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/filediff.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/filediff.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,75 @@ +{header} +{repo|escape}: {file|escape} diff + + + +
+ + +
+

{repo|escape}

+

diff {file|escape} @ {rev}:{node|short}

+ + + +
{desc|strip|escape|addbreaks}
+ + + + + + + + + + + + + + + + + + +{changesettag} +
author{author|obfuscate}
date{date|date} ({date|age} ago)
parents{parent%filerevparent}
children{child%filerevchild}
+ +
+ + + + + +
linediff
+{diff} + + + + +{footer} + + diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/filelog.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/filelog.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,59 @@ +{header} +{repo|escape}: {file|escape} history + + + + + + +
+ + +
+ +

{repo|escape}

+

log {file|escape}

+ + +{sessionvars%hiddenformentry} +

+ + + + +
+ + +{entries%filelogentry} +
age + author + description +
+ +
+
+ +{footer} diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/filelogentry.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/filelogentry.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,5 @@ + + {date|age} + {author|person} + {desc|strip|firstline|escape} + diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/filerevision.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/filerevision.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,74 @@ +{header} +{repo|escape}: {node|short} {file|escape} + + + +
+ + +
+ +

{repo|escape}

+

view {file|escape} @ {rev}:{node|short}

+ + + +
{desc|strip|escape|addbreaks}
+ + + + + + + + + + + + + + + + + + +{changesettag} +
author{author|obfuscate}
date{date|date} ({date|age} ago)
parents{parent%filerevparent}
children{child%filerevchild}
+ +
+ + + + + +{text%fileline} +
linesource
+
+
+
+ +{footer} diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/footer.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/footer.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,4 @@ +{motd} + + + diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/graph.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/graph.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,114 @@ +{header} +{repo|escape}: revision graph + + + + + + +
+ + +
+

{repo|escape}

+

graph

+ + + + + +
The revision graph only works with JavaScript-enabled browsers.
+ +
+
    + +
      +
      + + + + + + +
      +
      + +{footer} diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/header.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/header.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,7 @@ + + + + + + + diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/index.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/index.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,19 @@ +{header} +Mercurial repositories index + + + +

      Mercurial Repositories

      + + + + + + + + + + {entries%indexentry} +
      NameDescriptionContactLast change 
      + +{footer} diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/manifest.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/manifest.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,52 @@ +{header} +{repo|escape}: {node|short} {path|escape} + + + +
      + + +
      + +

      {repo|escape}

      +

      directory {path|escape} @ {rev}:{node|short} {tags%changelogtag}

      + + + + + + + + + + + + + + +{dentries%direntry} +{fentries%fileentry} +
      namesizepermissions
      [up]drwxr-xr-x
      +
      +
      +{footer} diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/map --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/map Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,70 @@ +default = 'shortlog' + +mimetype = 'text/html; charset={encoding}' +header = header.tmpl +footer = footer.tmpl +search = search.tmpl + +changelog = shortlog.tmpl +shortlog = shortlog.tmpl +shortlogentry = shortlogentry.tmpl +graph = graph.tmpl + +naventry = '{label|escape} ' +navshortentry = '{label|escape} ' +navgraphentry = '{label|escape} ' +filenaventry = '{label|escape} ' +filedifflink = '{file|escape} ' +filenodelink = '{file|escape} ' +fileellipses = '...' +changelogentry = shortlogentry.tmpl +searchentry = shortlogentry.tmpl +changeset = changeset.tmpl +manifest = manifest.tmpl + +direntry = ' {basename|escape}/drwxr-xr-x' +fileentry = ' {basename|escape}{size}{permissions|permissions}' + +filerevision = filerevision.tmpl +fileannotate = fileannotate.tmpl +filediff = filediff.tmpl +filelog = filelog.tmpl +fileline = '{linenumber}{line|escape}' +filelogentry = filelogentry.tmpl + +annotateline = '{author|user}@{rev}{linenumber}{line|escape}' + +diffblock = '{lines}
      ' +difflineplus = '{linenumber}{line|escape}' +difflineminus = '{linenumber}{line|escape}' +difflineat = '{linenumber}{line|escape}' +diffline = '{linenumber}{line|escape}' + +changelogparent = 'parent {rev}:{node|short}' + +changesetparent = '{node|short} ' + +filerevparent = '{rename%filerename}{node|short} ' +filerevchild = '{node|short} ' + +filerename = '{file|escape}@' +filelogrename = 'base:{file|escape}@{node|short}' +fileannotateparent = 'parent:{rename%filerename}{node|short}' +changesetchild = '{node|short}' +changelogchild = 'child{node|short}' +fileannotatechild = 'child:{node|short}' +tags = tags.tmpl +tagentry = '{tag|escape}{node|short}' +changelogtag = '{name|escape} ' +changesettag = '{tag|escape} ' +filediffparent = 'parent {rev}:{node|short}' +filelogparent = 'parent {rev}:{node|short}' +filediffchild = 'child {rev}:{node|short}' +filelogchild = 'child {rev}:{node|short}' +indexentry = '{name|escape}{description}{contact|obfuscate}{lastchange|age} agoRSS Atom {archives%archiveentry}' +index = index.tmpl +archiveentry = '
    • {type|escape}
    • ' +notfound = notfound.tmpl +error = error.tmpl +urlparameter = '{separator}{name}={value|urlescape}' +hiddenformentry = '' diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/notfound.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/notfound.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,12 @@ +{header} +Mercurial repository not found + + + +

      Mercurial repository not found

      + +The specified repository "{repo|escape}" is unknown, sorry. + +Please go back to the main repository list page. + +{footer} diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/search.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/search.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,41 @@ +{header} +{repo|escape}: searching for {query|escape} + + + +
      + + +
      + +

      {repo|escape}

      +

      searching for '{query|escape}'

      + + + + + + +{entries} +
      age + author + description +
      + +
      +
      + +{footer} diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/shortlog.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/shortlog.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,55 @@ +{header} +{repo|escape}: log + + + + + +
      + + +
      + +

      {repo|escape}

      +

      log

      + + + + + + + + +{entries%shortlogentry} +
      age + author + description +
      + + +
      +
      + +{footer} diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/shortlogentry.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/shortlogentry.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,5 @@ + + {date|age} + {author|person} + {desc|strip|firstline|escape}{tags%changelogtag} + diff -r 2e58f1a36046 -r d43707e09b02 templates/coal/tags.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/coal/tags.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,42 @@ +{header} +{repo|escape}: tags + + + + + +
      + + +
      +

      {repo|escape}

      +

      tags

      + + + + + + + + +{entries%tagentry} +
      tagnode
      +
      +
      + +{footer} diff -r 2e58f1a36046 -r d43707e09b02 templates/fileannotate.tmpl --- a/templates/fileannotate.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/fileannotate.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -6,6 +6,7 @@
      changelog shortlog +graph tags changeset files diff -r 2e58f1a36046 -r d43707e09b02 templates/filediff.tmpl --- a/templates/filediff.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/filediff.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -6,6 +6,7 @@
      changelog shortlog +graph tags changeset file diff -r 2e58f1a36046 -r d43707e09b02 templates/filelog.tmpl --- a/templates/filelog.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/filelog.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -11,6 +11,7 @@
      changelog shortlog +graph tags file annotate diff -r 2e58f1a36046 -r d43707e09b02 templates/filerevision.tmpl --- a/templates/filerevision.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/filerevision.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -6,6 +6,7 @@
      changelog shortlog +graph tags changeset files diff -r 2e58f1a36046 -r d43707e09b02 templates/gitweb/changelog.tmpl --- a/templates/gitweb/changelog.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/gitweb/changelog.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -19,7 +19,12 @@ diff -r 2e58f1a36046 -r d43707e09b02 templates/gitweb/changeset.tmpl --- a/templates/gitweb/changeset.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/gitweb/changeset.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -12,7 +12,7 @@
      diff -r 2e58f1a36046 -r d43707e09b02 templates/gitweb/fileannotate.tmpl --- a/templates/gitweb/fileannotate.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/gitweb/fileannotate.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -15,6 +15,7 @@ summary | shortlog | changelog | +graph | tags | files | changeset | diff -r 2e58f1a36046 -r d43707e09b02 templates/gitweb/filediff.tmpl --- a/templates/gitweb/filediff.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/gitweb/filediff.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -15,6 +15,7 @@ summary | shortlog | changelog | +graph | tags | files | changeset | diff -r 2e58f1a36046 -r d43707e09b02 templates/gitweb/filelog.tmpl --- a/templates/gitweb/filelog.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/gitweb/filelog.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -15,6 +15,7 @@ summary | shortlog | changelog | +graph | tags | file | revisions | diff -r 2e58f1a36046 -r d43707e09b02 templates/gitweb/filerevision.tmpl --- a/templates/gitweb/filerevision.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/gitweb/filerevision.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -15,6 +15,7 @@ summary | shortlog | changelog | +graph | tags | files | changeset | diff -r 2e58f1a36046 -r d43707e09b02 templates/gitweb/graph.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/gitweb/graph.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,123 @@ +#header# +#repo|escape#: Shortlog + + + + + + + +
      +{sessionvars%hiddenformentry} + +
      + + +
       
      + +
      The revision graph only works with JavaScript-enabled browsers.
      + +
      +
        + +
          +
          + + + + + + +#footer# diff -r 2e58f1a36046 -r d43707e09b02 templates/gitweb/manifest.tmpl --- a/templates/gitweb/manifest.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/gitweb/manifest.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -15,6 +15,7 @@ summary | shortlog | changelog | +graph | tags | files | changeset #archives%archiveentry#
          diff -r 2e58f1a36046 -r d43707e09b02 templates/gitweb/map --- a/templates/gitweb/map Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/gitweb/map Tue Jun 24 09:34:38 2008 +0200 @@ -9,6 +9,7 @@ notfound = notfound.tmpl naventry = '{label|escape} ' navshortentry = '{label|escape} ' +navgraphentry = '{label|escape} ' filenaventry = '{label|escape} ' filedifflink = '#file|escape# ' filenodelink = '#file|escape#file | annotate | diff | revisions' @@ -24,7 +25,7 @@ filediff = filediff.tmpl filelog = filelog.tmpl fileline = '
          #linenumber# #line|escape#
          ' -annotateline = '#author|obfuscate#@#rev#
          #linenumber#
          #line|escape#
          ' +annotateline = '#author|user#@#rev#
          #linenumber#
          #line|escape#
          ' difflineplus = '#linenumber# #line|escape#' difflineminus = '#linenumber# #line|escape#' difflineat = '#linenumber# #line|escape#' @@ -50,6 +51,7 @@ filediffchild = 'child {rev}{node|short}' filelogchild = 'child #rev#: #node|short#' shortlog = shortlog.tmpl +graph = graph.tmpl tagtag = '{name} ' branchtag = '{name} ' inbranchtag = '{name} ' diff -r 2e58f1a36046 -r d43707e09b02 templates/gitweb/search.tmpl --- a/templates/gitweb/search.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/gitweb/search.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -22,6 +22,7 @@ summary | shortlog | changelog | +graph | tags | files#archives%archiveentry#
          diff -r 2e58f1a36046 -r d43707e09b02 templates/gitweb/shortlog.tmpl --- a/templates/gitweb/shortlog.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/gitweb/shortlog.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -21,6 +21,7 @@ summary | shortlog | changelog | +graph | tags | files#archives%archiveentry#
          diff -r 2e58f1a36046 -r d43707e09b02 templates/gitweb/summary.tmpl --- a/templates/gitweb/summary.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/gitweb/summary.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -22,6 +22,7 @@ summary | shortlog | changelog | +graph | tags | files#archives%archiveentry#
          diff -r 2e58f1a36046 -r d43707e09b02 templates/gitweb/tags.tmpl --- a/templates/gitweb/tags.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/gitweb/tags.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -15,6 +15,7 @@ summary | shortlog | changelog | +graph | tags | files
          diff -r 2e58f1a36046 -r d43707e09b02 templates/graph.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/graph.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,97 @@ +#header# +#repo|escape#: graph + + + + + + + +

          graph

          + +
          +{sessionvars%hiddenformentry} +

          + + +navigate: #changenav%navgraphentry# +

          +
          + +
          The revision graph only works with JavaScript-enabled browsers.
          + +
          +
            + +
              +
              + + + + +
              +{sessionvars%hiddenformentry} +

              + + +navigate: #changenav%navgraphentry# +

              +
              + +#footer# diff -r 2e58f1a36046 -r d43707e09b02 templates/manifest.tmpl --- a/templates/manifest.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/manifest.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -6,6 +6,7 @@
              changelog shortlog +graph tags changeset #archives%archiveentry# diff -r 2e58f1a36046 -r d43707e09b02 templates/map --- a/templates/map Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/map Tue Jun 24 09:34:38 2008 +0200 @@ -6,8 +6,10 @@ changelog = changelog.tmpl shortlog = shortlog.tmpl shortlogentry = shortlogentry.tmpl +graph = graph.tmpl naventry = '{label|escape} ' navshortentry = '{label|escape} ' +navgraphentry = '{label|escape} ' filenaventry = '{label|escape} ' filedifflink = '#file|escape# ' filenodelink = '#file|escape# ' @@ -24,7 +26,7 @@ filelog = filelog.tmpl fileline = '
              #linenumber##line|escape#
              ' filelogentry = filelogentry.tmpl -annotateline = '#author|obfuscate#@#rev##linenumber#
              #line|escape#
              ' +annotateline = '#author|user#@#rev##linenumber#
              #line|escape#
              ' difflineplus = '#linenumber##line|escape#' difflineminus = '#linenumber##line|escape#' difflineat = '#linenumber##line|escape#' diff -r 2e58f1a36046 -r d43707e09b02 templates/paper/header.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/paper/header.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,7 @@ + + + + + + + diff -r 2e58f1a36046 -r d43707e09b02 templates/paper/map --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/paper/map Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,71 @@ +default = 'shortlog' + +mimetype = 'text/html; charset={encoding}' +header = header.tmpl +footer = ../coal/footer.tmpl +search = ../coal/search.tmpl + +changelog = ../coal/shortlog.tmpl +shortlog = ../coal/shortlog.tmpl +shortlogentry = ../coal/shortlogentry.tmpl +graph = ../coal/graph.tmpl + +naventry = '{label|escape} ' +navshortentry = '{label|escape} ' +navgraphentry = '{label|escape} ' +filenaventry = '{label|escape} ' +filedifflink = '{file|escape} ' +filenodelink = '{file|escape} ' +fileellipses = '...' +changelogentry = ../coal/shortlogentry.tmpl +searchentry = ../coal/shortlogentry.tmpl +changeset = ../coal/changeset.tmpl +manifest = ../coal/manifest.tmpl + +direntry = ' {basename|escape}/drwxr-xr-x' +fileentry = ' {basename|escape}{size}{permissions|permissions}' + +filerevision = ../coal/filerevision.tmpl +fileannotate = ../coal/fileannotate.tmpl +filediff = ../coal/filediff.tmpl +filelog = ../coal/filelog.tmpl +fileline = '{linenumber}{line|escape}' +filelogentry = ../coal/filelogentry.tmpl + +annotateline = '{author|user}@{rev}{linenumber}{line|escape}' + +diffblock = '{lines}
              ' +difflineplus = '{linenumber}{line|escape}' +difflineminus = '{linenumber}{line|escape}' +difflineat = '{linenumber}{line|escape}' +diffline = '{linenumber}{line|escape}' + +changelogparent = 'parent {rev}:{node|short}' + +changesetparent = '{node|short} ' + +filerevparent = '{rename%filerename}{node|short} ' +filerevchild = '{node|short} ' + +filerename = '{file|escape}@' +filelogrename = 'base:{file|escape}@{node|short}' +fileannotateparent = 'parent:{rename%filerename}{node|short}' +changesetchild = '{node|short}' +changelogchild = 'child{node|short}' +fileannotatechild = 'child:{node|short}' +tags = ../coal/tags.tmpl +tagentry = '{tag|escape}{node|short}' +changelogtag = 'tag:{tag|escape}' +changelogtag = '{name|escape} ' +changesettag = '{tag|escape} ' +filediffparent = 'parent {rev}:{node|short}' +filelogparent = 'parent {rev}:{node|short}' +filediffchild = 'child {rev}:{node|short}' +filelogchild = 'child {rev}:{node|short}' +indexentry = '{name|escape}{description}{contact|obfuscate}{lastchange|age} agoRSS Atom {archives%archiveentry}' +index = ../coal/index.tmpl +archiveentry = '
            • {type|escape}
            • ' +notfound = ../coal/notfound.tmpl +error = ../coal/error.tmpl +urlparameter = '{separator}{name}={value|urlescape}' +hiddenformentry = '' diff -r 2e58f1a36046 -r d43707e09b02 templates/search.tmpl --- a/templates/search.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/search.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -6,6 +6,7 @@
              changelog shortlog +graph tags files #archives%archiveentry# diff -r 2e58f1a36046 -r d43707e09b02 templates/shortlog.tmpl --- a/templates/shortlog.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/shortlog.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -9,6 +9,7 @@
              changelog +graph tags files #archives%archiveentry# diff -r 2e58f1a36046 -r d43707e09b02 templates/static/background.png Binary file templates/static/background.png has changed diff -r 2e58f1a36046 -r d43707e09b02 templates/static/coal-file.png Binary file templates/static/coal-file.png has changed diff -r 2e58f1a36046 -r d43707e09b02 templates/static/coal-folder.png Binary file templates/static/coal-folder.png has changed diff -r 2e58f1a36046 -r d43707e09b02 templates/static/excanvas.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/static/excanvas.js Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,19 @@ +if(!window.CanvasRenderingContext2D){(function(){var I=Math,i=I.round,L=I.sin,M=I.cos,m=10,A=m/2,Q={init:function(a){var b=a||document;if(/MSIE/.test(navigator.userAgent)&&!window.opera){var c=this;b.attachEvent("onreadystatechange",function(){c.r(b)})}},r:function(a){if(a.readyState=="complete"){if(!a.namespaces["s"]){a.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml")}var b=a.createStyleSheet();b.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}"; +var c=a.getElementsByTagName("canvas");for(var d=0;d"){var d="/"+a.tagName,e;while((e=a.nextSibling)&&e.tagName!=d){e.removeNode()}if(e){e.removeNode()}}a.parentNode.replaceChild(c,a);return c},initElement:function(a){a=this.q(a);a.getContext=function(){if(this.l){return this.l}return this.l=new K(this)};a.attachEvent("onpropertychange",V);a.attachEvent("onresize", +W);var b=a.attributes;if(b.width&&b.width.specified){a.style.width=b.width.nodeValue+"px"}else{a.width=a.clientWidth}if(b.height&&b.height.specified){a.style.height=b.height.nodeValue+"px"}else{a.height=a.clientHeight}return a}};function V(a){var b=a.srcElement;switch(a.propertyName){case "width":b.style.width=b.attributes.width.nodeValue+"px";b.getContext().clearRect();break;case "height":b.style.height=b.attributes.height.nodeValue+"px";b.getContext().clearRect();break}}function W(a){var b=a.srcElement; +if(b.firstChild){b.firstChild.style.width=b.clientWidth+"px";b.firstChild.style.height=b.clientHeight+"px"}}Q.init();var R=[];for(var E=0;E<16;E++){for(var F=0;F<16;F++){R[E*16+F]=E.toString(16)+F.toString(16)}}function J(){return[[1,0,0],[0,1,0],[0,0,1]]}function G(a,b){var c=J();for(var d=0;d<3;d++){for(var e=0;e<3;e++){var g=0;for(var h=0;h<3;h++){g+=a[d][h]*b[h][e]}c[d][e]=g}}return c}function N(a,b){b.fillStyle=a.fillStyle;b.lineCap=a.lineCap;b.lineJoin=a.lineJoin;b.lineWidth=a.lineWidth;b.miterLimit= +a.miterLimit;b.shadowBlur=a.shadowBlur;b.shadowColor=a.shadowColor;b.shadowOffsetX=a.shadowOffsetX;b.shadowOffsetY=a.shadowOffsetY;b.strokeStyle=a.strokeStyle;b.d=a.d;b.e=a.e}function O(a){var b,c=1;a=String(a);if(a.substring(0,3)=="rgb"){var d=a.indexOf("(",3),e=a.indexOf(")",d+1),g=a.substring(d+1,e).split(",");b="#";for(var h=0;h<3;h++){b+=R[Number(g[h])]}if(g.length==4&&a.substr(3,1)=="a"){c=g[3]}}else{b=a}return[b,c]}function S(a){switch(a){case "butt":return"flat";case "round":return"round"; +case "square":default:return"square"}}function K(a){this.a=J();this.m=[];this.k=[];this.c=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=m*1;this.globalAlpha=1;this.canvas=a;var b=a.ownerDocument.createElement("div");b.style.width=a.clientWidth+"px";b.style.height=a.clientHeight+"px";b.style.overflow="hidden";b.style.position="absolute";a.appendChild(b);this.j=b;this.d=1;this.e=1}var j=K.prototype;j.clearRect=function(){this.j.innerHTML= +"";this.c=[]};j.beginPath=function(){this.c=[]};j.moveTo=function(a,b){this.c.push({type:"moveTo",x:a,y:b});this.f=a;this.g=b};j.lineTo=function(a,b){this.c.push({type:"lineTo",x:a,y:b});this.f=a;this.g=b};j.bezierCurveTo=function(a,b,c,d,e,g){this.c.push({type:"bezierCurveTo",cp1x:a,cp1y:b,cp2x:c,cp2y:d,x:e,y:g});this.f=e;this.g=g};j.quadraticCurveTo=function(a,b,c,d){var e=this.f+0.6666666666666666*(a-this.f),g=this.g+0.6666666666666666*(b-this.g),h=e+(c-this.f)/3,l=g+(d-this.g)/3;this.bezierCurveTo(e, +g,h,l,c,d)};j.arc=function(a,b,c,d,e,g){c*=m;var h=g?"at":"wa",l=a+M(d)*c-A,n=b+L(d)*c-A,o=a+M(e)*c-A,f=b+L(e)*c-A;if(l==o&&!g){l+=0.125}this.c.push({type:h,x:a,y:b,radius:c,xStart:l,yStart:n,xEnd:o,yEnd:f})};j.rect=function(a,b,c,d){this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath()};j.strokeRect=function(a,b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.stroke()};j.fillRect=function(a, +b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.fill()};j.createLinearGradient=function(a,b,c,d){var e=new H("gradient");return e};j.createRadialGradient=function(a,b,c,d,e,g){var h=new H("gradientradial");h.n=c;h.o=g;h.i.x=a;h.i.y=b;return h};j.drawImage=function(a,b){var c,d,e,g,h,l,n,o,f=a.runtimeStyle.width,k=a.runtimeStyle.height;a.runtimeStyle.width="auto";a.runtimeStyle.height="auto";var q=a.width,r=a.height;a.runtimeStyle.width= +f;a.runtimeStyle.height=k;if(arguments.length==3){c=arguments[1];d=arguments[2];h=(l=0);n=(e=q);o=(g=r)}else if(arguments.length==5){c=arguments[1];d=arguments[2];e=arguments[3];g=arguments[4];h=(l=0);n=q;o=r}else if(arguments.length==9){h=arguments[1];l=arguments[2];n=arguments[3];o=arguments[4];c=arguments[5];d=arguments[6];e=arguments[7];g=arguments[8]}else{throw"Invalid number of arguments";}var s=this.b(c,d),t=[],v=10,w=10;t.push(" ','","");this.j.insertAdjacentHTML("BeforeEnd",t.join(""))};j.stroke=function(a){var b=[],c=O(a?this.fillStyle:this.strokeStyle),d=c[0],e=c[1]*this.globalAlpha,g=10,h=10;b.push("n.x){n.x=k.x}if(l.y== +null||k.yn.y){n.y=k.y}}}b.push(' ">');if(typeof this.fillStyle=="object"){var v={x:"50%",y:"50%"},w=n.x-l.x,x=n.y-l.y,p=w>x?w:x;v.x=i(this.fillStyle.i.x/w*100+50)+"%";v.y=i(this.fillStyle.i.y/x*100+50)+"%";var y=[];if(this.fillStyle.p=="gradientradial"){var z=this.fillStyle.n/p*100,B=this.fillStyle.o/p*100-z}else{var z=0,B=100}var C={offset:null,color:null},D={offset:null,color:null};this.fillStyle.h.sort(function(T,U){return T.offset-U.offset});for(var o=0;oC.offset||C.offset==null){C.offset=u.offset;C.color=u.color}if(u.offset')}else if(a){b.push('')}else{b.push("')}b.push("");this.j.insertAdjacentHTML("beforeEnd",b.join(""));this.c=[]};j.fill=function(){this.stroke(true)};j.closePath=function(){this.c.push({type:"close"})};j.b=function(a,b){return{x:m*(a*this.a[0][0]+b*this.a[1][0]+this.a[2][0])-A,y:m*(a*this.a[0][1]+b*this.a[1][1]+this.a[2][1])-A}};j.save=function(){var a={};N(this,a); +this.k.push(a);this.m.push(this.a);this.a=G(J(),this.a)};j.restore=function(){N(this.k.pop(),this);this.a=this.m.pop()};j.translate=function(a,b){var c=[[1,0,0],[0,1,0],[a,b,1]];this.a=G(c,this.a)};j.rotate=function(a){var b=M(a),c=L(a),d=[[b,c,0],[-c,b,0],[0,0,1]];this.a=G(d,this.a)};j.scale=function(a,b){this.d*=a;this.e*=b;var c=[[a,0,0],[0,b,0],[0,0,1]];this.a=G(c,this.a)};j.clip=function(){};j.arcTo=function(){};j.createPattern=function(){return new P};function H(a){this.p=a;this.n=0;this.o= +0;this.h=[];this.i={x:0,y:0}}H.prototype.addColorStop=function(a,b){b=O(b);this.h.push({offset:1-a,color:b})};function P(){}G_vmlCanvasManager=Q;CanvasRenderingContext2D=K;CanvasGradient=H;CanvasPattern=P})()}; diff -r 2e58f1a36046 -r d43707e09b02 templates/static/graph.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/static/graph.js Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,129 @@ +// branch_renderer.js - Rendering of branch DAGs on the client side +// +// Copyright 2008 Dirkjan Ochtman +// Copyright 2006 Alexander Schremmer +// +// derived from code written by Scott James Remnant +// Copyright 2005 Canonical Ltd. +// +// This software may be used and distributed according to the terms +// of the GNU General Public License, incorporated herein by reference. + +var colors = [ + [ 1.0, 0.0, 0.0 ], + [ 1.0, 1.0, 0.0 ], + [ 0.0, 1.0, 0.0 ], + [ 0.0, 1.0, 1.0 ], + [ 0.0, 0.0, 1.0 ], + [ 1.0, 0.0, 1.0 ] +]; + +function Graph() { + + this.canvas = document.getElementById('graph'); + if (navigator.userAgent.indexOf('MSIE') >= 0) this.canvas = window.G_vmlCanvasManager.initElement(this.canvas); + this.ctx = this.canvas.getContext('2d'); + this.ctx.strokeStyle = 'rgb(0, 0, 0)'; + this.ctx.fillStyle = 'rgb(0, 0, 0)'; + this.cur = [0, 0]; + this.line_width = 3; + this.bg = [0, 4]; + this.cell = [2, 0]; + this.columns = 0; + this.revlink = ''; + + this.scale = function(height) { + this.bg_height = height; + this.box_size = Math.floor(this.bg_height / 1.2); + this.cell_height = this.box_size; + } + + function colorPart(num) { + num *= 255 + num = num < 0 ? 0 : num; + num = num > 255 ? 255 : num; + var digits = Math.round(num).toString(16); + if (num < 16) { + return '0' + digits; + } else { + return digits; + } + } + + this.setColor = function(color, bg, fg) { + + // Set the colour. + // + // Picks a distinct colour based on an internal wheel; the bg + // parameter provides the value that should be assigned to the 'zero' + // colours and the fg parameter provides the multiplier that should be + // applied to the foreground colours. + + color %= colors.length; + var red = (colors[color][0] * fg) || bg; + var green = (colors[color][1] * fg) || bg; + var blue = (colors[color][2] * fg) || bg; + red = Math.round(red * 255); + green = Math.round(green * 255); + blue = Math.round(blue * 255); + var s = 'rgb(' + red + ', ' + green + ', ' + blue + ')'; + this.ctx.strokeStyle = s; + this.ctx.fillStyle = s; + return s; + + } + + this.render = function(data) { + + for (var i in data) { + + var parity = i % 2; + this.cell[1] += this.bg_height; + this.bg[1] += this.bg_height; + + var cur = data[i]; + var node = cur[1]; + var edges = cur[2]; + var fold = false; + + for (var j in edges) { + + line = edges[j]; + start = line[0]; + end = line[1]; + color = line[2]; + + if (end > this.columns || start > this.columns) { + this.columns += 1; + } + + if (start == this.columns && start > end) { + var fold = true; + } + + x0 = this.cell[0] + this.box_size * start + this.box_size / 2; + y0 = this.bg[1] - this.bg_height / 2; + x1 = this.cell[0] + this.box_size * end + this.box_size / 2; + y1 = this.bg[1] + this.bg_height / 2; + + this.edge(x0, y0, x1, y1, color); + + } + + // Draw the revision node in the right column + + column = node[0] + color = node[1] + + radius = this.box_size / 8; + x = this.cell[0] + this.box_size * column + this.box_size / 2; + y = this.bg[1] - this.bg_height / 2; + this.vertex(x, y, color, parity, cur); + + if (fold) this.columns -= 1; + + } + + } + +} diff -r 2e58f1a36046 -r d43707e09b02 templates/static/hgicon.png Binary file templates/static/hgicon.png has changed diff -r 2e58f1a36046 -r d43707e09b02 templates/static/hglogo.png Binary file templates/static/hglogo.png has changed diff -r 2e58f1a36046 -r d43707e09b02 templates/static/highlight.css --- a/templates/static/highlight.css Tue Jun 24 09:33:13 2008 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -.c { color: #808080 } /* Comment */ -.err { color: #F00000; background-color: #F0A0A0 } /* Error */ -.k { color: #008000; font-weight: bold } /* Keyword */ -.o { color: #303030 } /* Operator */ -.cm { color: #808080 } /* Comment.Multiline */ -.cp { color: #507090 } /* Comment.Preproc */ -.c1 { color: #808080 } /* Comment.Single */ -.cs { color: #cc0000; font-weight: bold } /* Comment.Special */ -.gd { color: #A00000 } /* Generic.Deleted */ -.ge { font-style: italic } /* Generic.Emph */ -.gr { color: #FF0000 } /* Generic.Error */ -.gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.gi { color: #00A000 } /* Generic.Inserted */ -.go { color: #808080 } /* Generic.Output */ -.gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ -.gs { font-weight: bold } /* Generic.Strong */ -.gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.gt { color: #0040D0 } /* Generic.Traceback */ -.kc { color: #008000; font-weight: bold } /* Keyword.Constant */ -.kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ -.kp { color: #003080; font-weight: bold } /* Keyword.Pseudo */ -.kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ -.kt { color: #303090; font-weight: bold } /* Keyword.Type */ -.m { color: #6000E0; font-weight: bold } /* Literal.Number */ -.s { background-color: #fff0f0 } /* Literal.String */ -.na { color: #0000C0 } /* Name.Attribute */ -.nb { color: #007020 } /* Name.Builtin */ -.nc { color: #B00060; font-weight: bold } /* Name.Class */ -.no { color: #003060; font-weight: bold } /* Name.Constant */ -.nd { color: #505050; font-weight: bold } /* Name.Decorator */ -.ni { color: #800000; font-weight: bold } /* Name.Entity */ -.ne { color: #F00000; font-weight: bold } /* Name.Exception */ -.nf { color: #0060B0; font-weight: bold } /* Name.Function */ -.nl { color: #907000; font-weight: bold } /* Name.Label */ -.nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ -.nt { color: #007000 } /* Name.Tag */ -.nv { color: #906030 } /* Name.Variable */ -.ow { color: #000000; font-weight: bold } /* Operator.Word */ -.w { color: #bbbbbb } /* Text.Whitespace */ -.mf { color: #6000E0; font-weight: bold } /* Literal.Number.Float */ -.mh { color: #005080; font-weight: bold } /* Literal.Number.Hex */ -.mi { color: #0000D0; font-weight: bold } /* Literal.Number.Integer */ -.mo { color: #4000E0; font-weight: bold } /* Literal.Number.Oct */ -.sb { background-color: #fff0f0 } /* Literal.String.Backtick */ -.sc { color: #0040D0 } /* Literal.String.Char */ -.sd { color: #D04020 } /* Literal.String.Doc */ -.s2 { background-color: #fff0f0 } /* Literal.String.Double */ -.se { color: #606060; font-weight: bold; background-color: #fff0f0 } /* Literal.String.Escape */ -.sh { background-color: #fff0f0 } /* Literal.String.Heredoc */ -.si { background-color: #e0e0e0 } /* Literal.String.Interpol */ -.sx { color: #D02000; background-color: #fff0f0 } /* Literal.String.Other */ -.sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */ -.s1 { background-color: #fff0f0 } /* Literal.String.Single */ -.ss { color: #A06000 } /* Literal.String.Symbol */ -.bp { color: #007020 } /* Name.Builtin.Pseudo */ -.vc { color: #306090 } /* Name.Variable.Class */ -.vg { color: #d07000; font-weight: bold } /* Name.Variable.Global */ -.vi { color: #3030B0 } /* Name.Variable.Instance */ -.il { color: #0000D0; font-weight: bold } /* Literal.Number.Integer.Long */ diff -r 2e58f1a36046 -r d43707e09b02 templates/static/style-coal.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/static/style-coal.css Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,197 @@ +body { + margin: 0; + padding: 0; + background: black url(background.png) repeat-x; + font-family: sans-serif; +} + +.container { + padding-right: 150px; +} + +.main { + position: relative; + background: white; + padding: 2em; + border-right: 15px solid black; + border-bottom: 15px solid black; +} + +.overflow { + width: 100%; + overflow: auto; +} + +.menu { + background: #999; + padding: 10px; + width: 75px; + margin: 0; + font-size: 80%; + text-align: left; + position: fixed; + top: 27px; + left: auto; + right: 27px; +} + +.menu ul { + list-style: none; + padding: 0; + margin: 10px 0 0 0; +} + +.menu li { + margin-bottom: 3px; + padding: 2px 4px; + background: white; + color: black; + font-weight: normal; +} + +.menu li.active { + background: black; + color: white; +} + +.menu a { color: black; display: block; } + +.search { + position: absolute; + top: .7em; + right: 2em; +} + +a { text-decoration:none; } +.age { white-space:nowrap; } +.date { white-space:nowrap; } +.indexlinks { white-space:nowrap; } +.parity0 { background-color: #f5f5f5; } +.parity1 { background-color: white; } +.plusline { color: green; } +.minusline { color: red; } +.atline { color: purple; } + +.navigate { + text-align: right; + font-size: 60%; + margin: 1em 0 1em 0; +} + +.tag { + color: #999; + font-size: 70%; + font-weight: normal; + margin-left: .5em; + vertical-align: baseline; +} + +/* Common */ +pre { margin: 0; } + +h2 { font-size: 120%; border-bottom: 1px solid #999; } +h3 { + margin-top: -.7em; + font-size: 100%; +} + +/* log and tags tables */ +.bigtable { + border-bottom: 1px solid #999; + border-collapse: collapse; + font-size: 90%; + width: 100%; + font-weight: normal; + text-align: left; +} + +.bigtable td { + padding: 1px 4px 1px 4px; + vertical-align: top; +} + +.bigtable th { + padding: 1px 4px 1px 4px; + border-bottom: 1px solid #999; + font-size: smaller; +} +.bigtable tr { border: none; } +.bigtable .age { width: 6em; } +.bigtable .author { width: 10em; } +.bigtable .description { } +.bigtable .node { width: 5em; font-family: monospace;} +.bigtable .lineno { width: 2em; text-align: right;} +.bigtable .lineno a { color: #999; font-size: smaller; font-family: monospace;} +.bigtable td.source { font-family: monospace; white-space: pre; } +.bigtable .permissions { width: 8em; text-align: left;} +.bigtable .size { width: 5em; text-align: right; } +.bigtable .annotate { text-align: right; } +.bigtable td.annotate { font-size: smaller; } + +.fileline { font-family: monospace; } +.fileline img { border: 0; } + +/* Changeset entry */ +#changesetEntry { + border-collapse: collapse; + font-size: 90%; + width: 100%; + margin-bottom: 1em; +} + +#changesetEntry th { + padding: 1px 4px 1px 4px; + width: 4em; + text-align: right; + font-weight: normal; + color: #999; + margin-right: .5em; + vertical-align: top; +} + +div.description { + border-left: 3px solid #999; + margin: 1em 0 1em 0; + padding: .3em; +} + +div#wrapper { + position: relative; + border-top: 1px solid black; + border-bottom: 1px solid black; + margin: 0; + padding: 0; +} + +canvas { + position: absolute; + z-index: 5; + top: -0.7em; + margin: 0; +} + +ul#graphnodes { + position: absolute; + z-index: 10; + top: -1.0em; + list-style: none inside none; + padding: 0; +} + +ul#nodebgs { + list-style: none inside none; + padding: 0; + margin: 0; + top: -0.7em; +} + +ul#graphnodes li, ul#nodebgs li { + height: 39px; +} + +ul#graphnodes li .info { + display: block; + font-size: 70%; + position: relative; + top: -3px; +} diff -r 2e58f1a36046 -r d43707e09b02 templates/static/style-gitweb.css --- a/templates/static/style-gitweb.css Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/static/style-gitweb.css Tue Jun 24 09:34:38 2008 +0200 @@ -79,3 +79,44 @@ background-color: #d5dde6; border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4; } + +/* Graph */ +div#wrapper { + position: relative; + margin: 0; + padding: 0; +} + +canvas { + position: absolute; + z-index: 5; + top: -0.9em; + margin: 0; +} + +ul#nodebgs { + list-style: none inside none; + padding: 0; + margin: 0; + top: -0.7em; +} + +ul#graphnodes li, ul#nodebgs li { + height: 39px; +} + +ul#graphnodes { + position: absolute; + z-index: 10; + top: -0.8em; + list-style: none inside none; + padding: 0; +} + +ul#graphnodes li .info { + display: block; + font-size: 100%; + position: relative; + top: -3px; + font-style: italic; +} diff -r 2e58f1a36046 -r d43707e09b02 templates/static/style-paper.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/static/style-paper.css Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,194 @@ +body { + margin: 0; + padding: 0; + background: white; + font-family: sans-serif; +} + +.container { + padding-left: 115px; +} + +.main { + position: relative; + background: white; + padding: 2em 2em 2em 0; +} + +.overflow { + width: 100%; + overflow: auto; +} + +.menu { + width: 90px; + margin: 0; + font-size: 80%; + text-align: left; + position: fixed; + top: 20px; + left: 20px; + right: auto; +} + +.menu ul { + list-style: none; + padding: 0; + margin: 10px 0 0 0; + border-left: 2px solid #999; +} + +.menu li { + margin-bottom: 3px; + padding: 2px 4px; + background: white; + color: black; + font-weight: normal; +} + +.menu li.active { + font-weight: bold; +} + +.menu a { color: black; display: block; } + +.search { + position: absolute; + top: .7em; + right: 2em; +} + +a { text-decoration:none; } +.age { white-space:nowrap; } +.date { white-space:nowrap; } +.indexlinks { white-space:nowrap; } +.parity0 { background-color: #f5f5f5; } +.parity1 { background-color: white; } +.plusline { color: green; } +.minusline { color: red; } +.atline { color: purple; } + +.navigate { + text-align: right; + font-size: 60%; + margin: 1em 0 1em 0; +} + +.tag { + color: #999; + font-size: 70%; + font-weight: normal; + margin-left: .5em; + vertical-align: baseline; +} + +/* Common */ +pre { margin: 0; } + +h2 { font-size: 120%; border-bottom: 1px solid #999; } +h3 { + margin-top: -.7em; + font-size: 100%; +} + +/* log and tags tables */ +.bigtable { + border-bottom: 1px solid #999; + border-collapse: collapse; + font-size: 90%; + width: 100%; + font-weight: normal; + text-align: left; +} + +.bigtable td { + padding: 1px 4px 1px 4px; + vertical-align: top; +} + +.bigtable th { + padding: 1px 4px 1px 4px; + border-bottom: 1px solid #999; + font-size: smaller; +} +.bigtable tr { border: none; } +.bigtable .age { width: 6em; } +.bigtable .author { width: 10em; } +.bigtable .description { } +.bigtable .node { width: 5em; font-family: monospace;} +.bigtable .lineno { width: 2em; text-align: right;} +.bigtable .lineno a { color: #999; font-size: smaller; font-family: monospace;} +.bigtable td.source { font-family: monospace; white-space: pre; } +.bigtable .permissions { width: 8em; text-align: left;} +.bigtable .size { width: 5em; text-align: right; } +.bigtable .annotate { text-align: right; } +.bigtable td.annotate { font-size: smaller; } + +.fileline { font-family: monospace; } +.fileline img { border: 0; } + +/* Changeset entry */ +#changesetEntry { + border-collapse: collapse; + font-size: 90%; + width: 100%; + margin-bottom: 1em; +} + +#changesetEntry th { + padding: 1px 4px 1px 4px; + width: 4em; + text-align: right; + font-weight: normal; + color: #999; + margin-right: .5em; + vertical-align: top; +} + +div.description { + border-left: 2px solid #999; + margin: 1em 0 1em 0; + padding: .3em; +} + +/* Graph */ +div#wrapper { + position: relative; + border-top: 1px solid black; + border-bottom: 1px solid black; + margin: 0; + padding: 0; +} + +canvas { + position: absolute; + z-index: 5; + top: -0.7em; + margin: 0; +} + +ul#nodebgs { + list-style: none inside none; + padding: 0; + margin: 0; + top: -0.7em; +} + +ul#graphnodes li, ul#nodebgs li { + height: 39px; +} + +ul#graphnodes { + position: absolute; + z-index: 10; + top: -1.0em; + list-style: none inside none; + padding: 0; +} + +ul#graphnodes li .info { + display: block; + font-size: 70%; + position: relative; + top: -3px; +} diff -r 2e58f1a36046 -r d43707e09b02 templates/static/style.css --- a/templates/static/style.css Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/static/style.css Tue Jun 24 09:34:38 2008 +0200 @@ -64,3 +64,42 @@ #filediffEntry { } #filediffEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; } +/* Graph */ +div#wrapper { + position: relative; + margin: 0; + padding: 0; +} + +canvas { + position: absolute; + z-index: 5; + top: -0.6em; + margin: 0; +} + +ul#nodebgs { + list-style: none inside none; + padding: 0; + margin: 0; + top: -0.7em; +} + +ul#graphnodes li, ul#nodebgs li { + height: 39px; +} + +ul#graphnodes { + position: absolute; + z-index: 10; + top: -0.85em; + list-style: none inside none; + padding: 0; +} + +ul#graphnodes li .info { + display: block; + font-size: 70%; + position: relative; + top: -1px; +} diff -r 2e58f1a36046 -r d43707e09b02 templates/tags.tmpl --- a/templates/tags.tmpl Tue Jun 24 09:33:13 2008 +0200 +++ b/templates/tags.tmpl Tue Jun 24 09:34:38 2008 +0200 @@ -10,6 +10,7 @@
              changelog shortlog +graph files rss atom diff -r 2e58f1a36046 -r d43707e09b02 tests/md5sum.py --- a/tests/md5sum.py Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/md5sum.py Tue Jun 24 09:34:38 2008 +0200 @@ -7,7 +7,11 @@ # GPL-compatible. import sys -import md5 + +try: + from hashlib import md5 +except ImportError: + from md5 import md5 for filename in sys.argv[1:]: try: @@ -16,7 +20,7 @@ sys.stderr.write('%s: Can\'t open: %s\n' % (filename, msg)) sys.exit(1) - m = md5.new() + m = md5() try: while 1: data = fp.read(8192) diff -r 2e58f1a36046 -r d43707e09b02 tests/test-1102 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-1102 Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,16 @@ +#!/bin/sh + +rm -rf a +hg init a +cd a +echo a > a +hg ci -Am0 +hg tag t1 # 1 +hg tag --remove t1 # 2 + +hg co 1 +hg tag -r0 t1 +hg tags + + + diff -r 2e58f1a36046 -r d43707e09b02 tests/test-1102.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-1102.out Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,4 @@ +adding a +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +tip 3:a49829c4fc11 +t1 0:f7b1eb17ad24 diff -r 2e58f1a36046 -r d43707e09b02 tests/test-add.out --- a/tests/test-add.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-add.out Tue Jun 24 09:34:38 2008 +0200 @@ -18,9 +18,7 @@ warning: conflicts during merge. merging a failed! 0 files updated, 0 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 2 - hg merge 1 +use 'hg resolve' to retry unresolved file merges M a ? a.orig % should fail diff -r 2e58f1a36046 -r d43707e09b02 tests/test-archive --- a/tests/test-archive Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-archive Tue Jun 24 09:34:38 2008 +0200 @@ -40,10 +40,11 @@ gzip -dc test-$QTIP.tar.gz | tar tf - | sed "s/$QTIP/TIP/" cat > md5comp.py < file.txt +hg ci -Ama +hg log | grep summary +hg bundle ../b1 ../t2 + +cd ../t2 +hg pull ../b1 +hg up +hg log | grep summary +cd .. + +for t in "None" "bzip2" "gzip"; do + echo % test bundle type $t + hg init t$t + cd t1 + hg bundle -t $t ../b$t ../t$t + head -n 1 ../b$t | cut -b 1-6 + cd ../t$t + hg pull ../b$t + hg up + hg log | grep summary + cd .. +done + +echo % test garbage file +echo garbage > bgarbage +hg init tgarbage +cd tgarbage +hg pull ../bgarbage +cd .. + +echo % test invalid bundle type +cd t1 +hg bundle -a -t garbage ../bgarbage +cd .. diff -r 2e58f1a36046 -r d43707e09b02 tests/test-bundle-type.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-bundle-type.out Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,58 @@ +% bundle w/o type option +adding file.txt +summary: a +searching for changes +1 changesets found +pulling from ../b1 +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +(run 'hg update' to get a working copy) +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +summary: a +% test bundle type None +searching for changes +1 changesets found +HG10UN +pulling from ../bNone +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +(run 'hg update' to get a working copy) +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +summary: a +% test bundle type bzip2 +searching for changes +1 changesets found +HG10BZ +pulling from ../bbzip2 +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +(run 'hg update' to get a working copy) +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +summary: a +% test bundle type gzip +searching for changes +1 changesets found +HG10GZ +pulling from ../bgzip +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +(run 'hg update' to get a working copy) +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +summary: a +% test garbage file +abort: ../bgarbage: not a Mercurial bundle file +% test invalid bundle type +1 changesets found +abort: unknown bundle type specified with --type diff -r 2e58f1a36046 -r d43707e09b02 tests/test-churn --- a/tests/test-churn Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-churn Tue Jun 24 09:34:38 2008 +0200 @@ -3,6 +3,8 @@ echo "[extensions]" >> $HGRCPATH echo "churn=" >> $HGRCPATH +COLUMNS=80; export COLUMNS + echo % create test repository hg init repo cd repo diff -r 2e58f1a36046 -r d43707e09b02 tests/test-command-template --- a/tests/test-command-template Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-command-template Tue Jun 24 09:34:38 2008 +0200 @@ -23,7 +23,7 @@ echo other 4 >> d hg add d hg commit -m 'new head' -d '1500000 0' -u 'person' -hg merge -q +hg merge -q foo hg commit -m 'merge' -d '1500001 0' -u 'person' # second branch starting at nullrev hg update null diff -r 2e58f1a36046 -r d43707e09b02 tests/test-commit --- a/tests/test-commit Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-commit Tue Jun 24 09:34:38 2008 +0200 @@ -7,6 +7,10 @@ hg add foo HGEDITOR=true hg commit -m "" hg commit -d '0 0' -m commit-1 +# An empty date was interpreted as epoch origin +echo foo >> foo +hg commit -d '' -m commit-no-date +hg tip --template '{date|isodate}\n' | grep '1970' echo foo >> foo hg commit -d '1 4444444' -m commit-3 hg commit -d '1 15.1' -m commit-4 diff -r 2e58f1a36046 -r d43707e09b02 tests/test-committer.out --- a/tests/test-committer.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-committer.out Tue Jun 24 09:34:38 2008 +0200 @@ -22,7 +22,5 @@ date: Mon Jan 12 13:46:40 1970 +0000 summary: commit-1 -transaction abort! -rollback completed abort: Please specify a username. No username found, using user@host instead diff -r 2e58f1a36046 -r d43707e09b02 tests/test-conflict.out --- a/tests/test-conflict.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-conflict.out Tue Jun 24 09:34:38 2008 +0200 @@ -4,9 +4,7 @@ warning: conflicts during merge. merging a failed! 0 files updated, 0 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 2 - hg merge 1 +use 'hg resolve' to retry unresolved file merges e7fe8eb3e180+0d24b7662d3e+ tip <<<<<<< local something else diff -r 2e58f1a36046 -r d43707e09b02 tests/test-convert-clonebranches --- a/tests/test-convert-clonebranches Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-convert-clonebranches Tue Jun 24 09:34:38 2008 +0200 @@ -17,7 +17,7 @@ echo b > b hg ci -qAm addb hg up -qC -hg merge +hg merge default hg ci -qm mergeab hg tag -ql mergeab cd .. diff -r 2e58f1a36046 -r d43707e09b02 tests/test-convert-cvs --- a/tests/test-convert-cvs Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-convert-cvs Tue Jun 24 09:34:38 2008 +0200 @@ -7,6 +7,11 @@ cvs -f $@ } +hgcat() +{ + hg --cwd src-hg cat -r tip "$1" +} + echo "[extensions]" >> $HGRCPATH echo "convert = " >> $HGRCPATH @@ -45,13 +50,13 @@ echo % convert fresh repo hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' -cat src-hg/a -cat src-hg/b/c +hgcat a +hgcat b/c echo % convert fresh repo with --filemap echo include b/c > filemap hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' -cat src-hg/b/c +hgcat b/c hg -R src-filemap log --template '#rev# #desc# files: #files#\n' echo % commit new file revisions @@ -64,12 +69,12 @@ echo % convert again hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' -cat src-hg/a -cat src-hg/b/c +hgcat a +hgcat b/c echo % convert again with --filemap hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' -cat src-hg/b/c +hgcat b/c hg -R src-filemap log --template '#rev# #desc# files: #files#\n' echo % commit branch @@ -84,12 +89,12 @@ echo % convert again hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' -cat src-hg/a -cat src-hg/b/c +hgcat a +hgcat b/c echo % convert again with --filemap hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' -cat src-hg/b/c +hgcat b/c hg -R src-filemap log --template '#rev# #desc# files: #files#\n' echo "graphlog = " >> $HGRCPATH diff -r 2e58f1a36046 -r d43707e09b02 tests/test-convert-cvs-branch --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-convert-cvs-branch Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,60 @@ +#!/bin/sh + +# This is http://www.selenic.com/mercurial/bts/issue1148 + +"$TESTDIR/hghave" cvs || exit 80 + +cvscall() +{ + cvs -f "$@" +} + +echo "[extensions]" >> $HGRCPATH +echo "convert = " >> $HGRCPATH +echo "graphlog = " >> $HGRCPATH +echo "[convert]" >> $HGRCPATH +echo "cvsps=builtin" >> $HGRCPATH + +echo % create cvs repository +mkdir cvsrepo +cd cvsrepo +export CVSROOT=`pwd` +export CVS_OPTIONS=-f +cd .. + +cvscall -q -d "$CVSROOT" init + +echo % Create a new project + +mkdir src +cd src +echo "1" > a > b +cvscall import -m "init" src v0 r0 +cd .. +cvscall co src +cd src + +echo % Branch the project + +cvscall tag -b BRANCH +cvscall up -r BRANCH + +echo % Modify file a, then b, then a + +echo "2" > a +cvscall ci -m "mod a" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g' + +echo "2" > b +cvscall ci -m "mod b" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g' + +echo "3" > a +cvscall ci -m "mod a again" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g' + +echo % Convert + +cd .. +hg convert src | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' + +echo % Check the result + +hg -R src-hg glog --template '#rev# (#branches#) #desc# files: #files#\n' diff -r 2e58f1a36046 -r d43707e09b02 tests/test-convert-cvs-branch.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-convert-cvs-branch.out Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,53 @@ +% create cvs repository +% Create a new project +N src/a +N src/b + +No conflicts created by this import + +cvs checkout: Updating src +U src/a +U src/b +% Branch the project +cvs tag: Tagging . +T a +T b +cvs update: Updating . +% Modify file a, then b, then a +cvs commit: Examining . +checking in src/a,v +cvs commit: Examining . +checking in src/b,v +cvs commit: Examining . +checking in src/a,v +% Convert +assuming destination src-hg +initializing destination src-hg repository +using builtin cvsps +collecting CVS rlog +7 log entries +creating changesets +5 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +4 Initial revision +3 init +2 mod a +1 mod b +0 mod a again +updating tags +% Check the result +o 5 () update tags files: .hgtags +| +| o 4 (BRANCH) mod a again files: a +| | +| o 3 (BRANCH) mod b files: b +| | +| o 2 (BRANCH) mod a files: a +| | +| o 1 (v0) init files: +|/ +o 0 () Initial revision files: a b + diff -r 2e58f1a36046 -r d43707e09b02 tests/test-convert-cvs-builtincvsps --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-convert-cvs-builtincvsps Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,104 @@ +#!/bin/sh + +"$TESTDIR/hghave" cvs || exit 80 + +cvscall() +{ + cvs -f "$@" +} + +hgcat() +{ + hg --cwd src-hg cat -r tip "$1" +} + +echo "[extensions]" >> $HGRCPATH +echo "convert = " >> $HGRCPATH +echo "graphlog = " >> $HGRCPATH +echo "[convert]" >> $HGRCPATH +echo "cvsps=builtin" >> $HGRCPATH + +echo % create cvs repository +mkdir cvsrepo +cd cvsrepo +export CVSROOT=`pwd` +export CVS_OPTIONS=-f +cd .. + +cvscall -q -d "$CVSROOT" init + +echo % create source directory +mkdir src-temp +cd src-temp +echo a > a +mkdir b +cd b +echo c > c +cd .. + +echo % import source directory +cvscall -q import -m import src INITIAL start +cd .. + +echo % checkout source directory +cvscall -q checkout src + +echo % commit a new revision changing b/c +cd src +sleep 1 +echo c >> b/c +cvscall -q commit -mci0 . | grep '<--' |\ + sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g' +cd .. + +echo % convert fresh repo +hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' +hgcat a +hgcat b/c + +echo % convert fresh repo with --filemap +echo include b/c > filemap +hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' +hgcat b/c +hg -R src-filemap log --template '#rev# #desc# files: #files#\n' + +echo % commit new file revisions +cd src +echo a >> a +echo c >> b/c +cvscall -q commit -mci1 . | grep '<--' |\ + sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g' +cd .. + +echo % convert again +hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' +hgcat a +hgcat b/c + +echo % convert again with --filemap +hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' +hgcat b/c +hg -R src-filemap log --template '#rev# #desc# files: #files#\n' + +echo % commit branch +cd src +cvs -q update -r1.1 b/c +cvs -q tag -b branch +cvs -q update -r branch +echo d >> b/c +cvs -q commit -mci2 . | grep '<--' |\ + sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g' +cd .. + +echo % convert again +hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' +hgcat a +hgcat b/c + +echo % convert again with --filemap +hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g' +hgcat b/c +hg -R src-filemap log --template '#rev# #desc# files: #files#\n' + +echo "graphlog = " >> $HGRCPATH +hg -R src-hg glog --template '#rev# (#branches#) #desc# files: #files#\n' diff -r 2e58f1a36046 -r d43707e09b02 tests/test-convert-cvs-builtincvsps.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-convert-cvs-builtincvsps.out Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,138 @@ +% create cvs repository +% create source directory +% import source directory +N src/a +N src/b/c + +No conflicts created by this import + +% checkout source directory +U src/a +U src/b/c +% commit a new revision changing b/c +checking in src/b/c,v +% convert fresh repo +initializing destination src-hg repository +using builtin cvsps +collecting CVS rlog +5 log entries +creating changesets +3 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +2 Initial revision +1 import +0 ci0 +updating tags +a +c +c +% convert fresh repo with --filemap +initializing destination src-filemap repository +using builtin cvsps +collecting CVS rlog +5 log entries +creating changesets +3 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +2 Initial revision +1 import +rolling back last transaction +0 ci0 +updating tags +c +c +2 update tags files: .hgtags +1 ci0 files: b/c +0 Initial revision files: b/c +% commit new file revisions +checking in src/a,v +checking in src/b/c,v +% convert again +using builtin cvsps +collecting CVS rlog +7 log entries +creating changesets +4 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +0 ci1 +a +a +c +c +c +% convert again with --filemap +using builtin cvsps +collecting CVS rlog +7 log entries +creating changesets +4 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +0 ci1 +c +c +c +3 ci1 files: b/c +2 update tags files: .hgtags +1 ci0 files: b/c +0 Initial revision files: b/c +% commit branch +U b/c +T a +T b/c +checking in src/b/c,v +% convert again +using builtin cvsps +collecting CVS rlog +8 log entries +creating changesets +5 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +0 ci2 +a +c +d +% convert again with --filemap +using builtin cvsps +collecting CVS rlog +8 log entries +creating changesets +5 changeset entries +connecting to cvsrepo +scanning source... +sorting... +converting... +0 ci2 +c +d +4 ci2 files: b/c +3 ci1 files: b/c +2 update tags files: .hgtags +1 ci0 files: b/c +0 Initial revision files: b/c +o 5 (branch) ci2 files: b/c +| +| o 4 () ci1 files: a b/c +| | +| o 3 () update tags files: .hgtags +| | +| o 2 () ci0 files: b/c +|/ +| o 1 (INITIAL) import files: +|/ +o 0 () Initial revision files: a b/c + diff -r 2e58f1a36046 -r d43707e09b02 tests/test-convert-cvs.out --- a/tests/test-convert-cvs.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-convert-cvs.out Tue Jun 24 09:34:38 2008 +0200 @@ -79,7 +79,6 @@ converting... 0 ci2 a -a c d % convert again with --filemap diff -r 2e58f1a36046 -r d43707e09b02 tests/test-convert-hg-source.out --- a/tests/test-convert-hg-source.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-convert-hg-source.out Tue Jun 24 09:34:38 2008 +0200 @@ -1,9 +1,9 @@ created new head -merging baz and foo +merging baz and foo to baz 1 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) 1 files updated, 0 files merged, 1 files removed, 0 files unresolved -merging foo and baz +merging foo and baz to baz 1 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) created new head diff -r 2e58f1a36046 -r d43707e09b02 tests/test-convert-svn-sink.out --- a/tests/test-convert-svn-sink.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-convert-svn-sink.out Tue Jun 24 09:34:38 2008 +0200 @@ -265,9 +265,7 @@ warning: conflicts during merge. merging b failed! 2 files updated, 0 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 2 - hg merge 4 +use 'hg resolve' to retry unresolved file merges assuming destination b-hg initializing svn repo 'b-hg' initializing svn wc 'b-hg-wc' diff -r 2e58f1a36046 -r d43707e09b02 tests/test-copy-move-merge.out --- a/tests/test-copy-move-merge.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-copy-move-merge.out Tue Jun 24 09:34:38 2008 +0200 @@ -12,16 +12,16 @@ checking for directory renames a: remote moved to c -> m a: remote moved to b -> m -copying a to b -copying a to c -picked tool 'internal:merge' for a (binary False symlink False) -merging a and b -my a@fb3948d97f07+ other b@40da226db0f0 ancestor a@583c7b748052 +preserving a for resolve of b +preserving a for resolve of c +removing a +picked tool 'internal:merge' for b (binary False symlink False) +merging a and b to b +my b@fb3948d97f07+ other b@40da226db0f0 ancestor a@583c7b748052 premerge successful -removing a -picked tool 'internal:merge' for a (binary False symlink False) -merging a and c -my a@fb3948d97f07+ other c@40da226db0f0 ancestor a@583c7b748052 +picked tool 'internal:merge' for c (binary False symlink False) +merging a and c to c +my c@fb3948d97f07+ other c@40da226db0f0 ancestor a@583c7b748052 premerge successful 0 files updated, 2 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) diff -r 2e58f1a36046 -r d43707e09b02 tests/test-debugcomplete.out --- a/tests/test-debugcomplete.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-debugcomplete.out Tue Jun 24 09:34:38 2008 +0200 @@ -33,6 +33,7 @@ recover remove rename +resolve revert rollback root @@ -79,6 +80,7 @@ recover remove rename +resolve revert rollback root diff -r 2e58f1a36046 -r d43707e09b02 tests/test-dispatch.out --- a/tests/test-dispatch.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-dispatch.out Tue Jun 24 09:34:38 2008 +0200 @@ -10,7 +10,7 @@ or tip if no revision is checked out. Output may be to a file, in which case the name of the file is - given using a format string. The formatting rules are the same as + given using a format string. The formatting rules are the same as for the export command, with the following additions: %s basename of file being printed diff -r 2e58f1a36046 -r d43707e09b02 tests/test-double-merge.out --- a/tests/test-double-merge.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-double-merge.out Tue Jun 24 09:34:38 2008 +0200 @@ -10,10 +10,11 @@ checking for directory renames foo: versions differ -> m foo: remote copied to bar -> m -copying foo to bar -picked tool 'internal:merge' for foo (binary False symlink False) -merging foo and bar -my foo@2092631ce82b+ other bar@7731dad1c2b9 ancestor foo@310fd17130da +preserving foo for resolve of bar +preserving foo for resolve of foo +picked tool 'internal:merge' for bar (binary False symlink False) +merging foo and bar to bar +my bar@2092631ce82b+ other bar@7731dad1c2b9 ancestor foo@310fd17130da premerge successful picked tool 'internal:merge' for foo (binary False symlink False) merging foo diff -r 2e58f1a36046 -r d43707e09b02 tests/test-dumprevlog --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-dumprevlog Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,47 @@ +#!/bin/sh + +CONTRIBDIR=$TESTDIR/../contrib + +mkdir repo-a +cd repo-a +hg init + +echo this is file a > a +hg add a +hg commit -m first -d '0 0' + +echo adding to file a >> a +hg commit -m second -d '0 0' + +echo adding more to file a >> a +hg commit -m third -d '0 0' + +hg verify + +echo dumping revlog of file a to stdout: +python $CONTRIBDIR/dumprevlog .hg/store/data/a.i +echo dumprevlog done + +# dump all revlogs to file repo.dump +find .hg/store -name "*.i" | sort | xargs python $CONTRIBDIR/dumprevlog > ../repo.dump + +cd .. + +mkdir repo-b +cd repo-b +hg init + +echo undumping: +python $CONTRIBDIR/undumprevlog < ../repo.dump +echo undumping done + +hg verify + +cd .. + +echo comparing repos: +hg -R repo-b incoming repo-a +hg -R repo-a incoming repo-b +echo comparing done + +exit 0 diff -r 2e58f1a36046 -r d43707e09b02 tests/test-dumprevlog.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-dumprevlog.out Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,53 @@ +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 3 changesets, 3 total revisions +dumping revlog of file a to stdout: +file: .hg/store/data/a.i +node: 183d2312b35066fb6b3b449b84efc370d50993d0 +linkrev: 0 +parents: 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 +length: 15 +-start- +this is file a + +-end- +node: b1047953b6e6b633c0d8197eaa5116fbdfd3095b +linkrev: 1 +parents: 183d2312b35066fb6b3b449b84efc370d50993d0 0000000000000000000000000000000000000000 +length: 32 +-start- +this is file a +adding to file a + +-end- +node: 8c4fd1f7129b8cdec6c7f58bf48fb5237a4030c1 +linkrev: 2 +parents: b1047953b6e6b633c0d8197eaa5116fbdfd3095b 0000000000000000000000000000000000000000 +length: 54 +-start- +this is file a +adding to file a +adding more to file a + +-end- +dumprevlog done +undumping: +.hg/store/00changelog.i +.hg/store/00manifest.i +.hg/store/data/a.i +undumping done +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 3 changesets, 3 total revisions +comparing repos: +comparing with repo-a +searching for changes +no changes found +comparing with repo-b +searching for changes +no changes found +comparing done diff -r 2e58f1a36046 -r d43707e09b02 tests/test-extension.out --- a/tests/test-extension.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-extension.out Tue Jun 24 09:34:38 2008 +0200 @@ -33,6 +33,13 @@ debugfoobar: yet another debug command +special help topics: + dates Date Formats + patterns File Name Patterns + environment, env Environment Variables + revs, revisions Specifying Single Revisions + mrevs, multirevs Specifying Multiple Revisions + global options: -R --repository repository root directory or symbolic path name --cwd change working directory diff -r 2e58f1a36046 -r d43707e09b02 tests/test-globalopts.out --- a/tests/test-globalopts.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-globalopts.out Tue Jun 24 09:34:38 2008 +0200 @@ -183,6 +183,7 @@ recover roll back an interrupted transaction remove remove the specified files on the next commit rename rename files; equivalent of copy + remove + resolve resolve file merges from a branch merge or update revert restore individual files or dirs to an earlier state rollback roll back the last transaction root print the root (top) of the current working dir @@ -236,6 +237,7 @@ recover roll back an interrupted transaction remove remove the specified files on the next commit rename rename files; equivalent of copy + remove + resolve resolve file merges from a branch merge or update revert restore individual files or dirs to an earlier state rollback roll back the last transaction root print the root (top) of the current working dir diff -r 2e58f1a36046 -r d43707e09b02 tests/test-help.out --- a/tests/test-help.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-help.out Tue Jun 24 09:34:38 2008 +0200 @@ -74,6 +74,7 @@ recover roll back an interrupted transaction remove remove the specified files on the next commit rename rename files; equivalent of copy + remove + resolve resolve file merges from a branch merge or update revert restore individual files or dirs to an earlier state rollback roll back the last transaction root print the root (top) of the current working dir @@ -123,6 +124,7 @@ recover roll back an interrupted transaction remove remove the specified files on the next commit rename rename files; equivalent of copy + remove + resolve resolve file merges from a branch merge or update revert restore individual files or dirs to an earlier state rollback roll back the last transaction root print the root (top) of the current working dir @@ -199,9 +201,9 @@ -r --rev revision -a --text treat all files as text - -p --show-function show which function each change is in -g --git use git extended diff format --nodates don't include dates in diff headers + -p --show-function show which function each change is in -w --ignore-all-space ignore white space when comparing lines -b --ignore-space-change ignore changes in the amount of white space -B --ignore-blank-lines ignore changes whose lines are all blank @@ -216,10 +218,10 @@ show changed files in the working directory - Show status of files in the repository. If names are given, only - files that match are shown. Files that are clean or ignored or + Show status of files in the repository. If names are given, only + files that match are shown. Files that are clean or ignored or source of a copy/move operation, are not listed unless -c (clean), - -i (ignored), -C (copies) or -A is given. Unless options described + -i (ignored), -C (copies) or -A is given. Unless options described with "show only ..." are given, the options -mardu are used. Option -q/--quiet hides untracked (unknown and ignored) files diff -r 2e58f1a36046 -r d43707e09b02 tests/test-hgweb-commands.out Binary file tests/test-hgweb-commands.out has changed diff -r 2e58f1a36046 -r d43707e09b02 tests/test-hgweb.out --- a/tests/test-hgweb.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-hgweb.out Tue Jun 24 09:34:38 2008 +0200 @@ -186,4 +186,45 @@ background-color: #d5dde6; border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4; } + +/* Graph */ +div#wrapper { + position: relative; + margin: 0; + padding: 0; +} + +canvas { + position: absolute; + z-index: 5; + top: -0.9em; + margin: 0; +} + +ul#nodebgs { + list-style: none inside none; + padding: 0; + margin: 0; + top: -0.7em; +} + +ul#graphnodes li, ul#nodebgs li { + height: 39px; +} + +ul#graphnodes { + position: absolute; + z-index: 10; + top: -0.8em; + list-style: none inside none; + padding: 0; +} + +ul#graphnodes li .info { + display: block; + font-size: 100%; + position: relative; + top: -3px; + font-style: italic; +} % errors diff -r 2e58f1a36046 -r d43707e09b02 tests/test-highlight --- a/tests/test-highlight Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-highlight Tue Jun 24 09:34:38 2008 +0200 @@ -5,6 +5,8 @@ cat <> $HGRCPATH [extensions] hgext.highlight = +[web] +pygments_style = friendly EOF hg init test @@ -24,6 +26,28 @@ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/annotate/tip/get-with-headers.py') \ | sed "s/[0-9]* years ago/long ago/g" +echo % hgweb highlightcss friendly +("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/highlightcss') \ + | head -n 4 + +echo % errors encountered +cat errors.log +kill `cat hg.pid` + +# Change the pygments style +cat > .hg/hgrc <> $DAEMON_PIDS + +echo % hgweb highlightcss fruity +("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/highlightcss') \ + | head -n 4 + echo % errors encountered cat errors.log diff -r 2e58f1a36046 -r d43707e09b02 tests/test-highlight.out --- a/tests/test-highlight.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-highlight.out Tue Jun 24 09:34:38 2008 +0200 @@ -10,7 +10,7 @@ - + test:get-with-headers.py @@ -18,6 +18,7 @@
              changelog shortlog +graph tags changeset files @@ -72,7 +73,7 @@ - + test: get-with-headers.py annotate @@ -80,6 +81,7 @@
              changelog shortlog +graph tags changeset files @@ -114,7 +116,7 @@
              - +
              test@0 1
              #!/usr/bin/env python
              test@0 2
              test@0 3
              __doc__ = """This does HTTP get requests given a host:port and path and returns
              test@0 4
              a subset of the headers plus the body of the result."""
              test@0 5
              test@0 6
              import httplib, sys
              test@0 7
              headers = [h.lower() for h in sys.argv[3:]]
              test@0 8
              conn = httplib.HTTPConnection(sys.argv[1])
              test@0 9
              conn.request("GET", sys.argv[2])
              test@0 10
              response = conn.getresponse()
              test@0 11
              print response.status, response.reason
              test@0 12
              for h in headers:
              test@0 13
                  if response.getheader(h, None) is not None:
              test@0 14
                      print "%s: %s" % (h, response.getheader(h))
              test@0 15
              print
              test@0 16
              sys.stdout.write(response.read())
              test@0 17
              test@0 18
              if 200 <= response.status <= 299:
              test@0 19
                  sys.exit(0)
              test@0 20
              sys.exit(1)
              test@0 1
              #!/usr/bin/env python
              test@0 2
              test@0 3
              __doc__ = """This does HTTP get requests given a host:port and path and returns
              test@0 4
              a subset of the headers plus the body of the result."""
              test@0 5
              test@0 6
              import httplib, sys
              test@0 7
              headers = [h.lower() for h in sys.argv[3:]]
              test@0 8
              conn = httplib.HTTPConnection(sys.argv[1])
              test@0 9
              conn.request("GET", sys.argv[2])
              test@0 10
              response = conn.getresponse()
              test@0 11
              print response.status, response.reason
              test@0 12
              for h in headers:
              test@0 13
                  if response.getheader(h, None) is not None:
              test@0 14
                      print "%s: %s" % (h, response.getheader(h))
              test@0 15
              print
              test@0 16
              sys.stdout.write(response.read())
              test@0 17
              test@0 18
              if 200 <= response.status <= 299:
              test@0 19
                  sys.exit(0)
              test@0 20
              sys.exit(1)
              @@ -126,4 +128,16 @@ +% hgweb highlightcss friendly +200 Script output follows + +/* pygments_style = friendly */ + % errors encountered +% hg serve again +% hgweb highlightcss fruity +200 Script output follows + +/* pygments_style = fruity */ + +% errors encountered diff -r 2e58f1a36046 -r d43707e09b02 tests/test-imerge diff -r 2e58f1a36046 -r d43707e09b02 tests/test-issue612.out --- a/tests/test-issue612.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-issue612.out Tue Jun 24 09:34:38 2008 +0200 @@ -3,7 +3,7 @@ 1 files updated, 0 files merged, 1 files removed, 0 files unresolved created new head ? src/a.o -merging src/a.c and source/a.c +merging src/a.c and source/a.c to source/a.c 1 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) M source/a.c diff -r 2e58f1a36046 -r d43707e09b02 tests/test-issue672.out --- a/tests/test-issue672.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-issue672.out Tue Jun 24 09:34:38 2008 +0200 @@ -30,8 +30,9 @@ 1a -> 1 * checking for directory renames 1a: local moved to 1 -> m +preserving 1a for resolve of 1a picked tool 'internal:merge' for 1a (binary False symlink False) -merging 1a and 1 +merging 1a and 1 to 1a my 1a@ac7575e3c052+ other 1@746e9549ea96 ancestor 1@81f4b099af3d premerge successful 0 files updated, 1 files merged, 0 files removed, 0 files unresolved @@ -47,11 +48,11 @@ 1a -> 1 * checking for directory renames 1: remote moved to 1a -> m -copying 1 to 1a -picked tool 'internal:merge' for 1 (binary False symlink False) -merging 1 and 1a -my 1@746e9549ea96+ other 1a@ac7575e3c052 ancestor 1@81f4b099af3d +preserving 1 for resolve of 1a +removing 1 +picked tool 'internal:merge' for 1a (binary False symlink False) +merging 1 and 1a to 1a +my 1a@746e9549ea96+ other 1a@ac7575e3c052 ancestor 1@81f4b099af3d premerge successful -removing 1 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) diff -r 2e58f1a36046 -r d43707e09b02 tests/test-keyword --- a/tests/test-keyword Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-keyword Tue Jun 24 09:34:38 2008 +0200 @@ -214,6 +214,8 @@ echo % hg cat hg cat sym a b echo +echo % annotate +hg annotate a echo % remove hg debugrebuildstate diff -r 2e58f1a36046 -r d43707e09b02 tests/test-keyword.out --- a/tests/test-keyword.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-keyword.out Tue Jun 24 09:34:38 2008 +0200 @@ -294,6 +294,11 @@ $Xinfo: User Name : firstline $ ignore $Id$ a +% annotate +1: expand $Id$ +1: do not process $Id: +1: xxx $ +2: $Xinfo$ % remove % status % rollback @@ -345,7 +350,7 @@ % kwexpand a overwriting a expanding keywords % kwexpand x/a should abort -abort: outstanding uncommitted changes in given files +abort: outstanding uncommitted changes x/a x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e overwriting x/a expanding keywords diff -r 2e58f1a36046 -r d43707e09b02 tests/test-mactext --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-mactext Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,32 @@ +#!/bin/sh + +cat > unix2mac.py < print.py <').replace('\r', '').replace('\0', '')) +EOF + +hg init +echo '[hooks]' >> .hg/hgrc +echo 'pretxncommit.cr = python:hgext.win32text.forbidcr' >> .hg/hgrc +echo 'pretxnchangegroup.cr = python:hgext.win32text.forbidcr' >> .hg/hgrc +cat .hg/hgrc +echo + +echo hello > f +hg add f +hg ci -m 1 -d'0 0' +echo + +python unix2mac.py f +hg ci -m 2 -d'0 0' +hg cat f | python print.py +cat f | python print.py diff -r 2e58f1a36046 -r d43707e09b02 tests/test-mactext.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-mactext.out Tue Jun 24 09:34:38 2008 +0200 @@ -0,0 +1,12 @@ +[hooks] +pretxncommit.cr = python:hgext.win32text.forbidcr +pretxnchangegroup.cr = python:hgext.win32text.forbidcr + + +Attempt to commit or push text file(s) using CR line endings +in dea860dc51ec: f +transaction abort! +rollback completed +abort: pretxncommit.cr hook failed +hello +hello diff -r 2e58f1a36046 -r d43707e09b02 tests/test-merge-commit.out --- a/tests/test-merge-commit.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-merge-commit.out Tue Jun 24 09:34:38 2008 +0200 @@ -1,6 +1,6 @@ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved created new head -merging bar and foo +merging bar and foo to bar 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) % contents of bar should be line0 line1 line2 @@ -27,6 +27,7 @@ ancestor 0a3ab4856510 local 2d2f9a22c82b+ remote 7d3b554bfdf1 searching for copies back to rev 1 bar: versions differ -> m +preserving bar for resolve of bar picked tool 'internal:merge' for bar (binary False symlink False) merging bar my bar@2d2f9a22c82b+ other bar@7d3b554bfdf1 ancestor bar@0a3ab4856510 @@ -49,7 +50,7 @@ adding file changes added 3 changesets with 3 changes to 2 files (+1 heads) 1 files updated, 0 files merged, 0 files removed, 0 files unresolved -merging foo and bar +merging foo and bar to bar 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) % contents of bar should be line0 line1 line2 @@ -76,6 +77,7 @@ ancestor 0a3ab4856510 local 2d2f9a22c82b+ remote 96ab80c60897 searching for copies back to rev 1 bar: versions differ -> m +preserving bar for resolve of bar picked tool 'internal:merge' for bar (binary False symlink False) merging bar my bar@2d2f9a22c82b+ other bar@96ab80c60897 ancestor bar@0a3ab4856510 diff -r 2e58f1a36046 -r d43707e09b02 tests/test-merge-default.out --- a/tests/test-merge-default.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-merge-default.out Tue Jun 24 09:34:38 2008 +0200 @@ -5,10 +5,10 @@ created new head 1 files updated, 0 files merged, 0 files removed, 0 files unresolved % should fail because not at a head -abort: repo has 3 heads - please merge with an explicit rev +abort: branch 'default' has 3 heads - please merge with an explicit rev 1 files updated, 0 files merged, 0 files removed, 0 files unresolved % should fail because > 2 heads -abort: repo has 3 heads - please merge with an explicit rev +abort: branch 'default' has 3 heads - please merge with an explicit rev % should succeed 0 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) diff -r 2e58f1a36046 -r d43707e09b02 tests/test-merge-internal-tools-pattern.out --- a/tests/test-merge-internal-tools-pattern.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-merge-internal-tools-pattern.out Tue Jun 24 09:34:38 2008 +0200 @@ -9,9 +9,7 @@ created new head # merge using internal:fail tool 0 files updated, 0 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 2 - hg merge 1 +use 'hg resolve' to retry unresolved file merges line 1 line 2 third line diff -r 2e58f1a36046 -r d43707e09b02 tests/test-merge-local.out --- a/tests/test-merge-local.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-merge-local.out Tue Jun 24 09:34:38 2008 +0200 @@ -21,10 +21,7 @@ merging zzz2_merge_bad merging zzz2_merge_bad failed! 3 files updated, 1 files merged, 2 files removed, 1 files unresolved -There are unresolved merges with locally modified files. -You can finish the partial merge using: - hg update 0 - hg update 1 +use 'hg resolve' to retry unresolved file merges 2 files updated, 0 files merged, 3 files removed, 0 files unresolved --- a/zzz1_merge_ok +++ b/zzz1_merge_ok @@ -42,10 +39,7 @@ warning: conflicts during merge. merging zzz2_merge_bad failed! 3 files updated, 1 files merged, 2 files removed, 1 files unresolved -There are unresolved merges with locally modified files. -You can finish the partial merge using: - hg update 0 - hg update 1 +use 'hg resolve' to retry unresolved file merges 2 files updated, 0 files merged, 3 files removed, 0 files unresolved --- a/zzz1_merge_ok +++ b/zzz1_merge_ok diff -r 2e58f1a36046 -r d43707e09b02 tests/test-merge-remove.out --- a/tests/test-merge-remove.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-merge-remove.out Tue Jun 24 09:34:38 2008 +0200 @@ -1,5 +1,5 @@ created new head -merging foo1 and foo +merging foo1 and foo to foo1 1 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) n 0 -2 bar @@ -13,7 +13,6 @@ copy: foo -> foo1 R bar R foo1 - foo % readding foo1 and bar adding bar adding foo1 diff -r 2e58f1a36046 -r d43707e09b02 tests/test-merge-revert2.out --- a/tests/test-merge-revert2.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-merge-revert2.out Tue Jun 24 09:34:38 2008 +0200 @@ -13,10 +13,7 @@ warning: conflicts during merge. merging file1 failed! 0 files updated, 0 files merged, 0 files removed, 1 files unresolved -There are unresolved merges with locally modified files. -You can redo the full merge using: - hg update 0 - hg update 1 +use 'hg resolve' to retry unresolved file merges diff -r f248da0d4c3e file1 --- a/file1 +++ b/file1 diff -r 2e58f1a36046 -r d43707e09b02 tests/test-merge10.out --- a/tests/test-merge10.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-merge10.out Tue Jun 24 09:34:38 2008 +0200 @@ -8,7 +8,7 @@ added 1 changesets with 1 changes to 1 files (+1 heads) (run 'hg heads' to see heads, 'hg merge' to merge) 0 files updated, 0 files merged, 0 files removed, 0 files unresolved -merging testdir/subdir/a and testdir/a +merging testdir/subdir/a and testdir/a to testdir/subdir/a 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) M testdir/subdir/a diff -r 2e58f1a36046 -r d43707e09b02 tests/test-merge7.out --- a/tests/test-merge7.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-merge7.out Tue Jun 24 09:34:38 2008 +0200 @@ -11,9 +11,7 @@ warning: conflicts during merge. merging test.txt failed! 0 files updated, 0 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 1 - hg merge 2 +use 'hg resolve' to retry unresolved file merges pulling from ../test-a searching for changes adding changesets @@ -26,15 +24,14 @@ ancestor faaea63e63a9 local 451c744aabcc+ remote a070d41e8360 searching for copies back to rev 1 test.txt: versions differ -> m +preserving test.txt for resolve of test.txt picked tool 'internal:merge' for test.txt (binary False symlink False) merging test.txt my test.txt@451c744aabcc+ other test.txt@a070d41e8360 ancestor test.txt@faaea63e63a9 warning: conflicts during merge. merging test.txt failed! 0 files updated, 0 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 3 - hg merge 4 +use 'hg resolve' to retry unresolved file merges one <<<<<<< local two-point-five diff -r 2e58f1a36046 -r d43707e09b02 tests/test-merge9 --- a/tests/test-merge9 Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-merge9 Tue Jun 24 09:34:38 2008 +0200 @@ -23,9 +23,31 @@ # test with the rename on the remote side HGMERGE=false hg merge +hg resolve -l # test with the rename on the local side hg up -C 1 HGMERGE=false hg merge +echo % show unresolved +hg resolve -l + +echo % unmark baz +hg resolve -u baz + +echo % show +hg resolve -l + +echo % re-resolve baz +hg resolve baz + +echo % after +hg resolve -l + +echo % resolve all +hg resolve + +echo % after +hg resolve -l + true diff -r 2e58f1a36046 -r d43707e09b02 tests/test-merge9.out --- a/tests/test-merge9.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-merge9.out Tue Jun 24 09:34:38 2008 +0200 @@ -5,16 +5,33 @@ created new head merging bar merging bar failed! -merging foo and baz +merging foo and baz to baz 1 files updated, 1 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 2 - hg merge 1 +use 'hg resolve' to retry unresolved file merges +U bar +R baz 3 files updated, 0 files merged, 1 files removed, 0 files unresolved merging bar merging bar failed! -merging baz and foo +merging baz and foo to baz 1 files updated, 1 files merged, 0 files removed, 1 files unresolved -There are unresolved merges, you can redo the full merge using: - hg update -C 1 - hg merge 2 +use 'hg resolve' to retry unresolved file merges +% show unresolved +U bar +R baz +% unmark baz +% show +U bar +U baz +% re-resolve baz +merging baz and foo to baz +% after +U bar +R baz +% resolve all +merging bar +warning: conflicts during merge. +merging bar failed! +% after +U bar +R baz diff -r 2e58f1a36046 -r d43707e09b02 tests/test-mq --- a/tests/test-mq Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-mq Tue Jun 24 09:34:38 2008 +0200 @@ -266,6 +266,14 @@ hg strip tip 2>&1 | sed 's/\(saving bundle to \).*/\1/' hg unbundle .hg/strip-backup/* +echo % strip with local changes, should complain +hg up +echo y>y +hg add y +hg strip tip | sed 's/\(saving bundle to \).*/\1/' +echo % --force strip with local changes +hg strip -f tip 2>&1 | sed 's/\(saving bundle to \).*/\1/' + echo '% cd b; hg qrefresh' hg init refresh cd refresh diff -r 2e58f1a36046 -r d43707e09b02 tests/test-mq-qdelete --- a/tests/test-mq-qdelete Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-mq-qdelete Tue Jun 24 09:34:38 2008 +0200 @@ -35,3 +35,33 @@ hg qdel -r qbase:e hg qapplied hg log --template '{rev} {desc}\n' + +cd .. +hg init b +cd b + +echo 'base' > base +hg ci -Ambase + +hg qfinish +hg qfinish -a + +hg qnew a +hg qnew b +hg qnew c + +hg qfinish 0 +hg qfinish b + +hg qpop +hg qfinish -a c +hg qpush + +hg qfinish qbase:b +hg qapplied +hg log --template '{rev} {desc}\n' + +hg qfinish -a c +hg qapplied +hg log --template '{rev} {desc}\n' +ls .hg/patches diff -r 2e58f1a36046 -r d43707e09b02 tests/test-mq-qdelete.out --- a/tests/test-mq-qdelete.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-mq-qdelete.out Tue Jun 24 09:34:38 2008 +0200 @@ -22,3 +22,23 @@ 2 [mq]: d 1 [mq]: a 0 base +adding base +abort: no revisions specified +no patches applied +abort: revision 0 is not managed +abort: cannot delete revision 2 above applied patches +Now at: b +abort: unknown revision 'c'! +applying c +Now at: c +c +3 imported patch c +2 [mq]: b +1 [mq]: a +0 base +3 imported patch c +2 [mq]: b +1 [mq]: a +0 base +series +status diff -r 2e58f1a36046 -r d43707e09b02 tests/test-mq-qdiff --- a/tests/test-mq-qdiff Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-mq-qdiff Tue Jun 24 09:34:38 2008 +0200 @@ -25,3 +25,35 @@ echo % qdiff dirname hg qdiff . | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" + +echo % qdiff filename +hg qdiff --nodates base + +echo % revert +hg revert -a + +echo % qpop +hg qpop + +echo % qdelete mqbase +hg qdelete mqbase + +echo % commit 2 +printf '1\n2\n3\n4\nhello world\ngoodbye world\n7\n8\n9\n' > lines +hg ci -Amlines -d '2 0' + +echo % qnew 2 +hg qnew -mmqbase2 mqbase2 +printf '\n\n1\n2\n3\n4\nhello world\n goodbye world\n7\n8\n9\n' > lines + +echo % qdiff -U 1 +hg qdiff --nodates -U 1 + +echo % qdiff -b +hg qdiff --nodates -b + +echo % qdiff -U 1 -B +hg qdiff --nodates -U 1 -B + +echo qdiff -w +hg qdiff --nodates -w diff -r 2e58f1a36046 -r d43707e09b02 tests/test-mq-qdiff.out --- a/tests/test-mq-qdiff.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-mq-qdiff.out Tue Jun 24 09:34:38 2008 +0200 @@ -17,3 +17,71 @@ @@ -1,1 +1,1 @@ -base +patched +% qdiff filename +diff -r 67e992f2c4f3 base +--- a/base ++++ b/base +@@ -1,1 +1,1 @@ +-base ++patched +% revert +% qpop +Patch queue now empty +% qdelete mqbase +% commit 2 +adding lines +% qnew 2 +% qdiff -U 1 +diff -r 35fb829491c1 lines +--- a/lines ++++ b/lines +@@ -1,1 +1,3 @@ ++ ++ + 1 +@@ -4,4 +6,4 @@ + 4 +-hello world +-goodbye world ++hello world ++ goodbye world + 7 +% qdiff -b +diff -r 35fb829491c1 lines +--- a/lines ++++ b/lines +@@ -1,9 +1,11 @@ ++ ++ + 1 + 2 + 3 + 4 +-hello world +-goodbye world ++hello world ++ goodbye world + 7 + 8 + 9 +% qdiff -U 1 -B +diff -r 35fb829491c1 lines +--- a/lines ++++ b/lines +@@ -4,4 +6,4 @@ + 4 +-hello world +-goodbye world ++hello world ++ goodbye world + 7 +qdiff -w +diff -r 35fb829491c1 lines +--- a/lines ++++ b/lines +@@ -1,3 +1,5 @@ ++ ++ + 1 + 2 + 3 diff -r 2e58f1a36046 -r d43707e09b02 tests/test-mq.out --- a/tests/test-mq.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-mq.out Tue Jun 24 09:34:38 2008 +0200 @@ -29,6 +29,7 @@ qcommit commit changes in the queue repository qdelete remove patches from queue qdiff diff of the current patch and subsequent modifications + qfinish move applied patches into repository history qfold fold the named patches into the current patch qgoto push or pop patches until named patch is at top of stack qguard set or print guards for a patch @@ -96,6 +97,7 @@ A somefile % qnew with uncommitted changes and missing file (issue 803) someotherfile: No such file or directory +someotherfile: No such file or directory A somefile issue803.patch Patch queue now empty @@ -250,6 +252,12 @@ adding file changes added 1 changesets with 1 changes to 1 files (run 'hg update' to get a working copy) +% strip with local changes, should complain +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +abort: local changes found +% --force strip with local changes +0 files updated, 0 files merged, 2 files removed, 0 files unresolved +saving bundle to % cd b; hg qrefresh adding a foo diff -r 2e58f1a36046 -r d43707e09b02 tests/test-newbranch --- a/tests/test-newbranch Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-newbranch Tue Jun 24 09:34:38 2008 +0200 @@ -24,7 +24,7 @@ echo bleah > a hg ci -m "modify a branch" -d "1000000 0" -hg merge +hg merge default hg branch hg ci -m "merge" -d "1000000 0" hg log @@ -73,4 +73,38 @@ hg parents hg manifest -exit 0 +echo % test merging, add 3 default heads and one test head +cd .. +hg init merges +cd merges +echo a > a +hg ci -Ama + +echo b > b +hg ci -Amb + +hg up 0 +echo c > c +hg ci -Amc + +hg up 0 +echo d > d +hg ci -Amd + +hg up 0 +hg branch test +echo e >> e +hg ci -Ame + +hg log + +echo % implicit merge with test branch as parent +hg merge +hg up -C default +echo % implicit merge with default branch as parent +hg merge +echo % 3 branch heads, explicit merge required +hg merge 2 +hg ci -m merge +echo % 2 branch heads, implicit merge works +hg merge diff -r 2e58f1a36046 -r d43707e09b02 tests/test-newbranch.out --- a/tests/test-newbranch.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-newbranch.out Tue Jun 24 09:34:38 2008 +0200 @@ -116,3 +116,57 @@ a ff +% test merging, add 3 default heads and one test head +adding a +adding b +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +adding c +created new head +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +adding d +created new head +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +marked working directory as branch test +adding e +created new head +changeset: 4:3a1e01ed1df4 +branch: test +tag: tip +parent: 0:cb9a9f314b8b +user: test +date: Thu Jan 01 00:00:00 1970 +0000 +summary: e + +changeset: 3:980f7dc84c29 +parent: 0:cb9a9f314b8b +user: test +date: Thu Jan 01 00:00:00 1970 +0000 +summary: d + +changeset: 2:d36c0562f908 +parent: 0:cb9a9f314b8b +user: test +date: Thu Jan 01 00:00:00 1970 +0000 +summary: c + +changeset: 1:d2ae7f538514 +user: test +date: Thu Jan 01 00:00:00 1970 +0000 +summary: b + +changeset: 0:cb9a9f314b8b +user: test +date: Thu Jan 01 00:00:00 1970 +0000 +summary: a + +% implicit merge with test branch as parent +abort: branch 'test' has one head - please merge with an explicit rev +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +% implicit merge with default branch as parent +abort: branch 'default' has 3 heads - please merge with an explicit rev +% 3 branch heads, explicit merge required +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +% 2 branch heads, implicit merge works +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) diff -r 2e58f1a36046 -r d43707e09b02 tests/test-rename-merge1.out --- a/tests/test-rename-merge1.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-rename-merge1.out Tue Jun 24 09:34:38 2008 +0200 @@ -19,12 +19,12 @@ a2: divergent renames -> dr a: remote moved to b -> m b2: remote created -> g -copying a to b -picked tool 'internal:merge' for a (binary False symlink False) -merging a and b -my a@f26ec4fc3fa3+ other b@8e765a822af2 ancestor a@af1939970a1c +preserving a for resolve of b +removing a +picked tool 'internal:merge' for b (binary False symlink False) +merging a and b to b +my b@f26ec4fc3fa3+ other b@8e765a822af2 ancestor a@af1939970a1c premerge successful -removing a warning: detected divergent renames of a2 to: c2 b2 diff -r 2e58f1a36046 -r d43707e09b02 tests/test-rename-merge2.out --- a/tests/test-rename-merge2.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-rename-merge2.out Tue Jun 24 09:34:38 2008 +0200 @@ -13,10 +13,11 @@ checking for directory renames rev: versions differ -> m a: remote copied to b -> m -copying a to b -picked tool 'python ../merge' for a (binary False symlink False) -merging a and b -my a@e300d1c794ec+ other b@735846fee2d7 ancestor a@924404dff337 +preserving a for resolve of b +preserving rev for resolve of rev +picked tool 'python ../merge' for b (binary False symlink False) +merging a and b to b +my b@e300d1c794ec+ other b@735846fee2d7 ancestor a@924404dff337 premerge successful picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -45,9 +46,11 @@ a: remote is newer -> g b: local copied to a -> m rev: versions differ -> m +preserving b for resolve of b +preserving rev for resolve of rev getting a picked tool 'python ../merge' for b (binary False symlink False) -merging b and a +merging b and a to b my b@ac809aeed39a+ other a@f4db7e329e71 ancestor a@924404dff337 premerge successful picked tool 'python ../merge' for rev (binary False symlink False) @@ -76,12 +79,13 @@ checking for directory renames rev: versions differ -> m a: remote moved to b -> m -copying a to b -picked tool 'python ../merge' for a (binary False symlink False) -merging a and b -my a@e300d1c794ec+ other b@e03727d2d66b ancestor a@924404dff337 +preserving a for resolve of b +preserving rev for resolve of rev +removing a +picked tool 'python ../merge' for b (binary False symlink False) +merging a and b to b +my b@e300d1c794ec+ other b@e03727d2d66b ancestor a@924404dff337 premerge successful -removing a picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@e300d1c794ec+ other rev@e03727d2d66b ancestor rev@924404dff337 @@ -107,8 +111,10 @@ checking for directory renames b: local moved to a -> m rev: versions differ -> m +preserving b for resolve of b +preserving rev for resolve of rev picked tool 'python ../merge' for b (binary False symlink False) -merging b and a +merging b and a to b my b@ecf3cb2a4219+ other a@f4db7e329e71 ancestor a@924404dff337 premerge successful picked tool 'python ../merge' for rev (binary False symlink False) @@ -136,6 +142,7 @@ checking for directory renames rev: versions differ -> m b: remote created -> g +preserving rev for resolve of rev getting b picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -161,6 +168,7 @@ b -> a checking for directory renames rev: versions differ -> m +preserving rev for resolve of rev picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@ac809aeed39a+ other rev@97c705ade336 ancestor rev@924404dff337 @@ -187,6 +195,7 @@ a: other deleted -> r rev: versions differ -> m b: remote created -> g +preserving rev for resolve of rev removing a getting b picked tool 'python ../merge' for rev (binary False symlink False) @@ -212,6 +221,7 @@ b -> a checking for directory renames rev: versions differ -> m +preserving rev for resolve of rev picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@ecf3cb2a4219+ other rev@97c705ade336 ancestor rev@924404dff337 @@ -231,6 +241,8 @@ searching for copies back to rev 1 b: versions differ -> m rev: versions differ -> m +preserving b for resolve of b +preserving rev for resolve of rev picked tool 'python ../merge' for b (binary False symlink False) merging b my b@ec03c2ca8642+ other b@79cc6877a3b7 ancestor a@924404dff337 @@ -262,6 +274,7 @@ a: divergent renames -> dr rev: versions differ -> m c: remote created -> g +preserving rev for resolve of rev warning: detected divergent renames of a to: b c @@ -286,6 +299,8 @@ searching for copies back to rev 1 b: versions differ -> m rev: versions differ -> m +preserving b for resolve of b +preserving rev for resolve of rev picked tool 'python ../merge' for b (binary False symlink False) merging b my b@ac809aeed39a+ other b@af30c7647fc7 ancestor b@000000000000 @@ -310,6 +325,8 @@ a: other deleted -> r b: versions differ -> m rev: versions differ -> m +preserving b for resolve of b +preserving rev for resolve of rev removing a picked tool 'python ../merge' for b (binary False symlink False) merging b @@ -334,6 +351,8 @@ a: remote is newer -> g b: versions differ -> m rev: versions differ -> m +preserving b for resolve of b +preserving rev for resolve of rev getting a picked tool 'python ../merge' for b (binary False symlink False) merging b @@ -359,6 +378,8 @@ a: other deleted -> r b: versions differ -> m rev: versions differ -> m +preserving b for resolve of b +preserving rev for resolve of rev removing a picked tool 'python ../merge' for b (binary False symlink False) merging b @@ -383,6 +404,8 @@ a: remote is newer -> g b: versions differ -> m rev: versions differ -> m +preserving b for resolve of b +preserving rev for resolve of rev getting a picked tool 'python ../merge' for b (binary False symlink False) merging b @@ -407,6 +430,8 @@ searching for copies back to rev 1 b: versions differ -> m rev: versions differ -> m +preserving b for resolve of b +preserving rev for resolve of rev picked tool 'python ../merge' for b (binary False symlink False) merging b my b@0b76e65c8289+ other b@735846fee2d7 ancestor b@000000000000 @@ -431,6 +456,8 @@ b: versions differ -> m rev: versions differ -> m a: prompt recreating -> g +preserving b for resolve of b +preserving rev for resolve of rev getting a picked tool 'python ../merge' for b (binary False symlink False) merging b @@ -455,6 +482,8 @@ searching for copies back to rev 1 b: versions differ -> m rev: versions differ -> m +preserving b for resolve of b +preserving rev for resolve of rev picked tool 'python ../merge' for b (binary False symlink False) merging b my b@0b76e65c8289+ other b@e03727d2d66b ancestor b@000000000000 @@ -483,11 +512,12 @@ checking for directory renames rev: versions differ -> m a: remote moved to b -> m -copying a to b -picked tool 'python ../merge' for a (binary False symlink False) -merging a and b -my a@e300d1c794ec+ other b@79cc6877a3b7 ancestor a@924404dff337 +preserving a for resolve of b +preserving rev for resolve of rev removing a +picked tool 'python ../merge' for b (binary False symlink False) +merging a and b to b +my b@e300d1c794ec+ other b@79cc6877a3b7 ancestor a@924404dff337 picked tool 'python ../merge' for rev (binary False symlink False) merging rev my rev@e300d1c794ec+ other rev@79cc6877a3b7 ancestor rev@924404dff337 @@ -513,8 +543,10 @@ checking for directory renames b: local moved to a -> m rev: versions differ -> m +preserving b for resolve of b +preserving rev for resolve of rev picked tool 'python ../merge' for b (binary False symlink False) -merging b and a +merging b and a to b my b@ec03c2ca8642+ other a@f4db7e329e71 ancestor a@924404dff337 picked tool 'python ../merge' for rev (binary False symlink False) merging rev @@ -544,8 +576,10 @@ b: local moved to a -> m rev: versions differ -> m c: remote created -> g +preserving b for resolve of b +preserving rev for resolve of rev picked tool 'python ../merge' for b (binary False symlink False) -merging b and a +merging b and a to b my b@ecf3cb2a4219+ other a@2b958612230f ancestor a@924404dff337 premerge successful getting c diff -r 2e58f1a36046 -r d43707e09b02 tests/test-revert.out diff -r 2e58f1a36046 -r d43707e09b02 tests/test-serve --- a/tests/test-serve Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-serve Tue Jun 24 09:34:38 2008 +0200 @@ -2,9 +2,11 @@ hgserve() { - hg serve -a localhost -p $HGPORT1 -d --pid-file=hg.pid -v $@ \ + hg serve -a localhost -p $HGPORT1 -d --pid-file=hg.pid -E errors.log -v $@ \ | sed -e 's/:[0-9][0-9]*//g' -e 's/http:\/\/[^/]*\//http:\/\/localhost\//' cat hg.pid >> "$DAEMON_PIDS" + echo % errors + cat errors.log sleep 1 kill `cat hg.pid` sleep 1 @@ -17,11 +19,13 @@ echo 'accesslog = access.log' >> .hg/hgrc echo % Without -v -hg serve -a localhost -p $HGPORT -d --pid-file=hg.pid +hg serve -a localhost -p $HGPORT -d --pid-file=hg.pid -E errors.log cat hg.pid >> "$DAEMON_PIDS" if [ -f access.log ]; then echo 'access log created - .hg/hgrc respected' fi +echo % errors +cat errors.log echo % With -v hgserve diff -r 2e58f1a36046 -r d43707e09b02 tests/test-serve.out --- a/tests/test-serve.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-serve.out Tue Jun 24 09:34:38 2008 +0200 @@ -1,12 +1,18 @@ % Without -v access log created - .hg/hgrc respected +% errors % With -v -listening at http://localhost/ (127.0.0.1) +listening at http://localhost/ (bound to 127.0.0.1) +% errors % With --prefix foo -listening at http://localhost/foo/ (127.0.0.1) +listening at http://localhost/foo/ (bound to 127.0.0.1) +% errors % With --prefix /foo -listening at http://localhost/foo/ (127.0.0.1) +listening at http://localhost/foo/ (bound to 127.0.0.1) +% errors % With --prefix foo/ -listening at http://localhost/foo/ (127.0.0.1) +listening at http://localhost/foo/ (bound to 127.0.0.1) +% errors % With --prefix /foo/ -listening at http://localhost/foo/ (127.0.0.1) +listening at http://localhost/foo/ (bound to 127.0.0.1) +% errors diff -r 2e58f1a36046 -r d43707e09b02 tests/test-tag.out --- a/tests/test-tag.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-tag.out Tue Jun 24 09:34:38 2008 +0200 @@ -36,7 +36,9 @@ 868cc8fbb43b754ad09fa109885d243fc49adae7 gawk 868cc8fbb43b754ad09fa109885d243fc49adae7 gorp 3807bcf62c5614cb6c16436b514d7764ca5f1631 gack +3807bcf62c5614cb6c16436b514d7764ca5f1631 gack 0000000000000000000000000000000000000000 gack +868cc8fbb43b754ad09fa109885d243fc49adae7 gorp 0000000000000000000000000000000000000000 gorp 3ecf002a1c572a2f3bb4e665417e60fca65bbd42 bleah1 0 files updated, 0 files merged, 1 files removed, 0 files unresolved diff -r 2e58f1a36046 -r d43707e09b02 tests/test-tags.out --- a/tests/test-tags.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-tags.out Tue Jun 24 09:34:38 2008 +0200 @@ -45,16 +45,16 @@ created new head tip 4:36195b728445 bar 1:b204a97e6e8d -changeset: 5:57e1983b4a60 +changeset: 5:1f98c77278de tag: tip user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: Removed tag bar -tip 5:57e1983b4a60 +tip 5:1f98c77278de % remove nonexistent tag abort: tag 'foobar' does not exist -changeset: 5:57e1983b4a60 +changeset: 5:1f98c77278de tag: tip user: test date: Mon Jan 12 13:46:40 1970 +0000 diff -r 2e58f1a36046 -r d43707e09b02 tests/test-transplant --- a/tests/test-transplant Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-transplant Tue Jun 24 09:34:38 2008 +0200 @@ -96,3 +96,21 @@ hg transplant --continue hg transplant 1:3 hg locate +cd .. + +# Test transplant --merge (issue 1111) +echo % test transplant merge +hg init t1111 +cd t1111 +echo a > a +hg ci -Am adda +echo b >> a +hg ci -m appendb +echo c >> a +hg ci -m appendc +hg up -C 0 +echo d >> a +hg ci -m appendd +echo % tranplant +hg transplant -m 1 +cd .. diff -r 2e58f1a36046 -r d43707e09b02 tests/test-transplant.out --- a/tests/test-transplant.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-transplant.out Tue Jun 24 09:34:38 2008 +0200 @@ -129,3 +129,10 @@ added bar foo +% test transplant merge +adding a +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +created new head +% tranplant +applying 42dc4432fd35 +1:42dc4432fd35 merged at a9f4acbac129 diff -r 2e58f1a36046 -r d43707e09b02 tests/test-up-local-change.out --- a/tests/test-up-local-change.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-up-local-change.out Tue Jun 24 09:34:38 2008 +0200 @@ -23,6 +23,7 @@ b a: versions differ -> m b: remote created -> g +preserving a for resolve of a picked tool 'true' for a (binary False symlink False) merging a my a@33aaa84a386b+ other a@802f095af299 ancestor a@33aaa84a386b @@ -60,6 +61,7 @@ b a: versions differ -> m b: remote created -> g +preserving a for resolve of a picked tool 'true' for a (binary False symlink False) merging a my a@33aaa84a386b+ other a@802f095af299 ancestor a@33aaa84a386b @@ -113,6 +115,8 @@ searching for copies back to rev 1 a: versions differ -> m b: versions differ -> m +preserving a for resolve of a +preserving b for resolve of b picked tool 'true' for a (binary False symlink False) merging a my a@802f095af299+ other a@030602aee63d ancestor a@33aaa84a386b diff -r 2e58f1a36046 -r d43707e09b02 tests/test-walk.out --- a/tests/test-walk.out Tue Jun 24 09:33:13 2008 +0200 +++ b/tests/test-walk.out Tue Jun 24 09:34:38 2008 +0200 @@ -275,10 +275,10 @@ fifo: unsupported file type (type is fifo) hg debugwalk fenugreek -m fenugreek fenugreek exact +f fenugreek fenugreek exact hg debugwalk fenugreek -m fenugreek fenugreek exact +f fenugreek fenugreek exact hg debugwalk new f new new exact