--- a/.hgignore Mon Mar 06 17:58:53 2006 +0100
+++ b/.hgignore Mon Mar 06 18:00:44 2006 +0100
@@ -12,6 +12,7 @@
build
dist
doc/*.[0-9]
+doc/*.[0-9].gendoc.txt
doc/*.[0-9].{x,ht}ml
MANIFEST
patches
--- a/contrib/bash_completion Mon Mar 06 17:58:53 2006 +0100
+++ b/contrib/bash_completion Mon Mar 06 18:00:44 2006 +0100
@@ -3,23 +3,26 @@
_hg_command_list()
{
"$hg" --debug help 2>/dev/null | \
- awk 'function command_line(line) {
- gsub(/,/, "", line)
- gsub(/:.*/, "", line)
- split(line, aliases)
+ awk -F', ' '/^list of commands:/ {commands=1}
+ commands==1 && /^ [^ ]/ {
+ line = substr($0, 2)
+ colon = index(line, ":")
+ if (colon > 0)
+ line = substr(line, 1, colon-1)
+ n = split(line, aliases)
command = aliases[1]
- delete aliases[1]
+ if (index(command, "debug") == 1) {
+ for (i=1; i<=n; i++)
+ debug[j++] = aliases[i]
+ next
+ }
print command
- for (i in aliases)
+ for (i=2; i<=n; i++)
if (index(command, aliases[i]) != 1)
print aliases[i]
}
- /^list of commands:/ {commands=1}
- commands && /^ debug/ {a[i++] = $0; next;}
- commands && /^ [^ ]/ {command_line($0)}
/^global options:/ {exit 0}
- END {for (i in a) command_line(a[i])}'
-
+ END {for (i in debug) print debug[i]}'
}
_hg_option_list()
--- a/contrib/hbisect.py Mon Mar 06 17:58:53 2006 +0100
+++ b/contrib/hbisect.py Mon Mar 06 18:00:44 2006 +0100
@@ -187,7 +187,7 @@
check_clean(self.ui, self.repo)
rev = self.next()
self.ui.write("Now testing %s\n" % hg.hex(rev))
- return self.repo.update(rev, allow=True, force=True)
+ return self.repo.update(rev, force=True)
def good(self, rev):
self.goodrevs.append(rev)
@@ -232,7 +232,7 @@
b.good(new_rev)
ui.write("it is good\n")
anc = b.ancestors()
- repo.update(new_rev, allow=True, force=True)
+ repo.update(new_rev, force=True)
for v in anc:
if v != rev:
ui.warn("fail to found cset! :(\n")
--- a/doc/Makefile Mon Mar 06 17:58:53 2006 +0100
+++ b/doc/Makefile Mon Mar 06 18:00:44 2006 +0100
@@ -8,6 +8,12 @@
html: $(HTML)
+hg.1.txt: hg.1.gendoc.txt
+ touch hg.1.txt
+
+hg.1.gendoc.txt: ../mercurial/commands.py
+ python gendoc.py > $@
+
%: %.xml
xmlto man $*.xml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/gendoc.py Mon Mar 06 18:00:44 2006 +0100
@@ -0,0 +1,92 @@
+import sys, textwrap
+# import from the live mercurial repo
+sys.path.insert(0, "..")
+from mercurial.commands import table, globalopts
+from mercurial.i18n import gettext as _
+
+def get_desc(docstr):
+ if not docstr:
+ return "", ""
+ # sanitize
+ docstr = docstr.strip("\n")
+ docstr = docstr.rstrip()
+ shortdesc = docstr.splitlines()[0].strip()
+
+ i = docstr.find("\n")
+ if i != -1:
+ desc = docstr[i+2:]
+ else:
+ desc = " %s" % shortdesc
+ return (shortdesc, desc)
+
+def get_opts(opts):
+ for shortopt, longopt, default, desc in opts:
+ allopts = []
+ if shortopt:
+ allopts.append("-%s" % shortopt)
+ if longopt:
+ allopts.append("--%s" % longopt)
+ desc += default and _(" (default: %s)") % default or ""
+ yield(", ".join(allopts), desc)
+
+def get_cmd(cmd):
+ d = {}
+ attr = table[cmd]
+ cmds = cmd.lstrip("^").split("|")
+
+ d['synopsis'] = attr[2]
+ d['cmd'] = cmds[0]
+ d['aliases'] = cmd.split("|")[1:]
+ d['desc'] = get_desc(attr[0].__doc__)
+ d['opts'] = list(get_opts(attr[1]))
+ return d
+
+
+def show_doc(ui):
+ def bold(s, text=""):
+ ui.write("%s\n%s\n%s\n" % (s, "="*len(s), text))
+ def underlined(s, text=""):
+ ui.write("%s\n%s\n%s\n" % (s, "-"*len(s), text))
+
+ # print options
+ underlined(_("OPTIONS"))
+ for optstr, desc in get_opts(globalopts):
+ ui.write("%s::\n %s\n\n" % (optstr, desc))
+
+ # print cmds
+ underlined(_("COMMANDS"))
+ h = {}
+ for c, attr in table.items():
+ f = c.split("|")[0]
+ f = f.lstrip("^")
+ h[f] = c
+ cmds = h.keys()
+ cmds.sort()
+
+ for f in cmds:
+ if f.startswith("debug"): continue
+ d = get_cmd(h[f])
+ # synopsis
+ ui.write("%s::\n" % d['synopsis'].replace("hg ","", 1))
+ # description
+ ui.write("%s\n\n" % d['desc'][1])
+ # options
+ opt_output = list(d['opts'])
+ if opt_output:
+ opts_len = max([len(line[0]) for line in opt_output])
+ ui.write(_(" options:\n"))
+ for optstr, desc in opt_output:
+ if desc:
+ s = "%-*s %s" % (opts_len, optstr, desc)
+ else:
+ s = optstr
+ s = textwrap.fill(s, initial_indent=4 * " ",
+ subsequent_indent=(6 + opts_len) * " ")
+ ui.write("%s\n" % s)
+ ui.write("\n")
+ # aliases
+ if d['aliases']:
+ ui.write(_(" aliases: %s\n\n") % " ".join(d['aliases']))
+
+if __name__ == "__main__":
+ show_doc(sys.stdout)
--- a/doc/hg.1.txt Mon Mar 06 17:58:53 2006 +0100
+++ b/doc/hg.1.txt Mon Mar 06 18:00:44 2006 +0100
@@ -14,42 +14,6 @@
-----------
The hg(1) command provides a command line interface to the Mercurial system.
-OPTIONS
--------
-
--R, --repository::
- repository root directory
-
---cwd::
- change working directory
-
--y, --noninteractive::
- do not prompt, assume 'yes' for any required answers
-
--q, --quiet::
- suppress output
-
--v, --verbose::
- enable additional output
-
---debug::
- enable debugging output
-
---traceback::
- print traceback on exception
-
---time::
- time how long the command takes
-
---profile::
- print command execution profile
-
---version::
- output version information and exit
-
--h, --help::
- display help and exit
-
COMMAND ELEMENTS
----------------
@@ -70,586 +34,8 @@
fast and the old-http:// protocol which is much slower but does not
require a special server on the web host.
-COMMANDS
---------
-add [options] [files ...]::
- Schedule files to be version controlled and added to the repository.
-
- The files will be added to the repository at the next commit.
-
- If no names are given, add all files in the current directory and
- its subdirectories.
-
-addremove [options] [files ...]::
- Add all new files and remove all missing files from the repository.
-
- New files are ignored if they match any of the patterns in .hgignore. As
- with add, these changes take effect at the next commit.
-
-annotate [-r <rev> -u -n -c -d] [files ...]::
- List changes in files, showing the revision id responsible for each line
-
- This command is useful to discover who did a change or when a change took
- place.
-
- Without the -a option, annotate will avoid processing files it
- detects as binary. With -a, annotate will generate an annotation
- anyway, probably with undesirable results.
-
- options:
- -a, --text treat all files as text
- -I, --include <pat> include names matching the given patterns
- -X, --exclude <pat> exclude names matching the given patterns
- -r, --revision <rev> annotate the specified revision
- -u, --user list the author
- -d, --date list the commit date
- -c, --changeset list the changeset
- -n, --number list the revision number (default)
-
-bundle <file> <other>::
- (EXPERIMENTAL)
-
- Generate a compressed changegroup file collecting all changesets
- not found in the other repository.
-
- This file can then be transferred using conventional means and
- applied to another repository with the unbundle command. This is
- useful when native push and pull are not available or when
- exporting an entire repository is undesirable. The standard file
- extension is ".hg".
-
- Unlike import/export, this exactly preserves all changeset
- contents including permissions, rename data, and revision history.
-
-cat [options] <file ...>::
- Print the specified files as they were at the given revision.
- If no revision is given then the tip is used.
-
- Output may be to a file, in which case the name of the file is
- given using a format string. The formatting rules are the same as
- for the export command, with the following additions:
-
- %s basename of file being printed
- %d dirname of file being printed, or '.' if in repo root
- %p root-relative path name of file being printed
-
- options:
- -I, --include <pat> include names matching the given patterns
- -X, --exclude <pat> exclude names matching the given patterns
- -o, --output <filespec> print output to file with formatted name
- -r, --rev <rev> print the given revision
-
-clone [options] <source> [dest]::
- Create a copy of an existing repository in a new directory.
-
- If no destination directory name is specified, it defaults to the
- basename of the source.
-
- The location of the source is added to the new repository's
- .hg/hgrc file, as the default to be used for future pulls.
-
- For efficiency, hardlinks are used for cloning whenever the source
- and destination are on the same filesystem. Some filesystems,
- such as AFS, implement hardlinking incorrectly, but do not report
- errors. In these cases, use the --pull option to avoid
- hardlinking.
-
- See pull for valid source format details.
-
- options:
- -U, --noupdate do not update the new working directory
- --pull use pull protocol to copy metadata
- -e, --ssh specify ssh command to use
- --remotecmd specify hg command to run on the remote side
-
-commit [options] [files...]::
- Commit changes to the given files into the repository.
-
- If a list of files is omitted, all changes reported by "hg status"
- from the root of the repository will be commited.
-
- The HGEDITOR or EDITOR environment variables are used to start an
- editor to add a commit comment.
-
- Options:
-
- -A, --addremove run addremove during commit
- -I, --include <pat> include names matching the given patterns
- -X, --exclude <pat> exclude names matching the given patterns
- -m, --message <text> use <text> as commit message
- -l, --logfile <file> read the commit message from <file>
- -d, --date <datecode> record datecode as commit date
- -u, --user <user> record user as commiter
-
- aliases: ci
-
-copy <source ...> <dest>::
- 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
- operation is recorded, but no copying is performed.
-
- This command takes effect in the next commit.
-
- NOTE: This command should be treated as experimental. While it
- should properly record copied files, this information is not yet
- fully used by merge, nor fully reported by log.
-
- Options:
- -A, --after record a copy that has already occurred
- -I, --include <pat> include names matching the given patterns
- -X, --exclude <pat> exclude names matching the given patterns
- -f, --force forcibly copy over an existing managed file
-
- aliases: cp
-
-diff [-a] [-r revision] [-r revision] [files ...]::
- Show differences between revisions for the specified files.
-
- Differences between files are shown using the unified diff format.
-
- When two revision arguments are given, then changes are shown
- between those revisions. If only one revision is specified then
- that revision is compared to the working directory, and, when no
- revisions are specified, the working directory files are compared
- to its parent.
-
- Without the -a option, diff will avoid generating diffs of files
- it detects as binary. With -a, diff will generate a diff anyway,
- probably with undesirable results.
-
- options:
- -a, --text treat all files as text
- -I, --include <pat> include names matching the given patterns
- -p, --show-function show which function each change is in
- -X, --exclude <pat> exclude names matching the given patterns
- -w, --ignore-all-space ignore white space when comparing lines
-
-export [-o filespec] [revision] ...::
- Print the changeset header and diffs for one or more revisions.
-
- The information shown in the changeset header is: author,
- changeset hash, parent and commit comment.
-
- 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:
-
- %% literal "%" character
- %H changeset hash (40 bytes of hexadecimal)
- %N number of patches being generated
- %R changeset revision number
- %b basename of the exporting repository
- %h short-form changeset hash (12 bytes of hexadecimal)
- %n zero-padded sequence number, starting at 1
- %r zero-padded changeset revision number
-
- Without the -a option, export will avoid generating diffs of files
- it detects as binary. With -a, export will generate a diff anyway,
- probably with undesirable results.
-
- options:
- -a, --text treat all files as text
- -o, --output <filespec> print output to file with formatted name
-
-forget [options] [files]::
- Undo an 'hg add' scheduled for the next commit.
-
- options:
- -I, --include <pat> include names matching the given patterns
- -X, --exclude <pat> exclude names matching the given patterns
-
-grep [options] pattern [files]::
- 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
- 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
- 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.
-
- options:
- -0, --print0 end fields with NUL
- -I, --include <pat> include names matching the given patterns
- -X, --exclude <pat> exclude names matching the given patterns
- --all print all revisions that match
- -i, --ignore-case ignore case when matching
- -l, --files-with-matches print only filenames and revs that match
- -n, --line-number print matching line numbers
- -r <rev>, --rev <rev> search in given revision range
- -u, --user print user who committed change
-
-heads::
- Show all repository head changesets.
-
- Repository "heads" are changesets that don't have children
- changesets. They are where development generally takes place and
- are the usual targets for update and merge operations.
-
-identify::
- Print a short summary of the current state of the repo.
-
- This summary identifies the repository state using one or two parent
- hash identifiers, followed by a "+" if there are uncommitted changes
- in the working directory, followed by a list of tags for this revision.
-
- aliases: id
-
-import [-p <n> -b <base> -f] <patches>::
- Import a list of patches and commit them individually.
-
- If there are outstanding changes in the working directory, import
- will abort unless given the -f flag.
-
- If a patch looks like a mail message (its first line starts with
- "From " or looks like an RFC822 header), it will not be applied
- unless the -f option is used. The importer neither parses nor
- discards mail headers, so use -f only to override the "mailness"
- safety check, not to import a real mail message.
-
- options:
- -p, --strip <n> directory strip option for patch. This has the same
- meaning as the corresponding patch option
- -b <path> base directory to read patches from
- -f, --force skip check for outstanding uncommitted changes
-
- aliases: patch
-
-incoming [-p] [source]::
- Show new changesets found in the specified repo or the default
- pull repo. These are the changesets that would be pulled if a pull
- was requested.
-
- Currently only local repositories are supported.
-
- options:
- -p, --patch show patch
-
- aliases: in
-
-init [dest]::
- 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.
-
-locate [options] [files]::
- Print all files under Mercurial control whose names match the
- given patterns.
-
- This command searches the current directory and its
- subdirectories. To search an entire repository, move to the root
- of the repository.
-
- If no patterns are given to match, this command prints all file
- names.
-
- If you want to feed the output of this command into the "xargs"
- command, use the "-0" option to both this command and "xargs".
- This will avoid the problem of "xargs" treating single filenames
- that contain white space as multiple filenames.
-
- options:
-
- -0, --print0 end filenames with NUL, for use with xargs
- -f, --fullpath print complete paths from the filesystem root
- -I, --include <pat> include names matching the given patterns
- -r, --rev <rev> search the repository as it stood at rev
- -X, --exclude <pat> exclude names matching the given patterns
-
-log [-r revision ...] [-p] [files]::
- Print the revision history of the specified files or the entire project.
-
- By default this command outputs: changeset id and hash, tags,
- parents, user, date and time, and a summary for each commit. The
- -v switch adds some more detail, such as changed files, manifest
- hashes or message signatures.
-
- options:
- -I, --include <pat> include names matching the given patterns
- -X, --exclude <pat> exclude names matching the given patterns
- -r, --rev <A> show the specified revision or range
- -p, --patch show patch
-
- aliases: history
-
-manifest [revision]::
- Print a list of version controlled files for the given revision.
-
- The manifest is the list of files being version controlled. If no revision
- is given then the tip is used.
-
-outgoing [-p] [dest]::
- Show changesets not found in the specified destination repo or the
- default push repo. These are the changesets that would be pushed
- if a push was requested.
-
- See pull for valid source format details.
-
- options:
- -p, --patch show patch
-
- aliases: out
-
-parents::
- Print the working directory's parent revisions.
-
-paths [NAME]::
- Show definition of symbolic path name NAME. If no name is given, show
- 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.
-
-pull <repository path>::
- Pull changes from a remote repository to a local one.
-
- This finds all changes from the repository at the specified path
- or URL and adds them to the local repository. By default, this
- does not update the copy of the project in the working directory.
-
- Valid URLs are of the form:
-
- local/filesystem/path
- http://[user@]host[:port][/path]
- https://[user@]host[:port][/path]
- ssh://[user@]host[:port][/path]
-
- SSH requires an accessible shell account on the destination machine
- and a copy of hg in the remote path. With SSH, paths are relative
- to the remote user's home directory by default; use two slashes at
- the start of a path to specify it as relative to the filesystem root.
-
- options:
- -u, --update update the working directory to tip after pull
- -e, --ssh specify ssh command to use
- --remotecmd specify hg command to run on the remote side
-
-push <destination>::
- Push changes from the local repository to the given destination.
-
- This is the symmetrical operation for pull. It helps to move
- changes from the current repository to a different one. If the
- destination is local this is identical to a pull in that directory
- from the current one.
-
- By default, push will refuse to run if it detects the result would
- increase the number of remote heads. This generally indicates the
- the client has forgotten to sync and merge before pushing.
-
- Valid URLs are of the form:
-
- local/filesystem/path
- ssh://[user@]host[:port][/path]
-
- SSH requires an accessible shell account on the destination
- machine and a copy of hg in the remote path.
-
- options:
-
- -f, --force force update
- -e, --ssh specify ssh command to use
- --remotecmd specify hg command to run on the remote side
-
-rawcommit [-p -d -u -F -m -l]::
- Lowlevel commit, for use in helper scripts. (DEPRECATED)
-
- This command is not intended to be used by normal users, as it is
- primarily useful for importing from other SCMs.
-
- This command is now deprecated and will be removed in a future
- release, please use debugsetparents and commit instead.
-
-recover::
- Recover from an interrupted commit or pull.
-
- This command tries to fix the repository status after an interrupted
- operation. It should only be necessary when Mercurial suggests it.
-
-remove [options] [files ...]::
- Schedule the indicated files for removal from the repository.
-
- This command schedules the files to be removed at the next commit.
- This only removes files from the current branch, not from the
- entire project history. If the files still exist in the working
- directory, they will be deleted from it.
-
- aliases: rm
-
-rename <source ...> <dest>::
- 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
- operation is recorded, but no copying is performed.
-
- This command takes effect in the next commit.
-
- NOTE: This command should be treated as experimental. While it
- should properly record rename files, this information is not yet
- fully used by merge, nor fully reported by log.
-
- Options:
- -A, --after record a rename that has already occurred
- -f, --force forcibly copy over an existing managed file
-
- aliases: mv
-
-revert [names ...]::
- The revert command has two modes of operation.
-
- In its default mode, it reverts any uncommitted modifications made
- to the named files or directories. This restores the contents of
- the affected files to an unmodified state.
-
- Using the -r option, it reverts the given files or directories to
- their state as of an earlier revision. This can be helpful to "roll
- back" some or all of a change that should not have been committed.
-
- Revert modifies the working directory. It does not commit any
- changes, or change the parent of the current working directory.
-
- If a file has been deleted, it is recreated. If the executable
- mode of a file was changed, it is reset.
-
- If a directory is given, all files in that directory and its
- subdirectories are reverted.
-
- If no arguments are given, all files in the current directory and
- its subdirectories are reverted.
-
- options:
- -r, --rev <rev> revision to revert to
- -n, --nonrecursive do not recurse into subdirectories
-
-root::
- Print the root directory of the current repository.
-
-serve [options]::
- 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.
-
- options:
- -A, --accesslog <file> name of access log file to write to
- -E, --errorlog <file> name of error log file to write to
- -a, --address <addr> address to use
- -p, --port <n> port to use (default: 8000)
- -n, --name <name> name to show in web pages (default: working dir)
- -t, --templatedir <path> web templates to use
- -6, --ipv6 use IPv6 in addition to IPv4
-
-status [options] [files]::
- Show changed files in the working directory. If no names are
- given, all files are shown. Otherwise, only files matching the
- given names are shown.
-
- The codes used to show the status of files are:
-
- M = changed
- A = added
- R = removed
- ? = not tracked
-
- options:
-
- -m, --modified show only modified files
- -a, --added show only added files
- -r, --removed show only removed files
- -u, --unknown show only unknown (not tracked) files
- -n, --no-status hide status prefix
- -0, --print0 end filenames with NUL, for use with xargs
- -I, --include <pat> include names matching the given patterns
- -X, --exclude <pat> exclude names matching the given patterns
-
-tag [-l -m <text> -d <datecode> -u <user>] <name> [revision]::
- Name a particular revision using <name>.
-
- Tags are used to name particular revisions of the repository and are
- very useful to compare different revision, to go back to significant
- earlier versions or to mark branch points as releases, etc.
-
- If no revision is given, the tip is used.
-
- 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.
-
- options:
- -l, --local make the tag local
- -m, --message <text> message for tag commit log entry
- -d, --date <datecode> datecode for commit
- -u, --user <user> user for commit
-
- Note: Local tags are not version-controlled or distributed and are
- stored in the .hg/localtags file. If there exists a local tag and
- a public tag with the same name, local tag is used.
-
-tags::
- List the repository tags.
-
- This lists both regular and local tags.
-
-tip [-p]::
- Show the tip revision.
-
- options:
- -p, --patch show patch
-
-unbundle <file>::
- (EXPERIMENTAL)
-
- Apply a compressed changegroup file generated by the bundle
- command.
-
-undo::
- Undo the last commit or pull transaction.
-
- Roll back the last pull or commit transaction on the
- repository, restoring the project to its earlier state.
-
- This command should be used with care. There is only one level of
- undo and there is no redo.
-
- This command is not intended for use on public repositories. Once
- a change is visible for pull by other users, undoing it locally is
- ineffective.
-
-update [-m -C] [revision]::
- Update the working directory to the specified revision.
-
- By default, update will refuse to run if doing so would require
- merging or discarding local changes.
-
- With the -m option, a merge will be performed.
-
- With the -C option, local changes will be lost.
-
- options:
- -m, --merge allow merging of branches
- -C, --clean overwrite locally modified files
-
- aliases: up checkout co
-
-verify::
- Verify the integrity of the current repository.
-
- This will perform an extensive check of the repository's
- integrity, validating the hashes and checksums of each entry in
- the changelog, manifest, and tracked files, as well as the
- integrity of their crosslinks and indices.
+include::hg.1.gendoc.txt[]
FILE NAME PATTERNS
------------------
--- a/doc/hgrc.5.txt Mon Mar 06 17:58:53 2006 +0100
+++ b/doc/hgrc.5.txt Mon Mar 06 18:00:44 2006 +0100
@@ -247,6 +247,9 @@
remote command to use for clone/push/pull operations. Default is 'hg'.
ssh;;
command to use for SSH connections. Default is 'ssh'.
+ timeout;;
+ The timeout used when a lock is held (in seconds), a negative value
+ means no timeout. Default is 600.
username;;
The committer of a changeset created when running "commit".
Typically a person's name and email address, e.g. "Fred Widget
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/mq.py Mon Mar 06 18:00:44 2006 +0100
@@ -0,0 +1,1308 @@
+#!/usr/bin/env python
+# queue.py - patch queues for mercurial
+#
+# Copyright 2005 Chris Mason <mason@suse.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+from mercurial.demandload import *
+demandload(globals(), "os sys re struct traceback errno bz2")
+from mercurial.i18n import gettext as _
+from mercurial import ui, hg, revlog, commands, util
+
+versionstr = "0.45"
+
+repomap = {}
+
+class queue:
+ def __init__(self, ui, path, patchdir=None):
+ self.opener = util.opener(path)
+ self.basepath = path
+ if patchdir:
+ self.path = patchdir
+ else:
+ self.path = os.path.join(path, "patches")
+ self.ui = ui
+ self.applied = []
+ self.full_series = []
+ self.applied_dirty = 0
+ self.series_dirty = 0
+ self.series_path = os.path.join(self.path, "series")
+ self.status_path = os.path.join(self.path, "status")
+
+ s = self.series_path
+ if os.path.exists(s):
+ self.full_series = self.opener(s).read().splitlines()
+ self.read_series(self.full_series)
+
+ s = self.status_path
+ if os.path.exists(s):
+ self.applied = self.opener(s).read().splitlines()
+
+ def find_series(self, patch):
+ pre = re.compile("(\s*)([^#]+)")
+ index = 0
+ for l in self.full_series:
+ m = pre.match(l)
+ if m:
+ s = m.group(2)
+ s = s.rstrip()
+ if s == patch:
+ return index
+ index += 1
+ return None
+
+ def read_series(self, list):
+ def matcher(list):
+ pre = re.compile("(\s*)([^#]+)")
+ for l in list:
+ m = pre.match(l)
+ if m:
+ s = m.group(2)
+ s = s.rstrip()
+ if len(s) > 0:
+ yield s
+ self.series = []
+ self.series = [ x for x in matcher(list) ]
+
+ def save_dirty(self):
+ if self.applied_dirty:
+ if len(self.applied) > 0:
+ nl = "\n"
+ else:
+ nl = ""
+ f = self.opener(self.status_path, "w")
+ f.write("\n".join(self.applied) + nl)
+ if self.series_dirty:
+ if len(self.full_series) > 0:
+ nl = "\n"
+ else:
+ nl = ""
+ f = self.opener(self.series_path, "w")
+ f.write("\n".join(self.full_series) + nl)
+
+ def readheaders(self, patch):
+ def eatdiff(lines):
+ while lines:
+ l = lines[-1]
+ if (l.startswith("diff -") or
+ l.startswith("Index:") or
+ l.startswith("===========")):
+ del lines[-1]
+ else:
+ break
+ def eatempty(lines):
+ while lines:
+ l = lines[-1]
+ if re.match('\s*$', l):
+ del lines[-1]
+ else:
+ break
+
+ pf = os.path.join(self.path, patch)
+ message = []
+ comments = []
+ user = None
+ format = None
+ subject = None
+ diffstart = 0
+
+ for line in file(pf):
+ line = line.rstrip()
+ if diffstart:
+ if line.startswith('+++ '):
+ diffstart = 2
+ break
+ if line.startswith("--- "):
+ diffstart = 1
+ continue
+ elif format == "hgpatch":
+ # parse values when importing the result of an hg export
+ if line.startswith("# User "):
+ user = line[7:]
+ elif not line.startswith("# ") and line:
+ message.append(line)
+ format = None
+ elif line == '# HG changeset patch':
+ format = "hgpatch"
+ elif (format != "tagdone" and (line.startswith("Subject: ") or
+ line.startswith("subject: "))):
+ subject = line[9:]
+ format = "tag"
+ elif (format != "tagdone" and (line.startswith("From: ") or
+ line.startswith("from: "))):
+ user = line[6:]
+ format = "tag"
+ elif format == "tag" and line == "":
+ # when looking for tags (subject: from: etc) they
+ # end once you find a blank line in the source
+ format = "tagdone"
+ else:
+ message.append(line)
+ comments.append(line)
+
+ eatdiff(message)
+ eatdiff(comments)
+ eatempty(message)
+ eatempty(comments)
+
+ # make sure message isn't empty
+ if format and format.startswith("tag") and subject:
+ message.insert(0, "")
+ message.insert(0, subject)
+ return (message, comments, user, diffstart > 1)
+
+ def mergeone(self, repo, mergeq, head, patch, rev, wlock):
+ # first try just applying the patch
+ (err, n) = self.apply(repo, [ patch ], update_status=False,
+ strict=True, merge=rev, wlock=wlock)
+
+ if err == 0:
+ return (err, n)
+
+ if n is None:
+ self.ui.warn("apply failed for patch %s\n" % patch)
+ sys.exit(1)
+
+ self.ui.warn("patch didn't work out, merging %s\n" % patch)
+
+ # apply failed, strip away that rev and merge.
+ repo.update(head, allow=False, force=True, wlock=wlock)
+ self.strip(repo, n, update=False, backup='strip', wlock=wlock)
+
+ c = repo.changelog.read(rev)
+ ret = repo.update(rev, allow=True, wlock=wlock)
+ if ret:
+ self.ui.warn("update returned %d\n" % ret)
+ sys.exit(1)
+ n = repo.commit(None, c[4], c[1], force=1, wlock=wlock)
+ if n == None:
+ self.ui.warn("repo commit failed\n")
+ sys.exit(1)
+ try:
+ message, comments, user, patchfound = mergeq.readheaders(patch)
+ except:
+ self.ui.warn("Unable to read %s\n" % patch)
+ sys.exit(1)
+
+ patchf = self.opener(os.path.join(self.path, patch), "w")
+ if comments:
+ comments = "\n".join(comments) + '\n\n'
+ patchf.write(comments)
+ commands.dodiff(patchf, self.ui, repo, head, n)
+ patchf.close()
+ return (0, n)
+
+ def qparents(self, repo, rev=None):
+ if rev is None:
+ (p1, p2) = repo.dirstate.parents()
+ if p2 == revlog.nullid:
+ return p1
+ if len(self.applied) == 0:
+ return None
+ (top, patch) = self.applied[-1].split(':')
+ top = revlog.bin(top)
+ return top
+ pp = repo.changelog.parents(rev)
+ if pp[1] != revlog.nullid:
+ arevs = [ x.split(':')[0] for x in self.applied ]
+ p0 = revlog.hex(pp[0])
+ p1 = revlog.hex(pp[1])
+ if p0 in arevs:
+ return pp[0]
+ if p1 in arevs:
+ return pp[1]
+ return None
+ return pp[0]
+
+ def mergepatch(self, repo, mergeq, series, wlock):
+ if len(self.applied) == 0:
+ # each of the patches merged in will have two parents. This
+ # can confuse the qrefresh, qdiff, and strip code because it
+ # needs to know which parent is actually in the patch queue.
+ # so, we insert a merge marker with only one parent. This way
+ # the first patch in the queue is never a merge patch
+ #
+ pname = ".hg.patches.merge.marker"
+ n = repo.commit(None, '[mq]: merge marker', user=None, force=1,
+ wlock=wlock)
+ self.applied.append(revlog.hex(n) + ":" + pname)
+ self.applied_dirty = 1
+
+ head = self.qparents(repo)
+
+ for patch in series:
+ patch = mergeq.lookup(patch)
+ if not patch:
+ self.ui.warn("patch %s does not exist\n" % patch)
+ return (1, None)
+
+ info = mergeq.isapplied(patch)
+ if not info:
+ self.ui.warn("patch %s is not applied\n" % patch)
+ return (1, None)
+ rev = revlog.bin(info[1])
+ (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock)
+ if head:
+ self.applied.append(revlog.hex(head) + ":" + patch)
+ self.applied_dirty = 1
+ if err:
+ return (err, head)
+ return (0, head)
+
+ def apply(self, repo, series, list=False, update_status=True,
+ strict=False, patchdir=None, merge=None, wlock=None):
+ # TODO unify with commands.py
+ if not patchdir:
+ patchdir = self.path
+ pwd = os.getcwd()
+ os.chdir(repo.root)
+ err = 0
+ if not wlock:
+ wlock = repo.wlock()
+ lock = repo.lock()
+ tr = repo.transaction()
+ n = None
+ for patch in series:
+ self.ui.warn("applying %s\n" % patch)
+ pf = os.path.join(patchdir, patch)
+
+ try:
+ message, comments, user, patchfound = self.readheaders(patch)
+ except:
+ self.ui.warn("Unable to read %s\n" % pf)
+ err = 1
+ break
+
+ if not message:
+ message = "imported patch %s\n" % patch
+ else:
+ if list:
+ message.append("\nimported patch %s" % patch)
+ message = '\n'.join(message)
+
+ try:
+ f = os.popen("patch -p1 --no-backup-if-mismatch < '%s'" % (pf))
+ except:
+ self.ui.warn("patch failed, unable to continue (try -v)\n")
+ err = 1
+ break
+ files = []
+ fuzz = False
+ for l in f:
+ l = l.rstrip('\r\n');
+ if self.ui.verbose:
+ self.ui.warn(l + "\n")
+ if l[:14] == 'patching file ':
+ pf = os.path.normpath(l[14:])
+ # when patch finds a space in the file name, it puts
+ # single quotes around the filename. strip them off
+ if pf[0] == "'" and pf[-1] == "'":
+ pf = pf[1:-1]
+ if pf not in files:
+ files.append(pf)
+ printed_file = False
+ file_str = l
+ elif l.find('with fuzz') >= 0:
+ if not printed_file:
+ self.ui.warn(file_str + '\n')
+ printed_file = True
+ self.ui.warn(l + '\n')
+ fuzz = True
+ elif l.find('saving rejects to file') >= 0:
+ self.ui.warn(l + '\n')
+ elif l.find('FAILED') >= 0:
+ if not printed_file:
+ self.ui.warn(file_str + '\n')
+ printed_file = True
+ self.ui.warn(l + '\n')
+ patcherr = f.close()
+
+ if merge and len(files) > 0:
+ # Mark as merged and update dirstate parent info
+ repo.dirstate.update(repo.dirstate.filterfiles(files), 'm')
+ p1, p2 = repo.dirstate.parents()
+ repo.dirstate.setparents(p1, merge)
+ if len(files) > 0:
+ commands.addremove_lock(self.ui, repo, files,
+ opts={}, wlock=wlock)
+ n = repo.commit(files, message, user, force=1, lock=lock,
+ wlock=wlock)
+
+ if n == None:
+ self.ui.warn("repo commit failed\n")
+ sys.exit(1)
+
+ if update_status:
+ self.applied.append(revlog.hex(n) + ":" + patch)
+
+ if patcherr:
+ if not patchfound:
+ self.ui.warn("patch %s is empty\n" % patch)
+ err = 0
+ else:
+ self.ui.warn("patch failed, rejects left in working dir\n")
+ err = 1
+ break
+
+ if fuzz and strict:
+ self.ui.warn("fuzz found when applying patch, stopping\n")
+ err = 1
+ break
+ tr.close()
+ os.chdir(pwd)
+ return (err, n)
+
+ def delete(self, repo, patch):
+ patch = self.lookup(patch)
+ info = self.isapplied(patch)
+ if info:
+ self.ui.warn("cannot delete applied patch %s\n" % patch)
+ sys.exit(1)
+ if patch not in self.series:
+ self.ui.warn("patch %s not in series file\n" % patch)
+ sys.exit(1)
+ i = self.find_series(patch)
+ del self.full_series[i]
+ self.read_series(self.full_series)
+ self.series_dirty = 1
+
+ def check_toppatch(self, repo):
+ if len(self.applied) > 0:
+ (top, patch) = self.applied[-1].split(':')
+ top = revlog.bin(top)
+ pp = repo.dirstate.parents()
+ if top not in pp:
+ self.ui.warn("queue top not at dirstate parents. top %s dirstate %s %s\n" %( revlog.short(top), revlog.short(pp[0]), revlog.short(pp[1])))
+ sys.exit(1)
+ return top
+ return None
+ def check_localchanges(self, repo):
+ (c, a, r, d, u) = repo.changes(None, None)
+ if c or a or d or r:
+ self.ui.write("Local changes found, refresh first\n")
+ sys.exit(1)
+ def new(self, repo, patch, msg=None, force=None):
+ if not force:
+ self.check_localchanges(repo)
+ self.check_toppatch(repo)
+ wlock = repo.wlock()
+ insert = self.series_end()
+ if msg:
+ n = repo.commit([], "[mq]: %s" % msg, force=True, wlock=wlock)
+ else:
+ n = repo.commit([],
+ "New patch: %s" % patch, force=True, wlock=wlock)
+ if n == None:
+ self.ui.warn("repo commit failed\n")
+ sys.exit(1)
+ self.full_series[insert:insert] = [patch]
+ self.applied.append(revlog.hex(n) + ":" + patch)
+ self.read_series(self.full_series)
+ self.series_dirty = 1
+ self.applied_dirty = 1
+ p = self.opener(os.path.join(self.path, patch), "w")
+ if msg:
+ msg = msg + "\n"
+ p.write(msg)
+ p.close()
+ wlock = None
+ r = self.qrepo()
+ if r: r.add([patch])
+
+ def strip(self, repo, rev, update=True, backup="all", wlock=None):
+ def limitheads(chlog, stop):
+ """return the list of all nodes that have no children"""
+ p = {}
+ h = []
+ stoprev = 0
+ if stop in chlog.nodemap:
+ stoprev = chlog.rev(stop)
+
+ for r in range(chlog.count() - 1, -1, -1):
+ n = chlog.node(r)
+ if n not in p:
+ h.append(n)
+ if n == stop:
+ break
+ if r < stoprev:
+ break
+ for pn in chlog.parents(n):
+ p[pn] = 1
+ return h
+
+ def bundle(cg):
+ backupdir = repo.join("strip-backup")
+ if not os.path.isdir(backupdir):
+ os.mkdir(backupdir)
+ name = os.path.join(backupdir, "%s" % revlog.short(rev))
+ name = savename(name)
+ self.ui.warn("saving bundle to %s\n" % name)
+ # TODO, exclusive open
+ f = open(name, "wb")
+ try:
+ f.write("HG10")
+ z = bz2.BZ2Compressor(9)
+ while 1:
+ chunk = cg.read(4096)
+ if not chunk:
+ break
+ f.write(z.compress(chunk))
+ f.write(z.flush())
+ except:
+ os.unlink(name)
+ raise
+ f.close()
+ return name
+
+ def stripall(rev, revnum):
+ cl = repo.changelog
+ c = cl.read(rev)
+ mm = repo.manifest.read(c[0])
+ seen = {}
+
+ for x in xrange(revnum, cl.count()):
+ c = cl.read(cl.node(x))
+ for f in c[3]:
+ if f in seen:
+ continue
+ seen[f] = 1
+ if f in mm:
+ filerev = mm[f]
+ else:
+ filerev = 0
+ seen[f] = filerev
+ # we go in two steps here so the strip loop happens in a
+ # sensible order. When stripping many files, this helps keep
+ # our disk access patterns under control.
+ list = seen.keys()
+ list.sort()
+ for f in list:
+ ff = repo.file(f)
+ filerev = seen[f]
+ if filerev != 0:
+ if filerev in ff.nodemap:
+ filerev = ff.rev(filerev)
+ else:
+ filerev = 0
+ ff.strip(filerev, revnum)
+
+ if not wlock:
+ wlock = repo.wlock()
+ lock = repo.lock()
+ chlog = repo.changelog
+ # TODO delete the undo files, and handle undo of merge sets
+ pp = chlog.parents(rev)
+ revnum = chlog.rev(rev)
+
+ if update:
+ urev = self.qparents(repo, rev)
+ repo.update(urev, allow=False, force=True, wlock=wlock)
+ repo.dirstate.write()
+
+ # save is a list of all the branches we are truncating away
+ # that we actually want to keep. changegroup will be used
+ # to preserve them and add them back after the truncate
+ saveheads = []
+ savebases = {}
+
+ tip = chlog.tip()
+ heads = limitheads(chlog, rev)
+ seen = {}
+
+ # search through all the heads, finding those where the revision
+ # we want to strip away is an ancestor. Also look for merges
+ # that might be turned into new heads by the strip.
+ while heads:
+ h = heads.pop()
+ n = h
+ while True:
+ seen[n] = 1
+ pp = chlog.parents(n)
+ if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum:
+ if pp[1] not in seen:
+ heads.append(pp[1])
+ if pp[0] == revlog.nullid:
+ break
+ if chlog.rev(pp[0]) < revnum:
+ break
+ n = pp[0]
+ if n == rev:
+ break
+ r = chlog.reachable(h, rev)
+ if rev not in r:
+ saveheads.append(h)
+ for x in r:
+ if chlog.rev(x) > revnum:
+ savebases[x] = 1
+
+ # create a changegroup for all the branches we need to keep
+ if backup is "all":
+ backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip')
+ bundle(backupch)
+ if saveheads:
+ backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip')
+ chgrpfile = bundle(backupch)
+
+ stripall(rev, revnum)
+
+ change = chlog.read(rev)
+ repo.manifest.strip(repo.manifest.rev(change[0]), revnum)
+ chlog.strip(revnum, revnum)
+ if saveheads:
+ self.ui.status("adding branch\n")
+ commands.unbundle(self.ui, repo, chgrpfile, update=False)
+ if backup is not "strip":
+ os.unlink(chgrpfile)
+
+ def isapplied(self, patch):
+ """returns (index, rev, patch)"""
+ for i in xrange(len(self.applied)):
+ p = self.applied[i]
+ a = p.split(':')
+ if a[1] == patch:
+ return (i, a[0], a[1])
+ return None
+
+ def lookup(self, patch):
+ if patch == None:
+ return None
+ if patch in self.series:
+ return patch
+ if not os.path.isfile(os.path.join(self.path, patch)):
+ try:
+ sno = int(patch)
+ except(ValueError, OverflowError):
+ self.ui.warn("patch %s not in series\n" % patch)
+ sys.exit(1)
+ if sno >= len(self.series):
+ self.ui.warn("patch number %d is out of range\n" % sno)
+ sys.exit(1)
+ patch = self.series[sno]
+ else:
+ self.ui.warn("patch %s not in series\n" % patch)
+ sys.exit(1)
+ return patch
+
+ def push(self, repo, patch=None, force=False, list=False,
+ mergeq=None, wlock=None):
+ if not wlock:
+ wlock = repo.wlock()
+ patch = self.lookup(patch)
+ if patch and self.isapplied(patch):
+ self.ui.warn("patch %s is already applied\n" % patch)
+ sys.exit(1)
+ if self.series_end() == len(self.series):
+ self.ui.warn("File series fully applied\n")
+ sys.exit(1)
+ if not force:
+ self.check_localchanges(repo)
+
+ self.applied_dirty = 1;
+ start = self.series_end()
+ if start > 0:
+ self.check_toppatch(repo)
+ if not patch:
+ patch = self.series[start]
+ end = start + 1
+ else:
+ end = self.series.index(patch, start) + 1
+ s = self.series[start:end]
+ if mergeq:
+ ret = self.mergepatch(repo, mergeq, s, wlock)
+ else:
+ ret = self.apply(repo, s, list, wlock=wlock)
+ top = self.applied[-1].split(':')[1]
+ if ret[0]:
+ self.ui.write("Errors during apply, please fix and refresh %s\n" %
+ top)
+ else:
+ self.ui.write("Now at: %s\n" % top)
+ return ret[0]
+
+ def pop(self, repo, patch=None, force=False, update=True, wlock=None):
+ def getfile(f, rev):
+ t = repo.file(f).read(rev)
+ try:
+ repo.wfile(f, "w").write(t)
+ except IOError:
+ os.makedirs(os.path.dirname(repo.wjoin(f)))
+ repo.wfile(f, "w").write(t)
+
+ if not wlock:
+ wlock = repo.wlock()
+ if patch:
+ # index, rev, patch
+ info = self.isapplied(patch)
+ if not info:
+ patch = self.lookup(patch)
+ info = self.isapplied(patch)
+ if not info:
+ self.ui.warn("patch %s is not applied\n" % patch)
+ sys.exit(1)
+ if len(self.applied) == 0:
+ self.ui.warn("No patches applied\n")
+ sys.exit(1)
+
+ if not update:
+ parents = repo.dirstate.parents()
+ rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ]
+ for p in parents:
+ if p in rr:
+ self.ui.warn("qpop: forcing dirstate update\n")
+ update = True
+
+ if not force and update:
+ self.check_localchanges(repo)
+
+ self.applied_dirty = 1;
+ end = len(self.applied)
+ if not patch:
+ info = [len(self.applied) - 1] + self.applied[-1].split(':')
+ start = info[0]
+ rev = revlog.bin(info[1])
+
+ # we know there are no local changes, so we can make a simplified
+ # form of hg.update.
+ if update:
+ top = self.check_toppatch(repo)
+ qp = self.qparents(repo, rev)
+ changes = repo.changelog.read(qp)
+ mf1 = repo.manifest.readflags(changes[0])
+ mmap = repo.manifest.read(changes[0])
+ (c, a, r, d, u) = repo.changes(qp, top)
+ if d:
+ raise util.Abort("deletions found between repo revs")
+ for f in c:
+ getfile(f, mmap[f])
+ for f in r:
+ getfile(f, mmap[f])
+ util.set_exec(repo.wjoin(f), mf1[f])
+ repo.dirstate.update(c + r, 'n')
+ for f in a:
+ try: os.unlink(repo.wjoin(f))
+ except: raise
+ try: os.removedirs(os.path.dirname(repo.wjoin(f)))
+ except: pass
+ if a:
+ repo.dirstate.forget(a)
+ repo.dirstate.setparents(qp, revlog.nullid)
+ self.strip(repo, rev, update=False, backup='strip', wlock=wlock)
+ del self.applied[start:end]
+ if len(self.applied):
+ self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1])
+ else:
+ self.ui.write("Patch queue now empty\n")
+
+ def diff(self, repo, files):
+ top = self.check_toppatch(repo)
+ if not top:
+ self.ui.write("No patches applied\n")
+ return
+ qp = self.qparents(repo, top)
+ commands.dodiff(sys.stdout, self.ui, repo, qp, None, files)
+
+ def refresh(self, repo, short=False):
+ if len(self.applied) == 0:
+ self.ui.write("No patches applied\n")
+ return
+ wlock = repo.wlock()
+ self.check_toppatch(repo)
+ qp = self.qparents(repo)
+ (top, patch) = self.applied[-1].split(':')
+ top = revlog.bin(top)
+ cparents = repo.changelog.parents(top)
+ patchparent = self.qparents(repo, top)
+ message, comments, user, patchfound = self.readheaders(patch)
+
+ patchf = self.opener(os.path.join(self.path, patch), "w")
+ if comments:
+ comments = "\n".join(comments) + '\n\n'
+ patchf.write(comments)
+
+ tip = repo.changelog.tip()
+ if top == tip:
+ # if the top of our patch queue is also the tip, there is an
+ # optimization here. We update the dirstate in place and strip
+ # off the tip commit. Then just commit the current directory
+ # tree. We can also send repo.commit the list of files
+ # changed to speed up the diff
+ #
+ # in short mode, we only diff the files included in the
+ # patch already
+ #
+ # this should really read:
+ #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent)
+ # but we do it backwards to take advantage of manifest/chlog
+ # caching against the next repo.changes call
+ #
+ (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip)
+ if short:
+ filelist = cc + aa + dd
+ else:
+ filelist = None
+ (c, a, r, d, u) = repo.changes(None, None, filelist)
+
+ # we might end up with files that were added between tip and
+ # the dirstate parent, but then changed in the local dirstate.
+ # in this case, we want them to only show up in the added section
+ for x in c:
+ if x not in aa:
+ cc.append(x)
+ # we might end up with files added by the local dirstate that
+ # were deleted by the patch. In this case, they should only
+ # show up in the changed section.
+ for x in a:
+ if x in dd:
+ del dd[dd.index(x)]
+ cc.append(x)
+ else:
+ aa.append(x)
+ # make sure any files deleted in the local dirstate
+ # are not in the add or change column of the patch
+ forget = []
+ for x in d + r:
+ if x in aa:
+ del aa[aa.index(x)]
+ forget.append(x)
+ continue
+ elif x in cc:
+ del cc[cc.index(x)]
+ dd.append(x)
+
+ c = list(util.unique(cc))
+ r = list(util.unique(dd))
+ a = list(util.unique(aa))
+ filelist = list(util.unique(c + r + a ))
+ commands.dodiff(patchf, self.ui, repo, patchparent, None,
+ filelist, changes=(c, a, r, [], u))
+ patchf.close()
+
+ changes = repo.changelog.read(tip)
+ repo.dirstate.setparents(*cparents)
+ repo.dirstate.update(a, 'a')
+ repo.dirstate.update(r, 'r')
+ repo.dirstate.update(c, 'n')
+ repo.dirstate.forget(forget)
+
+ if not message:
+ message = "patch queue: %s\n" % patch
+ else:
+ message = "\n".join(message)
+ self.strip(repo, top, update=False, backup='strip', wlock=wlock)
+ n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock)
+ self.applied[-1] = revlog.hex(n) + ':' + patch
+ self.applied_dirty = 1
+ else:
+ commands.dodiff(patchf, self.ui, repo, patchparent, None)
+ patchf.close()
+ self.pop(repo, force=True, wlock=wlock)
+ self.push(repo, force=True, wlock=wlock)
+
+ def init(self, repo, create=False):
+ if os.path.isdir(self.path):
+ raise util.Abort("patch queue directory already exists")
+ os.mkdir(self.path)
+ if create:
+ return self.qrepo(create=True)
+
+ def unapplied(self, repo, patch=None):
+ if patch and patch not in self.series:
+ self.ui.warn("%s not in the series file\n" % patch)
+ sys.exit(1)
+ if not patch:
+ start = self.series_end()
+ else:
+ start = self.series.index(patch) + 1
+ for p in self.series[start:]:
+ self.ui.write("%s\n" % p)
+
+ def qseries(self, repo, missing=None):
+ start = self.series_end()
+ if not missing:
+ for p in self.series[:start]:
+ if self.ui.verbose:
+ self.ui.write("%d A " % self.series.index(p))
+ self.ui.write("%s\n" % p)
+ for p in self.series[start:]:
+ if self.ui.verbose:
+ self.ui.write("%d U " % self.series.index(p))
+ self.ui.write("%s\n" % p)
+ else:
+ list = []
+ for root, dirs, files in os.walk(self.path):
+ d = root[len(self.path) + 1:]
+ for f in files:
+ fl = os.path.join(d, f)
+ if (fl not in self.series and fl != "status" and
+ fl != "series" and not fl.startswith('.')):
+ list.append(fl)
+ list.sort()
+ if list:
+ for x in list:
+ if self.ui.verbose:
+ self.ui.write("D ")
+ self.ui.write("%s\n" % x)
+
+ def issaveline(self, l):
+ name = l.split(':')[1]
+ if name == '.hg.patches.save.line':
+ return True
+
+ def qrepo(self, create=False):
+ if create or os.path.isdir(os.path.join(self.path, ".hg")):
+ return hg.repository(ui=self.ui, path=self.path, create=create)
+
+ def restore(self, repo, rev, delete=None, qupdate=None):
+ c = repo.changelog.read(rev)
+ desc = c[4].strip()
+ lines = desc.splitlines()
+ i = 0
+ datastart = None
+ series = []
+ applied = []
+ qpp = None
+ for i in xrange(0, len(lines)):
+ if lines[i] == 'Patch Data:':
+ datastart = i + 1
+ elif lines[i].startswith('Dirstate:'):
+ l = lines[i].rstrip()
+ l = l[10:].split(' ')
+ qpp = [ hg.bin(x) for x in l ]
+ elif datastart != None:
+ l = lines[i].rstrip()
+ index = l.index(':')
+ id = l[:index]
+ file = l[index + 1:]
+ if id:
+ applied.append(l)
+ series.append(file)
+ if datastart == None:
+ self.ui.warn("No saved patch data found\n")
+ return 1
+ self.ui.warn("restoring status: %s\n" % lines[0])
+ self.full_series = series
+ self.applied = applied
+ self.read_series(self.full_series)
+ self.series_dirty = 1
+ self.applied_dirty = 1
+ heads = repo.changelog.heads()
+ if delete:
+ if rev not in heads:
+ self.ui.warn("save entry has children, leaving it alone\n")
+ else:
+ self.ui.warn("removing save entry %s\n" % hg.short(rev))
+ pp = repo.dirstate.parents()
+ if rev in pp:
+ update = True
+ else:
+ update = False
+ self.strip(repo, rev, update=update, backup='strip')
+ if qpp:
+ self.ui.warn("saved queue repository parents: %s %s\n" %
+ (hg.short(qpp[0]), hg.short(qpp[1])))
+ if qupdate:
+ print "queue directory updating"
+ r = self.qrepo()
+ if not r:
+ self.ui.warn("Unable to load queue repository\n")
+ return 1
+ r.update(qpp[0], allow=False, force=True)
+
+ def save(self, repo, msg=None):
+ if len(self.applied) == 0:
+ self.ui.warn("save: no patches applied, exiting\n")
+ return 1
+ if self.issaveline(self.applied[-1]):
+ self.ui.warn("status is already saved\n")
+ return 1
+
+ ar = [ ':' + x for x in self.full_series ]
+ if not msg:
+ msg = "hg patches saved state"
+ else:
+ msg = "hg patches: " + msg.rstrip('\r\n')
+ r = self.qrepo()
+ if r:
+ pp = r.dirstate.parents()
+ msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
+ msg += "\n\nPatch Data:\n"
+ text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar)
+ + '\n' or "")
+ n = repo.commit(None, text, user=None, force=1)
+ if not n:
+ self.ui.warn("repo commit failed\n")
+ return 1
+ self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line')
+ self.applied_dirty = 1
+
+ def series_end(self):
+ end = 0
+ if len(self.applied) > 0:
+ (top, p) = self.applied[-1].split(':')
+ try:
+ end = self.series.index(p)
+ except ValueError:
+ return 0
+ return end + 1
+ return end
+
+ def qapplied(self, repo, patch=None):
+ if patch and patch not in self.series:
+ self.ui.warn("%s not in the series file\n" % patch)
+ sys.exit(1)
+ if not patch:
+ end = len(self.applied)
+ else:
+ end = self.series.index(patch) + 1
+ for x in xrange(end):
+ p = self.appliedname(x)
+ self.ui.write("%s\n" % p)
+
+ def appliedname(self, index):
+ p = self.applied[index]
+ if not self.ui.verbose:
+ p = p.split(':')[1]
+ return p
+
+ def top(self, repo):
+ if len(self.applied):
+ p = self.appliedname(-1)
+ self.ui.write(p + '\n')
+ else:
+ self.ui.write("No patches applied\n")
+
+ def next(self, repo):
+ end = self.series_end()
+ if end == len(self.series):
+ self.ui.write("All patches applied\n")
+ else:
+ self.ui.write(self.series[end] + '\n')
+
+ def prev(self, repo):
+ if len(self.applied) > 1:
+ p = self.appliedname(-2)
+ self.ui.write(p + '\n')
+ elif len(self.applied) == 1:
+ self.ui.write("Only one patch applied\n")
+ else:
+ self.ui.write("No patches applied\n")
+
+ def qimport(self, repo, files, patch=None, existing=None, force=None):
+ if len(files) > 1 and patch:
+ self.ui.warn("-n option not valid when importing multiple files\n")
+ sys.exit(1)
+ i = 0
+ for filename in files:
+ if existing:
+ if not patch:
+ patch = filename
+ if not os.path.isfile(os.path.join(self.path, patch)):
+ self.ui.warn("patch %s does not exist\n" % patch)
+ sys.exit(1)
+ else:
+ try:
+ text = file(filename).read()
+ except IOError:
+ self.ui.warn("Unable to read %s\n" % patch)
+ sys.exit(1)
+ if not patch:
+ patch = os.path.split(filename)[1]
+ if not force and os.path.isfile(os.path.join(self.path, patch)):
+ self.ui.warn("patch %s already exists\n" % patch)
+ sys.exit(1)
+ patchf = self.opener(os.path.join(self.path, patch), "w")
+ patchf.write(text)
+ if patch in self.series:
+ self.ui.warn("patch %s is already in the series file\n" % patch)
+ sys.exit(1)
+ index = self.series_end() + i
+ self.full_series[index:index] = [patch]
+ self.read_series(self.full_series)
+ self.ui.warn("adding %s to series file\n" % patch)
+ i += 1
+ patch = None
+ self.series_dirty = 1
+
+def delete(ui, repo, patch, **opts):
+ """remove a patch from the series file"""
+ q = repomap[repo]
+ q.delete(repo, patch)
+ q.save_dirty()
+ return 0
+
+def applied(ui, repo, patch=None, **opts):
+ """print the patches already applied"""
+ repomap[repo].qapplied(repo, patch)
+ return 0
+
+def unapplied(ui, repo, patch=None, **opts):
+ """print the patches not yet applied"""
+ repomap[repo].unapplied(repo, patch)
+ return 0
+
+def qimport(ui, repo, *filename, **opts):
+ """import a patch"""
+ q = repomap[repo]
+ q.qimport(repo, filename, patch=opts['name'],
+ existing=opts['existing'], force=opts['force'])
+ q.save_dirty()
+ return 0
+
+def init(ui, repo, **opts):
+ """init a new queue repository"""
+ q = repomap[repo]
+ r = q.init(repo, create=opts['create_repo'])
+ q.save_dirty()
+ if r:
+ fp = r.wopener('.hgignore', 'w')
+ print >> fp, 'syntax: glob'
+ print >> fp, 'status'
+ fp.close()
+ r.wopener('series', 'w').close()
+ r.add(['.hgignore', 'series'])
+ return 0
+
+def commit(ui, repo, *pats, **opts):
+ q = repomap[repo]
+ r = q.qrepo()
+ if not r: raise util.Abort('no queue repository')
+ commands.commit(r.ui, r, *pats, **opts)
+
+def series(ui, repo, **opts):
+ """print the entire series file"""
+ repomap[repo].qseries(repo, missing=opts['missing'])
+ return 0
+
+def top(ui, repo, **opts):
+ """print the name of the current patch"""
+ repomap[repo].top(repo)
+ return 0
+
+def next(ui, repo, **opts):
+ """print the name of the next patch"""
+ repomap[repo].next(repo)
+ return 0
+
+def prev(ui, repo, **opts):
+ """print the name of the previous patch"""
+ repomap[repo].prev(repo)
+ return 0
+
+def new(ui, repo, patch, **opts):
+ """create a new patch"""
+ q = repomap[repo]
+ q.new(repo, patch, msg=opts['message'], force=opts['force'])
+ q.save_dirty()
+ return 0
+
+def refresh(ui, repo, **opts):
+ """update the current patch"""
+ q = repomap[repo]
+ q.refresh(repo, short=opts['short'])
+ q.save_dirty()
+ return 0
+
+def diff(ui, repo, *files, **opts):
+ """diff of the current patch"""
+ repomap[repo].diff(repo, files)
+ return 0
+
+def lastsavename(path):
+ (dir, base) = os.path.split(path)
+ names = os.listdir(dir)
+ namere = re.compile("%s.([0-9]+)" % base)
+ max = None
+ maxname = None
+ for f in names:
+ m = namere.match(f)
+ if m:
+ index = int(m.group(1))
+ if max == None or index > max:
+ max = index
+ maxname = f
+ if maxname:
+ return (os.path.join(dir, maxname), max)
+ return (None, None)
+
+def savename(path):
+ (last, index) = lastsavename(path)
+ if last is None:
+ index = 0
+ newpath = path + ".%d" % (index + 1)
+ return newpath
+
+def push(ui, repo, patch=None, **opts):
+ """push the next patch onto the stack"""
+ q = repomap[repo]
+ mergeq = None
+
+ if opts['all']:
+ patch = q.series[-1]
+ if opts['merge']:
+ if opts['name']:
+ newpath = opts['name']
+ else:
+ newpath, i = lastsavename(q.path)
+ if not newpath:
+ ui.warn("no saved queues found, please use -n\n")
+ return 1
+ mergeq = queue(ui, repo.join(""), newpath)
+ ui.warn("merging with queue at: %s\n" % mergeq.path)
+ ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
+ mergeq=mergeq)
+ q.save_dirty()
+ return ret
+
+def pop(ui, repo, patch=None, **opts):
+ """pop the current patch off the stack"""
+ localupdate = True
+ if opts['name']:
+ q = queue(ui, repo.join(""), repo.join(opts['name']))
+ ui.warn('using patch queue: %s\n' % q.path)
+ localupdate = False
+ else:
+ q = repomap[repo]
+ if opts['all'] and len(q.applied) > 0:
+ patch = q.applied[0].split(':')[1]
+ q.pop(repo, patch, force=opts['force'], update=localupdate)
+ q.save_dirty()
+ return 0
+
+def restore(ui, repo, rev, **opts):
+ """restore the queue state saved by a rev"""
+ rev = repo.lookup(rev)
+ q = repomap[repo]
+ q.restore(repo, rev, delete=opts['delete'],
+ qupdate=opts['update'])
+ q.save_dirty()
+ return 0
+
+def save(ui, repo, **opts):
+ """save current queue state"""
+ q = repomap[repo]
+ ret = q.save(repo, msg=opts['message'])
+ if ret:
+ return ret
+ q.save_dirty()
+ if opts['copy']:
+ path = q.path
+ if opts['name']:
+ newpath = os.path.join(q.basepath, opts['name'])
+ if os.path.exists(newpath):
+ if not os.path.isdir(newpath):
+ ui.warn("destination %s exists and is not a directory\n" %
+ newpath)
+ sys.exit(1)
+ if not opts['force']:
+ ui.warn("destination %s exists, use -f to force\n" %
+ newpath)
+ sys.exit(1)
+ else:
+ newpath = savename(path)
+ ui.warn("copy %s to %s\n" % (path, newpath))
+ util.copyfiles(path, newpath)
+ if opts['empty']:
+ try:
+ os.unlink(q.status_path)
+ except:
+ pass
+ return 0
+
+def strip(ui, repo, rev, **opts):
+ """strip a revision and all later revs on the same branch"""
+ rev = repo.lookup(rev)
+ backup = 'all'
+ if opts['backup']:
+ backup = 'strip'
+ elif opts['nobackup']:
+ backup = 'none'
+ repomap[repo].strip(repo, rev, backup=backup)
+ return 0
+
+def version(ui, q=None):
+ """print the version number"""
+ ui.write("mq version %s\n" % versionstr)
+ return 0
+
+def reposetup(ui, repo):
+ repomap[repo] = queue(ui, repo.join(""))
+
+cmdtable = {
+ "qapplied": (applied, [], 'hg qapplied [patch]'),
+ "qcommit|qci":
+ (commit,
+ [('A', 'addremove', None, _('run addremove during commit')),
+ ('I', 'include', [], _('include names matching the given patterns')),
+ ('X', 'exclude', [], _('exclude names matching the given patterns')),
+ ('m', 'message', '', _('use <text> as commit message')),
+ ('l', 'logfile', '', _('read the commit message from <file>')),
+ ('d', 'date', '', _('record datecode as commit date')),
+ ('u', 'user', '', _('record user as commiter'))],
+ 'hg qcommit [options] [files]'),
+ "^qdiff": (diff, [], 'hg qdiff [files]'),
+ "qdelete": (delete, [], 'hg qdelete [patch]'),
+ "^qimport":
+ (qimport,
+ [('e', 'existing', None, 'import file in patch dir'),
+ ('n', 'name', '', 'patch file name'),
+ ('f', 'force', None, 'overwrite existing files')],
+ 'hg qimport'),
+ "^qinit":
+ (init,
+ [('c', 'create-repo', None, 'create patch repository')],
+ 'hg [-c] qinit'),
+ "qnew":
+ (new,
+ [('m', 'message', '', 'commit message'),
+ ('f', 'force', None, 'force')],
+ 'hg qnew [-m message ] patch'),
+ "qnext": (next, [], 'hg qnext'),
+ "qprev": (prev, [], 'hg qprev'),
+ "^qpop":
+ (pop,
+ [('a', 'all', None, 'pop all patches'),
+ ('n', 'name', '', 'queue name to pop'),
+ ('f', 'force', None, 'forget any local changes')],
+ 'hg qpop [options] [patch/index]'),
+ "^qpush":
+ (push,
+ [('f', 'force', None, 'apply if the patch has rejects'),
+ ('l', 'list', None, 'list patch name in commit text'),
+ ('a', 'all', None, 'apply all patches'),
+ ('m', 'merge', None, 'merge from another queue'),
+ ('n', 'name', '', 'merge queue name')],
+ 'hg qpush [options] [patch/index]'),
+ "^qrefresh":
+ (refresh,
+ [('s', 'short', None, 'short refresh')],
+ 'hg qrefresh'),
+ "qrestore":
+ (restore,
+ [('d', 'delete', None, 'delete save entry'),
+ ('u', 'update', None, 'update queue working dir')],
+ 'hg qrestore rev'),
+ "qsave":
+ (save,
+ [('m', 'message', '', 'commit message'),
+ ('c', 'copy', None, 'copy patch directory'),
+ ('n', 'name', '', 'copy directory name'),
+ ('e', 'empty', None, 'clear queue status file'),
+ ('f', 'force', None, 'force copy')],
+ 'hg qsave'),
+ "qseries":
+ (series,
+ [('m', 'missing', None, 'print patches not in series')],
+ 'hg qseries'),
+ "^strip":
+ (strip,
+ [('f', 'force', None, 'force multi-head removal'),
+ ('b', 'backup', None, 'bundle unrelated changesets'),
+ ('n', 'nobackup', None, 'no backups')],
+ 'hg strip rev'),
+ "qtop": (top, [], 'hg qtop'),
+ "qunapplied": (unapplied, [], 'hg qunapplied [patch]'),
+ "qversion": (version, [], 'hg qversion')
+}
+
--- a/hgext/patchbomb.py Mon Mar 06 17:58:53 2006 +0100
+++ b/hgext/patchbomb.py Mon Mar 06 18:00:44 2006 +0100
@@ -49,20 +49,11 @@
# to = recipient1, recipient2, ...
# cc = cc1, cc2, ...
-from email.MIMEMultipart import MIMEMultipart
-from email.MIMEText import MIMEText
-from email.Utils import parseaddr
-from mercurial import commands
-from mercurial import hg
-from mercurial import ui
+from mercurial.demandload import *
+demandload(globals(), '''email.MIMEMultipart email.MIMEText email.Utils
+ mercurial:commands,hg,ui
+ os popen2 smtplib socket sys tempfile time''')
from mercurial.i18n import gettext as _
-import os
-import popen2
-import smtplib
-import socket
-import sys
-import tempfile
-import time
try:
# readline gives raw_input editing capabilities, but is not
@@ -149,7 +140,7 @@
if opts['diffstat']:
body += cdiffstat('\n'.join(desc), patch) + '\n\n'
body += '\n'.join(patch)
- msg = MIMEText(body)
+ msg = email.MIMEText.MIMEText(body)
subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip())
if subj.endswith('.'): subj = subj[:-1]
msg['Subject'] = subj
@@ -194,7 +185,7 @@
sender = (opts['from'] or ui.config('patchbomb', 'from') or
prompt('From', ui.username()))
- msg = MIMEMultipart()
+ msg = email.MIMEMultipart.MIMEMultipart()
msg['Subject'] = '[PATCH 0 of %d] %s' % (
len(patches),
opts['subject'] or
@@ -217,13 +208,13 @@
if l == '.': break
body.append(l)
- msg.attach(MIMEText('\n'.join(body) + '\n'))
+ msg.attach(email.MIMEText.MIMEText('\n'.join(body) + '\n'))
ui.write('\n')
if opts['diffstat']:
d = cdiffstat(_('Final summary:\n'), jumbo)
- if d: msg.attach(MIMEText(d))
+ if d: msg.attach(email.MIMEText.MIMEText(d))
msgs.insert(0, msg)
@@ -241,7 +232,7 @@
s.login(username, password)
parent = None
tz = time.strftime('%z')
- sender_addr = parseaddr(sender)[1]
+ sender_addr = email.Utils.parseaddr(sender)[1]
for m in msgs:
try:
m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
--- a/hgmerge Mon Mar 06 17:58:53 2006 +0100
+++ b/hgmerge Mon Mar 06 18:00:44 2006 +0100
@@ -17,28 +17,32 @@
# find decent versions of our utilities, insisting on the GNU versions where we
# need to
-MERGE=merge
-DIFF3=gdiff3
-DIFF=gdiff
-PATCH=gpatch
+MERGE="merge"
+DIFF3="gdiff3"
+DIFF="gdiff"
+PATCH="gpatch"
-type $MERGE >/dev/null 2>&1 || MERGE=
-type $DIFF3 >/dev/null 2>&1 || DIFF3=diff3
-type $DIFF >/dev/null 2>&1 || DIFF=diff
-type $PATCH >/dev/null 2>&1 || PATCH=patch
+type "$MERGE" >/dev/null 2>&1 || MERGE=
+type "$DIFF3" >/dev/null 2>&1 || DIFF3="diff3"
$DIFF3 --version >/dev/null 2>&1 || DIFF3=
+type "$DIFF" >/dev/null 2>&1 || DIFF="diff"
+type "$DIFF" >/dev/null 2>&1 || DIFF=
+type "$PATCH" >/dev/null 2>&1 || PATCH="patch"
+type "$PATCH" >/dev/null 2>&1 || PATCH=
# find optional visual utilities
-FILEMERGE='/Developer/Applications/Utilities/FileMerge.app/Contents/MacOS/FileMerge'
-KDIFF3=kdiff3
-TKDIFF=tkdiff
+FILEMERGE="/Developer/Applications/Utilities/FileMerge.app/Contents/MacOS/FileMerge"
+KDIFF3="kdiff3"
+TKDIFF="tkdiff"
+MELD="meld"
-type $FILEMERGE >/dev/null 2>&1 || FILEMERGE=
-type $KDIFF3 >/dev/null 2>&1 || KDIFF3=
-type $TKDIFF >/dev/null 2>&1 || TKDIFF=
+type "$FILEMERGE" >/dev/null 2>&1 || FILEMERGE=
+type "$KDIFF3" >/dev/null 2>&1 || KDIFF3=
+type "$TKDIFF" >/dev/null 2>&1 || TKDIFF=
+type "$MELD" >/dev/null 2>&1 || MELD=
# random part of names
-RAND="$RANDOM.$RANDOM.$RANDOM.$$"
+RAND="$RANDOM$RANDOM"
# temporary directory for diff+patch merge
HGTMP="${TMPDIR-/tmp}/hgmerge.$RAND"
@@ -68,6 +72,19 @@
exit 1
}
+# Ask if the merge was successful
+ask_if_merged() {
+ while true; do
+ echo "$LOCAL seems unchanged."
+ echo "Was the merge successful? [y/n]"
+ read answer
+ case "$answer" in
+ y*|Y*) success;;
+ n*|N*) failure;;
+ esac
+ done
+}
+
# Clean up when interrupted
trap "failure" 1 2 3 6 15 # HUP INT QUIT ABRT TERM
@@ -76,18 +93,16 @@
cp "$BACKUP" "$LOCAL"
# Attempt to do a non-interactive merge
-if [ -n "$MERGE" ]; then
- $MERGE "$LOCAL" "$BASE" "$OTHER" 2> /dev/null && success
- cp "$BACKUP" "$LOCAL"
-elif [ -n "$DIFF3" ]; then
- echo $DIFF3 -m "$BACKUP" "$BASE" "$OTHER"
- $DIFF3 -m "$BACKUP" "$BASE" "$OTHER" > "$LOCAL" && success
- if [ $? -eq 2 ]; then
- echo "$DIFF3 failed! Exiting." 1>&2
- cp "$BACKUP" "$LOCAL"
+if [ -n "$MERGE" -o -n "$DIFF3" ]; then
+ if [ -n "$MERGE" ]; then
+ $MERGE "$LOCAL" "$BASE" "$OTHER" 2> /dev/null && success
+ elif [ -n "$DIFF3" ]; then
+ $DIFF3 -m "$BACKUP" "$BASE" "$OTHER" > "$LOCAL" && success
+ fi
+ if [ $? -gt 1 ]; then
+ echo "automatic merge failed! Exiting." 1>&2
failure
fi
- cp "$BACKUP" "$LOCAL"
fi
# on MacOS X try FileMerge.app, shipped with Apple's developer tools
@@ -97,71 +112,66 @@
# filemerge prefers the right by default
$FILEMERGE -left "$OTHER" -right "$LOCAL" -ancestor "$BASE" -merge "$LOCAL"
[ $? -ne 0 ] && echo "FileMerge failed to launch" && failure
- if test "$LOCAL" -nt "$CHGTEST"
- then
- success
- else
- echo "$LOCAL seems unchanged. Was the merge successful?"
- select answer in yes no
- do
- test "$answer" == "yes" && success || failure
- done
- fi
- failure
+ test "$LOCAL" -nt "$CHGTEST" && success || ask_if_merged
fi
if [ -n "$DISPLAY" ]; then
# try using kdiff3, which is fairly nice
if [ -n "$KDIFF3" ]; then
- $KDIFF3 --auto "$BASE" "$LOCAL" "$OTHER" -o "$LOCAL" || failure
- success
+ $KDIFF3 --auto "$BASE" "$BACKUP" "$OTHER" -o "$LOCAL" || failure
+ success
fi
# try using tkdiff, which is a bit less sophisticated
if [ -n "$TKDIFF" ]; then
- $TKDIFF "$LOCAL" "$OTHER" -a "$BASE" -o "$LOCAL" || failure
- success
+ $TKDIFF "$BACKUP" "$OTHER" -a "$BASE" -o "$LOCAL" || failure
+ success
+ fi
+
+ if [ -n "$MELD" ]; then
+ cp "$BACKUP" "$CHGTEST"
+ # protect our feet - meld allows us to save to the left file
+ cp "$BACKUP" "$LOCAL.tmp.$RAND"
+ # Meld doesn't have automatic merging, so to reduce intervention
+ # use the file with conflicts
+ $MELD "$LOCAL.tmp.$RAND" "$LOCAL" "$OTHER" || failure
+ # Also it doesn't return good error code
+ test "$LOCAL" -nt "$CHGTEST" && success || ask_if_merged
fi
fi
# Attempt to do a merge with $EDITOR
-if [ -n "$MERGE" ]; then
- echo "conflicts detected in $LOCAL"
- $MERGE "$LOCAL" "$BASE" "$OTHER" 2>/dev/null || $EDITOR "$LOCAL"
- success
-fi
-
-if [ -n "$DIFF3" ]; then
+if [ -n "$MERGE" -o -n "$DIFF3" ]; then
echo "conflicts detected in $LOCAL"
- $DIFF3 -m "$BACKUP" "$BASE" "$OTHER" > "$LOCAL" || {
- case $? in
- 1)
- $EDITOR "$LOCAL" ;;
- 2) echo "$DIFF3 failed! Exiting." 1>&2
- cp "$BACKUP" "$LOCAL"
- failure ;;
- esac
- success
- }
+ cp "$BACKUP" "$CHGTEST"
+ $EDITOR "$LOCAL" || failure
+ # Some editors do not return meaningful error codes
+ # Do not take any chances
+ test "$LOCAL" -nt "$CHGTEST" && success || ask_if_merged
fi
# attempt to manually merge with diff and patch
if [ -n "$DIFF" -a -n "$PATCH" ]; then
(umask 077 && mkdir "$HGTMP") || {
- echo "Could not create temporary directory $HGTMP" 1>&2
- failure
+ echo "Could not create temporary directory $HGTMP" 1>&2
+ failure
}
$DIFF -u "$BASE" "$OTHER" > "$HGTMP/diff" || :
if $PATCH "$LOCAL" < "$HGTMP/diff"; then
- success
+ success
else
- # If rejects are empty after using the editor, merge was ok
- $EDITOR "$LOCAL" "$LOCAL.rej" && test -s "$LOCAL.rej" || success
+ # If rejects are empty after using the editor, merge was ok
+ $EDITOR "$LOCAL" "$LOCAL.rej" || failure
+ test -s "$LOCAL.rej" || success
fi
failure
fi
-echo "hgmerge: unable to find merge, tkdiff, kdiff3, or diff+patch!"
+echo
+echo "hgmerge: unable to find any merge utility!"
+echo "supported programs:"
+echo "merge, FileMerge, tkdiff, kdiff3, meld, diff+patch"
+echo
failure
--- a/hgwebdir.cgi Mon Mar 06 17:58:53 2006 +0100
+++ b/hgwebdir.cgi Mon Mar 06 18:00:44 2006 +0100
@@ -8,10 +8,21 @@
# sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
from mercurial import hgweb
-# The config file looks like this:
+# The config file looks like this. You can have paths to individual
+# repos, collections of repos in a directory tree, or both.
+#
# [paths]
# virtual/path = /real/path
# virtual/path = /real/path
+#
+# [collections]
+# /prefix/to/strip/off = /root/of/tree/full/of/repos
+#
+# collections example: say directory tree /foo contains repos /foo/bar,
+# /foo/quux/baz. Give this config section:
+# [collections]
+# /foo = /foo
+# Then repos will list as bar and quux/baz.
# Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
# or use a dictionary with entries like 'virtual/path': '/real/path'
--- a/mercurial/bdiff.c Mon Mar 06 17:58:53 2006 +0100
+++ b/mercurial/bdiff.c Mon Mar 06 18:00:44 2006 +0100
@@ -17,6 +17,10 @@
#define inline
#endif
+#ifdef __SUNPRO_C
+# define inline
+#endif
+
#ifdef _WIN32
#ifdef _MSC_VER
#define inline __inline
--- a/mercurial/commands.py Mon Mar 06 17:58:53 2006 +0100
+++ b/mercurial/commands.py Mon Mar 06 18:00:44 2006 +0100
@@ -82,6 +82,21 @@
"iter", rev, None: in-order traversal of the revs earlier iterated
over with "add" - use to display data'''
+ def increasing_windows(start, end, windowsize=8, sizelimit=512):
+ if start < end:
+ while start < end:
+ yield start, min(windowsize, end-start)
+ start += windowsize
+ if windowsize < sizelimit:
+ windowsize *= 2
+ else:
+ while start > end:
+ yield start, min(windowsize, start-end-1)
+ start -= windowsize
+ if windowsize < sizelimit:
+ windowsize *= 2
+
+
files, matchfn, anypats = matchpats(repo, pats, opts)
if repo.changelog.count() == 0:
@@ -90,7 +105,6 @@
revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
wanted = {}
slowpath = anypats
- window = 300
fncache = {}
chcache = {}
@@ -106,17 +120,17 @@
if not slowpath:
# Only files, no patterns. Check the history of each file.
def filerevgen(filelog):
- for i in xrange(filelog.count() - 1, -1, -window):
+ for i, window in increasing_windows(filelog.count()-1, -1):
revs = []
- for j in xrange(max(0, i - window), i + 1):
+ for j in xrange(i - window, i + 1):
revs.append(filelog.linkrev(filelog.node(j)))
revs.reverse()
for rev in revs:
yield rev
minrev, maxrev = min(revs), max(revs)
- for file in files:
- filelog = repo.file(file)
+ for file_ in files:
+ filelog = repo.file(file_)
# A zero count may be a directory or deleted file, so
# try to find matching entries on the slow path.
if filelog.count() == 0:
@@ -127,13 +141,13 @@
if rev < minrev:
break
fncache.setdefault(rev, [])
- fncache[rev].append(file)
+ fncache[rev].append(file_)
wanted[rev] = 1
if slowpath:
# The slow path checks files modified in every changeset.
def changerevgen():
- for i in xrange(repo.changelog.count() - 1, -1, -window):
- for j in xrange(max(0, i - window), i + 1):
+ for i, window in increasing_windows(repo.changelog.count()-1, -1):
+ for j in xrange(i - window, i + 1):
yield j, getchange(j)[3]
for rev, changefiles in changerevgen():
@@ -143,9 +157,9 @@
wanted[rev] = 1
def iterate():
- for i in xrange(0, len(revs), window):
+ for i, window in increasing_windows(0, len(revs)):
yield 'window', revs[0] < revs[-1], revs[-1]
- nrevs = [rev for rev in revs[i:min(i+window, len(revs))]
+ nrevs = [rev for rev in revs[i:i+window]
if rev in wanted]
srevs = list(nrevs)
srevs.sort()
@@ -262,6 +276,14 @@
def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always,
changes=None, text=False, opts={}):
+ if not node1:
+ node1 = repo.dirstate.parents()[0]
+ # reading the data for node1 early allows it to play nicely
+ # with repo.changes and the revlog cache.
+ change = repo.changelog.read(node1)
+ mmap = repo.manifest.read(change[0])
+ date1 = util.datestr(change[2])
+
if not changes:
changes = repo.changes(node1, node2, files, match=match)
modified, added, removed, deleted, unknown = changes
@@ -280,8 +302,6 @@
return repo.file(f).read(mmap2[f])
else:
date2 = util.datestr()
- if not node1:
- node1 = repo.dirstate.parents()[0]
def read(f):
return repo.wread(f)
@@ -291,10 +311,6 @@
hexfunc = ui.verbose and hex or short
r = [hexfunc(node) for node in [node1, node2] if node]
- change = repo.changelog.read(node1)
- mmap = repo.manifest.read(change[0])
- date1 = util.datestr(change[2])
-
diffopts = ui.diffopts()
showfunc = opts.get('show_function') or diffopts['showfunc']
ignorews = opts.get('ignore_all_space') or diffopts['ignorews']
@@ -447,7 +463,6 @@
f = f.lstrip("^")
if not ui.debugflag and f.startswith("debug"):
continue
- d = ""
doc = e[0].__doc__
if not doc:
doc = _("(No help text available)")
@@ -681,6 +696,8 @@
such as AFS, implement hardlinking incorrectly, but do not report
errors. In these cases, use the --pull option to avoid
hardlinking.
+
+ See pull for valid source format details.
"""
if dest is None:
dest = os.path.basename(os.path.normpath(source))
@@ -725,8 +742,8 @@
# can end up with extra data in the cloned revlogs that's
# not pointed to by changesets, thus causing verify to
# fail
- l1 = lock.lock(os.path.join(source, ".hg", "lock"))
- except OSError:
+ l1 = other.lock()
+ except lock.LockException:
copy = False
if copy:
@@ -808,7 +825,8 @@
except ValueError, inst:
raise util.Abort(str(inst))
-def docopy(ui, repo, pats, opts):
+def docopy(ui, repo, pats, opts, wlock):
+ # called with the repo lock held
cwd = repo.getcwd()
errors = 0
copied = []
@@ -818,14 +836,19 @@
reasons = {'?': _('is not managed'),
'a': _('has been marked for add'),
'r': _('has been marked for remove')}
- reason = reasons.get(repo.dirstate.state(abs))
+ state = repo.dirstate.state(abs)
+ reason = reasons.get(state)
if reason:
+ if state == 'a':
+ origsrc = repo.dirstate.copied(abs)
+ if origsrc is not None:
+ return origsrc
if exact:
ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
else:
- return True
-
- def copy(abssrc, relsrc, target, exact):
+ return abs
+
+ def copy(origsrc, abssrc, relsrc, target, exact):
abstarget = util.canonpath(repo.root, cwd, target)
reltarget = util.pathto(cwd, abstarget)
prevsrc = targets.get(abstarget)
@@ -849,8 +872,16 @@
if not os.path.isdir(targetdir):
os.makedirs(targetdir)
try:
- shutil.copyfile(relsrc, reltarget)
- shutil.copymode(relsrc, reltarget)
+ restore = repo.dirstate.state(abstarget) == 'r'
+ if restore:
+ repo.undelete([abstarget], wlock)
+ try:
+ shutil.copyfile(relsrc, reltarget)
+ shutil.copymode(relsrc, reltarget)
+ restore = False
+ finally:
+ if restore:
+ repo.remove([abstarget], wlock)
except shutil.Error, inst:
raise util.Abort(str(inst))
except IOError, inst:
@@ -864,7 +895,8 @@
if ui.verbose or not exact:
ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
targets[abstarget] = abssrc
- repo.copy(abssrc, abstarget)
+ if abstarget != origsrc:
+ repo.copy(origsrc, abstarget, wlock)
copied.append((abssrc, relsrc, exact))
def targetpathfn(pat, dest, srcs):
@@ -938,8 +970,9 @@
for pat in pats:
srcs = []
for tag, abssrc, relsrc, exact in walk(repo, [pat], opts):
- if okaytocopy(abssrc, relsrc, exact):
- srcs.append((abssrc, relsrc, exact))
+ origsrc = okaytocopy(abssrc, relsrc, exact)
+ if origsrc:
+ srcs.append((origsrc, abssrc, relsrc, exact))
if not srcs:
continue
copylist.append((tfn(pat, dest, srcs), srcs))
@@ -947,8 +980,8 @@
raise util.Abort(_('no files to copy'))
for targetpath, srcs in copylist:
- for abssrc, relsrc, exact in srcs:
- copy(abssrc, relsrc, targetpath(abssrc), exact)
+ for origsrc, abssrc, relsrc, exact in srcs:
+ copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
if errors:
ui.warn(_('(consider using --after)\n'))
@@ -971,15 +1004,32 @@
should properly record copied files, this information is not yet
fully used by merge, nor fully reported by log.
"""
- errs, copied = docopy(ui, repo, pats, opts)
+ try:
+ wlock = repo.wlock(0)
+ errs, copied = docopy(ui, repo, pats, opts, wlock)
+ except lock.LockHeld, inst:
+ ui.warn(_("repository lock held by %s\n") % inst.args[0])
+ errs = 1
return errs
def debugancestor(ui, index, rev1, rev2):
"""find the ancestor revision of two revisions in a given index"""
- r = revlog.revlog(util.opener(os.getcwd()), index, "")
+ r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "")
a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
ui.write("%d:%s\n" % (r.rev(a), hex(a)))
+def debugrebuildstate(ui, repo, rev=None):
+ """rebuild the dirstate as it would look like for the given revision"""
+ if not rev:
+ rev = repo.changelog.tip()
+ else:
+ rev = repo.lookup(rev)
+ change = repo.changelog.read(rev)
+ n = change[0]
+ files = repo.manifest.readflags(n)
+ wlock = repo.wlock()
+ repo.dirstate.rebuild(rev, files.iteritems())
+
def debugcheckstate(ui, repo):
"""validate the correctness of the current dirstate"""
parent1, parent2 = repo.dirstate.parents()
@@ -1050,7 +1100,8 @@
def debugdata(ui, file_, rev):
"""dump the contents of an data file revision"""
- r = revlog.revlog(util.opener(os.getcwd()), file_[:-2] + ".i", file_)
+ r = revlog.revlog(util.opener(os.getcwd(), audit=False),
+ file_[:-2] + ".i", file_)
try:
ui.write(r.revision(r.lookup(rev)))
except KeyError:
@@ -1058,7 +1109,7 @@
def debugindex(ui, file_):
"""dump the contents of an index file"""
- r = revlog.revlog(util.opener(os.getcwd()), file_, "")
+ r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "")
ui.write(" rev offset length base linkrev" +
" nodeid p1 p2\n")
for i in range(r.count()):
@@ -1069,7 +1120,7 @@
def debugindexdot(ui, file_):
"""dump an index DAG as a .dot file"""
- r = revlog.revlog(util.opener(os.getcwd()), file_, "")
+ r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "")
ui.write("digraph G {\n")
for i in range(r.count()):
e = r.index[i]
@@ -1284,6 +1335,7 @@
s = linestate(line, lnum, cstart, cend)
m[s] = s
+ # FIXME: prev isn't used, why ?
prev = {}
ucache = {}
def display(fn, rev, states, prevstates):
@@ -1593,7 +1645,19 @@
self.write(*args)
def __getattr__(self, key):
return getattr(self.ui, key)
+
changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts)
+
+ if opts['limit']:
+ try:
+ limit = int(opts['limit'])
+ except ValueError:
+ raise util.Abort(_('limit must be a positive integer'))
+ if limit <= 0: raise util.Abort(_('limit must be positive'))
+ else:
+ limit = sys.maxint
+ count = 0
+
for st, rev, fns in changeiter:
if st == 'window':
du = dui(ui)
@@ -1607,7 +1671,6 @@
if opts['only_merges'] and len(parents) != 2:
continue
- br = None
if opts['keyword']:
changes = getchange(rev)
miss = 0
@@ -1620,7 +1683,8 @@
if miss:
continue
- if opts['branch']:
+ br = None
+ if opts['branches']:
br = repo.branchlookup([repo.changelog.node(rev)])
show_changeset(du, repo, rev, brinfo=br)
@@ -1629,8 +1693,11 @@
dodiff(du, du, repo, prev, changenode, match=matchfn)
du.write("\n\n")
elif st == 'iter':
- for args in du.hunk[rev]:
- ui.write(*args)
+ if count == limit: break
+ if du.hunk[rev]:
+ count += 1
+ for args in du.hunk[rev]:
+ ui.write(*args)
def manifest(ui, repo, rev=None):
"""output the latest or given revision of the project manifest
@@ -1664,6 +1731,8 @@
Show changesets not found in the specified destination repo or the
default push repo. These are the changesets that would be pushed
if a push was requested.
+
+ See pull for valid source format details.
"""
dest = ui.expandpath(dest, repo.root)
other = hg.repository(ui, dest)
@@ -1681,7 +1750,7 @@
dodiff(ui, ui, repo, prev, n)
ui.write("\n")
-def parents(ui, repo, rev=None, branch=None):
+def parents(ui, repo, rev=None, branches=None):
"""show the parents of the working dir or revision
Print the working directory's parent revisions.
@@ -1692,7 +1761,7 @@
p = repo.dirstate.parents()
br = None
- if branch is not None:
+ if branches is not None:
br = repo.branchlookup(p)
for n in p:
if n != nullid:
@@ -1767,7 +1836,7 @@
return r
-def push(ui, repo, dest="default-push", force=False, ssh=None, remotecmd=None):
+def push(ui, repo, dest="default-push", **opts):
"""push changes to the specified destination
Push changes from the local repository to the given destination.
@@ -1792,18 +1861,22 @@
dest = ui.expandpath(dest, repo.root)
ui.status('pushing to %s\n' % (dest))
- if ssh:
- ui.setconfig("ui", "ssh", ssh)
- if remotecmd:
- ui.setconfig("ui", "remotecmd", remotecmd)
+ if opts['ssh']:
+ ui.setconfig("ui", "ssh", opts['ssh'])
+ if opts['remotecmd']:
+ ui.setconfig("ui", "remotecmd", opts['remotecmd'])
other = hg.repository(ui, dest)
- r = repo.push(other, force)
+ revs = None
+ if opts['rev']:
+ revs = [repo.lookup(rev) for rev in opts['rev']]
+ r = repo.push(other, opts['force'], revs=revs)
return r
def rawcommit(ui, repo, *flist, **rc):
"""raw commit interface (DEPRECATED)
+ (DEPRECATED)
Lowlevel commit, for use in helper scripts.
This command is not intended to be used by normal users, as it is
@@ -1896,21 +1969,33 @@
should properly record rename files, this information is not yet
fully used by merge, nor fully reported by log.
"""
- errs, copied = docopy(ui, repo, pats, opts)
- names = []
- for abs, rel, exact in copied:
- if ui.verbose or not exact:
- ui.status(_('removing %s\n') % rel)
- names.append(abs)
- repo.remove(names, unlink=True)
+ try:
+ wlock = repo.wlock(0)
+ errs, copied = docopy(ui, repo, pats, opts, wlock)
+ names = []
+ for abs, rel, exact in copied:
+ if ui.verbose or not exact:
+ ui.status(_('removing %s\n') % rel)
+ names.append(abs)
+ repo.remove(names, True, wlock)
+ except lock.LockHeld, inst:
+ ui.warn(_("repository lock held by %s\n") % inst.args[0])
+ errs = 1
return errs
def revert(ui, repo, *pats, **opts):
"""revert modified files or dirs back to their unmodified states
- Revert any uncommitted modifications made to the named files or
- directories. This restores the contents of the affected files to
- an unmodified state.
+ In its default mode, it reverts any uncommitted modifications made
+ to the named files or directories. This restores the contents of
+ the affected files to an unmodified state.
+
+ Using the -r option, it reverts the given files or directories to
+ their state as of an earlier revision. This can be helpful to "roll
+ back" some or all of a change that should not have been committed.
+
+ Revert modifies the working directory. It does not commit any
+ changes, or change the parent of the current working directory.
If a file has been deleted, it is recreated. If the executable
mode of a file was changed, it is reset.
@@ -1925,7 +2010,7 @@
files, choose, anypats = matchpats(repo, pats, opts)
modified, added, removed, deleted, unknown = repo.changes(match=choose)
repo.forget(added)
- repo.undelete(removed + deleted)
+ repo.undelete(removed)
return repo.update(node, False, True, choose, False)
@@ -2022,6 +2107,16 @@
if opts[o]:
ui.setconfig("web", o, opts[o])
+ if opts['daemon'] and not opts['daemon_pipefds']:
+ rfd, wfd = os.pipe()
+ args = sys.argv[:]
+ args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
+ pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
+ args[0], args)
+ os.close(wfd)
+ os.read(rfd, 1)
+ os._exit(0)
+
try:
httpd = hgweb.create_server(repo)
except socket.error, inst:
@@ -2040,6 +2135,25 @@
ui.status(_('listening at http://%s:%d/\n') % (addr, port))
else:
ui.status(_('listening at http://%s/\n') % addr)
+
+ if opts['pid_file']:
+ fp = open(opts['pid_file'], 'w')
+ fp.write(str(os.getpid()))
+ fp.close()
+
+ if opts['daemon_pipefds']:
+ rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
+ os.close(rfd)
+ os.write(wfd, 'y')
+ os.close(wfd)
+ sys.stdout.flush()
+ sys.stderr.flush()
+ fd = os.open(util.nulldev, os.O_RDWR)
+ if fd != 0: os.dup2(fd, 0)
+ if fd != 1: os.dup2(fd, 1)
+ if fd != 2: os.dup2(fd, 2)
+ if fd not in (0, 1, 2): os.close(fd)
+
httpd.serve_forever()
def status(ui, repo, *pats, **opts):
@@ -2164,7 +2278,10 @@
Show the tip revision.
"""
n = repo.changelog.tip()
- show_changeset(ui, repo, changenode=n)
+ br = None
+ if opts['branches']:
+ br = repo.branchlookup([n])
+ show_changeset(ui, repo, changenode=n, brinfo=br)
if opts['patch']:
dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n)
@@ -2283,47 +2400,51 @@
('c', 'changeset', None, _('list the changeset')),
('I', 'include', [], _('include names matching the given patterns')),
('X', 'exclude', [], _('exclude names matching the given patterns'))],
- _('hg annotate [OPTION]... FILE...')),
+ _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
"bundle":
(bundle,
[],
_('hg bundle FILE DEST')),
"cat":
(cat,
- [('I', 'include', [], _('include names matching the given patterns')),
- ('X', 'exclude', [], _('exclude names matching the given patterns')),
- ('o', 'output', '', _('print output to file with formatted name')),
- ('r', 'rev', '', _('print the given revision'))],
+ [('o', 'output', '', _('print output to file with formatted name')),
+ ('r', 'rev', '', _('print the given revision')),
+ ('I', 'include', [], _('include names matching the given patterns')),
+ ('X', 'exclude', [], _('exclude names matching the given patterns'))],
_('hg cat [OPTION]... FILE...')),
"^clone":
(clone,
[('U', 'noupdate', None, _('do not update the new working directory')),
- ('e', 'ssh', '', _('specify ssh command to use')),
- ('', 'pull', None, _('use pull protocol to copy metadata')),
('r', 'rev', [],
_('a changeset you would like to have after cloning')),
+ ('', 'pull', None, _('use pull protocol to copy metadata')),
+ ('e', 'ssh', '', _('specify ssh command to use')),
('', 'remotecmd', '',
_('specify hg command to run on the remote side'))],
_('hg clone [OPTION]... SOURCE [DEST]')),
"^commit|ci":
(commit,
[('A', 'addremove', None, _('run addremove during commit')),
- ('I', 'include', [], _('include names matching the given patterns')),
- ('X', 'exclude', [], _('exclude names matching the given patterns')),
('m', 'message', '', _('use <text> as commit message')),
('l', 'logfile', '', _('read the commit message from <file>')),
('d', 'date', '', _('record datecode as commit date')),
- ('u', 'user', '', _('record user as commiter'))],
+ ('u', 'user', '', _('record user as commiter')),
+ ('I', 'include', [], _('include names matching the given patterns')),
+ ('X', 'exclude', [], _('exclude names matching the given patterns'))],
_('hg commit [OPTION]... [FILE]...')),
"copy|cp":
(copy,
- [('I', 'include', [], _('include names matching the given patterns')),
- ('X', 'exclude', [], _('exclude names matching the given patterns')),
- ('A', 'after', None, _('record a copy that has already occurred')),
+ [('A', 'after', None, _('record a copy that has already occurred')),
('f', 'force', None,
- _('forcibly copy over an existing managed file'))],
+ _('forcibly copy over an existing managed file')),
+ ('I', 'include', [], _('include names matching the given patterns')),
+ ('X', 'exclude', [], _('exclude names matching the given patterns'))],
_('hg copy [OPTION]... [SOURCE]... DEST')),
"debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
+ "debugrebuildstate":
+ (debugrebuildstate,
+ [('r', 'rev', '', _('revision to rebuild to'))],
+ _('debugrebuildstate [-r REV] [REV]')),
"debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
"debugconfig": (debugconfig, [], _('debugconfig')),
"debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
@@ -2341,20 +2462,19 @@
(diff,
[('r', 'rev', [], _('revision')),
('a', 'text', None, _('treat all files as text')),
- ('I', 'include', [], _('include names matching the given patterns')),
('p', 'show-function', None,
_('show which function each change is in')),
('w', 'ignore-all-space', None,
_('ignore white space when comparing lines')),
- ('X', 'exclude', [],
- _('exclude names matching the given patterns'))],
+ ('I', 'include', [], _('include names matching the given patterns')),
+ ('X', 'exclude', [], _('exclude names matching the given patterns'))],
_('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
"^export":
(export,
[('o', 'output', '', _('print output to file with formatted name')),
('a', 'text', None, _('treat all files as text')),
('', 'switch-parent', None, _('diff against the second parent'))],
- _('hg export [-a] [-o OUTFILE] REV...')),
+ _('hg export [-a] [-o OUTFILESPEC] REV...')),
"forget":
(forget,
[('I', 'include', [], _('include names matching the given patterns')),
@@ -2363,19 +2483,19 @@
"grep":
(grep,
[('0', 'print0', None, _('end fields with NUL')),
- ('I', 'include', [], _('include names matching the given patterns')),
- ('X', 'exclude', [], _('exclude names matching the given patterns')),
('', 'all', None, _('print all revisions that match')),
('i', 'ignore-case', None, _('ignore case when matching')),
('l', 'files-with-matches', None,
_('print only filenames and revs that match')),
('n', 'line-number', None, _('print matching line numbers')),
('r', 'rev', [], _('search in given revision range')),
- ('u', 'user', None, _('print user who committed change'))],
+ ('u', 'user', None, _('print user who committed change')),
+ ('I', 'include', [], _('include names matching the given patterns')),
+ ('X', 'exclude', [], _('exclude names matching the given patterns'))],
_('hg grep [OPTION]... PATTERN [FILE]...')),
"heads":
(heads,
- [('b', 'branches', None, _('find branch info')),
+ [('b', 'branches', None, _('show branches')),
('r', 'rev', '', _('show only heads which are descendants of rev'))],
_('hg heads [-b] [-r <rev>]')),
"help": (help_, [], _('hg help [COMMAND]')),
@@ -2385,10 +2505,10 @@
[('p', 'strip', 1,
_('directory strip option for patch. This has the same\n') +
_('meaning as the corresponding patch option')),
+ ('b', 'base', '', _('base path')),
('f', 'force', None,
- _('skip check for outstanding uncommitted changes')),
- ('b', 'base', '', _('base path'))],
- _('hg import [-f] [-p NUM] [-b BASE] PATCH...')),
+ _('skip check for outstanding uncommitted changes'))],
+ _('hg import [-p NUM] [-b BASE] [-f] PATCH...')),
"incoming|in": (incoming,
[('M', 'no-merges', None, _('do not show merges')),
('p', 'patch', None, _('show patch')),
@@ -2407,24 +2527,25 @@
_('hg locate [OPTION]... [PATTERN]...')),
"^log|history":
(log,
- [('I', 'include', [], _('include names matching the given patterns')),
- ('X', 'exclude', [], _('exclude names matching the given patterns')),
- ('b', 'branch', None, _('show branches')),
+ [('b', 'branches', None, _('show branches')),
('k', 'keyword', [], _('search for a keyword')),
+ ('l', 'limit', '', _('limit number of changes displayed')),
('r', 'rev', [], _('show the specified revision or range')),
('M', 'no-merges', None, _('do not show merges')),
('m', 'only-merges', None, _('show only merges')),
- ('p', 'patch', None, _('show patch'))],
- _('hg log [-I] [-X] [-r REV]... [-p] [FILE]')),
+ ('p', 'patch', None, _('show patch')),
+ ('I', 'include', [], _('include names matching the given patterns')),
+ ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+ _('hg log [OPTION]... [FILE]')),
"manifest": (manifest, [], _('hg manifest [REV]')),
"outgoing|out": (outgoing,
[('M', 'no-merges', None, _('do not show merges')),
('p', 'patch', None, _('show patch')),
('n', 'newest-first', None, _('show newest record first'))],
- _('hg outgoing [-p] [-n] [-M] [DEST]')),
+ _('hg outgoing [-M] [-p] [-n] [DEST]')),
"^parents":
(parents,
- [('b', 'branch', None, _('show branches'))],
+ [('b', 'branches', None, _('show branches'))],
_('hg parents [-b] [REV]')),
"paths": (paths, [], _('hg paths [NAME]')),
"^pull":
@@ -2435,15 +2556,16 @@
('r', 'rev', [], _('a specific revision you would like to pull')),
('', 'remotecmd', '',
_('specify hg command to run on the remote side'))],
- _('hg pull [-u] [-e FILE] [-r rev] [--remotecmd FILE] [SOURCE]')),
+ _('hg pull [-u] [-e FILE] [-r REV]... [--remotecmd FILE] [SOURCE]')),
"^push":
(push,
[('f', 'force', None, _('force push')),
('e', 'ssh', '', _('specify ssh command to use')),
+ ('r', 'rev', [], _('a specific revision you would like to push')),
('', 'remotecmd', '',
_('specify hg command to run on the remote side'))],
- _('hg push [-f] [-e FILE] [--remotecmd FILE] [DEST]')),
- "rawcommit":
+ _('hg push [-f] [-e FILE] [-r REV]... [--remotecmd FILE] [DEST]')),
+ "debugrawcommit|rawcommit":
(rawcommit,
[('p', 'parent', [], _('parent')),
('d', 'date', '', _('date code')),
@@ -2451,7 +2573,7 @@
('F', 'files', '', _('file list')),
('m', 'message', '', _('commit message')),
('l', 'logfile', '', _('commit message file'))],
- _('hg rawcommit [OPTION]... [FILE]...')),
+ _('hg debugrawcommit [OPTION]... [FILE]...')),
"recover": (recover, [], _('hg recover')),
"^remove|rm":
(remove,
@@ -2460,27 +2582,30 @@
_('hg remove [OPTION]... FILE...')),
"rename|mv":
(rename,
- [('I', 'include', [], _('include names matching the given patterns')),
- ('X', 'exclude', [], _('exclude names matching the given patterns')),
- ('A', 'after', None, _('record a rename that has already occurred')),
+ [('A', 'after', None, _('record a rename that has already occurred')),
('f', 'force', None,
- _('forcibly copy over an existing managed file'))],
+ _('forcibly copy over an existing managed file')),
+ ('I', 'include', [], _('include names matching the given patterns')),
+ ('X', 'exclude', [], _('exclude names matching the given patterns'))],
_('hg rename [OPTION]... [SOURCE]... DEST')),
"^revert":
(revert,
- [('I', 'include', [], _('include names matching the given patterns')),
- ('X', 'exclude', [], _('exclude names matching the given patterns')),
- ('r', 'rev', '', _('revision to revert to'))],
- _('hg revert [-n] [-r REV] [NAME]...')),
+ [('r', 'rev', '', _('revision to revert to')),
+ ('I', 'include', [], _('include names matching the given patterns')),
+ ('X', 'exclude', [], _('exclude names matching the given patterns'))],
+ _('hg revert [-r REV] [NAME]...')),
"root": (root, [], _('hg root')),
"^serve":
(serve,
[('A', 'accesslog', '', _('name of access log file to write to')),
+ ('d', 'daemon', None, _('run server in background')),
+ ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
('E', 'errorlog', '', _('name of error log file to write to')),
('p', 'port', 0, _('port to use (default: 8000)')),
('a', 'address', '', _('address to use')),
('n', 'name', '',
_('name to show in web pages (default: working dir)')),
+ ('', 'pid-file', '', _('name of file to write process ID to')),
('', 'stdio', None, _('for remote clients')),
('t', 'templates', '', _('web templates to use')),
('', 'style', '', _('template style to use')),
@@ -2506,9 +2631,13 @@
('d', 'date', '', _('record datecode as commit date')),
('u', 'user', '', _('record user as commiter')),
('r', 'rev', '', _('revision to tag'))],
- _('hg tag [-r REV] [OPTION]... NAME')),
+ _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
"tags": (tags, [], _('hg tags')),
- "tip": (tip, [('p', 'patch', None, _('show patch'))], _('hg tip')),
+ "tip":
+ (tip,
+ [('b', 'branches', None, _('show branches')),
+ ('p', 'patch', None, _('show patch'))],
+ _('hg tip [-b] [-p]')),
"unbundle":
(unbundle,
[('u', 'update', None,
@@ -2734,13 +2863,22 @@
if options['profile']:
import hotshot, hotshot.stats
prof = hotshot.Profile("hg.prof")
- r = prof.runcall(d)
- prof.close()
- stats = hotshot.stats.load("hg.prof")
- stats.strip_dirs()
- stats.sort_stats('time', 'calls')
- stats.print_stats(40)
- return r
+ try:
+ try:
+ return prof.runcall(d)
+ except:
+ try:
+ u.warn(_('exception raised - generating profile '
+ 'anyway\n'))
+ except:
+ pass
+ raise
+ finally:
+ prof.close()
+ stats = hotshot.stats.load("hg.prof")
+ stats.strip_dirs()
+ stats.sort_stats('time', 'calls')
+ stats.print_stats(40)
else:
return d()
except:
--- a/mercurial/demandload.py Mon Mar 06 17:58:53 2006 +0100
+++ b/mercurial/demandload.py Mon Mar 06 18:00:44 2006 +0100
@@ -1,15 +1,125 @@
-def demandload(scope, modules):
- class d:
- def __getattr__(self, name):
- mod = self.__dict__["mod"]
- scope = self.__dict__["scope"]
- scope[mod] = __import__(mod, scope, scope, [])
- return getattr(scope[mod], name)
+'''Demand load modules when used, not when imported.'''
+
+__author__ = '''Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>.
+This software may be used and distributed according to the terms
+of the GNU General Public License, incorporated herein by reference.'''
+
+# this is based on matt's original demandload module. it is a
+# complete rewrite. some time, we may need to support syntax of
+# "import foo as bar".
+
+class _importer(object):
+ '''import a module. it is not imported until needed, and is
+ imported at most once per scope.'''
+
+ def __init__(self, scope, modname, fromlist):
+ '''scope is context (globals() or locals()) in which import
+ should be made. modname is name of module to import.
+ fromlist is list of modules for "from foo import ..."
+ emulation.'''
+
+ self.scope = scope
+ self.modname = modname
+ self.fromlist = fromlist
+ self.mod = None
+
+ def module(self):
+ '''import the module if needed, and return.'''
+ if self.mod is None:
+ self.mod = __import__(self.modname, self.scope, self.scope,
+ self.fromlist)
+ del self.modname, self.fromlist
+ return self.mod
+
+class _replacer(object):
+ '''placeholder for a demand loaded module. demandload puts this in
+ a target scope. when an attribute of this object is looked up,
+ this object is replaced in the target scope with the actual
+ module.
+
+ we use __getattribute__ to avoid namespace clashes between
+ placeholder object and real module.'''
+
+ def __init__(self, importer, target):
+ self.importer = importer
+ self.target = target
+ # consider case where we do this:
+ # demandload(globals(), 'foo.bar foo.quux')
+ # foo will already exist in target scope when we get to
+ # foo.quux. so we remember that we will need to demandload
+ # quux into foo's scope when we really load it.
+ self.later = []
+
+ def module(self):
+ return object.__getattribute__(self, 'importer').module()
+
+ def __getattribute__(self, key):
+ '''look up an attribute in a module and return it. replace the
+ name of the module in the caller\'s dict with the actual
+ module.'''
- for m in modules.split():
- dl = d()
- dl.mod = m
- dl.scope = scope
- scope[m] = dl
+ module = object.__getattribute__(self, 'module')()
+ target = object.__getattribute__(self, 'target')
+ importer = object.__getattribute__(self, 'importer')
+ later = object.__getattribute__(self, 'later')
+
+ if later:
+ demandload(module.__dict__, ' '.join(later))
+
+ importer.scope[target] = module
+
+ return getattr(module, key)
+
+class _replacer_from(_replacer):
+ '''placeholder for a demand loaded module. used for "from foo
+ import ..." emulation. semantics of this are different than
+ regular import, so different implementation needed.'''
+
+ def module(self):
+ importer = object.__getattribute__(self, 'importer')
+ target = object.__getattribute__(self, 'target')
+
+ return getattr(importer.module(), target)
+
+def demandload(scope, modules):
+ '''import modules into scope when each is first used.
+
+ scope should be the value of globals() in the module calling this
+ function, or locals() in the calling function.
+
+ modules is a string listing module names, separated by white
+ space. names are handled like this:
+ foo import foo
+ foo bar import foo, bar
+ foo.bar import foo.bar
+ foo:bar from foo import bar
+ foo:bar,quux from foo import bar, quux
+ foo.bar:quux from foo.bar import quux'''
+ for mod in modules.split():
+ col = mod.find(':')
+ if col >= 0:
+ fromlist = mod[col+1:].split(',')
+ mod = mod[:col]
+ else:
+ fromlist = []
+ importer = _importer(scope, mod, fromlist)
+ if fromlist:
+ for name in fromlist:
+ scope[name] = _replacer_from(importer, name)
+ else:
+ dot = mod.find('.')
+ if dot >= 0:
+ basemod = mod[:dot]
+ val = scope.get(basemod)
+ # if base module has already been demandload()ed,
+ # remember to load this submodule into its namespace
+ # when needed.
+ if isinstance(val, _replacer):
+ later = object.__getattribute__(val, 'later')
+ later.append(mod[dot+1:])
+ continue
+ else:
+ basemod = mod
+ scope[basemod] = _replacer(importer, basemod)
--- a/mercurial/dirstate.py Mon Mar 06 17:58:53 2006 +0100
+++ b/mercurial/dirstate.py Mon Mar 06 18:00:44 2006 +0100
@@ -197,9 +197,24 @@
def clear(self):
self.map = {}
+ self.copies = {}
+ self.markdirty()
+
+ def rebuild(self, parent, files):
+ self.clear()
+ umask = os.umask(0)
+ os.umask(umask)
+ for f, mode in files:
+ if mode:
+ self.map[f] = ('n', ~umask, -1, 0)
+ else:
+ self.map[f] = ('n', ~umask & 0666, -1, 0)
+ self.pl = (parent, nullid)
self.markdirty()
def write(self):
+ if not self.dirty:
+ return
st = self.opener("dirstate", "w", atomic=True)
st.write("".join(self.pl))
for f, e in self.map.items():
@@ -270,11 +285,11 @@
elif not dc:
dc = self.filterfiles(files)
- def statmatch(file, stat):
- file = util.pconvert(file)
- if file not in dc and self.ignore(file):
+ def statmatch(file_, stat):
+ file_ = util.pconvert(file_)
+ if file_ not in dc and self.ignore(file_):
return False
- return match(file)
+ return match(file_)
return self.walkhelper(files=files, statmatch=statmatch, dc=dc)
@@ -350,9 +365,9 @@
continue
if stat.S_ISDIR(st.st_mode):
cmp1 = (lambda x, y: cmp(x[1], y[1]))
- sorted = [ x for x in findfiles(f) ]
- sorted.sort(cmp1)
- for e in sorted:
+ sorted_ = [ x for x in findfiles(f) ]
+ sorted_.sort(cmp1)
+ for e in sorted_:
yield e
else:
ff = util.normpath(ff)
@@ -380,7 +395,7 @@
for src, fn, st in self.statwalk(files, match):
try:
- type, mode, size, time = self[fn]
+ type_, mode, size, time = self[fn]
except KeyError:
unknown.append(fn)
continue
@@ -399,22 +414,23 @@
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 "nm":
+ if nonexistent and type_ in "nm":
deleted.append(fn)
continue
# check the common case first
- if type == 'n':
+ if type_ == 'n':
if not st:
st = os.stat(fn)
- if size != st.st_size or (mode ^ st.st_mode) & 0100:
+ if size >= 0 and (size != st.st_size
+ or (mode ^ st.st_mode) & 0100):
modified.append(fn)
elif time != st.st_mtime:
lookup.append(fn)
- elif type == 'm':
+ elif type_ == 'm':
modified.append(fn)
- elif type == 'a':
+ elif type_ == 'a':
added.append(fn)
- elif type == 'r':
+ elif type_ == 'r':
removed.append(fn)
return (lookup, modified, added, removed, deleted, unknown)
--- a/mercurial/hgweb.py Mon Mar 06 17:58:53 2006 +0100
+++ b/mercurial/hgweb.py Mon Mar 06 18:00:44 2006 +0100
@@ -7,6 +7,7 @@
# of the GNU General Public License, incorporated herein by reference.
import os, cgi, sys, urllib
+import mimetypes
from demandload import demandload
demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser")
demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util")
@@ -18,7 +19,11 @@
for f in "templates", "../templates":
p = os.path.join(os.path.dirname(__file__), f)
if os.path.isdir(p):
- return p
+ return os.path.normpath(p)
+ else:
+ # executable version (py2exe) doesn't support __file__
+ if hasattr(sys, 'frozen'):
+ return os.path.join(sys.prefix, "templates")
def age(x):
def plural(t, c):
@@ -71,6 +76,30 @@
else:
return os.stat(hg_path).st_mtime
+def staticfile(directory, fname):
+ """return a file inside directory with guessed content-type header
+
+ fname always uses '/' as directory separator and isn't allowed to
+ contain unusual path components.
+ Content-type is guessed using the mimetypes module.
+ Return an empty string if fname is illegal or file not found.
+
+ """
+ parts = fname.split('/')
+ path = directory
+ for part in parts:
+ if (part in ('', os.curdir, os.pardir) or
+ os.sep in part or os.altsep is not None and os.altsep in part):
+ return ""
+ path = os.path.join(path, part)
+ try:
+ os.stat(path)
+ ct = mimetypes.guess_type(path)[0] or "text/plain"
+ return "Content-type: %s\n\n%s" % (ct, file(path).read())
+ except (TypeError, OSError):
+ # illegal fname or unreadable file
+ return ""
+
class hgrequest(object):
def __init__(self, inp=None, out=None, env=None):
self.inp = inp or sys.stdin
@@ -660,9 +689,10 @@
i = self.repo.tagslist()
i.reverse()
- def entries(**map):
+ def entries(notip=False, **map):
parity = 0
for k,n in i:
+ if notip and k == "tip": continue
yield {"parity": parity,
"tag": k,
"tagmanifest": hex(cl.read(n)[0]),
@@ -672,7 +702,8 @@
yield self.t("tags",
manifest=hex(mf),
- entries=entries)
+ entries=lambda **x: entries(False, **x),
+ entriesnotip=lambda **x: entries(True, **x))
def summary(self):
cl = self.repo.changelog
@@ -843,6 +874,7 @@
'ca': [('cmd', ['archive']), ('node', None)],
'tags': [('cmd', ['tags'])],
'tip': [('cmd', ['changeset']), ('node', ['tip'])],
+ 'static': [('cmd', ['static']), ('file', None)]
}
for k in shortcuts.iterkeys():
@@ -858,6 +890,7 @@
expand_form(req.form)
t = self.repo.ui.config("web", "templates", templatepath())
+ static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
m = os.path.join(t, "map")
style = self.repo.ui.config("web", "style", "")
if req.form.has_key('style'):
@@ -981,6 +1014,11 @@
req.write(self.t("error"))
+ elif req.form['cmd'][0] == 'static':
+ fname = req.form['file'][0]
+ req.write(staticfile(static, fname)
+ or self.t("error", error="%r not found" % fname))
+
else:
req.write(self.t("error"))
@@ -1075,17 +1113,27 @@
class hgwebdir(object):
def __init__(self, config):
def cleannames(items):
- return [(name.strip('/'), path) for name, path in items]
+ return [(name.strip(os.sep), path) for name, path in items]
- if type(config) == type([]):
+ if isinstance(config, (list, tuple)):
self.repos = cleannames(config)
- elif type(config) == type({}):
+ elif isinstance(config, dict):
self.repos = cleannames(config.items())
self.repos.sort()
else:
cp = ConfigParser.SafeConfigParser()
cp.read(config)
- self.repos = cleannames(cp.items("paths"))
+ self.repos = []
+ if cp.has_section('paths'):
+ self.repos.extend(cleannames(cp.items('paths')))
+ if cp.has_section('collections'):
+ for prefix, root in cp.items('collections'):
+ for path in util.walkrepos(root):
+ repo = os.path.normpath(path)
+ name = repo
+ if name.startswith(prefix):
+ name = name[len(prefix):]
+ self.repos.append((name.lstrip(os.sep), repo))
self.repos.sort()
def run(self, req=hgrequest()):
@@ -1142,4 +1190,10 @@
else:
req.write(tmpl("notfound", repo=virtual))
else:
- req.write(tmpl("index", entries=entries))
+ if req.form.has_key('static'):
+ static = os.path.join(templatepath(), "static")
+ fname = req.form['static'][0]
+ req.write(staticfile(static, fname)
+ or tmpl("error", error="%r not found" % fname))
+ else:
+ req.write(tmpl("index", entries=entries))
--- a/mercurial/localrepo.py Mon Mar 06 17:58:53 2006 +0100
+++ b/mercurial/localrepo.py Mon Mar 06 18:00:44 2006 +0100
@@ -13,6 +13,8 @@
demandload(globals(), "re lock transaction tempfile stat mdiff errno")
class localrepository(object):
+ def __del__(self):
+ self.transhandle = None
def __init__(self, ui, path=None, create=0):
if not path:
p = os.getcwd()
@@ -37,6 +39,7 @@
self.nodetagscache = None
self.encodepats = None
self.decodepats = None
+ self.transhandle = None
if create:
os.mkdir(self.path)
@@ -215,6 +218,10 @@
return self.wopener(filename, 'w').write(data)
def transaction(self):
+ tr = self.transhandle
+ if tr != None and tr.running():
+ return tr.nest()
+
# save dirstate for undo
try:
ds = self.opener("dirstate").read()
@@ -222,21 +229,18 @@
ds = ""
self.opener("journal.dirstate", "w").write(ds)
- def after():
- util.rename(self.join("journal"), self.join("undo"))
- util.rename(self.join("journal.dirstate"),
- self.join("undo.dirstate"))
-
- return transaction.transaction(self.ui.warn, self.opener,
- self.join("journal"), after)
+ tr = transaction.transaction(self.ui.warn, self.opener,
+ self.join("journal"),
+ aftertrans(self.path))
+ self.transhandle = tr
+ return tr
def recover(self):
- lock = self.lock()
+ l = self.lock()
if os.path.exists(self.join("journal")):
self.ui.status(_("rolling back interrupted transaction\n"))
transaction.rollback(self.opener, self.join("journal"))
- self.manifest = manifest.manifest(self.opener)
- self.changelog = changelog.changelog(self.opener)
+ self.reload()
return True
else:
self.ui.warn(_("no interrupted transaction available\n"))
@@ -245,34 +249,51 @@
def undo(self, wlock=None):
if not wlock:
wlock = self.wlock()
- lock = self.lock()
+ l = self.lock()
if os.path.exists(self.join("undo")):
self.ui.status(_("rolling back last transaction\n"))
transaction.rollback(self.opener, self.join("undo"))
util.rename(self.join("undo.dirstate"), self.join("dirstate"))
- self.dirstate.read()
+ self.reload()
+ self.wreload()
else:
self.ui.warn(_("no undo information available\n"))
- def lock(self, wait=1):
+ def wreload(self):
+ self.dirstate.read()
+
+ def reload(self):
+ self.changelog.load()
+ self.manifest.load()
+ self.tagscache = None
+ self.nodetagscache = None
+
+ def do_lock(self, lockname, wait, releasefn=None, acquirefn=None):
try:
- return lock.lock(self.join("lock"), 0)
- except lock.LockHeld, inst:
- if wait:
- self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
- return lock.lock(self.join("lock"), wait)
- raise inst
-
- def wlock(self, wait=1):
- try:
- wlock = lock.lock(self.join("wlock"), 0, self.dirstate.write)
+ l = lock.lock(self.join(lockname), 0, releasefn)
except lock.LockHeld, inst:
if not wait:
raise inst
self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0])
- wlock = lock.lock(self.join("wlock"), wait, self.dirstate.write)
- self.dirstate.read()
- return wlock
+ try:
+ # default to 600 seconds timeout
+ l = lock.lock(self.join(lockname),
+ int(self.ui.config("ui", "timeout") or 600),
+ releasefn)
+ except lock.LockHeld, inst:
+ raise util.Abort(_("timeout while waiting for "
+ "lock held by %s") % inst.args[0])
+ if acquirefn:
+ acquirefn()
+ return l
+
+ def lock(self, wait=1):
+ return self.do_lock("lock", wait, acquirefn=self.reload)
+
+ def wlock(self, wait=1):
+ return self.do_lock("wlock", wait,
+ self.dirstate.write,
+ self.wreload)
def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
"determine whether a new filenode is needed"
@@ -311,7 +332,7 @@
if not wlock:
wlock = self.wlock()
- lock = self.lock()
+ l = self.lock()
tr = self.transaction()
mm = m1.copy()
mfm = mf1.copy()
@@ -350,7 +371,7 @@
self.dirstate.setparents(n, nullid)
def commit(self, files=None, text="", user=None, date=None,
- match=util.always, force=False, wlock=None):
+ match=util.always, force=False, lock=None, wlock=None):
commit = []
remove = []
changed = []
@@ -388,7 +409,8 @@
if not wlock:
wlock = self.wlock()
- lock = self.lock()
+ if not lock:
+ lock = self.lock()
tr = self.transaction()
# check in files
@@ -503,12 +525,18 @@
del mf[fn]
return mf
+ if node1:
+ # read the manifest from node1 before the manifest from node2,
+ # so that we'll hit the manifest cache if we're going through
+ # all the revisions in parent->child order.
+ mf1 = mfmatches(node1)
+
# are we comparing the working directory?
if not node2:
if not wlock:
try:
wlock = self.wlock(wait=0)
- except lock.LockHeld:
+ except lock.LockException:
wlock = None
lookup, modified, added, removed, deleted, unknown = (
self.dirstate.changes(files, match))
@@ -541,8 +569,6 @@
# flush lists from dirstate before comparing manifests
modified, added = [], []
- mf1 = mfmatches(node1)
-
for fn in mf2:
if mf1.has_key(fn):
if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
@@ -597,7 +623,6 @@
if os.path.exists(p):
self.ui.warn(_("%s still exists!\n") % f)
elif self.dirstate.state(f) == 'a':
- self.ui.warn(_("%s never committed!\n") % f)
self.dirstate.forget([f])
elif f not in self.dirstate:
self.ui.warn(_("%s not tracked!\n") % f)
@@ -932,7 +957,7 @@
return subset
def pull(self, remote, heads=None):
- lock = self.lock()
+ l = self.lock()
# if we have an empty repo, fetch everything
if self.changelog.tip() == nullid:
@@ -951,7 +976,7 @@
cg = remote.changegroupsubset(fetch, heads, 'pull')
return self.addchangegroup(cg)
- def push(self, remote, force=False):
+ def push(self, remote, force=False, revs=None):
lock = remote.lock()
base = {}
@@ -963,17 +988,25 @@
return 1
update = self.findoutgoing(remote, base)
- if not update:
+ if revs is not None:
+ msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
+ else:
+ bases, heads = update, self.changelog.heads()
+
+ if not bases:
self.ui.status(_("no changes found\n"))
return 1
elif not force:
- if len(heads) < len(self.changelog.heads()):
+ if len(bases) < len(heads):
self.ui.warn(_("abort: push creates new remote branches!\n"))
self.ui.status(_("(did you forget to merge?"
" use push -f to force)\n"))
return 1
- cg = self.changegroup(update, 'push')
+ if revs is None:
+ cg = self.changegroup(update, 'push')
+ else:
+ cg = self.changegroupsubset(update, revs, 'push')
return remote.addchangegroup(cg)
def changegroupsubset(self, bases, heads, source):
@@ -1646,6 +1679,7 @@
remove.sort()
for f in remove:
self.ui.note(_("removing %s\n") % f)
+ util.audit_path(f)
try:
util.unlink(self.wjoin(f))
except OSError, inst:
@@ -1852,3 +1886,13 @@
if errors[0]:
self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
return 1
+
+# used to avoid circular references so destructors work
+def aftertrans(base):
+ p = base
+ def a():
+ util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
+ util.rename(os.path.join(p, "journal.dirstate"),
+ os.path.join(p, "undo.dirstate"))
+ return a
+
--- a/mercurial/lock.py Mon Mar 06 17:58:53 2006 +0100
+++ b/mercurial/lock.py Mon Mar 06 18:00:44 2006 +0100
@@ -5,17 +5,21 @@
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
-import os, time
-import util
+from demandload import *
+demandload(globals(), 'errno os time util')
-class LockHeld(Exception):
+class LockException(Exception):
+ pass
+class LockHeld(LockException):
+ pass
+class LockUnavailable(LockException):
pass
class lock(object):
- def __init__(self, file, wait=1, releasefn=None):
+ def __init__(self, file, timeout=-1, releasefn=None):
self.f = file
self.held = 0
- self.wait = wait
+ self.timeout = timeout
self.releasefn = releasefn
self.lock()
@@ -23,13 +27,16 @@
self.release()
def lock(self):
+ timeout = self.timeout
while 1:
try:
self.trylock()
return 1
except LockHeld, inst:
- if self.wait:
+ if timeout != 0:
time.sleep(1)
+ if timeout > 0:
+ timeout -= 1
continue
raise inst
@@ -38,8 +45,11 @@
try:
util.makelock(str(pid), self.f)
self.held = 1
- except (OSError, IOError):
- raise LockHeld(util.readlock(self.f))
+ except (OSError, IOError), why:
+ if why.errno == errno.EEXIST:
+ raise LockHeld(util.readlock(self.f))
+ else:
+ raise LockUnavailable(why)
def release(self):
if self.held:
--- a/mercurial/mpatch.c Mon Mar 06 17:58:53 2006 +0100
+++ b/mercurial/mpatch.c Mon Mar 06 18:00:44 2006 +0100
@@ -66,7 +66,7 @@
a = NULL;
} else
a->head = a->tail = a->base;
- return a;
+ return a;
}
if (!PyErr_Occurred())
PyErr_NoMemory();
--- a/mercurial/revlog.py Mon Mar 06 17:58:53 2006 +0100
+++ b/mercurial/revlog.py Mon Mar 06 18:00:44 2006 +0100
@@ -13,7 +13,7 @@
from node import *
from i18n import gettext as _
from demandload import demandload
-demandload(globals(), "binascii errno heapq mdiff sha struct zlib")
+demandload(globals(), "binascii errno heapq mdiff os sha struct zlib")
def hash(text, p1, p2):
"""generate a hash from the given text and its parent hashes
@@ -187,15 +187,33 @@
self.indexfile = indexfile
self.datafile = datafile
self.opener = opener
+
+ self.indexstat = None
self.cache = None
self.chunkcache = None
+ self.load()
+ def load(self):
try:
- i = self.opener(self.indexfile).read()
+ f = self.opener(self.indexfile)
except IOError, inst:
if inst.errno != errno.ENOENT:
raise
i = ""
+ else:
+ try:
+ st = os.fstat(f.fileno())
+ except AttributeError, inst:
+ st = None
+ else:
+ oldst = self.indexstat
+ if (oldst and st.st_dev == oldst.st_dev
+ and st.st_ino == oldst.st_ino
+ and st.st_mtime == oldst.st_mtime
+ and st.st_ctime == oldst.st_ctime):
+ return
+ self.indexstat = st
+ i = f.read()
if i and i[:4] != "\0\0\0\0":
raise RevlogError(_("incompatible revlog signature on %s") %
@@ -624,12 +642,10 @@
# we store negative distances because heap returns smallest member
h = [(-dist[node], node)]
seen = {}
- earliest = self.count()
while h:
d, n = heapq.heappop(h)
if n not in seen:
seen[n] = 1
- r = self.rev(n)
yield (-d, n)
for p in self.parents(n):
heapq.heappush(h, (-dist[p], p))
@@ -690,11 +706,6 @@
p = self.parents(self.node(revs[0]))[0]
revs.insert(0, self.rev(p))
- # helper to reconstruct intermediate versions
- def construct(text, base, rev):
- bins = [self.chunk(r) for r in xrange(base + 1, rev + 1)]
- return mdiff.patches(text, bins)
-
# build deltas
for d in xrange(0, len(revs) - 1):
a, b = revs[d], revs[d + 1]
@@ -738,10 +749,10 @@
base = prev = -1
start = end = measure = 0
if r:
- start = self.start(self.base(t))
+ base = self.base(t)
+ start = self.start(base)
end = self.end(t)
- measure = self.length(self.base(t))
- base = self.base(t)
+ measure = self.length(base)
prev = self.tip()
transaction.add(self.datafile, end)
@@ -793,14 +804,15 @@
raise RevlogError(_("consistency error adding group"))
measure = len(text)
else:
- e = (end, len(cdelta), self.base(t), link, p1, p2, node)
+ e = (end, len(cdelta), base, link, p1, p2, node)
self.index.append(e)
self.nodemap[node] = r
dfh.write(cdelta)
ifh.write(struct.pack(indexformat, *e))
t, r, chain, prev = r, r + 1, node, node
- start = self.start(self.base(t))
+ base = self.base(t)
+ start = self.start(base)
end = self.end(t)
dfh.close()
--- a/mercurial/statichttprepo.py Mon Mar 06 17:58:53 2006 +0100
+++ b/mercurial/statichttprepo.py Mon Mar 06 18:00:44 2006 +0100
@@ -15,8 +15,10 @@
def read(self, size=None):
try:
return httprangereader.httprangereader.read(self, size)
+ except urllib2.HTTPError, inst:
+ raise IOError(None, inst)
except urllib2.URLError, inst:
- raise IOError(None, str(inst))
+ raise IOError(None, inst.reason[1])
def opener(base):
"""return a function that opens files over http"""
--- a/mercurial/transaction.py Mon Mar 06 17:58:53 2006 +0100
+++ b/mercurial/transaction.py Mon Mar 06 18:00:44 2006 +0100
@@ -22,6 +22,7 @@
if os.path.exists(journal):
raise AssertionError(_("journal already exists - run hg recover"))
+ self.count = 1
self.report = report
self.opener = opener
self.after = after
@@ -46,7 +47,17 @@
self.file.write("%s\0%d\n" % (file, offset))
self.file.flush()
+ def nest(self):
+ self.count += 1
+ return self
+
+ def running(self):
+ return self.count > 0
+
def close(self):
+ self.count -= 1
+ if self.count != 0:
+ return
self.file.close()
self.entries = []
if self.after:
--- a/mercurial/util.py Mon Mar 06 17:58:53 2006 +0100
+++ b/mercurial/util.py Mon Mar 06 18:00:44 2006 +0100
@@ -179,7 +179,7 @@
if root == os.sep:
rootsep = os.sep
else:
- rootsep = root + os.sep
+ rootsep = root + os.sep
name = myname
if not name.startswith(os.sep):
name = os.path.join(root, cwd, name)
@@ -363,7 +363,14 @@
else:
shutil.copy(src, dst)
-def opener(base):
+def audit_path(path):
+ """Abort if path contains dangerous components"""
+ parts = os.path.normcase(path).split(os.sep)
+ if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
+ or os.pardir in parts):
+ raise Abort(_("path contains illegal component: %s\n") % path)
+
+def opener(base, audit=True):
"""
return a function that opens files relative to base
@@ -371,6 +378,7 @@
remote file access from higher level code.
"""
p = base
+ audit_p = audit
def mktempcopy(name):
d, fn = os.path.split(name)
@@ -401,6 +409,8 @@
self.close()
def o(path, mode="r", text=False, atomic=False):
+ if audit_p:
+ audit_path(path)
f = os.path.join(p, path)
if not text:
@@ -690,3 +700,16 @@
(time.strftime(format, time.gmtime(float(t) - tz)),
-tz / 3600,
((-tz % 3600) / 60)))
+
+def walkrepos(path):
+ '''yield every hg repository under path, recursively.'''
+ def errhandler(err):
+ if err.filename == path:
+ raise err
+
+ for root, dirs, files in os.walk(path, onerror=errhandler):
+ for d in dirs:
+ if d == '.hg':
+ yield root
+ dirs[:] = []
+ break
--- a/setup.py Mon Mar 06 17:58:53 2006 +0100
+++ b/setup.py Mon Mar 06 18:00:44 2006 +0100
@@ -89,7 +89,9 @@
data_files=[('mercurial/templates',
['templates/map'] +
glob.glob('templates/map-*') +
- glob.glob('templates/*.tmpl'))],
+ glob.glob('templates/*.tmpl')),
+ ('mercurial/templates/static',
+ glob.glob('templates/static/*'))],
cmdclass=cmdclass,
scripts=['hg', 'hgmerge'],
options=dict(bdist_mpkg=dict(zipdist=True,
--- a/templates/error-gitweb.tmpl Mon Mar 06 17:58:53 2006 +0100
+++ b/templates/error-gitweb.tmpl Mon Mar 06 18:00:44 2006 +0100
@@ -1,11 +1,21 @@
#header#
+<title>#repo|escape#: Error</title>
+<link rel="alternate" type="application/rss+xml"
+ href="?cmd=changelog;style=rss" title="RSS feed for #repo|escape#">
+</head>
+<body>
+
+<div class="page_header">
+<a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="?cmd=summary;style=gitweb">#repo|escape#</a> / error
+</div>
+
<div class="page_nav">
-<a href="?cmd=summary;style=gitweb">summary</a> | <a href="?cmd=changelog;style=gitweb">log</a> | <a href="?cmd=tags;style=gitweb">tags</a> | <a href="?cmd=manifest;manifest=#manifest#;path=/;style=gitweb">manifest</a><br/>
+<a href="?cmd=summary;style=gitweb">summary</a> | <a href="?cmd=changelog;style=gitweb">changelog</a> | <a href="?cmd=tags;style=gitweb">tags</a> | <a href="?cmd=manifest;manifest=#manifest#;path=/;style=gitweb">manifest</a><br/>
</div>
<div>
<br/>
-<i>Error parsing query string</i><br/>
+<i>An error occured while processing your request</i><br/>
<br/>
</div>
--- a/templates/header-gitweb.tmpl Mon Mar 06 17:58:53 2006 +0100
+++ b/templates/header-gitweb.tmpl Mon Mar 06 18:00:44 2006 +0100
@@ -4,56 +4,8 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
<head>
+<link rel="icon" href="?static=hgicon.png" type="image/png">
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<meta name="robots" content="index, nofollow"/>
-<style type="text/css">
-body { font-family: sans-serif; font-size: 12px; margin:0px; border:solid #d9d8d1; border-width:1px; margin:10px; }
-a { color:#0000cc; }
-a:hover, a:visited, a:active { color:#880000; }
-div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
-div.page_header a:visited { color:#0000cc; }
-div.page_header a:hover { color:#880000; }
-div.page_nav { padding:8px; }
-div.page_nav a:visited { color:#0000cc; }
-div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
-div.page_footer { height:17px; padding:4px 8px; background-color: #d9d8d1; }
-div.page_footer_text { float:left; color:#555555; font-style:italic; }
-div.page_body { padding:8px; }
-div.title, a.title {
- display:block; padding:6px 8px;
- font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
-}
-a.title:hover { background-color: #d9d8d1; }
-div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
-div.log_body { padding:8px 8px 8px 150px; }
-span.age { position:relative; float:left; width:142px; font-style:italic; }
-div.log_link {
- padding:0px 8px;
- font-size:10px; font-family:sans-serif; font-style:normal;
- position:relative; float:left; width:136px;
-}
-div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
-a.list { text-decoration:none; color:#000000; }
-a.list:hover { text-decoration:underline; color:#880000; }
-table { padding:8px 4px; }
-th { padding:2px 5px; font-size:12px; text-align:left; }
-tr.light:hover, .parity0:hover { background-color:#edece6; }
-tr.dark, .parity1 { background-color:#f6f6f0; }
-tr.dark:hover, .parity1:hover { background-color:#edece6; }
-td { padding:2px 5px; font-size:12px; vertical-align:top; }
-td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
-div.pre { font-family:monospace; font-size:12px; white-space:pre; }
-div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
-div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
-div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
-.linenr { color:#999999; text-decoration:none }
-a.rss_logo {
- float:right; padding:3px 0px; width:35px; line-height:10px;
- border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
- color:#ffffff; background-color:#ff6600;
- font-weight:bold; font-family:sans-serif; font-size:10px;
- text-align:center; text-decoration:none;
-}
-a.rss_logo:hover { background-color:#ee5500; }
-</style>
+<style type="text/css">/*<![CDATA[*/ @import "?static=style-gitweb.css"; /*]]>*/</style>
--- a/templates/header.tmpl Mon Mar 06 17:58:53 2006 +0100
+++ b/templates/header.tmpl Mon Mar 06 18:00:44 2006 +0100
@@ -3,78 +3,6 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
+<link rel="icon" href="?static=hgicon.png" type="image/png">
<meta name="robots" content="index, nofollow" />
-<style type="text/css">
-<!--
-a { text-decoration:none; }
-.parity0 { background-color: #dddddd; }
-.parity1 { background-color: #eeeeee; }
-.lineno { width: 60px; color: #aaaaaa; font-size: smaller;
- text-align: right; padding-right:1em; }
-.plusline { color: green; }
-.minusline { color: red; }
-.atline { color: purple; }
-.annotate { font-size: smaller; text-align: right; padding-right: 1em; }
-.buttons a {
- background-color: #666666;
- padding: 2pt;
- color: white;
- font-family: sans;
- font-weight: bold;
-}
-.navigate a {
- background-color: #ccc;
- padding: 2pt;
- font-family: sans;
- color: black;
-}
-
-.metatag {
- background-color: #888888;
- color: white;
- text-align: right;
-}
-
-/* Common */
-pre { margin: 0; }
-
-.logo {
- background-color: #333;
- padding: 4pt;
- margin: 8pt 0 8pt 8pt;
- font-family: sans;
- font-size: 60%;
- color: white;
- float: right;
- clear: right;
- text-align: left;
-}
-
-.logo a {
- font-weight: bold;
- font-size: 150%;
- color: #999;
-}
-
-/* Changelog entries */
-.changelogEntry { width: 100%; }
-.changelogEntry th { font-weight: normal; text-align: right; vertical-align: top; }
-.changelogEntry th.age, .changelogEntry th.firstline { font-weight: bold; }
-.changelogEntry th.firstline { text-align: left; width: inherit; }
-
-/* Tag entries */
-#tagEntries { list-style: none; margin: 0; padding: 0; }
-#tagEntries .tagEntry { list-style: none; margin: 0; padding: 0; }
-#tagEntries .tagEntry span.node { font-family: monospace; }
-
-/* Changeset entry */
-#changesetEntry { }
-#changesetEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
-#changesetEntry th.files, #changesetEntry th.description { vertical-align: top; }
-
-/* File diff view */
-#filediffEntry { }
-#filediffEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
-
--->
-</style>
+<style type="text/css">/*<![CDATA[*/ @import "?static=style.css"; /*]]>*/</style>
--- a/templates/map-rss Mon Mar 06 17:58:53 2006 +0100
+++ b/templates/map-rss Mon Mar 06 18:00:44 2006 +0100
@@ -4,3 +4,5 @@
changelogentry = changelogentry-rss.tmpl
filelog = filelog-rss.tmpl
filelogentry = filelogentry-rss.tmpl
+tags = tags-rss.tmpl
+tagentry = tagentry-rss.tmpl
Binary file templates/static/hgicon.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/static/style-gitweb.css Mon Mar 06 18:00:44 2006 +0100
@@ -0,0 +1,48 @@
+body { font-family: sans-serif; font-size: 12px; margin:0px; border:solid #d9d8d1; border-width:1px; margin:10px; }
+a { color:#0000cc; }
+a:hover, a:visited, a:active { color:#880000; }
+div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
+div.page_header a:visited { color:#0000cc; }
+div.page_header a:hover { color:#880000; }
+div.page_nav { padding:8px; }
+div.page_nav a:visited { color:#0000cc; }
+div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
+div.page_footer { height:17px; padding:4px 8px; background-color: #d9d8d1; }
+div.page_footer_text { float:left; color:#555555; font-style:italic; }
+div.page_body { padding:8px; }
+div.title, a.title {
+ display:block; padding:6px 8px;
+ font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
+}
+a.title:hover { background-color: #d9d8d1; }
+div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
+div.log_body { padding:8px 8px 8px 150px; }
+span.age { position:relative; float:left; width:142px; font-style:italic; }
+div.log_link {
+ padding:0px 8px;
+ font-size:10px; font-family:sans-serif; font-style:normal;
+ position:relative; float:left; width:136px;
+}
+div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
+a.list { text-decoration:none; color:#000000; }
+a.list:hover { text-decoration:underline; color:#880000; }
+table { padding:8px 4px; }
+th { padding:2px 5px; font-size:12px; text-align:left; }
+tr.light:hover, .parity0:hover { background-color:#edece6; }
+tr.dark, .parity1 { background-color:#f6f6f0; }
+tr.dark:hover, .parity1:hover { background-color:#edece6; }
+td { padding:2px 5px; font-size:12px; vertical-align:top; }
+td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
+div.pre { font-family:monospace; font-size:12px; white-space:pre; }
+div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
+div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
+div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
+.linenr { color:#999999; text-decoration:none }
+a.rss_logo {
+ float:right; padding:3px 0px; width:35px; line-height:10px;
+ border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
+ color:#ffffff; background-color:#ff6600;
+ font-weight:bold; font-family:sans-serif; font-size:10px;
+ text-align:center; text-decoration:none;
+}
+a.rss_logo:hover { background-color:#ee5500; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/static/style.css Mon Mar 06 18:00:44 2006 +0100
@@ -0,0 +1,70 @@
+a { text-decoration:none; }
+.parity0 { background-color: #dddddd; }
+.parity1 { background-color: #eeeeee; }
+.lineno { width: 60px; color: #aaaaaa; font-size: smaller;
+ text-align: right; padding-right:1em; }
+.plusline { color: green; }
+.minusline { color: red; }
+.atline { color: purple; }
+.annotate { font-size: smaller; text-align: right; padding-right: 1em; }
+.buttons a {
+ background-color: #666666;
+ padding: 2pt;
+ color: white;
+ font-family: sans;
+ font-weight: bold;
+}
+.navigate a {
+ background-color: #ccc;
+ padding: 2pt;
+ font-family: sans;
+ color: black;
+}
+
+.metatag {
+ background-color: #888888;
+ color: white;
+ text-align: right;
+}
+
+/* Common */
+pre { margin: 0; }
+
+.logo {
+ background-color: #333;
+ padding: 4pt;
+ margin: 8pt 0 8pt 8pt;
+ font-family: sans;
+ font-size: 60%;
+ color: white;
+ float: right;
+ clear: right;
+ text-align: left;
+}
+
+.logo a {
+ font-weight: bold;
+ font-size: 150%;
+ color: #999;
+}
+
+/* Changelog entries */
+.changelogEntry { width: 100%; }
+.changelogEntry th { font-weight: normal; text-align: right; vertical-align: top; }
+.changelogEntry th.age, .changelogEntry th.firstline { font-weight: bold; }
+.changelogEntry th.firstline { text-align: left; width: inherit; }
+
+/* Tag entries */
+#tagEntries { list-style: none; margin: 0; padding: 0; }
+#tagEntries .tagEntry { list-style: none; margin: 0; padding: 0; }
+#tagEntries .tagEntry span.node { font-family: monospace; }
+
+/* Changeset entry */
+#changesetEntry { }
+#changesetEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
+#changesetEntry th.files, #changesetEntry th.description { vertical-align: top; }
+
+/* File diff view */
+#filediffEntry { }
+#filediffEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; }
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/tagentry-rss.tmpl Mon Mar 06 18:00:44 2006 +0100
@@ -0,0 +1,6 @@
+<item>
+ <title>#tag|escape#</title>
+ <link>#url#?cs=#node|short#</link>
+ <description><![CDATA[#tag|strip|escape|addbreaks#]]></description>
+ <pubDate>#date|rfc822date#</pubDate>
+</item>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/tags-rss.tmpl Mon Mar 06 18:00:44 2006 +0100
@@ -0,0 +1,6 @@
+#header#
+ <title>#repo|escape#: tags </title>
+ <description>#repo|escape# tag history</description>
+ #entriesnotip%tagentry#
+ </channel>
+</rss>
--- a/templates/tags.tmpl Mon Mar 06 17:58:53 2006 +0100
+++ b/templates/tags.tmpl Mon Mar 06 18:00:44 2006 +0100
@@ -1,11 +1,14 @@
#header#
<title>#repo|escape#: tags</title>
+<link rel="alternate" type="application/rss+xml"
+ href="?cmd=tags;style=rss" title="RSS feed for #repo|escape#: tags">
</head>
<body>
<div class="buttons">
<a href="?cl=tip">changelog</a>
<a href="?mf=#manifest|short#;path=/">manifest</a>
+<a type="application/rss+xml" href="?cmd=tags;style=rss">rss</a>
</div>
<h2>tags:</h2>
--- a/tests/test-archive Mon Mar 06 17:58:53 2006 +0100
+++ b/tests/test-archive Mon Mar 06 18:00:44 2006 +0100
@@ -18,8 +18,7 @@
echo "allowzip = true" >> .hg/hgrc
echo "allowgz = true" >> .hg/hgrc
echo "allowbz2 = true" >> .hg/hgrc
-hg serve -p 20059 > /dev/null &
-sleep 1 # wait for server to be started
+hg serve -p 20059 -d --pid-file=hg.pid
TIP=`hg id -v | cut -f1 -d' '`
QTIP=`hg id -q`
@@ -35,5 +34,5 @@
http_proxy= python getarchive.py "$TIP" zip > archive.zip
unzip -t archive.zip | sed "s/$QTIP/TIP/"
-kill $!
+kill `cat hg.pid`
sleep 1 # wait for server to scream and die
--- a/tests/test-archive.out Mon Mar 06 17:58:53 2006 +0100
+++ b/tests/test-archive.out Mon Mar 06 18:00:44 2006 +0100
@@ -12,4 +12,3 @@
testing: test-archive-TIP/baz/bletch OK
testing: test-archive-TIP/foo OK
No errors detected in compressed data of archive.zip.
-killed!
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-clone-pull-corruption Mon Mar 06 18:00:44 2006 +0100
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Corrupt an hg repo with a pull started during an aborted commit
+#
+
+# Create two repos, so that one of them can pull from the other one.
+hg init source
+cd source
+touch foo
+hg add foo
+hg ci -m 'add foo'
+hg clone . ../corrupted
+echo >> foo
+hg ci -m 'change foo'
+
+# Add a hook to wait 5 seconds and then abort the commit
+cd ../corrupted
+echo '[hooks]' >> .hg/hgrc
+echo 'pretxncommit = sleep 5; exit 1' >> .hg/hgrc
+
+# start a commit...
+touch bar
+hg add bar
+hg ci -m 'add bar' &
+
+# ... and start a pull while the commit is still running
+sleep 1
+hg pull ../source 2>/dev/null
+
+# see what happened
+wait
+hg verify
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-clone-pull-corruption.out Mon Mar 06 18:00:44 2006 +0100
@@ -0,0 +1,15 @@
+pulling from ../source
+abort: pretxncommit hook exited with status 1
+transaction abort!
+rollback completed
+searching for 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)
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 2 changesets, 2 total revisions
--- a/tests/test-commit.out Mon Mar 06 17:58:53 2006 +0100
+++ b/tests/test-commit.out Mon Mar 06 18:00:44 2006 +0100
@@ -1,5 +1,3 @@
-transaction abort!
-rollback completed
abort: impossible time zone offset: 4444444
transaction abort!
rollback completed
@@ -13,4 +11,6 @@
transaction abort!
rollback completed
abort: date exceeds 32 bits: 111111111111
+transaction abort!
+rollback completed
abort: No such file or directory: .../test/bar
--- a/tests/test-help.out Mon Mar 06 17:58:53 2006 +0100
+++ b/tests/test-help.out Mon Mar 06 18:00:44 2006 +0100
@@ -64,7 +64,6 @@
paths show definition of symbolic path names
pull pull changes from the specified source
push push changes to the specified destination
- rawcommit raw commit interface (DEPRECATED)
recover roll back an interrupted transaction
remove remove the specified files on the next commit
rename rename files; equivalent of copy + remove
@@ -106,7 +105,6 @@
paths show definition of symbolic path names
pull pull changes from the specified source
push push changes to the specified destination
- rawcommit raw commit interface (DEPRECATED)
recover roll back an interrupted transaction
remove remove the specified files on the next commit
rename rename files; equivalent of copy + remove
@@ -173,9 +171,9 @@
-r --rev revision
-a --text treat all files as text
- -I --include include names matching the given patterns
-p --show-function show which function each change is in
-w --ignore-all-space ignore white space when comparing lines
+ -I --include include names matching the given patterns
-X --exclude exclude names matching the given patterns
hg status [OPTION]... [FILE]...
--- a/tests/test-merge3.out Mon Mar 06 17:58:53 2006 +0100
+++ b/tests/test-merge3.out Mon Mar 06 18:00:44 2006 +0100
@@ -1,3 +1,2 @@
removing b
-b never committed!
nothing changed
--- a/tests/test-pull Mon Mar 06 17:58:53 2006 +0100
+++ b/tests/test-pull Mon Mar 06 18:00:44 2006 +0100
@@ -7,8 +7,7 @@
hg addremove
hg commit -m 1
hg verify
-hg serve -p 20059 > /dev/null &
-sleep 1 # wait for server to be started
+hg serve -p 20059 -d --pid-file=hg.pid
cd ..
http_proxy= hg clone http://localhost:20059/ copy
@@ -19,4 +18,4 @@
hg manifest
hg pull
-kill $!
+kill `cat ../test/hg.pid`
--- a/tests/test-pull-permission Mon Mar 06 17:58:53 2006 +0100
+++ b/tests/test-pull-permission Mon Mar 06 18:00:44 2006 +0100
@@ -12,9 +12,8 @@
cd ..
hg clone a b
+
+chmod +w a/.hg # let test clean up
+
cd b
hg verify
-
-cd ..
-
-chmod +w a/.hg # let test clean up
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-pull-pull-corruption Mon Mar 06 18:00:44 2006 +0100
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Corrupt an hg repo with two pulls.
+#
+
+# create one repo with a long history
+hg init source1
+cd source1
+touch foo
+hg add foo
+for i in 1 2 3 4 5 6 7 8 9 10; do
+ echo $i >> foo
+ hg ci -m $i
+done
+cd ..
+
+# create one repo with a shorter history
+hg clone -r 0 source1 source2
+cd source2
+echo a >> foo
+hg ci -m a
+cd ..
+
+# create a third repo to pull both other repos into it
+hg init corrupted
+cd corrupted
+# use a hook to make the second pull start while the first one is still running
+echo '[hooks]' >> .hg/hgrc
+echo 'prechangegroup = sleep 5' >> .hg/hgrc
+
+# start a pull...
+hg pull ../source1 &
+
+# ... and start another pull before the first one has finished
+sleep 1
+hg pull ../source2 2>/dev/null
+
+# see the result
+wait
+hg verify
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-pull-pull-corruption.out Mon Mar 06 18:00:44 2006 +0100
@@ -0,0 +1,24 @@
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+pulling from ../source2
+pulling from ../source1
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 10 changesets with 10 changes to 1 files
+(run 'hg update' to get a working copy)
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files (+1 heads)
+(run 'hg update' to get a working copy)
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 11 changesets, 11 total revisions
--- a/tests/test-pull.out Mon Mar 06 17:58:53 2006 +0100
+++ b/tests/test-pull.out Mon Mar 06 18:00:44 2006 +0100
@@ -19,4 +19,3 @@
pulling from http://localhost:20059/
searching for changes
no changes found
-killed!
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-push-r Mon Mar 06 18:00:44 2006 +0100
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+hg init test
+cd test
+cat >>afile <<EOF
+0
+EOF
+hg add afile
+hg commit -m "0.0"
+cat >>afile <<EOF
+1
+EOF
+hg commit -m "0.1"
+cat >>afile <<EOF
+2
+EOF
+hg commit -m "0.2"
+cat >>afile <<EOF
+3
+EOF
+hg commit -m "0.3"
+hg update -C 0
+cat >>afile <<EOF
+1
+EOF
+hg commit -m "1.1"
+cat >>afile <<EOF
+2
+EOF
+hg commit -m "1.2"
+cat >fred <<EOF
+a line
+EOF
+cat >>afile <<EOF
+3
+EOF
+hg add fred
+hg commit -m "1.3"
+hg mv afile adifferentfile
+hg commit -m "1.3m"
+hg update -C 3
+hg mv afile anotherfile
+hg commit -m "0.3m"
+hg debugindex .hg/data/afile.i
+hg debugindex .hg/data/adifferentfile.i
+hg debugindex .hg/data/anotherfile.i
+hg debugindex .hg/data/fred.i
+hg debugindex .hg/00manifest.i
+hg verify
+cd ..
+for i in 0 1 2 3 4 5 6 7 8; do
+ mkdir test-"$i"
+ hg --cwd test-"$i" init
+ hg -R test push -r "$i" test-"$i"
+ cd test-"$i"
+ hg verify
+ cd ..
+done
+cd test-8
+hg pull ../test-7
+hg verify
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-push-r.out Mon Mar 06 18:00:44 2006 +0100
@@ -0,0 +1,135 @@
+ rev offset length base linkrev nodeid p1 p2
+ 0 0 3 0 0 362fef284ce2 000000000000 000000000000
+ 1 3 5 1 1 125144f7e028 362fef284ce2 000000000000
+ 2 8 7 2 2 4c982badb186 125144f7e028 000000000000
+ 3 15 9 3 3 19b1fc555737 4c982badb186 000000000000
+ rev offset length base linkrev nodeid p1 p2
+ 0 0 75 0 7 905359268f77 000000000000 000000000000
+ rev offset length base linkrev nodeid p1 p2
+ 0 0 75 0 8 905359268f77 000000000000 000000000000
+ rev offset length base linkrev nodeid p1 p2
+ 0 0 8 0 6 12ab3bcc5ea4 000000000000 000000000000
+ rev offset length base linkrev nodeid p1 p2
+ 0 0 48 0 0 43eadb1d2d06 000000000000 000000000000
+ 1 48 48 1 1 8b89697eba2c 43eadb1d2d06 000000000000
+ 2 96 48 2 2 626a32663c2f 8b89697eba2c 000000000000
+ 3 144 48 3 3 f54c32f13478 626a32663c2f 000000000000
+ 4 192 58 3 6 de68e904d169 626a32663c2f 000000000000
+ 5 250 68 3 7 3b45cc2ab868 de68e904d169 000000000000
+ 6 318 54 6 8 24d86153a002 f54c32f13478 000000000000
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+4 files, 9 changesets, 7 total revisions
+pushing to test-0
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 1 changesets, 1 total revisions
+pushing to test-1
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 2 changesets with 2 changes to 1 files
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 2 changesets, 2 total revisions
+pushing to test-2
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 3 changesets with 3 changes to 1 files
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 3 changesets, 3 total revisions
+pushing to test-3
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 4 changesets with 4 changes to 1 files
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 4 changesets, 4 total revisions
+pushing to test-4
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 2 changesets with 2 changes to 1 files
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 2 changesets, 2 total revisions
+pushing to test-5
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 3 changesets with 3 changes to 1 files
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+1 files, 3 changesets, 3 total revisions
+pushing to test-6
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 4 changesets with 5 changes to 2 files
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+2 files, 4 changesets, 5 total revisions
+pushing to test-7
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 5 changesets with 6 changes to 3 files
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+3 files, 5 changesets, 6 total revisions
+pushing to test-8
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 5 changesets with 5 changes to 2 files
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+2 files, 5 changesets, 5 total revisions
+pulling from ../test-7
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 4 changesets with 2 changes to 3 files (+1 heads)
+(run 'hg update' to get a working copy)
+checking changesets
+checking manifests
+crosschecking files in changesets and manifests
+checking files
+4 files, 9 changesets, 7 total revisions
--- a/tests/test-rename Mon Mar 06 17:58:53 2006 +0100
+++ b/tests/test-rename Mon Mar 06 18:00:44 2006 +0100
@@ -158,3 +158,24 @@
hg rename d1 d3
hg status
hg update -C
+
+echo "# transitive rename"
+hg rename d1/b d1/bb
+hg rename d1/bb d1/bc
+hg status
+hg update -C
+
+echo "# transitive rename --after"
+hg rename d1/b d1/bb
+mv d1/bb d1/bc
+hg rename --after d1/bb d1/bc
+hg status
+hg update -C
+
+echo "# idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)"
+hg rename d1/b d1/bb
+echo "some stuff added to d1/bb" >> d1/bb
+hg rename d1/bb d1/b
+hg status
+hg debugstate | grep copy
+hg update -C
--- a/tests/test-rename.out Mon Mar 06 17:58:53 2006 +0100
+++ b/tests/test-rename.out Mon Mar 06 18:00:44 2006 +0100
@@ -246,3 +246,11 @@
R d1/b
R d1/ba
R d1/d11/a1
+# transitive rename
+A d1/bc
+R d1/b
+# transitive rename --after
+A d1/bc
+R d1/b
+# idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)
+M d1/b
--- a/tests/test-static-http.out Mon Mar 06 17:58:53 2006 +0100
+++ b/tests/test-static-http.out Mon Mar 06 18:00:44 2006 +0100
@@ -1,5 +1,5 @@
255
-abort: <urlopen error (, 'Connection refused')>
+abort: Connection refused
ls: copy: No such file or directory
changeset: 0:61c9426e69fe
tag: tip