merge with crew.

This commit is contained in:
Vadim Gelfer 2006-05-08 08:04:46 -07:00
commit dd7442a4f4
25 changed files with 539 additions and 305 deletions

View File

@ -2,7 +2,7 @@ include hg
recursive-include mercurial *.py
include hgweb.cgi hgwebdir.cgi
include hgeditor rewrite-log
include tests/README tests/run-tests tests/md5sum.py tests/test-*[a-z0-9] tests/*.out
include tests/README tests/coverage.py tests/run-tests.py tests/md5sum.py tests/test-*[a-z0-9] tests/*.out
prune tests/*.err
include *.txt
include templates/map templates/map-*[a-z0-9]

View File

@ -16,10 +16,10 @@ dist: tests doc
TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py sdist --force-manifest
tests:
cd tests && ./run-tests
cd tests && $(PYTHON) run-tests.py
test-%:
cd tests && ./run-tests $@
cd tests && $(PYTHON) run-tests.py $@
doc:
$(MAKE) -C doc

24
README
View File

@ -11,13 +11,16 @@ Setting up Mercurial:
$ tar xvzf mercurial-<ver>.tar.gz
$ cd mercurial-<ver>
When installing, change python to python2.3 or python2.4 if 2.2 is the
default on your system.
To install system-wide:
$ python setup.py install # change python to python2.3 if 2.2 is default
$ python setup.py install --force
To install in your home directory (~/bin and ~/lib, actually), run:
$ python2.3 setup.py install --home=~
$ python setup.py install --home=${HOME} --force
$ export PYTHONPATH=${HOME}/lib/python # (or lib64/ on some systems)
$ export PATH=${HOME}/bin:$PATH # add these to your .bashrc
@ -30,10 +33,12 @@ Setting up Mercurial:
Setting up a Mercurial project:
$ cd project/
$ hg init # creates .hg
$ hg addremove # add all unknown files and remove all missing files
$ hg commit # commit all changes, edit changelog entry
$ hg init project # creates project directory
$ cd project
# copy files in, edit them
$ hg add # add all unknown files
$ hg remove --after # remove deleted files
$ hg commit # commit all changes, edit changelog entry
Mercurial will look for a file named .hgignore in the root of your
repository which contains a set of regular expressions to ignore in
@ -47,7 +52,7 @@ Branching and merging:
$ hg commit
$ cd ../linux
$ hg pull ../linux-work # pull changesets from linux-work
$ hg update -m # merge the new tip from linux-work into
$ hg merge # merge the new tip from linux-work into
# our working directory
$ hg commit # commit the result of the merge
@ -55,8 +60,7 @@ Importing patches:
Fast:
$ patch < ../p/foo.patch
$ hg addremove
$ hg commit
$ hg commit -A
Faster:
$ patch < ../p/foo.patch
@ -87,7 +91,7 @@ Network support:
# merge changes from a remote machine
bar$ hg pull http://foo/
bar$ hg update -m # merge changes into your working directory
bar$ hg merge # merge changes into your working directory
# Set up a CGI server on your webserver
foo$ cp hgweb.cgi ~/public_html/hg/index.cgi

View File

@ -14,7 +14,7 @@
</head>
<body>
<h1>Mercurial version 0.8 for Windows</h1>
<h1>Mercurial version 0.8.1 for Windows</h1>
<p>Welcome to Mercurial for Windows!</p>
@ -117,7 +117,7 @@ hg
href="http://www.serpentine.com/blog">Bryan
O'Sullivan</a>.</p>
<p>Mercurial is Copyright 2005 Matt Mackall and others. See the
<p>Mercurial is Copyright 2005, 2006 Matt Mackall and others. See the
<tt>Contributors.txt</tt> file for a list of contributors.</p>
<p>Mercurial is free software; you can redistribute it and/or

View File

@ -2,9 +2,9 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
[Setup]
AppCopyright=Copyright 2005 Matt Mackall and others
AppCopyright=Copyright 2005, 2006 Matt Mackall and others
AppName=Mercurial
AppVerName=Mercurial version 0.8
AppVerName=Mercurial version 0.8.1
InfoAfterFile=contrib/win32/postinstall.txt
LicenseFile=COPYING
ShowLanguageDialog=yes
@ -14,12 +14,12 @@ AppSupportURL=http://www.selenic.com/mercurial
AppUpdatesURL=http://www.selenic.com/mercurial
AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3}
AppContact=mercurial@selenic.com
OutputBaseFilename=Mercurial-0.8
OutputBaseFilename=Mercurial-0.8.1
DefaultDirName={sd}\Mercurial
SourceDir=C:\hg\hg-release
VersionInfoVersion=0.8
VersionInfoVersion=0.8.1
VersionInfoDescription=Mercurial distributed SCM
VersionInfoCopyright=Copyright 2005 Matt Mackall and others
VersionInfoCopyright=Copyright 2005, 2006 Matt Mackall and others
VersionInfoCompany=Matt Mackall and others
InternalCompressLevel=max
SolidCompression=true

View File

@ -8,6 +8,22 @@ file that comes with this package.
Release Notes
-------------
2006-04-07 v0.8.1
* Major changes from 0.8 to 0.8.1:
- new extensions:
mq (manage a queue of patches, like quilt only better)
email (send changes as series of email patches)
- new command: merge (replaces "update -m")
- improved commands: log (--limit option added), pull/push ("-r" works
on specific revisions), revert (rewritten, much better)
- comprehensive hook support
- output templating added, supporting e.g. GNU changelog style
- Windows, Mac OS X: prebuilt binary packages, better support
- many reliability, performance, and memory usage improvements
2006-01-29 v0.8
* Upgrade notes:

View File

@ -180,7 +180,7 @@ FILES
-----
.hgignore::
This file contains regular expressions (one per line) that describe file
names that should be ignored by hg.
names that should be ignored by hg. For details, see hgignore(5).
.hgtags::
This file contains changeset hash values and text tag names (one of each
@ -200,7 +200,7 @@ when you find them.
SEE ALSO
--------
hgrc(5)
hgignore(5), hgrc(5)
AUTHOR
------

92
doc/hgignore.5.txt Normal file
View File

@ -0,0 +1,92 @@
HGIGNORE(5)
===========
Vadim Gelfer <vadim.gelfer@gmail.com>
NAME
----
hgignore - syntax for Mercurial ignore files
SYNOPSIS
--------
The Mercurial system uses a file called .hgignore in the root
directory of a repository to control its behavior when it finds files
that it is not currently managing.
DESCRIPTION
-----------
Mercurial ignores every unmanaged file that matches any pattern in an
ignore file. The patterns in an ignore file do not apply to files
managed by Mercurial. To control Mercurial's handling of files that
it manages, see the hg(1) man page. Look for the "-I" and "-X"
options.
In addition, a Mercurial configuration file can point to a set of
per-user or global ignore files. See the hgrc(5) man page for details
of how to configure these files. Look for the "ignore" entry in the
"ui" section.
SYNTAX
------
An ignore file is a plain text file consisting of a list of patterns,
with one pattern per line. Empty lines are skipped. The "#"
character is treated as a comment character, and the "\" character is
treated as an escape character.
Mercurial supports several pattern syntaxes. The default syntax used
is Python/Perl-style regular expressions.
To change the syntax used, use a line of the following form:
syntax: NAME
where NAME is one of the following:
regexp::
Regular expression, Python/Perl syntax.
glob::
Shell-style glob.
The chosen syntax stays in effect when parsing all patterns that
follow, until another syntax is selected.
Neither glob nor regexp patterns are rooted. A glob-syntax pattern of
the form "*.c" will match a file ending in ".c" in any directory, and
a regexp pattern of the form "\.c$" will do the same. To root a
regexp pattern, start it with "^".
EXAMPLE
-------
Here is an example ignore file.
# use glob syntax.
syntax: glob
*.elc
*.pyc
*~
.*.swp
# switch to regexp syntax.
syntax: regexp
^\.pc/
AUTHOR
------
Vadim Gelfer <vadim.gelfer@gmail.com>
Mercurial was written by Matt Mackall <mpm@selenic.com>.
SEE ALSO
--------
hg(1), hgrc(5)
COPYING
-------
This manual page is copyright 2006 Vadim Gelfer.
Mercurial is copyright 2005, 2006 Matt Mackall.
Free use of this software is granted under the terms of the GNU General
Public License (GPL).

View File

@ -130,6 +130,24 @@ decode/encode::
# them to the working dir
**.txt = tempfile: unix2dos -n INFILE OUTFILE
email::
Settings for extensions that send email messages.
from;;
Optional. Email address to use in "From" header and SMTP envelope
of outgoing messages.
extensions::
Mercurial has an extension mechanism for adding new features. To
enable an extension, create an entry for it in this section.
If you know that the extension is already in Python's search path,
you can give the name of the module, followed by "=", with nothing
after the "=".
Otherwise, give a name that you choose, followed by "=", followed by
the path to the ".py" file (including the file name extension) that
defines the extension.
hooks::
Commands or Python functions that get automatically executed by
various actions such as starting or finishing a commit. Multiple
@ -240,6 +258,24 @@ http_proxy::
user;;
Optional. User name to authenticate with at the proxy server.
smtp::
Configuration for extensions that need to send email messages.
host;;
Optional. Host name of mail server. Default: "mail".
port;;
Optional. Port to connect to on mail server. Default: 25.
tls;;
Optional. Whether to connect to mail server using TLS. True or
False. Default: False.
username;;
Optional. User name to authenticate to SMTP server with.
If username is specified, password must also be specified.
Default: none.
password;;
Optional. Password to authenticate to SMTP server with.
If username is specified, password must also be specified.
Default: none.
paths::
Assigns symbolic names to repositories. The left side is the
symbolic name, and the right gives the directory or URL that is the
@ -256,7 +292,8 @@ ui::
the same format as a repository-wide .hgignore file. This option
supports hook syntax, so if you want to specify multiple ignore
files, you can do so by setting something like
"ignore.other = ~/.hgignore2".
"ignore.other = ~/.hgignore2". For details of the ignore file
format, see the hgignore(5) man page.
interactive;;
Allow to prompt the user. True or False. Default is True.
logtemplate;;
@ -300,6 +337,10 @@ web::
allowzip;;
Whether to allow .zip downloading of repo revisions. Default is false.
This feature creates temporary files.
baseurl;;
Base URL to use when publishing URLs in other locations, so
third-party tools like email notification hooks can construct URLs.
Example: "http://hgserver/repos/"
description;;
Textual description of the repository's purpose or contents.
Default is "unknown".
@ -330,11 +371,11 @@ Mercurial was written by Matt Mackall <mpm@selenic.com>.
SEE ALSO
--------
hg(1)
hg(1), hgignore(5)
COPYING
-------
This manual page is copyright 2005 Bryan O'Sullivan.
Mercurial is copyright 2005 Matt Mackall.
Mercurial is copyright 2005, 2006 Matt Mackall.
Free use of this software is granted under the terms of the GNU General
Public License (GPL).

View File

@ -30,7 +30,6 @@
# OPTIONAL:
# bzuser = ... # bugzilla user id to record comments with
# db = bugs # database to connect to
# hgweb = http:// # root of hg web site for browsing commits
# notify = ... # command to run to get bugzilla to send mail
# regexp = ... # regexp to match bug ids (must contain one "()" group)
# strip = 0 # number of slashes to strip for url paths
@ -38,11 +37,13 @@
# template = ... # template to use when formatting comments
# timeout = 5 # database connection timeout (seconds)
# user = bugs # user to connect to database as
# [web]
# baseurl = http://hgserver/... # root of hg web site for browsing commits
from mercurial.demandload import *
from mercurial.i18n import gettext as _
from mercurial.node import *
demandload(globals(), 'cStringIO mercurial:templater,util os re time')
demandload(globals(), 'mercurial:templater,util os re time')
MySQLdb = None
@ -237,23 +238,9 @@ class bugzilla(object):
count -= 1
return root
class stringio(object):
'''wrap cStringIO.'''
def __init__(self):
self.fp = cStringIO.StringIO()
def write(self, *args):
for a in args:
self.fp.write(a)
write_header = write
def getvalue(self):
return self.fp.getvalue()
mapfile = self.ui.config('bugzilla', 'style')
tmpl = self.ui.config('bugzilla', 'template')
sio = stringio()
sio = templater.stringio()
t = templater.changeset_templater(self.ui, self.repo, mapfile, sio)
if not mapfile and not tmpl:
tmpl = _('changeset {node|short} in repo {root} refers '
@ -263,7 +250,7 @@ class bugzilla(object):
t.use_template(tmpl)
t.show(changenode=node, changes=changes,
bug=str(bugid),
hgweb=self.ui.config('bugzilla', 'hgweb'),
hgweb=self.ui.config('web', 'baseurl'),
root=self.repo.root,
webroot=webroot(self.repo.root))
self.add_comment(bugid, sio.getvalue(), templater.email(changes[1]))

258
hgext/notify.py Normal file
View File

@ -0,0 +1,258 @@
# notify.py - email notifications for mercurial
#
# 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.
#
# hook extension to email notifications to people when changesets are
# committed to a repo they subscribe to.
#
# default mode is to print messages to stdout, for testing and
# configuring.
#
# to use, configure notify extension and enable in hgrc like this:
#
# [extensions]
# hgext.notify =
#
# [hooks]
# # one email for each incoming changeset
# incoming.notify = python:hgext.notify.hook
# # batch emails when many changesets incoming at one time
# changegroup.notify = python:hgext.notify.hook
#
# [notify]
# # config items go in here
#
# config items:
#
# REQUIRED:
# config = /path/to/file # file containing subscriptions
#
# OPTIONAL:
# test = True # print messages to stdout for testing
# strip = 3 # number of slashes to strip for url paths
# domain = example.com # domain to use if committer missing domain
# style = ... # style file to use when formatting email
# template = ... # template to use when formatting email
# incoming = ... # template to use when run as incoming hook
# changegroup = ... # template when run as changegroup hook
# maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
# maxsubject = 67 # truncate subject line longer than this
# [email]
# from = user@host.com # email address to send as if none given
# [web]
# baseurl = http://hgserver/... # root of hg web site for browsing commits
#
# notify config file has same format as regular hgrc. it has two
# sections so you can express subscriptions in whatever way is handier
# for you.
#
# [usersubs]
# # key is subscriber email, value is ","-separated list of glob patterns
# user@host = pattern
#
# [reposubs]
# # key is glob pattern, value is ","-separated list of subscriber emails
# pattern = user@host
#
# glob patterns are matched against path to repo root.
#
# if you like, you can put notify config file in repo that users can
# push changes to, they can manage their own subscriptions.
from mercurial.demandload import *
from mercurial.i18n import gettext as _
from mercurial.node import *
demandload(globals(), 'email.Parser mercurial:commands,templater,util')
demandload(globals(), 'fnmatch socket time')
# template for single changeset can include email headers.
single_template = '''
Subject: changeset in {webroot}: {desc|firstline|strip}
From: {author}
changeset {node|short} in {root}
details: {baseurl}{webroot}?cmd=changeset;node={node|short}
description:
\t{desc|tabindent|strip}
'''.lstrip()
# template for multiple changesets should not contain email headers,
# because only first set of headers will be used and result will look
# strange.
multiple_template = '''
changeset {node|short} in {root}
details: {baseurl}{webroot}?cmd=changeset;node={node|short}
summary: {desc|firstline}
'''
deftemplates = {
'changegroup': multiple_template,
}
class notifier(object):
'''email notification class.'''
def __init__(self, ui, repo, hooktype):
self.ui = ui
self.ui.readconfig(self.ui.config('notify', 'config'))
self.repo = repo
self.stripcount = int(self.ui.config('notify', 'strip', 0))
self.root = self.strip(self.repo.root)
self.domain = self.ui.config('notify', 'domain')
self.sio = templater.stringio()
self.subs = self.subscribers()
mapfile = self.ui.config('notify', 'style')
template = (self.ui.config('notify', hooktype) or
self.ui.config('notify', 'template'))
self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
self.sio)
if not mapfile and not template:
template = deftemplates.get(hooktype) or single_template
if template:
template = templater.parsestring(template, quoted=False)
self.t.use_template(template)
def strip(self, path):
'''strip leading slashes from local path, turn into web-safe path.'''
path = util.pconvert(path)
count = self.stripcount
while path and count >= 0:
c = path.find('/')
if c == -1:
break
path = path[c+1:]
count -= 1
return path
def fixmail(self, addr):
'''try to clean up email addresses.'''
addr = templater.email(addr.strip())
a = addr.find('@localhost')
if a != -1:
addr = addr[:a]
if '@' not in addr:
return addr + '@' + self.domain
return addr
def subscribers(self):
'''return list of email addresses of subscribers to this repo.'''
subs = {}
for user, pats in self.ui.configitems('usersubs'):
for pat in pats.split(','):
if fnmatch.fnmatch(self.repo.root, pat.strip()):
subs[self.fixmail(user)] = 1
for pat, users in self.ui.configitems('reposubs'):
if fnmatch.fnmatch(self.repo.root, pat):
for user in users.split(','):
subs[self.fixmail(user)] = 1
subs = subs.keys()
subs.sort()
return subs
def url(self, path=None):
return self.ui.config('web', 'baseurl') + (path or self.root)
def node(self, node):
'''format one changeset.'''
self.t.show(changenode=node, changes=self.repo.changelog.read(node),
baseurl=self.ui.config('web', 'baseurl'),
root=self.repo.root,
webroot=self.root)
def send(self, node, count):
'''send message.'''
p = email.Parser.Parser()
self.sio.seek(0)
msg = p.parse(self.sio)
def fix_subject():
'''try to make subject line exist and be useful.'''
subject = msg['Subject']
if not subject:
if count > 1:
subject = _('%s: %d new changesets') % (self.root, count)
else:
changes = self.repo.changelog.read(node)
s = changes[4].lstrip().split('\n', 1)[0].rstrip()
subject = '%s: %s' % (self.root, s)
maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
if maxsubject and len(subject) > maxsubject:
subject = subject[:maxsubject-3] + '...'
del msg['Subject']
msg['Subject'] = subject
def fix_sender():
'''try to make message have proper sender.'''
sender = msg['From']
if not sender:
sender = self.ui.config('email', 'from') or self.ui.username()
if '@' not in sender or '@localhost' in sender:
sender = self.fixmail(sender)
del msg['From']
msg['From'] = sender
fix_subject()
fix_sender()
msg['X-Hg-Notification'] = 'changeset ' + short(node)
if not msg['Message-Id']:
msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
(short(node), int(time.time()),
hash(self.repo.root), socket.getfqdn()))
msgtext = msg.as_string(0)
if self.ui.configbool('notify', 'test', True):
self.ui.write(msgtext)
if not msgtext.endswith('\n'):
self.ui.write('\n')
else:
mail = self.ui.sendmail()
mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
def diff(self, node):
maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
if maxdiff == 0:
return
fp = templater.stringio()
commands.dodiff(fp, self.ui, self.repo, node,
self.repo.changelog.tip())
difflines = fp.getvalue().splitlines(1)
if maxdiff > 0 and len(difflines) > maxdiff:
self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
(len(difflines), maxdiff))
difflines = difflines[:maxdiff]
elif difflines:
self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
self.sio.write(*difflines)
def hook(ui, repo, hooktype, node=None, **kwargs):
'''send email notifications to interested subscribers.
if used as changegroup hook, send one email for all changesets in
changegroup. else send one email per changeset.'''
n = notifier(ui, repo, hooktype)
if not n.subs: return True
node = bin(node)
if hooktype == 'changegroup':
start = repo.changelog.rev(node)
end = repo.changelog.count()
count = end - start
for rev in xrange(start, end):
n.node(repo.changelog.node(rev))
else:
count = 1
n.node(node)
n.diff(node)
n.send(node, count)
return True

View File

@ -31,20 +31,10 @@
# the messages directly. This can be reviewed e.g. with "mutt -R -f mbox",
# and finally sent with "formail -s sendmail -bm -t < mbox".
#
# To configure a default mail host, add a section like this to your
# hgrc file:
#
# [smtp]
# host = my_mail_host
# port = 1025
# tls = yes # or omit if not needed
# username = user # if SMTP authentication required
# password = password # if SMTP authentication required - PLAINTEXT
#
# To configure other defaults, add a section like this to your hgrc
# file:
#
# [patchbomb]
# [email]
# from = My Name <my@email>
# to = recipient1, recipient2, ...
# cc = cc1, cc2, ...
@ -52,7 +42,7 @@
from mercurial.demandload import *
demandload(globals(), '''email.MIMEMultipart email.MIMEText email.Utils
mercurial:commands,hg,ui
os errno popen2 smtplib socket sys tempfile time''')
os errno popen2 socket sys tempfile time''')
from mercurial.i18n import gettext as _
try:
@ -183,11 +173,13 @@ def patchbomb(ui, repo, *revs, **opts):
jumbo.extend(p)
msgs.append(makepatch(p, i + 1, len(patches)))
sender = (opts['from'] or ui.config('patchbomb', 'from') or
sender = (opts['from'] or ui.config('email', 'from') or
ui.config('patchbomb', 'from') or
prompt('From', ui.username()))
def getaddrs(opt, prpt, default = None):
addrs = opts[opt] or (ui.config('patchbomb', opt) or
addrs = opts[opt] or (ui.config('email', opt) or
ui.config('patchbomb', opt) or
prompt(prpt, default = default)).split(',')
return [a.strip() for a in addrs if a.strip()]
to = getaddrs('to', 'To')
@ -223,17 +215,7 @@ def patchbomb(ui, repo, *revs, **opts):
ui.write('\n')
if not opts['test'] and not opts['mbox']:
s = smtplib.SMTP()
s.connect(host = ui.config('smtp', 'host', 'mail'),
port = int(ui.config('smtp', 'port', 25)))
if ui.configbool('smtp', 'tls'):
s.ehlo()
s.starttls()
s.ehlo()
username = ui.config('smtp', 'username')
password = ui.config('smtp', 'password')
if username and password:
s.login(username, password)
mail = ui.sendmail()
parent = None
tz = time.strftime('%z')
sender_addr = email.Utils.parseaddr(sender)[1]
@ -271,9 +253,9 @@ def patchbomb(ui, repo, *revs, **opts):
fp.close()
else:
ui.status('Sending ', m['Subject'], ' ...\n')
s.sendmail(sender, to + cc, m.as_string(0))
mail.sendmail(sender, to + cc, m.as_string(0))
if not opts['test'] and not opts['mbox']:
s.close()
mail.close()
cmdtable = {
'email':

View File

@ -2232,21 +2232,26 @@ def rename(ui, repo, *pats, **opts):
return errs
def revert(ui, repo, *pats, **opts):
"""revert modified files or dirs back to their unmodified states
"""revert modified files or dirs to their states as of some revision
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.
With no revision specified, revert the named files or directories
to the contents they had in the parent of the working directory.
This restores the contents of the affected files to an unmodified
state. If the working directory has two parents, you must
explicitly specify the revision to revert to.
Modified files are saved with a .orig suffix before reverting.
To disable these backups, use --no-backup.
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
Using the -r option, revert the given files or directories to
their contents as of a specific 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.
changes, or change the parent of the working directory. If you
revert to a revision other than the parent of the working
directory, the reverted files will thus appear modified
afterwards.
If a file has been deleted, it is recreated. If the executable
mode of a file was changed, it is reset.
@ -2255,8 +2260,14 @@ def revert(ui, repo, *pats, **opts):
If no arguments are given, all files in the repository are reverted.
"""
parent = repo.dirstate.parents()[0]
node = opts['rev'] and repo.lookup(opts['rev']) or parent
parent, p2 = repo.dirstate.parents()
if opts['rev']:
node = repo.lookup(opts['rev'])
elif p2 != nullid:
raise util.Abort(_('working dir has two parents; '
'you must specify the revision to revert to'))
else:
node = parent
mf = repo.manifest.read(repo.changelog.read(node)[0])
wlock = repo.wlock()

View File

@ -499,3 +499,17 @@ class changeset_templater(object):
inst.args[0]))
except SyntaxError, inst:
raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
class stringio(object):
'''wrap cStringIO for use by changeset_templater.'''
def __init__(self):
self.fp = cStringIO.StringIO()
def write(self, *args):
for a in args:
self.fp.write(a)
write_header = write
def __getattr__(self, key):
return getattr(self.fp, key)

View File

@ -8,7 +8,7 @@
import ConfigParser
from i18n import gettext as _
from demandload import *
demandload(globals(), "errno os re socket sys tempfile util")
demandload(globals(), "errno os re smtplib socket sys tempfile util")
class ui(object):
def __init__(self, verbose=False, debug=False, quiet=False,
@ -242,7 +242,8 @@ class ui(object):
def debug(self, *msg):
if self.debugflag: self.write(*msg)
def edit(self, text, user):
(fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt")
(fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
text=True)
try:
f = os.fdopen(fd, "w")
f.write(text)
@ -264,3 +265,17 @@ class ui(object):
os.unlink(name)
return t
def sendmail(self):
s = smtplib.SMTP()
s.connect(host = self.config('smtp', 'host', 'mail'),
port = int(self.config('smtp', 'port', 25)))
if self.configbool('smtp', 'tls'):
s.ehlo()
s.starttls()
s.ehlo()
username = self.config('smtp', 'username')
password = self.config('smtp', 'password')
if username and password:
s.login(username, password)
return s

View File

@ -231,7 +231,7 @@ def canonpath(root, cwd, myname):
name_st = os.stat(name)
except OSError:
break
if os.path.samestat(name_st, root_st):
if samestat(name_st, root_st):
rel.reverse()
name = os.path.join(*rel)
audit_path(name)
@ -561,6 +561,9 @@ if os.name == 'nt':
makelock = _makelock_file
readlock = _readlock_file
def samestat(s1, s2):
return False
def explain_exit(code):
return _("exited with status %d") % code, code
@ -627,6 +630,7 @@ else:
return path
normpath = os.path.normpath
samestat = os.path.samestat
def makelock(info, pathname):
try:

View File

@ -1,5 +1,5 @@
<div>
<a class="title" href="?cmd=changeset;node=#node#;style=gitweb"><span class="age">#date|age# ago</span>#desc|firstline|escape#</a>
<a class="title" href="?cmd=changeset;node=#node#;style=gitweb"><span class="age">#date|age# ago</span>#desc|strip|firstline|escape#</a>
</div>
<div class="title_text">
<div class="log_link">
@ -8,7 +8,7 @@
<i>#author|obfuscate# [#date|rfc822date#] rev #rev#</i><br/>
</div>
<div class="log_body">
#desc|escape|addbreaks#
#desc|strip|escape|addbreaks#
<br/>
<br/>
</div>

View File

@ -14,7 +14,7 @@
</div>
<div>
<a class="title" href="?cmd=changeset;node=#node#;style=raw">#desc|escape|firstline#</a>
<a class="title" href="?cmd=changeset;node=#node#;style=raw">#desc|strip|escape|firstline#</a>
</div>
<div class="title_text">
<table cellspacing="0">
@ -28,7 +28,7 @@
</table></div>
<div class="title_text">
#desc|escape|addbreaks#
#desc|strip|escape|addbreaks#
</div>
<div class="title_text">

View File

@ -45,6 +45,6 @@ filelogparent = '<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="?cmd
filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>'
filelogchild = '<tr><td align="right">child #rev#:&nbsp;</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>'
shortlog = shortlog-gitweb.tmpl
shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> | <a href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>'
shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> | <a href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">manifest</a></td></tr>'
filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>'
archiveentry = ' | <a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> '

View File

@ -3,7 +3,7 @@ A simple testing framework
To run the tests, do:
cd tests/
./run-tests
python run-tests.py
This finds all scripts in the test directory named test-* and executes
them. The scripts can be either shell scripts or Python. Each test is

View File

@ -1,197 +0,0 @@
#!/bin/sh -e
#
# environment variables:
#
# TEST_COVERAGE - set non-empty if you want to print test coverage report
# COVERAGE_STDLIB - set non-empty to report coverage of standard library
LANG="C"; export LANG
LC_CTYPE="C"; export LC_CTYPE
LC_NUMERIC="C"; export LC_NUMERIC
LC_TIME="C"; export LC_TIME
LC_COLLATE="C"; export LC_COLLATE
LC_MONETARY="C"; export LC_MONETARY
LC_MESSAGES="C"; export LC_MESSAGES
LC_PAPER="C"; export LC_PAPER
LC_NAME="C"; export LC_NAME
LC_ADDRESS="C"; export LC_ADDRESS
LC_TELEPHONE="C"; export LC_TELEPHONE
LC_MEASUREMENT="C"; export LC_MEASUREMENT
LC_IDENTIFICATION="C"; export LC_IDENTIFICATION
LC_ALL=""; export LC_ALL
TZ=GMT; export TZ
HGEDITOR=true; export HGEDITOR
HGMERGE=true; export HGMERGE
HGUSER="test"; export HGUSER
HGRCPATH=""; export HGRCPATH
OS=`uname`
case "$OS" in
HP-UX|SunOS)
DIFFOPTS=
;;
*)
DIFFOPTS=-u
;;
esac
if [ `echo -n HG` = "-n HG" ]
then
ECHO_N=echo
NNL="\c"
else
ECHO_N="echo -n"
NNL=
fi
umask 022
tests=0
failed=0
HGTMP=""
cleanup_exit() {
rm -rf "$HGTMP"
}
# Remove temporary files even if we get interrupted
trap "cleanup_exit" 0 # normal exit
trap "exit 255" 1 2 3 6 15 # HUP INT QUIT ABRT TERM
HGTMP="${TMPDIR-/tmp}/hgtests.$RANDOM.$RANDOM.$RANDOM.$$"
(umask 077 && mkdir "$HGTMP") || {
echo "Could not create temporary directory! Exiting." 1>&2
exit 1
}
TESTDIR="`pwd`"
export TESTDIR
INST="$HGTMP/install"
PYTHONDIR="$INST/lib/python"
cd ..
if ${PYTHON-python} setup.py clean --all install --force --home="$INST" \
--install-lib="$PYTHONDIR" > tests/install.err 2>&1
then
rm tests/install.err
else
cat tests/install.err
exit 1
fi
cd "$TESTDIR"
BINDIR="$INST/bin"; export BINDIR
if [ -n "$TEST_COVERAGE" ]; then
COVERAGE_FILE="$TESTDIR/.coverage"; export COVERAGE_FILE
rm -f "$COVERAGE_FILE"
mv "$BINDIR/hg" "$BINDIR/hg.py"
{
echo '#!/bin/sh'
echo "exec \"${PYTHON-python}\" \"$TESTDIR/coverage.py\"" \
"-x \"$BINDIR/hg.py\" \"\$@\""
} > "$BINDIR/hg"
chmod 700 "$BINDIR/hg"
fi
PATH="$BINDIR:$PATH"; export PATH
if [ -n "$PYTHON" ]; then
{
echo "#!/bin/sh"
echo "exec \"$PYTHON"'" "$@"'
} > "$BINDIR/python"
chmod 755 "$BINDIR/python"
fi
PYTHONPATH="$PYTHONDIR"; export PYTHONPATH
run_one() {
rm -f "$1.err"
mkdir "$HGTMP/$1"
cd "$HGTMP/$1"
fail=0
HOME="$HGTMP/$1"; export HOME
OUT="$HGTMP/$1.out"
OUTOK="$TESTDIR/$1.out"
ERR="$TESTDIR/$1.err"
if "$TESTDIR/$1" > "$OUT" 2>&1; then
: no error
else
echo "$1 failed with error code $?"
fail=1
fi
if [ -s "$OUT" -a ! -s "$OUTOK" ] ; then
cp "$OUT" "$ERR"
echo
echo "$1 generated unexpected output:"
cat "$ERR"
fail=1
elif [ -r "$OUTOK" ]; then
if diff $DIFFOPTS "$OUTOK" "$OUT" > /dev/null; then
: no differences
else
cp "$OUT" "$ERR"
echo
echo "$1 output changed:"
diff $DIFFOPTS "$OUTOK" "$ERR" || true
fail=1
fi
fi
cd "$TESTDIR"
rm -f "$HGTMP/$1.out"
rm -rf "$HGTMP/$1"
return $fail
}
# list of prerequisite programs
# stuff from coreutils (cat, rm, etc) are not tested
prereqs="python merge diff grep unzip gunzip bunzip2 sed"
missing=''
for pre in $prereqs ; do
if type $pre > /dev/null 2>&1 ; then
: prereq exists
else
missing="$pre $missing"
fi
done
if [ "$missing" != '' ] ; then
echo "ERROR: the test suite needs some programs to execute correctly."
echo "The following programs are missing: "
for pre in $missing; do
echo " $pre"
done
exit 1
fi
TESTS="$*"
if [ -z "$TESTS" ] ; then
TESTS=`ls test-* | grep -v "[.~]"`
fi
for f in $TESTS ; do
$ECHO_N ".${NNL}"
run_one $f || failed=`expr $failed + 1`
tests=`expr $tests + 1`
done
echo
echo "Ran $tests tests, $failed failed."
if [ -n "$TEST_COVERAGE" ]; then
unset PYTHONPATH
$ECHO_N "$BINDIR,$TESTDIR,$HGTMP/test-," > "$HGTMP/omit"
if [ -z "$COVERAGE_STDLIB" ]; then
"${PYTHON-python}" -c 'import sys; print ",".join(sys.path)' \
>> "$HGTMP/omit"
fi
cd "$PYTHONDIR"
"${PYTHON-python}" "$TESTDIR/coverage.py" -r --omit="`cat \"$HGTMP/omit\"`"
fi
if [ $failed -gt 0 ] ; then
exit 1
fi
exit 0

View File

@ -176,18 +176,20 @@ def run_one(test):
ret, out = run(cmd)
vlog("# Ret was:", ret)
if ret == 0:
# If reference output file exists, check test output against it
if os.path.exists(ref):
f = open(ref, "r")
ref_out = f.read().splitlines()
f.close()
if out != ref_out:
ret = 1
print "\nERROR: %s output changed" % (test)
show_diff(ref_out, out)
else:
diffret = 0
# If reference output file exists, check test output against it
if os.path.exists(ref):
f = open(ref, "r")
ref_out = f.read().splitlines()
f.close()
if out != ref_out:
diffret = 1
print "\nERROR: %s output changed" % (test)
show_diff(ref_out, out)
if ret:
print "\nERROR: %s failed with error code %d" % (test, ret)
elif diffret:
ret = diffret
if ret != 0: # Save errors to a file for diagnosis
f = open(err, "w")

View File

@ -41,9 +41,12 @@ hg remove a
echo "%%% should show a removed and b added"
hg status
echo "reverting..."
echo "%%% revert should fail"
hg revert
echo "%%% revert should be ok now"
hg revert -r2
echo "%%% should show b unknown and a marked modified (merged)"
hg status

View File

@ -16,7 +16,9 @@ foo-b
%%% should show a removed and b added
A b
R a
reverting...
%%% revert should fail
abort: working dir has two parents; you must specify the revision to revert to
%%% revert should be ok now
undeleting a
forgetting b
%%% should show b unknown and a marked modified (merged)

View File

@ -14,7 +14,7 @@ basic commands (use "hg help" for the full list or option "-v" for details):
pull pull changes from the specified source
push push changes to the specified destination
remove remove the specified files on the next commit
revert revert modified files or dirs back to their unmodified states
revert revert modified files or dirs to their states as of some revision
serve export the repository via HTTP
status show changed files in the working directory
update update or merge working directory
@ -30,7 +30,7 @@ basic commands (use "hg help" for the full list or option "-v" for details):
pull pull changes from the specified source
push push changes to the specified destination
remove remove the specified files on the next commit
revert revert modified files or dirs back to their unmodified states
revert revert modified files or dirs to their states as of some revision
serve export the repository via HTTP
status show changed files in the working directory
update update or merge working directory
@ -68,7 +68,7 @@ list of commands (use "hg help -v" to show aliases and global options):
recover roll back an interrupted transaction
remove remove the specified files on the next commit
rename rename files; equivalent of copy + remove
revert revert modified files or dirs back to their unmodified states
revert revert modified files or dirs to their states as of some revision
root print the root (top) of the current working dir
serve export the repository via HTTP
status show changed files in the working directory
@ -110,7 +110,7 @@ list of commands (use "hg help -v" to show aliases and global options):
recover roll back an interrupted transaction
remove remove the specified files on the next commit
rename rename files; equivalent of copy + remove
revert revert modified files or dirs back to their unmodified states
revert revert modified files or dirs to their states as of some revision
root print the root (top) of the current working dir
serve export the repository via HTTP
status show changed files in the working directory
@ -226,7 +226,7 @@ basic commands (use "hg help" for the full list or option "-v" for details):
pull pull changes from the specified source
push push changes to the specified destination
remove remove the specified files on the next commit
revert revert modified files or dirs back to their unmodified states
revert revert modified files or dirs to their states as of some revision
serve export the repository via HTTP
status show changed files in the working directory
update update or merge working directory
@ -247,7 +247,7 @@ basic commands (use "hg help" for the full list or option "-v" for details):
pull pull changes from the specified source
push push changes to the specified destination
remove remove the specified files on the next commit
revert revert modified files or dirs back to their unmodified states
revert revert modified files or dirs to their states as of some revision
serve export the repository via HTTP
status show changed files in the working directory
update update or merge working directory