##// END OF EJS Templates
ui: fold readsections into readconfig...
Matt Mackall -
r8142:912bfef1 default
parent child Browse files
Show More
@@ -1,90 +1,90 b''
1 # acl.py - changeset access control for mercurial
1 # acl.py - changeset access control for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 #
7 #
8 # this hook allows to allow or deny access to parts of a repo when
8 # this hook allows to allow or deny access to parts of a repo when
9 # taking incoming changesets.
9 # taking incoming changesets.
10 #
10 #
11 # authorization is against local user name on system where hook is
11 # authorization is against local user name on system where hook is
12 # run, not committer of original changeset (since that is easy to
12 # run, not committer of original changeset (since that is easy to
13 # spoof).
13 # spoof).
14 #
14 #
15 # acl hook is best to use if you use hgsh to set up restricted shells
15 # acl hook is best to use if you use hgsh to set up restricted shells
16 # for authenticated users to only push to / pull from. not safe if
16 # for authenticated users to only push to / pull from. not safe if
17 # user has interactive shell access, because they can disable hook.
17 # user has interactive shell access, because they can disable hook.
18 # also not safe if remote users share one local account, because then
18 # also not safe if remote users share one local account, because then
19 # no way to tell remote users apart.
19 # no way to tell remote users apart.
20 #
20 #
21 # to use, configure acl extension in hgrc like this:
21 # to use, configure acl extension in hgrc like this:
22 #
22 #
23 # [extensions]
23 # [extensions]
24 # hgext.acl =
24 # hgext.acl =
25 #
25 #
26 # [hooks]
26 # [hooks]
27 # pretxnchangegroup.acl = python:hgext.acl.hook
27 # pretxnchangegroup.acl = python:hgext.acl.hook
28 #
28 #
29 # [acl]
29 # [acl]
30 # sources = serve # check if source of incoming changes in this list
30 # sources = serve # check if source of incoming changes in this list
31 # # ("serve" == ssh or http, "push", "pull", "bundle")
31 # # ("serve" == ssh or http, "push", "pull", "bundle")
32 #
32 #
33 # allow and deny lists have subtree pattern (default syntax is glob)
33 # allow and deny lists have subtree pattern (default syntax is glob)
34 # on left, user names on right. deny list checked before allow list.
34 # on left, user names on right. deny list checked before allow list.
35 #
35 #
36 # [acl.allow]
36 # [acl.allow]
37 # # if acl.allow not present, all users allowed by default
37 # # if acl.allow not present, all users allowed by default
38 # # empty acl.allow = no users allowed
38 # # empty acl.allow = no users allowed
39 # docs/** = doc_writer
39 # docs/** = doc_writer
40 # .hgtags = release_engineer
40 # .hgtags = release_engineer
41 #
41 #
42 # [acl.deny]
42 # [acl.deny]
43 # # if acl.deny not present, no users denied by default
43 # # if acl.deny not present, no users denied by default
44 # # empty acl.deny = all users allowed
44 # # empty acl.deny = all users allowed
45 # glob pattern = user4, user5
45 # glob pattern = user4, user5
46 # ** = user6
46 # ** = user6
47
47
48 from mercurial.i18n import _
48 from mercurial.i18n import _
49 from mercurial import util
49 from mercurial import util
50 import getpass
50 import getpass
51
51
52 def buildmatch(ui, repo, user, key):
52 def buildmatch(ui, repo, user, key):
53 '''return tuple of (match function, list enabled).'''
53 '''return tuple of (match function, list enabled).'''
54 if not ui.has_section(key):
54 if not ui.has_section(key):
55 ui.debug(_('acl: %s not enabled\n') % key)
55 ui.debug(_('acl: %s not enabled\n') % key)
56 return None
56 return None
57
57
58 pats = [pat for pat, users in ui.configitems(key)
58 pats = [pat for pat, users in ui.configitems(key)
59 if user in users.replace(',', ' ').split()]
59 if user in users.replace(',', ' ').split()]
60 ui.debug(_('acl: %s enabled, %d entries for user %s\n') %
60 ui.debug(_('acl: %s enabled, %d entries for user %s\n') %
61 (key, len(pats), user))
61 (key, len(pats), user))
62 if pats:
62 if pats:
63 return util.matcher(repo.root, names=pats)[1]
63 return util.matcher(repo.root, names=pats)[1]
64 return util.never
64 return util.never
65
65
66 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
66 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
67 if hooktype != 'pretxnchangegroup':
67 if hooktype != 'pretxnchangegroup':
68 raise util.Abort(_('config error - hook type "%s" cannot stop '
68 raise util.Abort(_('config error - hook type "%s" cannot stop '
69 'incoming changesets') % hooktype)
69 'incoming changesets') % hooktype)
70 if source not in ui.config('acl', 'sources', 'serve').split():
70 if source not in ui.config('acl', 'sources', 'serve').split():
71 ui.debug(_('acl: changes have source "%s" - skipping\n') % source)
71 ui.debug(_('acl: changes have source "%s" - skipping\n') % source)
72 return
72 return
73
73
74 user = getpass.getuser()
74 user = getpass.getuser()
75 cfg = ui.config('acl', 'config')
75 cfg = ui.config('acl', 'config')
76 if cfg:
76 if cfg:
77 ui.readsections(cfg, 'acl.allow', 'acl.deny')
77 ui.readconfig(cfg, sections = ['acl.allow', 'acl.deny'])
78 allow = buildmatch(ui, repo, user, 'acl.allow')
78 allow = buildmatch(ui, repo, user, 'acl.allow')
79 deny = buildmatch(ui, repo, user, 'acl.deny')
79 deny = buildmatch(ui, repo, user, 'acl.deny')
80
80
81 for rev in xrange(repo[node], len(repo)):
81 for rev in xrange(repo[node], len(repo)):
82 ctx = repo[rev]
82 ctx = repo[rev]
83 for f in ctx.files():
83 for f in ctx.files():
84 if deny and deny(f):
84 if deny and deny(f):
85 ui.debug(_('acl: user %s denied on %s\n') % (user, f))
85 ui.debug(_('acl: user %s denied on %s\n') % (user, f))
86 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
86 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
87 if allow and not allow(f):
87 if allow and not allow(f):
88 ui.debug(_('acl: user %s not allowed on %s\n') % (user, f))
88 ui.debug(_('acl: user %s not allowed on %s\n') % (user, f))
89 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
89 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
90 ui.debug(_('acl: allowing changeset %s\n') % ctx)
90 ui.debug(_('acl: allowing changeset %s\n') % ctx)
@@ -1,417 +1,417 b''
1 # bugzilla.py - bugzilla integration for mercurial
1 # bugzilla.py - bugzilla integration for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 '''Bugzilla integration
8 '''Bugzilla integration
9
9
10 This hook extension adds comments on bugs in Bugzilla when changesets
10 This hook extension adds comments on bugs in Bugzilla when changesets
11 that refer to bugs by Bugzilla ID are seen. The hook does not change
11 that refer to bugs by Bugzilla ID are seen. The hook does not change
12 bug status.
12 bug status.
13
13
14 The hook updates the Bugzilla database directly. Only Bugzilla
14 The hook updates the Bugzilla database directly. Only Bugzilla
15 installations using MySQL are supported.
15 installations using MySQL are supported.
16
16
17 The hook relies on a Bugzilla script to send bug change notification
17 The hook relies on a Bugzilla script to send bug change notification
18 emails. That script changes between Bugzilla versions; the
18 emails. That script changes between Bugzilla versions; the
19 'processmail' script used prior to 2.18 is replaced in 2.18 and
19 'processmail' script used prior to 2.18 is replaced in 2.18 and
20 subsequent versions by 'config/sendbugmail.pl'. Note that these will
20 subsequent versions by 'config/sendbugmail.pl'. Note that these will
21 be run by Mercurial as the user pushing the change; you will need to
21 be run by Mercurial as the user pushing the change; you will need to
22 ensure the Bugzilla install file permissions are set appropriately.
22 ensure the Bugzilla install file permissions are set appropriately.
23
23
24 Configuring the extension:
24 Configuring the extension:
25
25
26 [bugzilla]
26 [bugzilla]
27
27
28 host Hostname of the MySQL server holding the Bugzilla
28 host Hostname of the MySQL server holding the Bugzilla
29 database.
29 database.
30 db Name of the Bugzilla database in MySQL. Default 'bugs'.
30 db Name of the Bugzilla database in MySQL. Default 'bugs'.
31 user Username to use to access MySQL server. Default 'bugs'.
31 user Username to use to access MySQL server. Default 'bugs'.
32 password Password to use to access MySQL server.
32 password Password to use to access MySQL server.
33 timeout Database connection timeout (seconds). Default 5.
33 timeout Database connection timeout (seconds). Default 5.
34 version Bugzilla version. Specify '3.0' for Bugzilla versions
34 version Bugzilla version. Specify '3.0' for Bugzilla versions
35 3.0 and later, '2.18' for Bugzilla versions from 2.18
35 3.0 and later, '2.18' for Bugzilla versions from 2.18
36 and '2.16' for versions prior to 2.18.
36 and '2.16' for versions prior to 2.18.
37 bzuser Fallback Bugzilla user name to record comments with, if
37 bzuser Fallback Bugzilla user name to record comments with, if
38 changeset committer cannot be found as a Bugzilla user.
38 changeset committer cannot be found as a Bugzilla user.
39 bzdir Bugzilla install directory. Used by default notify.
39 bzdir Bugzilla install directory. Used by default notify.
40 Default '/var/www/html/bugzilla'.
40 Default '/var/www/html/bugzilla'.
41 notify The command to run to get Bugzilla to send bug change
41 notify The command to run to get Bugzilla to send bug change
42 notification emails. Substitutes from a map with 3
42 notification emails. Substitutes from a map with 3
43 keys, 'bzdir', 'id' (bug id) and 'user' (committer
43 keys, 'bzdir', 'id' (bug id) and 'user' (committer
44 bugzilla email). Default depends on version; from 2.18
44 bugzilla email). Default depends on version; from 2.18
45 it is "cd %(bzdir)s && perl -T contrib/sendbugmail.pl
45 it is "cd %(bzdir)s && perl -T contrib/sendbugmail.pl
46 %(id)s %(user)s".
46 %(id)s %(user)s".
47 regexp Regular expression to match bug IDs in changeset commit
47 regexp Regular expression to match bug IDs in changeset commit
48 message. Must contain one "()" group. The default
48 message. Must contain one "()" group. The default
49 expression matches 'Bug 1234', 'Bug no. 1234', 'Bug
49 expression matches 'Bug 1234', 'Bug no. 1234', 'Bug
50 number 1234', 'Bugs 1234,5678', 'Bug 1234 and 5678' and
50 number 1234', 'Bugs 1234,5678', 'Bug 1234 and 5678' and
51 variations thereof. Matching is case insensitive.
51 variations thereof. Matching is case insensitive.
52 style The style file to use when formatting comments.
52 style The style file to use when formatting comments.
53 template Template to use when formatting comments. Overrides
53 template Template to use when formatting comments. Overrides
54 style if specified. In addition to the usual Mercurial
54 style if specified. In addition to the usual Mercurial
55 keywords, the extension specifies:
55 keywords, the extension specifies:
56 {bug} The Bugzilla bug ID.
56 {bug} The Bugzilla bug ID.
57 {root} The full pathname of the Mercurial
57 {root} The full pathname of the Mercurial
58 repository.
58 repository.
59 {webroot} Stripped pathname of the Mercurial
59 {webroot} Stripped pathname of the Mercurial
60 repository.
60 repository.
61 {hgweb} Base URL for browsing Mercurial
61 {hgweb} Base URL for browsing Mercurial
62 repositories.
62 repositories.
63 Default 'changeset {node|short} in repo {root} refers '
63 Default 'changeset {node|short} in repo {root} refers '
64 'to bug {bug}.\\ndetails:\\n\\t{desc|tabindent}'
64 'to bug {bug}.\\ndetails:\\n\\t{desc|tabindent}'
65 strip The number of slashes to strip from the front of {root}
65 strip The number of slashes to strip from the front of {root}
66 to produce {webroot}. Default 0.
66 to produce {webroot}. Default 0.
67 usermap Path of file containing Mercurial committer ID to
67 usermap Path of file containing Mercurial committer ID to
68 Bugzilla user ID mappings. If specified, the file
68 Bugzilla user ID mappings. If specified, the file
69 should contain one mapping per line,
69 should contain one mapping per line,
70 "committer"="Bugzilla user". See also the [usermap]
70 "committer"="Bugzilla user". See also the [usermap]
71 section.
71 section.
72
72
73 [usermap]
73 [usermap]
74 Any entries in this section specify mappings of Mercurial
74 Any entries in this section specify mappings of Mercurial
75 committer ID to Bugzilla user ID. See also [bugzilla].usermap.
75 committer ID to Bugzilla user ID. See also [bugzilla].usermap.
76 "committer"="Bugzilla user"
76 "committer"="Bugzilla user"
77
77
78 [web]
78 [web]
79 baseurl Base URL for browsing Mercurial repositories. Reference
79 baseurl Base URL for browsing Mercurial repositories. Reference
80 from templates as {hgweb}.
80 from templates as {hgweb}.
81
81
82 Activating the extension:
82 Activating the extension:
83
83
84 [extensions]
84 [extensions]
85 hgext.bugzilla =
85 hgext.bugzilla =
86
86
87 [hooks]
87 [hooks]
88 # run bugzilla hook on every change pulled or pushed in here
88 # run bugzilla hook on every change pulled or pushed in here
89 incoming.bugzilla = python:hgext.bugzilla.hook
89 incoming.bugzilla = python:hgext.bugzilla.hook
90
90
91 Example configuration:
91 Example configuration:
92
92
93 This example configuration is for a collection of Mercurial
93 This example configuration is for a collection of Mercurial
94 repositories in /var/local/hg/repos/ used with a local Bugzilla 3.2
94 repositories in /var/local/hg/repos/ used with a local Bugzilla 3.2
95 installation in /opt/bugzilla-3.2.
95 installation in /opt/bugzilla-3.2.
96
96
97 [bugzilla]
97 [bugzilla]
98 host=localhost
98 host=localhost
99 password=XYZZY
99 password=XYZZY
100 version=3.0
100 version=3.0
101 bzuser=unknown@domain.com
101 bzuser=unknown@domain.com
102 bzdir=/opt/bugzilla-3.2
102 bzdir=/opt/bugzilla-3.2
103 template=Changeset {node|short} in {root|basename}.\\n{hgweb}/{webroot}/rev/{node|short}\\n\\n{desc}\\n
103 template=Changeset {node|short} in {root|basename}.\\n{hgweb}/{webroot}/rev/{node|short}\\n\\n{desc}\\n
104 strip=5
104 strip=5
105
105
106 [web]
106 [web]
107 baseurl=http://dev.domain.com/hg
107 baseurl=http://dev.domain.com/hg
108
108
109 [usermap]
109 [usermap]
110 user@emaildomain.com=user.name@bugzilladomain.com
110 user@emaildomain.com=user.name@bugzilladomain.com
111
111
112 Commits add a comment to the Bugzilla bug record of the form:
112 Commits add a comment to the Bugzilla bug record of the form:
113
113
114 Changeset 3b16791d6642 in repository-name.
114 Changeset 3b16791d6642 in repository-name.
115 http://dev.domain.com/hg/repository-name/rev/3b16791d6642
115 http://dev.domain.com/hg/repository-name/rev/3b16791d6642
116
116
117 Changeset commit comment. Bug 1234.
117 Changeset commit comment. Bug 1234.
118 '''
118 '''
119
119
120 from mercurial.i18n import _
120 from mercurial.i18n import _
121 from mercurial.node import short
121 from mercurial.node import short
122 from mercurial import cmdutil, templater, util
122 from mercurial import cmdutil, templater, util
123 import re, time
123 import re, time
124
124
125 MySQLdb = None
125 MySQLdb = None
126
126
127 def buglist(ids):
127 def buglist(ids):
128 return '(' + ','.join(map(str, ids)) + ')'
128 return '(' + ','.join(map(str, ids)) + ')'
129
129
130 class bugzilla_2_16(object):
130 class bugzilla_2_16(object):
131 '''support for bugzilla version 2.16.'''
131 '''support for bugzilla version 2.16.'''
132
132
133 def __init__(self, ui):
133 def __init__(self, ui):
134 self.ui = ui
134 self.ui = ui
135 host = self.ui.config('bugzilla', 'host', 'localhost')
135 host = self.ui.config('bugzilla', 'host', 'localhost')
136 user = self.ui.config('bugzilla', 'user', 'bugs')
136 user = self.ui.config('bugzilla', 'user', 'bugs')
137 passwd = self.ui.config('bugzilla', 'password')
137 passwd = self.ui.config('bugzilla', 'password')
138 db = self.ui.config('bugzilla', 'db', 'bugs')
138 db = self.ui.config('bugzilla', 'db', 'bugs')
139 timeout = int(self.ui.config('bugzilla', 'timeout', 5))
139 timeout = int(self.ui.config('bugzilla', 'timeout', 5))
140 usermap = self.ui.config('bugzilla', 'usermap')
140 usermap = self.ui.config('bugzilla', 'usermap')
141 if usermap:
141 if usermap:
142 self.ui.readsections(usermap, 'usermap')
142 self.ui.readconfig(usermap, 'usermap')
143 self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
143 self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
144 (host, db, user, '*' * len(passwd)))
144 (host, db, user, '*' * len(passwd)))
145 self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
145 self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
146 db=db, connect_timeout=timeout)
146 db=db, connect_timeout=timeout)
147 self.cursor = self.conn.cursor()
147 self.cursor = self.conn.cursor()
148 self.longdesc_id = self.get_longdesc_id()
148 self.longdesc_id = self.get_longdesc_id()
149 self.user_ids = {}
149 self.user_ids = {}
150 self.default_notify = "cd %(bzdir)s && ./processmail %(id)s %(user)s"
150 self.default_notify = "cd %(bzdir)s && ./processmail %(id)s %(user)s"
151
151
152 def run(self, *args, **kwargs):
152 def run(self, *args, **kwargs):
153 '''run a query.'''
153 '''run a query.'''
154 self.ui.note(_('query: %s %s\n') % (args, kwargs))
154 self.ui.note(_('query: %s %s\n') % (args, kwargs))
155 try:
155 try:
156 self.cursor.execute(*args, **kwargs)
156 self.cursor.execute(*args, **kwargs)
157 except MySQLdb.MySQLError:
157 except MySQLdb.MySQLError:
158 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
158 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
159 raise
159 raise
160
160
161 def get_longdesc_id(self):
161 def get_longdesc_id(self):
162 '''get identity of longdesc field'''
162 '''get identity of longdesc field'''
163 self.run('select fieldid from fielddefs where name = "longdesc"')
163 self.run('select fieldid from fielddefs where name = "longdesc"')
164 ids = self.cursor.fetchall()
164 ids = self.cursor.fetchall()
165 if len(ids) != 1:
165 if len(ids) != 1:
166 raise util.Abort(_('unknown database schema'))
166 raise util.Abort(_('unknown database schema'))
167 return ids[0][0]
167 return ids[0][0]
168
168
169 def filter_real_bug_ids(self, ids):
169 def filter_real_bug_ids(self, ids):
170 '''filter not-existing bug ids from list.'''
170 '''filter not-existing bug ids from list.'''
171 self.run('select bug_id from bugs where bug_id in %s' % buglist(ids))
171 self.run('select bug_id from bugs where bug_id in %s' % buglist(ids))
172 return util.sort([c[0] for c in self.cursor.fetchall()])
172 return util.sort([c[0] for c in self.cursor.fetchall()])
173
173
174 def filter_unknown_bug_ids(self, node, ids):
174 def filter_unknown_bug_ids(self, node, ids):
175 '''filter bug ids from list that already refer to this changeset.'''
175 '''filter bug ids from list that already refer to this changeset.'''
176
176
177 self.run('''select bug_id from longdescs where
177 self.run('''select bug_id from longdescs where
178 bug_id in %s and thetext like "%%%s%%"''' %
178 bug_id in %s and thetext like "%%%s%%"''' %
179 (buglist(ids), short(node)))
179 (buglist(ids), short(node)))
180 unknown = dict.fromkeys(ids)
180 unknown = dict.fromkeys(ids)
181 for (id,) in self.cursor.fetchall():
181 for (id,) in self.cursor.fetchall():
182 self.ui.status(_('bug %d already knows about changeset %s\n') %
182 self.ui.status(_('bug %d already knows about changeset %s\n') %
183 (id, short(node)))
183 (id, short(node)))
184 unknown.pop(id, None)
184 unknown.pop(id, None)
185 return util.sort(unknown.keys())
185 return util.sort(unknown.keys())
186
186
187 def notify(self, ids, committer):
187 def notify(self, ids, committer):
188 '''tell bugzilla to send mail.'''
188 '''tell bugzilla to send mail.'''
189
189
190 self.ui.status(_('telling bugzilla to send mail:\n'))
190 self.ui.status(_('telling bugzilla to send mail:\n'))
191 (user, userid) = self.get_bugzilla_user(committer)
191 (user, userid) = self.get_bugzilla_user(committer)
192 for id in ids:
192 for id in ids:
193 self.ui.status(_(' bug %s\n') % id)
193 self.ui.status(_(' bug %s\n') % id)
194 cmdfmt = self.ui.config('bugzilla', 'notify', self.default_notify)
194 cmdfmt = self.ui.config('bugzilla', 'notify', self.default_notify)
195 bzdir = self.ui.config('bugzilla', 'bzdir', '/var/www/html/bugzilla')
195 bzdir = self.ui.config('bugzilla', 'bzdir', '/var/www/html/bugzilla')
196 try:
196 try:
197 # Backwards-compatible with old notify string, which
197 # Backwards-compatible with old notify string, which
198 # took one string. This will throw with a new format
198 # took one string. This will throw with a new format
199 # string.
199 # string.
200 cmd = cmdfmt % id
200 cmd = cmdfmt % id
201 except TypeError:
201 except TypeError:
202 cmd = cmdfmt % {'bzdir': bzdir, 'id': id, 'user': user}
202 cmd = cmdfmt % {'bzdir': bzdir, 'id': id, 'user': user}
203 self.ui.note(_('running notify command %s\n') % cmd)
203 self.ui.note(_('running notify command %s\n') % cmd)
204 fp = util.popen('(%s) 2>&1' % cmd)
204 fp = util.popen('(%s) 2>&1' % cmd)
205 out = fp.read()
205 out = fp.read()
206 ret = fp.close()
206 ret = fp.close()
207 if ret:
207 if ret:
208 self.ui.warn(out)
208 self.ui.warn(out)
209 raise util.Abort(_('bugzilla notify command %s') %
209 raise util.Abort(_('bugzilla notify command %s') %
210 util.explain_exit(ret)[0])
210 util.explain_exit(ret)[0])
211 self.ui.status(_('done\n'))
211 self.ui.status(_('done\n'))
212
212
213 def get_user_id(self, user):
213 def get_user_id(self, user):
214 '''look up numeric bugzilla user id.'''
214 '''look up numeric bugzilla user id.'''
215 try:
215 try:
216 return self.user_ids[user]
216 return self.user_ids[user]
217 except KeyError:
217 except KeyError:
218 try:
218 try:
219 userid = int(user)
219 userid = int(user)
220 except ValueError:
220 except ValueError:
221 self.ui.note(_('looking up user %s\n') % user)
221 self.ui.note(_('looking up user %s\n') % user)
222 self.run('''select userid from profiles
222 self.run('''select userid from profiles
223 where login_name like %s''', user)
223 where login_name like %s''', user)
224 all = self.cursor.fetchall()
224 all = self.cursor.fetchall()
225 if len(all) != 1:
225 if len(all) != 1:
226 raise KeyError(user)
226 raise KeyError(user)
227 userid = int(all[0][0])
227 userid = int(all[0][0])
228 self.user_ids[user] = userid
228 self.user_ids[user] = userid
229 return userid
229 return userid
230
230
231 def map_committer(self, user):
231 def map_committer(self, user):
232 '''map name of committer to bugzilla user name.'''
232 '''map name of committer to bugzilla user name.'''
233 for committer, bzuser in self.ui.configitems('usermap'):
233 for committer, bzuser in self.ui.configitems('usermap'):
234 if committer.lower() == user.lower():
234 if committer.lower() == user.lower():
235 return bzuser
235 return bzuser
236 return user
236 return user
237
237
238 def get_bugzilla_user(self, committer):
238 def get_bugzilla_user(self, committer):
239 '''see if committer is a registered bugzilla user. Return
239 '''see if committer is a registered bugzilla user. Return
240 bugzilla username and userid if so. If not, return default
240 bugzilla username and userid if so. If not, return default
241 bugzilla username and userid.'''
241 bugzilla username and userid.'''
242 user = self.map_committer(committer)
242 user = self.map_committer(committer)
243 try:
243 try:
244 userid = self.get_user_id(user)
244 userid = self.get_user_id(user)
245 except KeyError:
245 except KeyError:
246 try:
246 try:
247 defaultuser = self.ui.config('bugzilla', 'bzuser')
247 defaultuser = self.ui.config('bugzilla', 'bzuser')
248 if not defaultuser:
248 if not defaultuser:
249 raise util.Abort(_('cannot find bugzilla user id for %s') %
249 raise util.Abort(_('cannot find bugzilla user id for %s') %
250 user)
250 user)
251 userid = self.get_user_id(defaultuser)
251 userid = self.get_user_id(defaultuser)
252 user = defaultuser
252 user = defaultuser
253 except KeyError:
253 except KeyError:
254 raise util.Abort(_('cannot find bugzilla user id for %s or %s') %
254 raise util.Abort(_('cannot find bugzilla user id for %s or %s') %
255 (user, defaultuser))
255 (user, defaultuser))
256 return (user, userid)
256 return (user, userid)
257
257
258 def add_comment(self, bugid, text, committer):
258 def add_comment(self, bugid, text, committer):
259 '''add comment to bug. try adding comment as committer of
259 '''add comment to bug. try adding comment as committer of
260 changeset, otherwise as default bugzilla user.'''
260 changeset, otherwise as default bugzilla user.'''
261 (user, userid) = self.get_bugzilla_user(committer)
261 (user, userid) = self.get_bugzilla_user(committer)
262 now = time.strftime('%Y-%m-%d %H:%M:%S')
262 now = time.strftime('%Y-%m-%d %H:%M:%S')
263 self.run('''insert into longdescs
263 self.run('''insert into longdescs
264 (bug_id, who, bug_when, thetext)
264 (bug_id, who, bug_when, thetext)
265 values (%s, %s, %s, %s)''',
265 values (%s, %s, %s, %s)''',
266 (bugid, userid, now, text))
266 (bugid, userid, now, text))
267 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
267 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
268 values (%s, %s, %s, %s)''',
268 values (%s, %s, %s, %s)''',
269 (bugid, userid, now, self.longdesc_id))
269 (bugid, userid, now, self.longdesc_id))
270 self.conn.commit()
270 self.conn.commit()
271
271
272 class bugzilla_2_18(bugzilla_2_16):
272 class bugzilla_2_18(bugzilla_2_16):
273 '''support for bugzilla 2.18 series.'''
273 '''support for bugzilla 2.18 series.'''
274
274
275 def __init__(self, ui):
275 def __init__(self, ui):
276 bugzilla_2_16.__init__(self, ui)
276 bugzilla_2_16.__init__(self, ui)
277 self.default_notify = "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s"
277 self.default_notify = "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s"
278
278
279 class bugzilla_3_0(bugzilla_2_18):
279 class bugzilla_3_0(bugzilla_2_18):
280 '''support for bugzilla 3.0 series.'''
280 '''support for bugzilla 3.0 series.'''
281
281
282 def __init__(self, ui):
282 def __init__(self, ui):
283 bugzilla_2_18.__init__(self, ui)
283 bugzilla_2_18.__init__(self, ui)
284
284
285 def get_longdesc_id(self):
285 def get_longdesc_id(self):
286 '''get identity of longdesc field'''
286 '''get identity of longdesc field'''
287 self.run('select id from fielddefs where name = "longdesc"')
287 self.run('select id from fielddefs where name = "longdesc"')
288 ids = self.cursor.fetchall()
288 ids = self.cursor.fetchall()
289 if len(ids) != 1:
289 if len(ids) != 1:
290 raise util.Abort(_('unknown database schema'))
290 raise util.Abort(_('unknown database schema'))
291 return ids[0][0]
291 return ids[0][0]
292
292
293 class bugzilla(object):
293 class bugzilla(object):
294 # supported versions of bugzilla. different versions have
294 # supported versions of bugzilla. different versions have
295 # different schemas.
295 # different schemas.
296 _versions = {
296 _versions = {
297 '2.16': bugzilla_2_16,
297 '2.16': bugzilla_2_16,
298 '2.18': bugzilla_2_18,
298 '2.18': bugzilla_2_18,
299 '3.0': bugzilla_3_0
299 '3.0': bugzilla_3_0
300 }
300 }
301
301
302 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
302 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
303 r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)')
303 r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)')
304
304
305 _bz = None
305 _bz = None
306
306
307 def __init__(self, ui, repo):
307 def __init__(self, ui, repo):
308 self.ui = ui
308 self.ui = ui
309 self.repo = repo
309 self.repo = repo
310
310
311 def bz(self):
311 def bz(self):
312 '''return object that knows how to talk to bugzilla version in
312 '''return object that knows how to talk to bugzilla version in
313 use.'''
313 use.'''
314
314
315 if bugzilla._bz is None:
315 if bugzilla._bz is None:
316 bzversion = self.ui.config('bugzilla', 'version')
316 bzversion = self.ui.config('bugzilla', 'version')
317 try:
317 try:
318 bzclass = bugzilla._versions[bzversion]
318 bzclass = bugzilla._versions[bzversion]
319 except KeyError:
319 except KeyError:
320 raise util.Abort(_('bugzilla version %s not supported') %
320 raise util.Abort(_('bugzilla version %s not supported') %
321 bzversion)
321 bzversion)
322 bugzilla._bz = bzclass(self.ui)
322 bugzilla._bz = bzclass(self.ui)
323 return bugzilla._bz
323 return bugzilla._bz
324
324
325 def __getattr__(self, key):
325 def __getattr__(self, key):
326 return getattr(self.bz(), key)
326 return getattr(self.bz(), key)
327
327
328 _bug_re = None
328 _bug_re = None
329 _split_re = None
329 _split_re = None
330
330
331 def find_bug_ids(self, ctx):
331 def find_bug_ids(self, ctx):
332 '''find valid bug ids that are referred to in changeset
332 '''find valid bug ids that are referred to in changeset
333 comments and that do not already have references to this
333 comments and that do not already have references to this
334 changeset.'''
334 changeset.'''
335
335
336 if bugzilla._bug_re is None:
336 if bugzilla._bug_re is None:
337 bugzilla._bug_re = re.compile(
337 bugzilla._bug_re = re.compile(
338 self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re),
338 self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re),
339 re.IGNORECASE)
339 re.IGNORECASE)
340 bugzilla._split_re = re.compile(r'\D+')
340 bugzilla._split_re = re.compile(r'\D+')
341 start = 0
341 start = 0
342 ids = {}
342 ids = {}
343 while True:
343 while True:
344 m = bugzilla._bug_re.search(ctx.description(), start)
344 m = bugzilla._bug_re.search(ctx.description(), start)
345 if not m:
345 if not m:
346 break
346 break
347 start = m.end()
347 start = m.end()
348 for id in bugzilla._split_re.split(m.group(1)):
348 for id in bugzilla._split_re.split(m.group(1)):
349 if not id: continue
349 if not id: continue
350 ids[int(id)] = 1
350 ids[int(id)] = 1
351 ids = ids.keys()
351 ids = ids.keys()
352 if ids:
352 if ids:
353 ids = self.filter_real_bug_ids(ids)
353 ids = self.filter_real_bug_ids(ids)
354 if ids:
354 if ids:
355 ids = self.filter_unknown_bug_ids(ctx.node(), ids)
355 ids = self.filter_unknown_bug_ids(ctx.node(), ids)
356 return ids
356 return ids
357
357
358 def update(self, bugid, ctx):
358 def update(self, bugid, ctx):
359 '''update bugzilla bug with reference to changeset.'''
359 '''update bugzilla bug with reference to changeset.'''
360
360
361 def webroot(root):
361 def webroot(root):
362 '''strip leading prefix of repo root and turn into
362 '''strip leading prefix of repo root and turn into
363 url-safe path.'''
363 url-safe path.'''
364 count = int(self.ui.config('bugzilla', 'strip', 0))
364 count = int(self.ui.config('bugzilla', 'strip', 0))
365 root = util.pconvert(root)
365 root = util.pconvert(root)
366 while count > 0:
366 while count > 0:
367 c = root.find('/')
367 c = root.find('/')
368 if c == -1:
368 if c == -1:
369 break
369 break
370 root = root[c+1:]
370 root = root[c+1:]
371 count -= 1
371 count -= 1
372 return root
372 return root
373
373
374 mapfile = self.ui.config('bugzilla', 'style')
374 mapfile = self.ui.config('bugzilla', 'style')
375 tmpl = self.ui.config('bugzilla', 'template')
375 tmpl = self.ui.config('bugzilla', 'template')
376 t = cmdutil.changeset_templater(self.ui, self.repo,
376 t = cmdutil.changeset_templater(self.ui, self.repo,
377 False, None, mapfile, False)
377 False, None, mapfile, False)
378 if not mapfile and not tmpl:
378 if not mapfile and not tmpl:
379 tmpl = _('changeset {node|short} in repo {root} refers '
379 tmpl = _('changeset {node|short} in repo {root} refers '
380 'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
380 'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
381 if tmpl:
381 if tmpl:
382 tmpl = templater.parsestring(tmpl, quoted=False)
382 tmpl = templater.parsestring(tmpl, quoted=False)
383 t.use_template(tmpl)
383 t.use_template(tmpl)
384 self.ui.pushbuffer()
384 self.ui.pushbuffer()
385 t.show(ctx, changes=ctx.changeset(),
385 t.show(ctx, changes=ctx.changeset(),
386 bug=str(bugid),
386 bug=str(bugid),
387 hgweb=self.ui.config('web', 'baseurl'),
387 hgweb=self.ui.config('web', 'baseurl'),
388 root=self.repo.root,
388 root=self.repo.root,
389 webroot=webroot(self.repo.root))
389 webroot=webroot(self.repo.root))
390 data = self.ui.popbuffer()
390 data = self.ui.popbuffer()
391 self.add_comment(bugid, data, util.email(ctx.user()))
391 self.add_comment(bugid, data, util.email(ctx.user()))
392
392
393 def hook(ui, repo, hooktype, node=None, **kwargs):
393 def hook(ui, repo, hooktype, node=None, **kwargs):
394 '''add comment to bugzilla for each changeset that refers to a
394 '''add comment to bugzilla for each changeset that refers to a
395 bugzilla bug id. only add a comment once per bug, so same change
395 bugzilla bug id. only add a comment once per bug, so same change
396 seen multiple times does not fill bug with duplicate data.'''
396 seen multiple times does not fill bug with duplicate data.'''
397 try:
397 try:
398 import MySQLdb as mysql
398 import MySQLdb as mysql
399 global MySQLdb
399 global MySQLdb
400 MySQLdb = mysql
400 MySQLdb = mysql
401 except ImportError, err:
401 except ImportError, err:
402 raise util.Abort(_('python mysql support not available: %s') % err)
402 raise util.Abort(_('python mysql support not available: %s') % err)
403
403
404 if node is None:
404 if node is None:
405 raise util.Abort(_('hook type %s does not pass a changeset id') %
405 raise util.Abort(_('hook type %s does not pass a changeset id') %
406 hooktype)
406 hooktype)
407 try:
407 try:
408 bz = bugzilla(ui, repo)
408 bz = bugzilla(ui, repo)
409 ctx = repo[node]
409 ctx = repo[node]
410 ids = bz.find_bug_ids(ctx)
410 ids = bz.find_bug_ids(ctx)
411 if ids:
411 if ids:
412 for id in ids:
412 for id in ids:
413 bz.update(id, ctx)
413 bz.update(id, ctx)
414 bz.notify(ids, util.email(ctx.user()))
414 bz.notify(ids, util.email(ctx.user()))
415 except MySQLdb.MySQLError, err:
415 except MySQLdb.MySQLError, err:
416 raise util.Abort(_('database error: %s') % err[1])
416 raise util.Abort(_('database error: %s') % err[1])
417
417
@@ -1,290 +1,290 b''
1 # notify.py - email notifications for mercurial
1 # notify.py - email notifications for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 '''hook extension to email notifications on commits/pushes
8 '''hook extension to email notifications on commits/pushes
9
9
10 Subscriptions can be managed through hgrc. Default mode is to print
10 Subscriptions can be managed through hgrc. Default mode is to print
11 messages to stdout, for testing and configuring.
11 messages to stdout, for testing and configuring.
12
12
13 To use, configure notify extension and enable in hgrc like this:
13 To use, configure notify extension and enable in hgrc like this:
14
14
15 [extensions]
15 [extensions]
16 hgext.notify =
16 hgext.notify =
17
17
18 [hooks]
18 [hooks]
19 # one email for each incoming changeset
19 # one email for each incoming changeset
20 incoming.notify = python:hgext.notify.hook
20 incoming.notify = python:hgext.notify.hook
21 # batch emails when many changesets incoming at one time
21 # batch emails when many changesets incoming at one time
22 changegroup.notify = python:hgext.notify.hook
22 changegroup.notify = python:hgext.notify.hook
23
23
24 [notify]
24 [notify]
25 # config items go in here
25 # config items go in here
26
26
27 config items:
27 config items:
28
28
29 REQUIRED:
29 REQUIRED:
30 config = /path/to/file # file containing subscriptions
30 config = /path/to/file # file containing subscriptions
31
31
32 OPTIONAL:
32 OPTIONAL:
33 test = True # print messages to stdout for testing
33 test = True # print messages to stdout for testing
34 strip = 3 # number of slashes to strip for url paths
34 strip = 3 # number of slashes to strip for url paths
35 domain = example.com # domain to use if committer missing domain
35 domain = example.com # domain to use if committer missing domain
36 style = ... # style file to use when formatting email
36 style = ... # style file to use when formatting email
37 template = ... # template to use when formatting email
37 template = ... # template to use when formatting email
38 incoming = ... # template to use when run as incoming hook
38 incoming = ... # template to use when run as incoming hook
39 changegroup = ... # template when run as changegroup hook
39 changegroup = ... # template when run as changegroup hook
40 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
40 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
41 maxsubject = 67 # truncate subject line longer than this
41 maxsubject = 67 # truncate subject line longer than this
42 diffstat = True # add a diffstat before the diff content
42 diffstat = True # add a diffstat before the diff content
43 sources = serve # notify if source of incoming changes in this list
43 sources = serve # notify if source of incoming changes in this list
44 # (serve == ssh or http, push, pull, bundle)
44 # (serve == ssh or http, push, pull, bundle)
45 [email]
45 [email]
46 from = user@host.com # email address to send as if none given
46 from = user@host.com # email address to send as if none given
47 [web]
47 [web]
48 baseurl = http://hgserver/... # root of hg web site for browsing commits
48 baseurl = http://hgserver/... # root of hg web site for browsing commits
49
49
50 notify config file has same format as regular hgrc. it has two
50 notify config file has same format as regular hgrc. it has two
51 sections so you can express subscriptions in whatever way is handier
51 sections so you can express subscriptions in whatever way is handier
52 for you.
52 for you.
53
53
54 [usersubs]
54 [usersubs]
55 # key is subscriber email, value is ","-separated list of glob patterns
55 # key is subscriber email, value is ","-separated list of glob patterns
56 user@host = pattern
56 user@host = pattern
57
57
58 [reposubs]
58 [reposubs]
59 # key is glob pattern, value is ","-separated list of subscriber emails
59 # key is glob pattern, value is ","-separated list of subscriber emails
60 pattern = user@host
60 pattern = user@host
61
61
62 glob patterns are matched against path to repository root.
62 glob patterns are matched against path to repository root.
63
63
64 if you like, you can put notify config file in repository that users
64 if you like, you can put notify config file in repository that users
65 can push changes to, they can manage their own subscriptions.'''
65 can push changes to, they can manage their own subscriptions.'''
66
66
67 from mercurial.i18n import _
67 from mercurial.i18n import _
68 from mercurial import patch, cmdutil, templater, util, mail
68 from mercurial import patch, cmdutil, templater, util, mail
69 import email.Parser, fnmatch, socket, time
69 import email.Parser, fnmatch, socket, time
70
70
71 # template for single changeset can include email headers.
71 # template for single changeset can include email headers.
72 single_template = '''
72 single_template = '''
73 Subject: changeset in {webroot}: {desc|firstline|strip}
73 Subject: changeset in {webroot}: {desc|firstline|strip}
74 From: {author}
74 From: {author}
75
75
76 changeset {node|short} in {root}
76 changeset {node|short} in {root}
77 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
77 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
78 description:
78 description:
79 \t{desc|tabindent|strip}
79 \t{desc|tabindent|strip}
80 '''.lstrip()
80 '''.lstrip()
81
81
82 # template for multiple changesets should not contain email headers,
82 # template for multiple changesets should not contain email headers,
83 # because only first set of headers will be used and result will look
83 # because only first set of headers will be used and result will look
84 # strange.
84 # strange.
85 multiple_template = '''
85 multiple_template = '''
86 changeset {node|short} in {root}
86 changeset {node|short} in {root}
87 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
87 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
88 summary: {desc|firstline}
88 summary: {desc|firstline}
89 '''
89 '''
90
90
91 deftemplates = {
91 deftemplates = {
92 'changegroup': multiple_template,
92 'changegroup': multiple_template,
93 }
93 }
94
94
95 class notifier(object):
95 class notifier(object):
96 '''email notification class.'''
96 '''email notification class.'''
97
97
98 def __init__(self, ui, repo, hooktype):
98 def __init__(self, ui, repo, hooktype):
99 self.ui = ui
99 self.ui = ui
100 cfg = self.ui.config('notify', 'config')
100 cfg = self.ui.config('notify', 'config')
101 if cfg:
101 if cfg:
102 self.ui.readsections(cfg, 'usersubs', 'reposubs')
102 self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
103 self.repo = repo
103 self.repo = repo
104 self.stripcount = int(self.ui.config('notify', 'strip', 0))
104 self.stripcount = int(self.ui.config('notify', 'strip', 0))
105 self.root = self.strip(self.repo.root)
105 self.root = self.strip(self.repo.root)
106 self.domain = self.ui.config('notify', 'domain')
106 self.domain = self.ui.config('notify', 'domain')
107 self.test = self.ui.configbool('notify', 'test', True)
107 self.test = self.ui.configbool('notify', 'test', True)
108 self.charsets = mail._charsets(self.ui)
108 self.charsets = mail._charsets(self.ui)
109 self.subs = self.subscribers()
109 self.subs = self.subscribers()
110
110
111 mapfile = self.ui.config('notify', 'style')
111 mapfile = self.ui.config('notify', 'style')
112 template = (self.ui.config('notify', hooktype) or
112 template = (self.ui.config('notify', hooktype) or
113 self.ui.config('notify', 'template'))
113 self.ui.config('notify', 'template'))
114 self.t = cmdutil.changeset_templater(self.ui, self.repo,
114 self.t = cmdutil.changeset_templater(self.ui, self.repo,
115 False, None, mapfile, False)
115 False, None, mapfile, False)
116 if not mapfile and not template:
116 if not mapfile and not template:
117 template = deftemplates.get(hooktype) or single_template
117 template = deftemplates.get(hooktype) or single_template
118 if template:
118 if template:
119 template = templater.parsestring(template, quoted=False)
119 template = templater.parsestring(template, quoted=False)
120 self.t.use_template(template)
120 self.t.use_template(template)
121
121
122 def strip(self, path):
122 def strip(self, path):
123 '''strip leading slashes from local path, turn into web-safe path.'''
123 '''strip leading slashes from local path, turn into web-safe path.'''
124
124
125 path = util.pconvert(path)
125 path = util.pconvert(path)
126 count = self.stripcount
126 count = self.stripcount
127 while count > 0:
127 while count > 0:
128 c = path.find('/')
128 c = path.find('/')
129 if c == -1:
129 if c == -1:
130 break
130 break
131 path = path[c+1:]
131 path = path[c+1:]
132 count -= 1
132 count -= 1
133 return path
133 return path
134
134
135 def fixmail(self, addr):
135 def fixmail(self, addr):
136 '''try to clean up email addresses.'''
136 '''try to clean up email addresses.'''
137
137
138 addr = util.email(addr.strip())
138 addr = util.email(addr.strip())
139 if self.domain:
139 if self.domain:
140 a = addr.find('@localhost')
140 a = addr.find('@localhost')
141 if a != -1:
141 if a != -1:
142 addr = addr[:a]
142 addr = addr[:a]
143 if '@' not in addr:
143 if '@' not in addr:
144 return addr + '@' + self.domain
144 return addr + '@' + self.domain
145 return addr
145 return addr
146
146
147 def subscribers(self):
147 def subscribers(self):
148 '''return list of email addresses of subscribers to this repo.'''
148 '''return list of email addresses of subscribers to this repo.'''
149 subs = {}
149 subs = {}
150 for user, pats in self.ui.configitems('usersubs'):
150 for user, pats in self.ui.configitems('usersubs'):
151 for pat in pats.split(','):
151 for pat in pats.split(','):
152 if fnmatch.fnmatch(self.repo.root, pat.strip()):
152 if fnmatch.fnmatch(self.repo.root, pat.strip()):
153 subs[self.fixmail(user)] = 1
153 subs[self.fixmail(user)] = 1
154 for pat, users in self.ui.configitems('reposubs'):
154 for pat, users in self.ui.configitems('reposubs'):
155 if fnmatch.fnmatch(self.repo.root, pat):
155 if fnmatch.fnmatch(self.repo.root, pat):
156 for user in users.split(','):
156 for user in users.split(','):
157 subs[self.fixmail(user)] = 1
157 subs[self.fixmail(user)] = 1
158 subs = util.sort(subs)
158 subs = util.sort(subs)
159 return [mail.addressencode(self.ui, s, self.charsets, self.test)
159 return [mail.addressencode(self.ui, s, self.charsets, self.test)
160 for s in subs]
160 for s in subs]
161
161
162 def url(self, path=None):
162 def url(self, path=None):
163 return self.ui.config('web', 'baseurl') + (path or self.root)
163 return self.ui.config('web', 'baseurl') + (path or self.root)
164
164
165 def node(self, ctx):
165 def node(self, ctx):
166 '''format one changeset.'''
166 '''format one changeset.'''
167 self.t.show(ctx, changes=ctx.changeset(),
167 self.t.show(ctx, changes=ctx.changeset(),
168 baseurl=self.ui.config('web', 'baseurl'),
168 baseurl=self.ui.config('web', 'baseurl'),
169 root=self.repo.root, webroot=self.root)
169 root=self.repo.root, webroot=self.root)
170
170
171 def skipsource(self, source):
171 def skipsource(self, source):
172 '''true if incoming changes from this source should be skipped.'''
172 '''true if incoming changes from this source should be skipped.'''
173 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
173 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
174 return source not in ok_sources
174 return source not in ok_sources
175
175
176 def send(self, ctx, count, data):
176 def send(self, ctx, count, data):
177 '''send message.'''
177 '''send message.'''
178
178
179 p = email.Parser.Parser()
179 p = email.Parser.Parser()
180 msg = p.parsestr(data)
180 msg = p.parsestr(data)
181
181
182 # store sender and subject
182 # store sender and subject
183 sender, subject = msg['From'], msg['Subject']
183 sender, subject = msg['From'], msg['Subject']
184 del msg['From'], msg['Subject']
184 del msg['From'], msg['Subject']
185 # store remaining headers
185 # store remaining headers
186 headers = msg.items()
186 headers = msg.items()
187 # create fresh mime message from msg body
187 # create fresh mime message from msg body
188 text = msg.get_payload()
188 text = msg.get_payload()
189 # for notification prefer readability over data precision
189 # for notification prefer readability over data precision
190 msg = mail.mimeencode(self.ui, text, self.charsets, self.test)
190 msg = mail.mimeencode(self.ui, text, self.charsets, self.test)
191 # reinstate custom headers
191 # reinstate custom headers
192 for k, v in headers:
192 for k, v in headers:
193 msg[k] = v
193 msg[k] = v
194
194
195 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
195 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
196
196
197 # try to make subject line exist and be useful
197 # try to make subject line exist and be useful
198 if not subject:
198 if not subject:
199 if count > 1:
199 if count > 1:
200 subject = _('%s: %d new changesets') % (self.root, count)
200 subject = _('%s: %d new changesets') % (self.root, count)
201 else:
201 else:
202 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
202 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
203 subject = '%s: %s' % (self.root, s)
203 subject = '%s: %s' % (self.root, s)
204 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
204 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
205 if maxsubject and len(subject) > maxsubject:
205 if maxsubject and len(subject) > maxsubject:
206 subject = subject[:maxsubject-3] + '...'
206 subject = subject[:maxsubject-3] + '...'
207 msg['Subject'] = mail.headencode(self.ui, subject,
207 msg['Subject'] = mail.headencode(self.ui, subject,
208 self.charsets, self.test)
208 self.charsets, self.test)
209
209
210 # try to make message have proper sender
210 # try to make message have proper sender
211 if not sender:
211 if not sender:
212 sender = self.ui.config('email', 'from') or self.ui.username()
212 sender = self.ui.config('email', 'from') or self.ui.username()
213 if '@' not in sender or '@localhost' in sender:
213 if '@' not in sender or '@localhost' in sender:
214 sender = self.fixmail(sender)
214 sender = self.fixmail(sender)
215 msg['From'] = mail.addressencode(self.ui, sender,
215 msg['From'] = mail.addressencode(self.ui, sender,
216 self.charsets, self.test)
216 self.charsets, self.test)
217
217
218 msg['X-Hg-Notification'] = 'changeset %s' % ctx
218 msg['X-Hg-Notification'] = 'changeset %s' % ctx
219 if not msg['Message-Id']:
219 if not msg['Message-Id']:
220 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
220 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
221 (ctx, int(time.time()),
221 (ctx, int(time.time()),
222 hash(self.repo.root), socket.getfqdn()))
222 hash(self.repo.root), socket.getfqdn()))
223 msg['To'] = ', '.join(self.subs)
223 msg['To'] = ', '.join(self.subs)
224
224
225 msgtext = msg.as_string(0)
225 msgtext = msg.as_string(0)
226 if self.test:
226 if self.test:
227 self.ui.write(msgtext)
227 self.ui.write(msgtext)
228 if not msgtext.endswith('\n'):
228 if not msgtext.endswith('\n'):
229 self.ui.write('\n')
229 self.ui.write('\n')
230 else:
230 else:
231 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
231 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
232 (len(self.subs), count))
232 (len(self.subs), count))
233 mail.sendmail(self.ui, util.email(msg['From']),
233 mail.sendmail(self.ui, util.email(msg['From']),
234 self.subs, msgtext)
234 self.subs, msgtext)
235
235
236 def diff(self, ctx, ref=None):
236 def diff(self, ctx, ref=None):
237
237
238 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
238 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
239 prev = ctx.parents()[0].node()
239 prev = ctx.parents()[0].node()
240 ref = ref and ref.node() or ctx.node()
240 ref = ref and ref.node() or ctx.node()
241 chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
241 chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
242 difflines = ''.join(chunks).splitlines()
242 difflines = ''.join(chunks).splitlines()
243
243
244 if self.ui.configbool('notify', 'diffstat', True):
244 if self.ui.configbool('notify', 'diffstat', True):
245 s = patch.diffstat(difflines)
245 s = patch.diffstat(difflines)
246 # s may be nil, don't include the header if it is
246 # s may be nil, don't include the header if it is
247 if s:
247 if s:
248 self.ui.write('\ndiffstat:\n\n%s' % s)
248 self.ui.write('\ndiffstat:\n\n%s' % s)
249
249
250 if maxdiff == 0:
250 if maxdiff == 0:
251 return
251 return
252 elif maxdiff > 0 and len(difflines) > maxdiff:
252 elif maxdiff > 0 and len(difflines) > maxdiff:
253 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
253 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
254 self.ui.write(msg % (len(difflines), maxdiff))
254 self.ui.write(msg % (len(difflines), maxdiff))
255 difflines = difflines[:maxdiff]
255 difflines = difflines[:maxdiff]
256 elif difflines:
256 elif difflines:
257 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
257 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
258
258
259 self.ui.write("\n".join(difflines))
259 self.ui.write("\n".join(difflines))
260
260
261 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
261 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
262 '''send email notifications to interested subscribers.
262 '''send email notifications to interested subscribers.
263
263
264 if used as changegroup hook, send one email for all changesets in
264 if used as changegroup hook, send one email for all changesets in
265 changegroup. else send one email per changeset.'''
265 changegroup. else send one email per changeset.'''
266
266
267 n = notifier(ui, repo, hooktype)
267 n = notifier(ui, repo, hooktype)
268 ctx = repo[node]
268 ctx = repo[node]
269
269
270 if not n.subs:
270 if not n.subs:
271 ui.debug(_('notify: no subscribers to repository %s\n') % n.root)
271 ui.debug(_('notify: no subscribers to repository %s\n') % n.root)
272 return
272 return
273 if n.skipsource(source):
273 if n.skipsource(source):
274 ui.debug(_('notify: changes have source "%s" - skipping\n') % source)
274 ui.debug(_('notify: changes have source "%s" - skipping\n') % source)
275 return
275 return
276
276
277 ui.pushbuffer()
277 ui.pushbuffer()
278 if hooktype == 'changegroup':
278 if hooktype == 'changegroup':
279 start, end = ctx.rev(), len(repo)
279 start, end = ctx.rev(), len(repo)
280 count = end - start
280 count = end - start
281 for rev in xrange(start, end):
281 for rev in xrange(start, end):
282 n.node(repo[rev])
282 n.node(repo[rev])
283 n.diff(ctx, repo['tip'])
283 n.diff(ctx, repo['tip'])
284 else:
284 else:
285 count = 1
285 count = 1
286 n.node(ctx)
286 n.node(ctx)
287 n.diff(ctx)
287 n.diff(ctx)
288
288
289 data = ui.popbuffer()
289 data = ui.popbuffer()
290 n.send(ctx, count, data)
290 n.send(ctx, count, data)
@@ -1,435 +1,409 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import errno, getpass, os, re, socket, sys, tempfile
9 import errno, getpass, os, re, socket, sys, tempfile
10 import ConfigParser, traceback, util
10 import ConfigParser, traceback, util
11
11
12 def dupconfig(orig):
12 def dupconfig(orig):
13 new = util.configparser(orig.defaults())
13 new = util.configparser(orig.defaults())
14 updateconfig(orig, new)
14 updateconfig(orig, new)
15 return new
15 return new
16
16
17 def updateconfig(source, dest, sections=None):
17 def updateconfig(source, dest, sections=None):
18 if not sections:
18 if not sections:
19 sections = source.sections()
19 sections = source.sections()
20 for section in sections:
20 for section in sections:
21 if not dest.has_section(section):
21 if not dest.has_section(section):
22 dest.add_section(section)
22 dest.add_section(section)
23 if not source.has_section(section):
24 continue
23 for name, value in source.items(section, raw=True):
25 for name, value in source.items(section, raw=True):
24 dest.set(section, name, value)
26 dest.set(section, name, value)
25
27
26 class ui(object):
28 class ui(object):
27 def __init__(self, parentui=None):
29 def __init__(self, parentui=None):
28 self.buffers = []
30 self.buffers = []
29 self.quiet = self.verbose = self.debugflag = self.traceback = False
31 self.quiet = self.verbose = self.debugflag = self.traceback = False
30 self.interactive = self.report_untrusted = True
32 self.interactive = self.report_untrusted = True
31
33
32 if parentui is None:
34 if parentui is None:
33 # this is the parent of all ui children
35 # this is the parent of all ui children
34 self.parentui = None
36 self.parentui = None
35 self.trusted_users = {}
37 self.trusted_users = {}
36 self.trusted_groups = {}
38 self.trusted_groups = {}
37 self.overlay = util.configparser()
39 self.overlay = util.configparser()
38 self.cdata = util.configparser()
40 self.cdata = util.configparser()
39 self.ucdata = util.configparser()
41 self.ucdata = util.configparser()
40
42
41 # we always trust global config files
43 # we always trust global config files
42 self.readconfig(util.rcpath(), assumetrusted=True)
44 for f in util.rcpath():
45 self.readconfig(f, assumetrusted=True)
43 else:
46 else:
44 # parentui may point to an ui object which is already a child
47 # parentui may point to an ui object which is already a child
45 self.parentui = parentui.parentui or parentui
48 self.parentui = parentui.parentui or parentui
46 self.buffers = parentui.buffers
49 self.buffers = parentui.buffers
47 self.trusted_users = parentui.trusted_users.copy()
50 self.trusted_users = parentui.trusted_users.copy()
48 self.trusted_groups = parentui.trusted_groups.copy()
51 self.trusted_groups = parentui.trusted_groups.copy()
49 self.cdata = dupconfig(self.parentui.cdata)
52 self.cdata = dupconfig(self.parentui.cdata)
50 self.ucdata = dupconfig(self.parentui.ucdata)
53 self.ucdata = dupconfig(self.parentui.ucdata)
51
54
52 # we want the overlay from the parent, not the root
55 # we want the overlay from the parent, not the root
53 self.overlay = dupconfig(parentui.overlay)
56 self.overlay = dupconfig(parentui.overlay)
57 self.fixconfig()
54
58
55 def __getattr__(self, key):
59 def __getattr__(self, key):
56 return getattr(self.parentui, key)
60 return getattr(self.parentui, key)
57
61
58 _isatty = None
62 _isatty = None
59 def isatty(self):
63 def isatty(self):
60 if ui._isatty is None:
64 if ui._isatty is None:
61 try:
65 try:
62 ui._isatty = sys.stdin.isatty()
66 ui._isatty = sys.stdin.isatty()
63 except AttributeError: # not a real file object
67 except AttributeError: # not a real file object
64 ui._isatty = False
68 ui._isatty = False
65 return ui._isatty
69 return ui._isatty
66
70
67 def _is_trusted(self, fp, f):
71 def _is_trusted(self, fp, f):
68 st = util.fstat(fp)
72 st = util.fstat(fp)
69 if util.isowner(fp, st):
73 if util.isowner(fp, st):
70 return True
74 return True
71
75
72 tusers = self.trusted_users
76 tusers = self.trusted_users
73 tgroups = self.trusted_groups
77 tgroups = self.trusted_groups
74 if '*' in tusers or '*' in tgroups:
78 if '*' in tusers or '*' in tgroups:
75 return True
79 return True
76
80
77 user = util.username(st.st_uid)
81 user = util.username(st.st_uid)
78 group = util.groupname(st.st_gid)
82 group = util.groupname(st.st_gid)
79 if user in tusers or group in tgroups or user == util.username():
83 if user in tusers or group in tgroups or user == util.username():
80 return True
84 return True
81
85
82 if self.report_untrusted:
86 if self.report_untrusted:
83 self.warn(_('Not trusting file %s from untrusted '
87 self.warn(_('Not trusting file %s from untrusted '
84 'user %s, group %s\n') % (f, user, group))
88 'user %s, group %s\n') % (f, user, group))
85 return False
89 return False
86
90
87 def readconfig(self, fn, root=None, assumetrusted=False):
91 def readconfig(self, filename, root=None, assumetrusted=False,
88 cdata = util.configparser()
92 sections = None):
93 try:
94 fp = open(filename)
95 except IOError:
96 if not sections: # ignore unless we were looking for something
97 return
98 raise
89
99
90 if isinstance(fn, basestring):
100 cdata = util.configparser()
91 fn = [fn]
101 trusted = sections or assumetrusted or self._is_trusted(fp, filename)
92 for f in fn:
93 try:
94 fp = open(f)
95 except IOError:
96 continue
97
98 trusted = assumetrusted or self._is_trusted(fp, f)
99
102
100 try:
103 try:
101 cdata.readfp(fp, f)
104 cdata.readfp(fp, filename)
102 except ConfigParser.ParsingError, inst:
105 except ConfigParser.ParsingError, inst:
103 msg = _("Failed to parse %s\n%s") % (f, inst)
106 msg = _("Failed to parse %s\n%s") % (filename, inst)
104 if trusted:
107 if trusted:
105 raise util.Abort(msg)
108 raise util.Abort(msg)
106 self.warn(_("Ignored: %s\n") % msg)
109 self.warn(_("Ignored: %s\n") % msg)
107
110
108 if trusted:
111 if trusted:
109 updateconfig(cdata, self.cdata)
112 updateconfig(cdata, self.cdata, sections)
110 updateconfig(self.overlay, self.cdata)
113 updateconfig(self.overlay, self.cdata, sections)
111 updateconfig(cdata, self.ucdata)
114 updateconfig(cdata, self.ucdata, sections)
112 updateconfig(self.overlay, self.ucdata)
115 updateconfig(self.overlay, self.ucdata, sections)
113
116
114 if root is None:
117 if root is None:
115 root = os.path.expanduser('~')
118 root = os.path.expanduser('~')
116 self.fixconfig(root=root)
119 self.fixconfig(root=root)
117
120
118 def readsections(self, filename, *sections):
119 """Read filename and add only the specified sections to the config data
120
121 The settings are added to the trusted config data.
122 """
123 if not sections:
124 return
125
126 cdata = util.configparser()
127 try:
128 try:
129 fp = open(filename)
130 except IOError, inst:
131 raise util.Abort(_("unable to open %s: %s") %
132 (filename, getattr(inst, "strerror", inst)))
133 try:
134 cdata.readfp(fp, filename)
135 finally:
136 fp.close()
137 except ConfigParser.ParsingError, inst:
138 raise util.Abort(_("failed to parse %s\n%s") % (filename, inst))
139
140 for section in sections:
141 if not cdata.has_section(section):
142 cdata.add_section(section)
143
144 updateconfig(cdata, self.cdata, sections)
145 updateconfig(cdata, self.ucdata, sections)
146
147 def fixconfig(self, section=None, name=None, value=None, root=None):
121 def fixconfig(self, section=None, name=None, value=None, root=None):
148 # translate paths relative to root (or home) into absolute paths
122 # translate paths relative to root (or home) into absolute paths
149 if section is None or section == 'paths':
123 if section is None or section == 'paths':
150 if root is None:
124 if root is None:
151 root = os.getcwd()
125 root = os.getcwd()
152 items = section and [(name, value)] or []
126 items = section and [(name, value)] or []
153 for cdata in self.cdata, self.ucdata, self.overlay:
127 for cdata in self.cdata, self.ucdata, self.overlay:
154 if not items and cdata.has_section('paths'):
128 if not items and cdata.has_section('paths'):
155 pathsitems = cdata.items('paths')
129 pathsitems = cdata.items('paths')
156 else:
130 else:
157 pathsitems = items
131 pathsitems = items
158 for n, path in pathsitems:
132 for n, path in pathsitems:
159 if path and "://" not in path and not os.path.isabs(path):
133 if path and "://" not in path and not os.path.isabs(path):
160 cdata.set("paths", n,
134 cdata.set("paths", n,
161 os.path.normpath(os.path.join(root, path)))
135 os.path.normpath(os.path.join(root, path)))
162
136
163 # update ui options
137 # update ui options
164 if section is None or section == 'ui':
138 if section is None or section == 'ui':
165 self.debugflag = self.configbool('ui', 'debug')
139 self.debugflag = self.configbool('ui', 'debug')
166 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
140 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
167 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
141 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
168 if self.verbose and self.quiet:
142 if self.verbose and self.quiet:
169 self.quiet = self.verbose = False
143 self.quiet = self.verbose = False
170
144
171 self.report_untrusted = self.configbool("ui", "report_untrusted",
145 self.report_untrusted = self.configbool("ui", "report_untrusted",
172 True)
146 True)
173 self.interactive = self.configbool("ui", "interactive",
147 self.interactive = self.configbool("ui", "interactive",
174 self.isatty())
148 self.isatty())
175 self.traceback = self.configbool('ui', 'traceback', False)
149 self.traceback = self.configbool('ui', 'traceback', False)
176
150
177 # update trust information
151 # update trust information
178 if section is None or section == 'trusted':
152 if section is None or section == 'trusted':
179 for user in self.configlist('trusted', 'users'):
153 for user in self.configlist('trusted', 'users'):
180 self.trusted_users[user] = 1
154 self.trusted_users[user] = 1
181 for group in self.configlist('trusted', 'groups'):
155 for group in self.configlist('trusted', 'groups'):
182 self.trusted_groups[group] = 1
156 self.trusted_groups[group] = 1
183
157
184 def setconfig(self, section, name, value):
158 def setconfig(self, section, name, value):
185 for cdata in (self.overlay, self.cdata, self.ucdata):
159 for cdata in (self.overlay, self.cdata, self.ucdata):
186 if not cdata.has_section(section):
160 if not cdata.has_section(section):
187 cdata.add_section(section)
161 cdata.add_section(section)
188 cdata.set(section, name, value)
162 cdata.set(section, name, value)
189 self.fixconfig(section, name, value)
163 self.fixconfig(section, name, value)
190
164
191 def _get_cdata(self, untrusted):
165 def _get_cdata(self, untrusted):
192 if untrusted:
166 if untrusted:
193 return self.ucdata
167 return self.ucdata
194 return self.cdata
168 return self.cdata
195
169
196 def _config(self, section, name, default, funcname, untrusted, abort):
170 def _config(self, section, name, default, funcname, untrusted, abort):
197 cdata = self._get_cdata(untrusted)
171 cdata = self._get_cdata(untrusted)
198 if cdata.has_option(section, name):
172 if cdata.has_option(section, name):
199 try:
173 try:
200 func = getattr(cdata, funcname)
174 func = getattr(cdata, funcname)
201 return func(section, name)
175 return func(section, name)
202 except (ConfigParser.InterpolationError, ValueError), inst:
176 except (ConfigParser.InterpolationError, ValueError), inst:
203 msg = _("Error in configuration section [%s] "
177 msg = _("Error in configuration section [%s] "
204 "parameter '%s':\n%s") % (section, name, inst)
178 "parameter '%s':\n%s") % (section, name, inst)
205 if abort:
179 if abort:
206 raise util.Abort(msg)
180 raise util.Abort(msg)
207 self.warn(_("Ignored: %s\n") % msg)
181 self.warn(_("Ignored: %s\n") % msg)
208 return default
182 return default
209
183
210 def _configcommon(self, section, name, default, funcname, untrusted):
184 def _configcommon(self, section, name, default, funcname, untrusted):
211 value = self._config(section, name, default, funcname,
185 value = self._config(section, name, default, funcname,
212 untrusted, abort=True)
186 untrusted, abort=True)
213 if self.debugflag and not untrusted:
187 if self.debugflag and not untrusted:
214 uvalue = self._config(section, name, None, funcname,
188 uvalue = self._config(section, name, None, funcname,
215 untrusted=True, abort=False)
189 untrusted=True, abort=False)
216 if uvalue is not None and uvalue != value:
190 if uvalue is not None and uvalue != value:
217 self.warn(_("Ignoring untrusted configuration option "
191 self.warn(_("Ignoring untrusted configuration option "
218 "%s.%s = %s\n") % (section, name, uvalue))
192 "%s.%s = %s\n") % (section, name, uvalue))
219 return value
193 return value
220
194
221 def config(self, section, name, default=None, untrusted=False):
195 def config(self, section, name, default=None, untrusted=False):
222 return self._configcommon(section, name, default, 'get', untrusted)
196 return self._configcommon(section, name, default, 'get', untrusted)
223
197
224 def configbool(self, section, name, default=False, untrusted=False):
198 def configbool(self, section, name, default=False, untrusted=False):
225 return self._configcommon(section, name, default, 'getboolean',
199 return self._configcommon(section, name, default, 'getboolean',
226 untrusted)
200 untrusted)
227
201
228 def configlist(self, section, name, default=None, untrusted=False):
202 def configlist(self, section, name, default=None, untrusted=False):
229 """Return a list of comma/space separated strings"""
203 """Return a list of comma/space separated strings"""
230 result = self.config(section, name, untrusted=untrusted)
204 result = self.config(section, name, untrusted=untrusted)
231 if result is None:
205 if result is None:
232 result = default or []
206 result = default or []
233 if isinstance(result, basestring):
207 if isinstance(result, basestring):
234 result = result.replace(",", " ").split()
208 result = result.replace(",", " ").split()
235 return result
209 return result
236
210
237 def has_section(self, section, untrusted=False):
211 def has_section(self, section, untrusted=False):
238 '''tell whether section exists in config.'''
212 '''tell whether section exists in config.'''
239 cdata = self._get_cdata(untrusted)
213 cdata = self._get_cdata(untrusted)
240 return cdata.has_section(section)
214 return cdata.has_section(section)
241
215
242 def _configitems(self, section, untrusted, abort):
216 def _configitems(self, section, untrusted, abort):
243 items = {}
217 items = {}
244 cdata = self._get_cdata(untrusted)
218 cdata = self._get_cdata(untrusted)
245 if cdata.has_section(section):
219 if cdata.has_section(section):
246 try:
220 try:
247 items.update(dict(cdata.items(section)))
221 items.update(dict(cdata.items(section)))
248 except ConfigParser.InterpolationError, inst:
222 except ConfigParser.InterpolationError, inst:
249 msg = _("Error in configuration section [%s]:\n"
223 msg = _("Error in configuration section [%s]:\n"
250 "%s") % (section, inst)
224 "%s") % (section, inst)
251 if abort:
225 if abort:
252 raise util.Abort(msg)
226 raise util.Abort(msg)
253 self.warn(_("Ignored: %s\n") % msg)
227 self.warn(_("Ignored: %s\n") % msg)
254 return items
228 return items
255
229
256 def configitems(self, section, untrusted=False):
230 def configitems(self, section, untrusted=False):
257 items = self._configitems(section, untrusted=untrusted, abort=True)
231 items = self._configitems(section, untrusted=untrusted, abort=True)
258 if self.debugflag and not untrusted:
232 if self.debugflag and not untrusted:
259 uitems = self._configitems(section, untrusted=True, abort=False)
233 uitems = self._configitems(section, untrusted=True, abort=False)
260 for k in util.sort(uitems):
234 for k in util.sort(uitems):
261 if uitems[k] != items.get(k):
235 if uitems[k] != items.get(k):
262 self.warn(_("Ignoring untrusted configuration option "
236 self.warn(_("Ignoring untrusted configuration option "
263 "%s.%s = %s\n") % (section, k, uitems[k]))
237 "%s.%s = %s\n") % (section, k, uitems[k]))
264 return util.sort(items.items())
238 return util.sort(items.items())
265
239
266 def walkconfig(self, untrusted=False):
240 def walkconfig(self, untrusted=False):
267 cdata = self._get_cdata(untrusted)
241 cdata = self._get_cdata(untrusted)
268 sections = cdata.sections()
242 sections = cdata.sections()
269 sections.sort()
243 sections.sort()
270 for section in sections:
244 for section in sections:
271 for name, value in self.configitems(section, untrusted):
245 for name, value in self.configitems(section, untrusted):
272 yield section, name, str(value).replace('\n', '\\n')
246 yield section, name, str(value).replace('\n', '\\n')
273
247
274 def username(self):
248 def username(self):
275 """Return default username to be used in commits.
249 """Return default username to be used in commits.
276
250
277 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
251 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
278 and stop searching if one of these is set.
252 and stop searching if one of these is set.
279 If not found and ui.askusername is True, ask the user, else use
253 If not found and ui.askusername is True, ask the user, else use
280 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
254 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
281 """
255 """
282 user = os.environ.get("HGUSER")
256 user = os.environ.get("HGUSER")
283 if user is None:
257 if user is None:
284 user = self.config("ui", "username")
258 user = self.config("ui", "username")
285 if user is None:
259 if user is None:
286 user = os.environ.get("EMAIL")
260 user = os.environ.get("EMAIL")
287 if user is None and self.configbool("ui", "askusername"):
261 if user is None and self.configbool("ui", "askusername"):
288 user = self.prompt(_("enter a commit username:"), default=None)
262 user = self.prompt(_("enter a commit username:"), default=None)
289 if user is None:
263 if user is None:
290 try:
264 try:
291 user = '%s@%s' % (util.getuser(), socket.getfqdn())
265 user = '%s@%s' % (util.getuser(), socket.getfqdn())
292 self.warn(_("No username found, using '%s' instead\n") % user)
266 self.warn(_("No username found, using '%s' instead\n") % user)
293 except KeyError:
267 except KeyError:
294 pass
268 pass
295 if not user:
269 if not user:
296 raise util.Abort(_("Please specify a username."))
270 raise util.Abort(_("Please specify a username."))
297 if "\n" in user:
271 if "\n" in user:
298 raise util.Abort(_("username %s contains a newline\n") % repr(user))
272 raise util.Abort(_("username %s contains a newline\n") % repr(user))
299 return user
273 return user
300
274
301 def shortuser(self, user):
275 def shortuser(self, user):
302 """Return a short representation of a user name or email address."""
276 """Return a short representation of a user name or email address."""
303 if not self.verbose: user = util.shortuser(user)
277 if not self.verbose: user = util.shortuser(user)
304 return user
278 return user
305
279
306 def expandpath(self, loc, default=None):
280 def expandpath(self, loc, default=None):
307 """Return repository location relative to cwd or from [paths]"""
281 """Return repository location relative to cwd or from [paths]"""
308 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
282 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
309 return loc
283 return loc
310
284
311 path = self.config("paths", loc)
285 path = self.config("paths", loc)
312 if not path and default is not None:
286 if not path and default is not None:
313 path = self.config("paths", default)
287 path = self.config("paths", default)
314 return path or loc
288 return path or loc
315
289
316 def pushbuffer(self):
290 def pushbuffer(self):
317 self.buffers.append([])
291 self.buffers.append([])
318
292
319 def popbuffer(self):
293 def popbuffer(self):
320 return "".join(self.buffers.pop())
294 return "".join(self.buffers.pop())
321
295
322 def write(self, *args):
296 def write(self, *args):
323 if self.buffers:
297 if self.buffers:
324 self.buffers[-1].extend([str(a) for a in args])
298 self.buffers[-1].extend([str(a) for a in args])
325 else:
299 else:
326 for a in args:
300 for a in args:
327 sys.stdout.write(str(a))
301 sys.stdout.write(str(a))
328
302
329 def write_err(self, *args):
303 def write_err(self, *args):
330 try:
304 try:
331 if not sys.stdout.closed: sys.stdout.flush()
305 if not sys.stdout.closed: sys.stdout.flush()
332 for a in args:
306 for a in args:
333 sys.stderr.write(str(a))
307 sys.stderr.write(str(a))
334 # stderr may be buffered under win32 when redirected to files,
308 # stderr may be buffered under win32 when redirected to files,
335 # including stdout.
309 # including stdout.
336 if not sys.stderr.closed: sys.stderr.flush()
310 if not sys.stderr.closed: sys.stderr.flush()
337 except IOError, inst:
311 except IOError, inst:
338 if inst.errno != errno.EPIPE:
312 if inst.errno != errno.EPIPE:
339 raise
313 raise
340
314
341 def flush(self):
315 def flush(self):
342 try: sys.stdout.flush()
316 try: sys.stdout.flush()
343 except: pass
317 except: pass
344 try: sys.stderr.flush()
318 try: sys.stderr.flush()
345 except: pass
319 except: pass
346
320
347 def _readline(self, prompt=''):
321 def _readline(self, prompt=''):
348 if self.isatty():
322 if self.isatty():
349 try:
323 try:
350 # magically add command line editing support, where
324 # magically add command line editing support, where
351 # available
325 # available
352 import readline
326 import readline
353 # force demandimport to really load the module
327 # force demandimport to really load the module
354 readline.read_history_file
328 readline.read_history_file
355 # windows sometimes raises something other than ImportError
329 # windows sometimes raises something other than ImportError
356 except Exception:
330 except Exception:
357 pass
331 pass
358 line = raw_input(prompt)
332 line = raw_input(prompt)
359 # When stdin is in binary mode on Windows, it can cause
333 # When stdin is in binary mode on Windows, it can cause
360 # raw_input() to emit an extra trailing carriage return
334 # raw_input() to emit an extra trailing carriage return
361 if os.linesep == '\r\n' and line and line[-1] == '\r':
335 if os.linesep == '\r\n' and line and line[-1] == '\r':
362 line = line[:-1]
336 line = line[:-1]
363 return line
337 return line
364
338
365 def prompt(self, msg, pat=None, default="y"):
339 def prompt(self, msg, pat=None, default="y"):
366 """Prompt user with msg, read response, and ensure it matches pat
340 """Prompt user with msg, read response, and ensure it matches pat
367
341
368 If not interactive -- the default is returned
342 If not interactive -- the default is returned
369 """
343 """
370 if not self.interactive:
344 if not self.interactive:
371 self.note(msg, ' ', default, "\n")
345 self.note(msg, ' ', default, "\n")
372 return default
346 return default
373 while True:
347 while True:
374 try:
348 try:
375 r = self._readline(msg + ' ')
349 r = self._readline(msg + ' ')
376 if not r:
350 if not r:
377 return default
351 return default
378 if not pat or re.match(pat, r):
352 if not pat or re.match(pat, r):
379 return r
353 return r
380 else:
354 else:
381 self.write(_("unrecognized response\n"))
355 self.write(_("unrecognized response\n"))
382 except EOFError:
356 except EOFError:
383 raise util.Abort(_('response expected'))
357 raise util.Abort(_('response expected'))
384
358
385 def getpass(self, prompt=None, default=None):
359 def getpass(self, prompt=None, default=None):
386 if not self.interactive: return default
360 if not self.interactive: return default
387 try:
361 try:
388 return getpass.getpass(prompt or _('password: '))
362 return getpass.getpass(prompt or _('password: '))
389 except EOFError:
363 except EOFError:
390 raise util.Abort(_('response expected'))
364 raise util.Abort(_('response expected'))
391 def status(self, *msg):
365 def status(self, *msg):
392 if not self.quiet: self.write(*msg)
366 if not self.quiet: self.write(*msg)
393 def warn(self, *msg):
367 def warn(self, *msg):
394 self.write_err(*msg)
368 self.write_err(*msg)
395 def note(self, *msg):
369 def note(self, *msg):
396 if self.verbose: self.write(*msg)
370 if self.verbose: self.write(*msg)
397 def debug(self, *msg):
371 def debug(self, *msg):
398 if self.debugflag: self.write(*msg)
372 if self.debugflag: self.write(*msg)
399 def edit(self, text, user):
373 def edit(self, text, user):
400 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
374 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
401 text=True)
375 text=True)
402 try:
376 try:
403 f = os.fdopen(fd, "w")
377 f = os.fdopen(fd, "w")
404 f.write(text)
378 f.write(text)
405 f.close()
379 f.close()
406
380
407 editor = self.geteditor()
381 editor = self.geteditor()
408
382
409 util.system("%s \"%s\"" % (editor, name),
383 util.system("%s \"%s\"" % (editor, name),
410 environ={'HGUSER': user},
384 environ={'HGUSER': user},
411 onerr=util.Abort, errprefix=_("edit failed"))
385 onerr=util.Abort, errprefix=_("edit failed"))
412
386
413 f = open(name)
387 f = open(name)
414 t = f.read()
388 t = f.read()
415 f.close()
389 f.close()
416 t = re.sub("(?m)^HG:.*\n", "", t)
390 t = re.sub("(?m)^HG:.*\n", "", t)
417 finally:
391 finally:
418 os.unlink(name)
392 os.unlink(name)
419
393
420 return t
394 return t
421
395
422 def print_exc(self):
396 def print_exc(self):
423 '''print exception traceback if traceback printing enabled.
397 '''print exception traceback if traceback printing enabled.
424 only to call in exception handler. returns true if traceback
398 only to call in exception handler. returns true if traceback
425 printed.'''
399 printed.'''
426 if self.traceback:
400 if self.traceback:
427 traceback.print_exc()
401 traceback.print_exc()
428 return self.traceback
402 return self.traceback
429
403
430 def geteditor(self):
404 def geteditor(self):
431 '''return editor to use'''
405 '''return editor to use'''
432 return (os.environ.get("HGEDITOR") or
406 return (os.environ.get("HGEDITOR") or
433 self.config("ui", "editor") or
407 self.config("ui", "editor") or
434 os.environ.get("VISUAL") or
408 os.environ.get("VISUAL") or
435 os.environ.get("EDITOR", "vi"))
409 os.environ.get("EDITOR", "vi"))
@@ -1,587 +1,587 b''
1 3:911600dab2ae
1 3:911600dab2ae
2 requesting all changes
2 requesting all changes
3 adding changesets
3 adding changesets
4 adding manifests
4 adding manifests
5 adding file changes
5 adding file changes
6 added 1 changesets with 3 changes to 3 files
6 added 1 changesets with 3 changes to 3 files
7 updating working directory
7 updating working directory
8 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
8 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
9
9
10 Extension disabled for lack of a hook
10 Extension disabled for lack of a hook
11 Pushing as user fred
11 Pushing as user fred
12 hgrc = """
12 hgrc = """
13 """
13 """
14 pushing to ../b
14 pushing to ../b
15 searching for changes
15 searching for changes
16 common changesets up to 6675d58eff77
16 common changesets up to 6675d58eff77
17 3 changesets found
17 3 changesets found
18 list of changesets:
18 list of changesets:
19 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
19 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
20 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
20 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
21 911600dab2ae7a9baff75958b84fe606851ce955
21 911600dab2ae7a9baff75958b84fe606851ce955
22 adding changesets
22 adding changesets
23 add changeset ef1ea85a6374
23 add changeset ef1ea85a6374
24 add changeset f9cafe1212c8
24 add changeset f9cafe1212c8
25 add changeset 911600dab2ae
25 add changeset 911600dab2ae
26 adding manifests
26 adding manifests
27 adding file changes
27 adding file changes
28 adding foo/Bar/file.txt revisions
28 adding foo/Bar/file.txt revisions
29 adding foo/file.txt revisions
29 adding foo/file.txt revisions
30 adding quux/file.py revisions
30 adding quux/file.py revisions
31 added 3 changesets with 3 changes to 3 files
31 added 3 changesets with 3 changes to 3 files
32 updating the branch cache
32 updating the branch cache
33 rolling back last transaction
33 rolling back last transaction
34 0:6675d58eff77
34 0:6675d58eff77
35
35
36 Extension disabled for lack of acl.sources
36 Extension disabled for lack of acl.sources
37 Pushing as user fred
37 Pushing as user fred
38 hgrc = """
38 hgrc = """
39 [hooks]
39 [hooks]
40 pretxnchangegroup.acl = python:hgext.acl.hook
40 pretxnchangegroup.acl = python:hgext.acl.hook
41 """
41 """
42 pushing to ../b
42 pushing to ../b
43 searching for changes
43 searching for changes
44 common changesets up to 6675d58eff77
44 common changesets up to 6675d58eff77
45 3 changesets found
45 3 changesets found
46 list of changesets:
46 list of changesets:
47 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
47 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
48 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
48 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
49 911600dab2ae7a9baff75958b84fe606851ce955
49 911600dab2ae7a9baff75958b84fe606851ce955
50 adding changesets
50 adding changesets
51 add changeset ef1ea85a6374
51 add changeset ef1ea85a6374
52 add changeset f9cafe1212c8
52 add changeset f9cafe1212c8
53 add changeset 911600dab2ae
53 add changeset 911600dab2ae
54 adding manifests
54 adding manifests
55 adding file changes
55 adding file changes
56 adding foo/Bar/file.txt revisions
56 adding foo/Bar/file.txt revisions
57 adding foo/file.txt revisions
57 adding foo/file.txt revisions
58 adding quux/file.py revisions
58 adding quux/file.py revisions
59 added 3 changesets with 3 changes to 3 files
59 added 3 changesets with 3 changes to 3 files
60 calling hook pretxnchangegroup.acl: hgext.acl.hook
60 calling hook pretxnchangegroup.acl: hgext.acl.hook
61 acl: changes have source "push" - skipping
61 acl: changes have source "push" - skipping
62 updating the branch cache
62 updating the branch cache
63 rolling back last transaction
63 rolling back last transaction
64 0:6675d58eff77
64 0:6675d58eff77
65
65
66 No [acl.allow]/[acl.deny]
66 No [acl.allow]/[acl.deny]
67 Pushing as user fred
67 Pushing as user fred
68 hgrc = """
68 hgrc = """
69 [hooks]
69 [hooks]
70 pretxnchangegroup.acl = python:hgext.acl.hook
70 pretxnchangegroup.acl = python:hgext.acl.hook
71 [acl]
71 [acl]
72 sources = push
72 sources = push
73 """
73 """
74 pushing to ../b
74 pushing to ../b
75 searching for changes
75 searching for changes
76 common changesets up to 6675d58eff77
76 common changesets up to 6675d58eff77
77 3 changesets found
77 3 changesets found
78 list of changesets:
78 list of changesets:
79 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
79 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
80 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
80 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
81 911600dab2ae7a9baff75958b84fe606851ce955
81 911600dab2ae7a9baff75958b84fe606851ce955
82 adding changesets
82 adding changesets
83 add changeset ef1ea85a6374
83 add changeset ef1ea85a6374
84 add changeset f9cafe1212c8
84 add changeset f9cafe1212c8
85 add changeset 911600dab2ae
85 add changeset 911600dab2ae
86 adding manifests
86 adding manifests
87 adding file changes
87 adding file changes
88 adding foo/Bar/file.txt revisions
88 adding foo/Bar/file.txt revisions
89 adding foo/file.txt revisions
89 adding foo/file.txt revisions
90 adding quux/file.py revisions
90 adding quux/file.py revisions
91 added 3 changesets with 3 changes to 3 files
91 added 3 changesets with 3 changes to 3 files
92 calling hook pretxnchangegroup.acl: hgext.acl.hook
92 calling hook pretxnchangegroup.acl: hgext.acl.hook
93 acl: acl.allow not enabled
93 acl: acl.allow not enabled
94 acl: acl.deny not enabled
94 acl: acl.deny not enabled
95 acl: allowing changeset ef1ea85a6374
95 acl: allowing changeset ef1ea85a6374
96 acl: allowing changeset f9cafe1212c8
96 acl: allowing changeset f9cafe1212c8
97 acl: allowing changeset 911600dab2ae
97 acl: allowing changeset 911600dab2ae
98 updating the branch cache
98 updating the branch cache
99 rolling back last transaction
99 rolling back last transaction
100 0:6675d58eff77
100 0:6675d58eff77
101
101
102 Empty [acl.allow]
102 Empty [acl.allow]
103 Pushing as user fred
103 Pushing as user fred
104 hgrc = """
104 hgrc = """
105 [hooks]
105 [hooks]
106 pretxnchangegroup.acl = python:hgext.acl.hook
106 pretxnchangegroup.acl = python:hgext.acl.hook
107 [acl]
107 [acl]
108 sources = push
108 sources = push
109 [acl.allow]
109 [acl.allow]
110 """
110 """
111 pushing to ../b
111 pushing to ../b
112 searching for changes
112 searching for changes
113 common changesets up to 6675d58eff77
113 common changesets up to 6675d58eff77
114 3 changesets found
114 3 changesets found
115 list of changesets:
115 list of changesets:
116 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
116 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
117 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
117 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
118 911600dab2ae7a9baff75958b84fe606851ce955
118 911600dab2ae7a9baff75958b84fe606851ce955
119 adding changesets
119 adding changesets
120 add changeset ef1ea85a6374
120 add changeset ef1ea85a6374
121 add changeset f9cafe1212c8
121 add changeset f9cafe1212c8
122 add changeset 911600dab2ae
122 add changeset 911600dab2ae
123 adding manifests
123 adding manifests
124 adding file changes
124 adding file changes
125 adding foo/Bar/file.txt revisions
125 adding foo/Bar/file.txt revisions
126 adding foo/file.txt revisions
126 adding foo/file.txt revisions
127 adding quux/file.py revisions
127 adding quux/file.py revisions
128 added 3 changesets with 3 changes to 3 files
128 added 3 changesets with 3 changes to 3 files
129 calling hook pretxnchangegroup.acl: hgext.acl.hook
129 calling hook pretxnchangegroup.acl: hgext.acl.hook
130 acl: acl.allow enabled, 0 entries for user fred
130 acl: acl.allow enabled, 0 entries for user fred
131 acl: acl.deny not enabled
131 acl: acl.deny not enabled
132 acl: user fred not allowed on foo/file.txt
132 acl: user fred not allowed on foo/file.txt
133 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
133 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
134 transaction abort!
134 transaction abort!
135 rollback completed
135 rollback completed
136 abort: acl: access denied for changeset ef1ea85a6374
136 abort: acl: access denied for changeset ef1ea85a6374
137 no rollback information available
137 no rollback information available
138 0:6675d58eff77
138 0:6675d58eff77
139
139
140 fred is allowed inside foo/
140 fred is allowed inside foo/
141 Pushing as user fred
141 Pushing as user fred
142 hgrc = """
142 hgrc = """
143 [hooks]
143 [hooks]
144 pretxnchangegroup.acl = python:hgext.acl.hook
144 pretxnchangegroup.acl = python:hgext.acl.hook
145 [acl]
145 [acl]
146 sources = push
146 sources = push
147 [acl.allow]
147 [acl.allow]
148 foo/** = fred
148 foo/** = fred
149 """
149 """
150 pushing to ../b
150 pushing to ../b
151 searching for changes
151 searching for changes
152 common changesets up to 6675d58eff77
152 common changesets up to 6675d58eff77
153 3 changesets found
153 3 changesets found
154 list of changesets:
154 list of changesets:
155 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
155 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
156 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
156 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
157 911600dab2ae7a9baff75958b84fe606851ce955
157 911600dab2ae7a9baff75958b84fe606851ce955
158 adding changesets
158 adding changesets
159 add changeset ef1ea85a6374
159 add changeset ef1ea85a6374
160 add changeset f9cafe1212c8
160 add changeset f9cafe1212c8
161 add changeset 911600dab2ae
161 add changeset 911600dab2ae
162 adding manifests
162 adding manifests
163 adding file changes
163 adding file changes
164 adding foo/Bar/file.txt revisions
164 adding foo/Bar/file.txt revisions
165 adding foo/file.txt revisions
165 adding foo/file.txt revisions
166 adding quux/file.py revisions
166 adding quux/file.py revisions
167 added 3 changesets with 3 changes to 3 files
167 added 3 changesets with 3 changes to 3 files
168 calling hook pretxnchangegroup.acl: hgext.acl.hook
168 calling hook pretxnchangegroup.acl: hgext.acl.hook
169 acl: acl.allow enabled, 1 entries for user fred
169 acl: acl.allow enabled, 1 entries for user fred
170 acl: acl.deny not enabled
170 acl: acl.deny not enabled
171 acl: allowing changeset ef1ea85a6374
171 acl: allowing changeset ef1ea85a6374
172 acl: allowing changeset f9cafe1212c8
172 acl: allowing changeset f9cafe1212c8
173 acl: user fred not allowed on quux/file.py
173 acl: user fred not allowed on quux/file.py
174 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
174 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
175 transaction abort!
175 transaction abort!
176 rollback completed
176 rollback completed
177 abort: acl: access denied for changeset 911600dab2ae
177 abort: acl: access denied for changeset 911600dab2ae
178 no rollback information available
178 no rollback information available
179 0:6675d58eff77
179 0:6675d58eff77
180
180
181 Empty [acl.deny]
181 Empty [acl.deny]
182 Pushing as user barney
182 Pushing as user barney
183 hgrc = """
183 hgrc = """
184 [hooks]
184 [hooks]
185 pretxnchangegroup.acl = python:hgext.acl.hook
185 pretxnchangegroup.acl = python:hgext.acl.hook
186 [acl]
186 [acl]
187 sources = push
187 sources = push
188 [acl.allow]
188 [acl.allow]
189 foo/** = fred
189 foo/** = fred
190 [acl.deny]
190 [acl.deny]
191 """
191 """
192 pushing to ../b
192 pushing to ../b
193 searching for changes
193 searching for changes
194 common changesets up to 6675d58eff77
194 common changesets up to 6675d58eff77
195 3 changesets found
195 3 changesets found
196 list of changesets:
196 list of changesets:
197 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
197 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
198 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
198 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
199 911600dab2ae7a9baff75958b84fe606851ce955
199 911600dab2ae7a9baff75958b84fe606851ce955
200 adding changesets
200 adding changesets
201 add changeset ef1ea85a6374
201 add changeset ef1ea85a6374
202 add changeset f9cafe1212c8
202 add changeset f9cafe1212c8
203 add changeset 911600dab2ae
203 add changeset 911600dab2ae
204 adding manifests
204 adding manifests
205 adding file changes
205 adding file changes
206 adding foo/Bar/file.txt revisions
206 adding foo/Bar/file.txt revisions
207 adding foo/file.txt revisions
207 adding foo/file.txt revisions
208 adding quux/file.py revisions
208 adding quux/file.py revisions
209 added 3 changesets with 3 changes to 3 files
209 added 3 changesets with 3 changes to 3 files
210 calling hook pretxnchangegroup.acl: hgext.acl.hook
210 calling hook pretxnchangegroup.acl: hgext.acl.hook
211 acl: acl.allow enabled, 0 entries for user barney
211 acl: acl.allow enabled, 0 entries for user barney
212 acl: acl.deny enabled, 0 entries for user barney
212 acl: acl.deny enabled, 0 entries for user barney
213 acl: user barney not allowed on foo/file.txt
213 acl: user barney not allowed on foo/file.txt
214 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
214 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
215 transaction abort!
215 transaction abort!
216 rollback completed
216 rollback completed
217 abort: acl: access denied for changeset ef1ea85a6374
217 abort: acl: access denied for changeset ef1ea85a6374
218 no rollback information available
218 no rollback information available
219 0:6675d58eff77
219 0:6675d58eff77
220
220
221 fred is allowed inside foo/, but not foo/bar/ (case matters)
221 fred is allowed inside foo/, but not foo/bar/ (case matters)
222 Pushing as user fred
222 Pushing as user fred
223 hgrc = """
223 hgrc = """
224 [hooks]
224 [hooks]
225 pretxnchangegroup.acl = python:hgext.acl.hook
225 pretxnchangegroup.acl = python:hgext.acl.hook
226 [acl]
226 [acl]
227 sources = push
227 sources = push
228 [acl.allow]
228 [acl.allow]
229 foo/** = fred
229 foo/** = fred
230 [acl.deny]
230 [acl.deny]
231 foo/bar/** = fred
231 foo/bar/** = fred
232 """
232 """
233 pushing to ../b
233 pushing to ../b
234 searching for changes
234 searching for changes
235 common changesets up to 6675d58eff77
235 common changesets up to 6675d58eff77
236 3 changesets found
236 3 changesets found
237 list of changesets:
237 list of changesets:
238 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
238 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
239 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
239 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
240 911600dab2ae7a9baff75958b84fe606851ce955
240 911600dab2ae7a9baff75958b84fe606851ce955
241 adding changesets
241 adding changesets
242 add changeset ef1ea85a6374
242 add changeset ef1ea85a6374
243 add changeset f9cafe1212c8
243 add changeset f9cafe1212c8
244 add changeset 911600dab2ae
244 add changeset 911600dab2ae
245 adding manifests
245 adding manifests
246 adding file changes
246 adding file changes
247 adding foo/Bar/file.txt revisions
247 adding foo/Bar/file.txt revisions
248 adding foo/file.txt revisions
248 adding foo/file.txt revisions
249 adding quux/file.py revisions
249 adding quux/file.py revisions
250 added 3 changesets with 3 changes to 3 files
250 added 3 changesets with 3 changes to 3 files
251 calling hook pretxnchangegroup.acl: hgext.acl.hook
251 calling hook pretxnchangegroup.acl: hgext.acl.hook
252 acl: acl.allow enabled, 1 entries for user fred
252 acl: acl.allow enabled, 1 entries for user fred
253 acl: acl.deny enabled, 1 entries for user fred
253 acl: acl.deny enabled, 1 entries for user fred
254 acl: allowing changeset ef1ea85a6374
254 acl: allowing changeset ef1ea85a6374
255 acl: allowing changeset f9cafe1212c8
255 acl: allowing changeset f9cafe1212c8
256 acl: user fred not allowed on quux/file.py
256 acl: user fred not allowed on quux/file.py
257 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
257 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
258 transaction abort!
258 transaction abort!
259 rollback completed
259 rollback completed
260 abort: acl: access denied for changeset 911600dab2ae
260 abort: acl: access denied for changeset 911600dab2ae
261 no rollback information available
261 no rollback information available
262 0:6675d58eff77
262 0:6675d58eff77
263
263
264 fred is allowed inside foo/, but not foo/Bar/
264 fred is allowed inside foo/, but not foo/Bar/
265 Pushing as user fred
265 Pushing as user fred
266 hgrc = """
266 hgrc = """
267 [hooks]
267 [hooks]
268 pretxnchangegroup.acl = python:hgext.acl.hook
268 pretxnchangegroup.acl = python:hgext.acl.hook
269 [acl]
269 [acl]
270 sources = push
270 sources = push
271 [acl.allow]
271 [acl.allow]
272 foo/** = fred
272 foo/** = fred
273 [acl.deny]
273 [acl.deny]
274 foo/bar/** = fred
274 foo/bar/** = fred
275 foo/Bar/** = fred
275 foo/Bar/** = fred
276 """
276 """
277 pushing to ../b
277 pushing to ../b
278 searching for changes
278 searching for changes
279 common changesets up to 6675d58eff77
279 common changesets up to 6675d58eff77
280 3 changesets found
280 3 changesets found
281 list of changesets:
281 list of changesets:
282 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
282 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
283 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
283 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
284 911600dab2ae7a9baff75958b84fe606851ce955
284 911600dab2ae7a9baff75958b84fe606851ce955
285 adding changesets
285 adding changesets
286 add changeset ef1ea85a6374
286 add changeset ef1ea85a6374
287 add changeset f9cafe1212c8
287 add changeset f9cafe1212c8
288 add changeset 911600dab2ae
288 add changeset 911600dab2ae
289 adding manifests
289 adding manifests
290 adding file changes
290 adding file changes
291 adding foo/Bar/file.txt revisions
291 adding foo/Bar/file.txt revisions
292 adding foo/file.txt revisions
292 adding foo/file.txt revisions
293 adding quux/file.py revisions
293 adding quux/file.py revisions
294 added 3 changesets with 3 changes to 3 files
294 added 3 changesets with 3 changes to 3 files
295 calling hook pretxnchangegroup.acl: hgext.acl.hook
295 calling hook pretxnchangegroup.acl: hgext.acl.hook
296 acl: acl.allow enabled, 1 entries for user fred
296 acl: acl.allow enabled, 1 entries for user fred
297 acl: acl.deny enabled, 2 entries for user fred
297 acl: acl.deny enabled, 2 entries for user fred
298 acl: allowing changeset ef1ea85a6374
298 acl: allowing changeset ef1ea85a6374
299 acl: user fred denied on foo/Bar/file.txt
299 acl: user fred denied on foo/Bar/file.txt
300 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset f9cafe1212c8
300 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset f9cafe1212c8
301 transaction abort!
301 transaction abort!
302 rollback completed
302 rollback completed
303 abort: acl: access denied for changeset f9cafe1212c8
303 abort: acl: access denied for changeset f9cafe1212c8
304 no rollback information available
304 no rollback information available
305 0:6675d58eff77
305 0:6675d58eff77
306
306
307 barney is not mentioned => not allowed anywhere
307 barney is not mentioned => not allowed anywhere
308 Pushing as user barney
308 Pushing as user barney
309 hgrc = """
309 hgrc = """
310 [hooks]
310 [hooks]
311 pretxnchangegroup.acl = python:hgext.acl.hook
311 pretxnchangegroup.acl = python:hgext.acl.hook
312 [acl]
312 [acl]
313 sources = push
313 sources = push
314 [acl.allow]
314 [acl.allow]
315 foo/** = fred
315 foo/** = fred
316 [acl.deny]
316 [acl.deny]
317 foo/bar/** = fred
317 foo/bar/** = fred
318 foo/Bar/** = fred
318 foo/Bar/** = fred
319 """
319 """
320 pushing to ../b
320 pushing to ../b
321 searching for changes
321 searching for changes
322 common changesets up to 6675d58eff77
322 common changesets up to 6675d58eff77
323 3 changesets found
323 3 changesets found
324 list of changesets:
324 list of changesets:
325 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
325 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
326 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
326 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
327 911600dab2ae7a9baff75958b84fe606851ce955
327 911600dab2ae7a9baff75958b84fe606851ce955
328 adding changesets
328 adding changesets
329 add changeset ef1ea85a6374
329 add changeset ef1ea85a6374
330 add changeset f9cafe1212c8
330 add changeset f9cafe1212c8
331 add changeset 911600dab2ae
331 add changeset 911600dab2ae
332 adding manifests
332 adding manifests
333 adding file changes
333 adding file changes
334 adding foo/Bar/file.txt revisions
334 adding foo/Bar/file.txt revisions
335 adding foo/file.txt revisions
335 adding foo/file.txt revisions
336 adding quux/file.py revisions
336 adding quux/file.py revisions
337 added 3 changesets with 3 changes to 3 files
337 added 3 changesets with 3 changes to 3 files
338 calling hook pretxnchangegroup.acl: hgext.acl.hook
338 calling hook pretxnchangegroup.acl: hgext.acl.hook
339 acl: acl.allow enabled, 0 entries for user barney
339 acl: acl.allow enabled, 0 entries for user barney
340 acl: acl.deny enabled, 0 entries for user barney
340 acl: acl.deny enabled, 0 entries for user barney
341 acl: user barney not allowed on foo/file.txt
341 acl: user barney not allowed on foo/file.txt
342 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
342 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset ef1ea85a6374
343 transaction abort!
343 transaction abort!
344 rollback completed
344 rollback completed
345 abort: acl: access denied for changeset ef1ea85a6374
345 abort: acl: access denied for changeset ef1ea85a6374
346 no rollback information available
346 no rollback information available
347 0:6675d58eff77
347 0:6675d58eff77
348
348
349 barney is allowed everywhere
349 barney is allowed everywhere
350 Pushing as user barney
350 Pushing as user barney
351 hgrc = """
351 hgrc = """
352 [hooks]
352 [hooks]
353 pretxnchangegroup.acl = python:hgext.acl.hook
353 pretxnchangegroup.acl = python:hgext.acl.hook
354 [acl]
354 [acl]
355 sources = push
355 sources = push
356 [acl.allow]
356 [acl.allow]
357 foo/** = fred
357 foo/** = fred
358 [acl.deny]
358 [acl.deny]
359 foo/bar/** = fred
359 foo/bar/** = fred
360 foo/Bar/** = fred
360 foo/Bar/** = fred
361 [acl.allow]
361 [acl.allow]
362 ** = barney
362 ** = barney
363 """
363 """
364 pushing to ../b
364 pushing to ../b
365 searching for changes
365 searching for changes
366 common changesets up to 6675d58eff77
366 common changesets up to 6675d58eff77
367 3 changesets found
367 3 changesets found
368 list of changesets:
368 list of changesets:
369 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
369 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
370 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
370 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
371 911600dab2ae7a9baff75958b84fe606851ce955
371 911600dab2ae7a9baff75958b84fe606851ce955
372 adding changesets
372 adding changesets
373 add changeset ef1ea85a6374
373 add changeset ef1ea85a6374
374 add changeset f9cafe1212c8
374 add changeset f9cafe1212c8
375 add changeset 911600dab2ae
375 add changeset 911600dab2ae
376 adding manifests
376 adding manifests
377 adding file changes
377 adding file changes
378 adding foo/Bar/file.txt revisions
378 adding foo/Bar/file.txt revisions
379 adding foo/file.txt revisions
379 adding foo/file.txt revisions
380 adding quux/file.py revisions
380 adding quux/file.py revisions
381 added 3 changesets with 3 changes to 3 files
381 added 3 changesets with 3 changes to 3 files
382 calling hook pretxnchangegroup.acl: hgext.acl.hook
382 calling hook pretxnchangegroup.acl: hgext.acl.hook
383 acl: acl.allow enabled, 1 entries for user barney
383 acl: acl.allow enabled, 1 entries for user barney
384 acl: acl.deny enabled, 0 entries for user barney
384 acl: acl.deny enabled, 0 entries for user barney
385 acl: allowing changeset ef1ea85a6374
385 acl: allowing changeset ef1ea85a6374
386 acl: allowing changeset f9cafe1212c8
386 acl: allowing changeset f9cafe1212c8
387 acl: allowing changeset 911600dab2ae
387 acl: allowing changeset 911600dab2ae
388 updating the branch cache
388 updating the branch cache
389 rolling back last transaction
389 rolling back last transaction
390 0:6675d58eff77
390 0:6675d58eff77
391
391
392 wilma can change files with a .txt extension
392 wilma can change files with a .txt extension
393 Pushing as user wilma
393 Pushing as user wilma
394 hgrc = """
394 hgrc = """
395 [hooks]
395 [hooks]
396 pretxnchangegroup.acl = python:hgext.acl.hook
396 pretxnchangegroup.acl = python:hgext.acl.hook
397 [acl]
397 [acl]
398 sources = push
398 sources = push
399 [acl.allow]
399 [acl.allow]
400 foo/** = fred
400 foo/** = fred
401 [acl.deny]
401 [acl.deny]
402 foo/bar/** = fred
402 foo/bar/** = fred
403 foo/Bar/** = fred
403 foo/Bar/** = fred
404 [acl.allow]
404 [acl.allow]
405 ** = barney
405 ** = barney
406 **/*.txt = wilma
406 **/*.txt = wilma
407 """
407 """
408 pushing to ../b
408 pushing to ../b
409 searching for changes
409 searching for changes
410 common changesets up to 6675d58eff77
410 common changesets up to 6675d58eff77
411 3 changesets found
411 3 changesets found
412 list of changesets:
412 list of changesets:
413 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
413 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
414 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
414 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
415 911600dab2ae7a9baff75958b84fe606851ce955
415 911600dab2ae7a9baff75958b84fe606851ce955
416 adding changesets
416 adding changesets
417 add changeset ef1ea85a6374
417 add changeset ef1ea85a6374
418 add changeset f9cafe1212c8
418 add changeset f9cafe1212c8
419 add changeset 911600dab2ae
419 add changeset 911600dab2ae
420 adding manifests
420 adding manifests
421 adding file changes
421 adding file changes
422 adding foo/Bar/file.txt revisions
422 adding foo/Bar/file.txt revisions
423 adding foo/file.txt revisions
423 adding foo/file.txt revisions
424 adding quux/file.py revisions
424 adding quux/file.py revisions
425 added 3 changesets with 3 changes to 3 files
425 added 3 changesets with 3 changes to 3 files
426 calling hook pretxnchangegroup.acl: hgext.acl.hook
426 calling hook pretxnchangegroup.acl: hgext.acl.hook
427 acl: acl.allow enabled, 1 entries for user wilma
427 acl: acl.allow enabled, 1 entries for user wilma
428 acl: acl.deny enabled, 0 entries for user wilma
428 acl: acl.deny enabled, 0 entries for user wilma
429 acl: allowing changeset ef1ea85a6374
429 acl: allowing changeset ef1ea85a6374
430 acl: allowing changeset f9cafe1212c8
430 acl: allowing changeset f9cafe1212c8
431 acl: user wilma not allowed on quux/file.py
431 acl: user wilma not allowed on quux/file.py
432 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
432 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
433 transaction abort!
433 transaction abort!
434 rollback completed
434 rollback completed
435 abort: acl: access denied for changeset 911600dab2ae
435 abort: acl: access denied for changeset 911600dab2ae
436 no rollback information available
436 no rollback information available
437 0:6675d58eff77
437 0:6675d58eff77
438
438
439 file specified by acl.config does not exist
439 file specified by acl.config does not exist
440 Pushing as user barney
440 Pushing as user barney
441 hgrc = """
441 hgrc = """
442 [hooks]
442 [hooks]
443 pretxnchangegroup.acl = python:hgext.acl.hook
443 pretxnchangegroup.acl = python:hgext.acl.hook
444 [acl]
444 [acl]
445 sources = push
445 sources = push
446 [acl.allow]
446 [acl.allow]
447 foo/** = fred
447 foo/** = fred
448 [acl.deny]
448 [acl.deny]
449 foo/bar/** = fred
449 foo/bar/** = fred
450 foo/Bar/** = fred
450 foo/Bar/** = fred
451 [acl.allow]
451 [acl.allow]
452 ** = barney
452 ** = barney
453 **/*.txt = wilma
453 **/*.txt = wilma
454 [acl]
454 [acl]
455 config = ../acl.config
455 config = ../acl.config
456 """
456 """
457 pushing to ../b
457 pushing to ../b
458 searching for changes
458 searching for changes
459 common changesets up to 6675d58eff77
459 common changesets up to 6675d58eff77
460 3 changesets found
460 3 changesets found
461 list of changesets:
461 list of changesets:
462 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
462 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
463 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
463 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
464 911600dab2ae7a9baff75958b84fe606851ce955
464 911600dab2ae7a9baff75958b84fe606851ce955
465 adding changesets
465 adding changesets
466 add changeset ef1ea85a6374
466 add changeset ef1ea85a6374
467 add changeset f9cafe1212c8
467 add changeset f9cafe1212c8
468 add changeset 911600dab2ae
468 add changeset 911600dab2ae
469 adding manifests
469 adding manifests
470 adding file changes
470 adding file changes
471 adding foo/Bar/file.txt revisions
471 adding foo/Bar/file.txt revisions
472 adding foo/file.txt revisions
472 adding foo/file.txt revisions
473 adding quux/file.py revisions
473 adding quux/file.py revisions
474 added 3 changesets with 3 changes to 3 files
474 added 3 changesets with 3 changes to 3 files
475 calling hook pretxnchangegroup.acl: hgext.acl.hook
475 calling hook pretxnchangegroup.acl: hgext.acl.hook
476 error: pretxnchangegroup.acl hook failed: unable to open ../acl.config: No such file or directory
476 error: pretxnchangegroup.acl hook raised an exception: [Errno 2] No such file or directory: '../acl.config'
477 transaction abort!
477 transaction abort!
478 rollback completed
478 rollback completed
479 abort: unable to open ../acl.config: No such file or directory
479 abort: No such file or directory: ../acl.config
480 no rollback information available
480 no rollback information available
481 0:6675d58eff77
481 0:6675d58eff77
482
482
483 betty is allowed inside foo/ by a acl.config file
483 betty is allowed inside foo/ by a acl.config file
484 Pushing as user betty
484 Pushing as user betty
485 hgrc = """
485 hgrc = """
486 [hooks]
486 [hooks]
487 pretxnchangegroup.acl = python:hgext.acl.hook
487 pretxnchangegroup.acl = python:hgext.acl.hook
488 [acl]
488 [acl]
489 sources = push
489 sources = push
490 [acl.allow]
490 [acl.allow]
491 foo/** = fred
491 foo/** = fred
492 [acl.deny]
492 [acl.deny]
493 foo/bar/** = fred
493 foo/bar/** = fred
494 foo/Bar/** = fred
494 foo/Bar/** = fred
495 [acl.allow]
495 [acl.allow]
496 ** = barney
496 ** = barney
497 **/*.txt = wilma
497 **/*.txt = wilma
498 [acl]
498 [acl]
499 config = ../acl.config
499 config = ../acl.config
500 """
500 """
501 acl.config = """
501 acl.config = """
502 [acl.allow]
502 [acl.allow]
503 foo/** = betty
503 foo/** = betty
504 """
504 """
505 pushing to ../b
505 pushing to ../b
506 searching for changes
506 searching for changes
507 common changesets up to 6675d58eff77
507 common changesets up to 6675d58eff77
508 3 changesets found
508 3 changesets found
509 list of changesets:
509 list of changesets:
510 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
510 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
511 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
511 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
512 911600dab2ae7a9baff75958b84fe606851ce955
512 911600dab2ae7a9baff75958b84fe606851ce955
513 adding changesets
513 adding changesets
514 add changeset ef1ea85a6374
514 add changeset ef1ea85a6374
515 add changeset f9cafe1212c8
515 add changeset f9cafe1212c8
516 add changeset 911600dab2ae
516 add changeset 911600dab2ae
517 adding manifests
517 adding manifests
518 adding file changes
518 adding file changes
519 adding foo/Bar/file.txt revisions
519 adding foo/Bar/file.txt revisions
520 adding foo/file.txt revisions
520 adding foo/file.txt revisions
521 adding quux/file.py revisions
521 adding quux/file.py revisions
522 added 3 changesets with 3 changes to 3 files
522 added 3 changesets with 3 changes to 3 files
523 calling hook pretxnchangegroup.acl: hgext.acl.hook
523 calling hook pretxnchangegroup.acl: hgext.acl.hook
524 acl: acl.allow enabled, 1 entries for user betty
524 acl: acl.allow enabled, 1 entries for user betty
525 acl: acl.deny enabled, 0 entries for user betty
525 acl: acl.deny enabled, 0 entries for user betty
526 acl: allowing changeset ef1ea85a6374
526 acl: allowing changeset ef1ea85a6374
527 acl: allowing changeset f9cafe1212c8
527 acl: allowing changeset f9cafe1212c8
528 acl: user betty not allowed on quux/file.py
528 acl: user betty not allowed on quux/file.py
529 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
529 error: pretxnchangegroup.acl hook failed: acl: access denied for changeset 911600dab2ae
530 transaction abort!
530 transaction abort!
531 rollback completed
531 rollback completed
532 abort: acl: access denied for changeset 911600dab2ae
532 abort: acl: access denied for changeset 911600dab2ae
533 no rollback information available
533 no rollback information available
534 0:6675d58eff77
534 0:6675d58eff77
535
535
536 acl.config can set only [acl.allow]/[acl.deny]
536 acl.config can set only [acl.allow]/[acl.deny]
537 Pushing as user barney
537 Pushing as user barney
538 hgrc = """
538 hgrc = """
539 [hooks]
539 [hooks]
540 pretxnchangegroup.acl = python:hgext.acl.hook
540 pretxnchangegroup.acl = python:hgext.acl.hook
541 [acl]
541 [acl]
542 sources = push
542 sources = push
543 [acl.allow]
543 [acl.allow]
544 foo/** = fred
544 foo/** = fred
545 [acl.deny]
545 [acl.deny]
546 foo/bar/** = fred
546 foo/bar/** = fred
547 foo/Bar/** = fred
547 foo/Bar/** = fred
548 [acl.allow]
548 [acl.allow]
549 ** = barney
549 ** = barney
550 **/*.txt = wilma
550 **/*.txt = wilma
551 [acl]
551 [acl]
552 config = ../acl.config
552 config = ../acl.config
553 """
553 """
554 acl.config = """
554 acl.config = """
555 [acl.allow]
555 [acl.allow]
556 foo/** = betty
556 foo/** = betty
557 [hooks]
557 [hooks]
558 changegroup.acl = false
558 changegroup.acl = false
559 """
559 """
560 pushing to ../b
560 pushing to ../b
561 searching for changes
561 searching for changes
562 common changesets up to 6675d58eff77
562 common changesets up to 6675d58eff77
563 3 changesets found
563 3 changesets found
564 list of changesets:
564 list of changesets:
565 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
565 ef1ea85a6374b77d6da9dcda9541f498f2d17df7
566 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
566 f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
567 911600dab2ae7a9baff75958b84fe606851ce955
567 911600dab2ae7a9baff75958b84fe606851ce955
568 adding changesets
568 adding changesets
569 add changeset ef1ea85a6374
569 add changeset ef1ea85a6374
570 add changeset f9cafe1212c8
570 add changeset f9cafe1212c8
571 add changeset 911600dab2ae
571 add changeset 911600dab2ae
572 adding manifests
572 adding manifests
573 adding file changes
573 adding file changes
574 adding foo/Bar/file.txt revisions
574 adding foo/Bar/file.txt revisions
575 adding foo/file.txt revisions
575 adding foo/file.txt revisions
576 adding quux/file.py revisions
576 adding quux/file.py revisions
577 added 3 changesets with 3 changes to 3 files
577 added 3 changesets with 3 changes to 3 files
578 calling hook pretxnchangegroup.acl: hgext.acl.hook
578 calling hook pretxnchangegroup.acl: hgext.acl.hook
579 acl: acl.allow enabled, 1 entries for user barney
579 acl: acl.allow enabled, 1 entries for user barney
580 acl: acl.deny enabled, 0 entries for user barney
580 acl: acl.deny enabled, 0 entries for user barney
581 acl: allowing changeset ef1ea85a6374
581 acl: allowing changeset ef1ea85a6374
582 acl: allowing changeset f9cafe1212c8
582 acl: allowing changeset f9cafe1212c8
583 acl: allowing changeset 911600dab2ae
583 acl: allowing changeset 911600dab2ae
584 updating the branch cache
584 updating the branch cache
585 rolling back last transaction
585 rolling back last transaction
586 0:6675d58eff77
586 0:6675d58eff77
587
587
@@ -1,100 +1,100 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 cat <<EOF >> $HGRCPATH
3 cat <<EOF >> $HGRCPATH
4 [extensions]
4 [extensions]
5 notify=
5 notify=
6
6
7 [hooks]
7 [hooks]
8 incoming.notify = python:hgext.notify.hook
8 incoming.notify = python:hgext.notify.hook
9
9
10 [notify]
10 [notify]
11 sources = pull
11 sources = pull
12 diffstat = False
12 diffstat = False
13
13
14 [usersubs]
14 [usersubs]
15 foo@bar = *
15 foo@bar = *
16
16
17 [reposubs]
17 [reposubs]
18 * = baz
18 * = baz
19 EOF
19 EOF
20
20
21 hg help notify
21 hg help notify
22 hg init a
22 hg init a
23 echo a > a/a
23 echo a > a/a
24 echo % commit
24 echo % commit
25 hg --traceback --cwd a commit -Ama -d '0 0'
25 hg --traceback --cwd a commit -Ama -d '0 0'
26
26
27 echo % clone
27 echo % clone
28 hg --traceback clone a b
28 hg --traceback clone a b
29
29
30 echo a >> a/a
30 echo a >> a/a
31 echo % commit
31 echo % commit
32 hg --traceback --cwd a commit -Amb -d '1 0'
32 hg --traceback --cwd a commit -Amb -d '1 0'
33
33
34 # on Mac OS X 10.5 the tmp path is very long so would get stripped in the subject line
34 # on Mac OS X 10.5 the tmp path is very long so would get stripped in the subject line
35 cat <<EOF >> $HGRCPATH
35 cat <<EOF >> $HGRCPATH
36 [notify]
36 [notify]
37 maxsubject = 200
37 maxsubject = 200
38 EOF
38 EOF
39
39
40 # the python call below wraps continuation lines, which appear on Mac OS X 10.5 because
40 # the python call below wraps continuation lines, which appear on Mac OS X 10.5 because
41 # of the very long subject line
41 # of the very long subject line
42 echo '% pull (minimal config)'
42 echo '% pull (minimal config)'
43 hg --traceback --cwd b pull ../a 2>&1 |
43 hg --traceback --cwd b pull ../a 2>&1 |
44 python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),' |
44 python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),' |
45 sed -e 's/\(Message-Id:\).*/\1/' \
45 sed -e 's/\(Message-Id:\).*/\1/' \
46 -e 's/changeset \([0-9a-f]* *\)in .*test-notif/changeset \1in test-notif/' \
46 -e 's/changeset \([0-9a-f]* *\)in .*test-notif/changeset \1in test-notif/' \
47 -e 's/^details: .*test-notify/details: test-notify/' \
47 -e 's/^details: .*test-notify/details: test-notify/' \
48 -e 's/^Date:.*/Date:/'
48 -e 's/^Date:.*/Date:/'
49
49
50 cat <<EOF >> $HGRCPATH
50 cat <<EOF >> $HGRCPATH
51 [notify]
51 [notify]
52 config = $HGTMP/.notify.conf
52 config = $HGTMP/.notify.conf
53 domain = test.com
53 domain = test.com
54 strip = 3
54 strip = 3
55 template = Subject: {desc|firstline|strip}\nFrom: {author}\nX-Test: foo\n\nchangeset {node|short} in {webroot}\ndescription:\n\t{desc|tabindent|strip}
55 template = Subject: {desc|firstline|strip}\nFrom: {author}\nX-Test: foo\n\nchangeset {node|short} in {webroot}\ndescription:\n\t{desc|tabindent|strip}
56
56
57 [web]
57 [web]
58 baseurl = http://test/
58 baseurl = http://test/
59 EOF
59 EOF
60
60
61 echo % fail for config file is missing
61 echo % fail for config file is missing
62 hg --cwd b rollback
62 hg --cwd b rollback
63 hg --cwd b pull ../a 2>&1 | grep 'unable to open.*\.notify\.conf' > /dev/null && echo pull failed
63 hg --cwd b pull ../a 2>&1 | grep 'error.*\.notify\.conf' > /dev/null && echo pull failed
64
64
65 touch "$HGTMP/.notify.conf"
65 touch "$HGTMP/.notify.conf"
66
66
67 echo % pull
67 echo % pull
68 hg --cwd b rollback
68 hg --cwd b rollback
69 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
69 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
70 -e 's/changeset \([0-9a-f]*\) in .*/changeset \1/' \
70 -e 's/changeset \([0-9a-f]*\) in .*/changeset \1/' \
71 -e 's/^Date:.*/Date:/'
71 -e 's/^Date:.*/Date:/'
72
72
73 cat << EOF >> $HGRCPATH
73 cat << EOF >> $HGRCPATH
74 [hooks]
74 [hooks]
75 incoming.notify = python:hgext.notify.hook
75 incoming.notify = python:hgext.notify.hook
76
76
77 [notify]
77 [notify]
78 sources = pull
78 sources = pull
79 diffstat = True
79 diffstat = True
80 EOF
80 EOF
81
81
82 echo % pull
82 echo % pull
83 hg --cwd b rollback
83 hg --cwd b rollback
84 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
84 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
85 -e 's/changeset \([0-9a-f]*\) in .*/changeset \1/' \
85 -e 's/changeset \([0-9a-f]*\) in .*/changeset \1/' \
86 -e 's/^Date:.*/Date:/'
86 -e 's/^Date:.*/Date:/'
87
87
88 echo % test merge
88 echo % test merge
89 cd a
89 cd a
90 hg up -C 0
90 hg up -C 0
91 echo a >> a
91 echo a >> a
92 hg ci -Am adda2 -d '2 0'
92 hg ci -Am adda2 -d '2 0'
93 hg merge
93 hg merge
94 hg ci -m merge -d '3 0'
94 hg ci -m merge -d '3 0'
95 cd ..
95 cd ..
96
96
97 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
97 hg --traceback --cwd b pull ../a 2>&1 | sed -e 's/\(Message-Id:\).*/\1/' \
98 -e 's/changeset \([0-9a-f]*\) in .*/changeset \1/' \
98 -e 's/changeset \([0-9a-f]*\) in .*/changeset \1/' \
99 -e 's/^Date:.*/Date:/'
99 -e 's/^Date:.*/Date:/'
100
100
@@ -1,211 +1,211 b''
1 # Since it's not easy to write a test that portably deals
1 # Since it's not easy to write a test that portably deals
2 # with files from different users/groups, we cheat a bit by
2 # with files from different users/groups, we cheat a bit by
3 # monkey-patching some functions in the util module
3 # monkey-patching some functions in the util module
4
4
5 import os
5 import os
6 from mercurial import ui, util
6 from mercurial import ui, util
7
7
8 hgrc = os.environ['HGRCPATH']
8 hgrc = os.environ['HGRCPATH']
9 f = open(hgrc)
9 f = open(hgrc)
10 basehgrc = f.read()
10 basehgrc = f.read()
11 f.close()
11 f.close()
12
12
13 def testui(user='foo', group='bar', tusers=(), tgroups=(),
13 def testui(user='foo', group='bar', tusers=(), tgroups=(),
14 cuser='foo', cgroup='bar', debug=False, silent=False):
14 cuser='foo', cgroup='bar', debug=False, silent=False):
15 # user, group => owners of the file
15 # user, group => owners of the file
16 # tusers, tgroups => trusted users/groups
16 # tusers, tgroups => trusted users/groups
17 # cuser, cgroup => user/group of the current process
17 # cuser, cgroup => user/group of the current process
18
18
19 # write a global hgrc with the list of trusted users/groups and
19 # write a global hgrc with the list of trusted users/groups and
20 # some setting so that we can be sure it was read
20 # some setting so that we can be sure it was read
21 f = open(hgrc, 'w')
21 f = open(hgrc, 'w')
22 f.write(basehgrc)
22 f.write(basehgrc)
23 f.write('\n[paths]\n')
23 f.write('\n[paths]\n')
24 f.write('global = /some/path\n\n')
24 f.write('global = /some/path\n\n')
25
25
26 if tusers or tgroups:
26 if tusers or tgroups:
27 f.write('[trusted]\n')
27 f.write('[trusted]\n')
28 if tusers:
28 if tusers:
29 f.write('users = %s\n' % ', '.join(tusers))
29 f.write('users = %s\n' % ', '.join(tusers))
30 if tgroups:
30 if tgroups:
31 f.write('groups = %s\n' % ', '.join(tgroups))
31 f.write('groups = %s\n' % ', '.join(tgroups))
32 f.close()
32 f.close()
33
33
34 # override the functions that give names to uids and gids
34 # override the functions that give names to uids and gids
35 def username(uid=None):
35 def username(uid=None):
36 if uid is None:
36 if uid is None:
37 return cuser
37 return cuser
38 return user
38 return user
39 util.username = username
39 util.username = username
40
40
41 def groupname(gid=None):
41 def groupname(gid=None):
42 if gid is None:
42 if gid is None:
43 return 'bar'
43 return 'bar'
44 return group
44 return group
45 util.groupname = groupname
45 util.groupname = groupname
46
46
47 def isowner(fp, st=None):
47 def isowner(fp, st=None):
48 return user == cuser
48 return user == cuser
49 util.isowner = isowner
49 util.isowner = isowner
50
50
51 # try to read everything
51 # try to read everything
52 #print '# File belongs to user %s, group %s' % (user, group)
52 #print '# File belongs to user %s, group %s' % (user, group)
53 #print '# trusted users = %s; trusted groups = %s' % (tusers, tgroups)
53 #print '# trusted users = %s; trusted groups = %s' % (tusers, tgroups)
54 kind = ('different', 'same')
54 kind = ('different', 'same')
55 who = ('', 'user', 'group', 'user and the group')
55 who = ('', 'user', 'group', 'user and the group')
56 trusted = who[(user in tusers) + 2*(group in tgroups)]
56 trusted = who[(user in tusers) + 2*(group in tgroups)]
57 if trusted:
57 if trusted:
58 trusted = ', but we trust the ' + trusted
58 trusted = ', but we trust the ' + trusted
59 print '# %s user, %s group%s' % (kind[user == cuser], kind[group == cgroup],
59 print '# %s user, %s group%s' % (kind[user == cuser], kind[group == cgroup],
60 trusted)
60 trusted)
61
61
62 parentui = ui.ui()
62 parentui = ui.ui()
63 parentui.setconfig('ui', 'debug', str(bool(debug)))
63 parentui.setconfig('ui', 'debug', str(bool(debug)))
64 u = ui.ui(parentui=parentui)
64 u = ui.ui(parentui=parentui)
65 u.readconfig('.hg/hgrc')
65 u.readconfig('.hg/hgrc')
66 if silent:
66 if silent:
67 return u
67 return u
68 print 'trusted'
68 print 'trusted'
69 for name, path in u.configitems('paths'):
69 for name, path in u.configitems('paths'):
70 print ' ', name, '=', path
70 print ' ', name, '=', path
71 print 'untrusted'
71 print 'untrusted'
72 for name, path in u.configitems('paths', untrusted=True):
72 for name, path in u.configitems('paths', untrusted=True):
73 print '.',
73 print '.',
74 u.config('paths', name) # warning with debug=True
74 u.config('paths', name) # warning with debug=True
75 print '.',
75 print '.',
76 u.config('paths', name, untrusted=True) # no warnings
76 u.config('paths', name, untrusted=True) # no warnings
77 print name, '=', path
77 print name, '=', path
78 print
78 print
79
79
80 return u
80 return u
81
81
82 os.mkdir('repo')
82 os.mkdir('repo')
83 os.chdir('repo')
83 os.chdir('repo')
84 os.mkdir('.hg')
84 os.mkdir('.hg')
85 f = open('.hg/hgrc', 'w')
85 f = open('.hg/hgrc', 'w')
86 f.write('[paths]\n')
86 f.write('[paths]\n')
87 f.write('local = /another/path\n\n')
87 f.write('local = /another/path\n\n')
88 f.write('interpolated = %(global)s%(local)s\n\n')
88 f.write('interpolated = %(global)s%(local)s\n\n')
89 f.close()
89 f.close()
90
90
91 #print '# Everything is run by user foo, group bar\n'
91 #print '# Everything is run by user foo, group bar\n'
92
92
93 # same user, same group
93 # same user, same group
94 testui()
94 testui()
95 # same user, different group
95 # same user, different group
96 testui(group='def')
96 testui(group='def')
97 # different user, same group
97 # different user, same group
98 testui(user='abc')
98 testui(user='abc')
99 # ... but we trust the group
99 # ... but we trust the group
100 testui(user='abc', tgroups=['bar'])
100 testui(user='abc', tgroups=['bar'])
101 # different user, different group
101 # different user, different group
102 testui(user='abc', group='def')
102 testui(user='abc', group='def')
103 # ... but we trust the user
103 # ... but we trust the user
104 testui(user='abc', group='def', tusers=['abc'])
104 testui(user='abc', group='def', tusers=['abc'])
105 # ... but we trust the group
105 # ... but we trust the group
106 testui(user='abc', group='def', tgroups=['def'])
106 testui(user='abc', group='def', tgroups=['def'])
107 # ... but we trust the user and the group
107 # ... but we trust the user and the group
108 testui(user='abc', group='def', tusers=['abc'], tgroups=['def'])
108 testui(user='abc', group='def', tusers=['abc'], tgroups=['def'])
109 # ... but we trust all users
109 # ... but we trust all users
110 print '# we trust all users'
110 print '# we trust all users'
111 testui(user='abc', group='def', tusers=['*'])
111 testui(user='abc', group='def', tusers=['*'])
112 # ... but we trust all groups
112 # ... but we trust all groups
113 print '# we trust all groups'
113 print '# we trust all groups'
114 testui(user='abc', group='def', tgroups=['*'])
114 testui(user='abc', group='def', tgroups=['*'])
115 # ... but we trust the whole universe
115 # ... but we trust the whole universe
116 print '# we trust all users and groups'
116 print '# we trust all users and groups'
117 testui(user='abc', group='def', tusers=['*'], tgroups=['*'])
117 testui(user='abc', group='def', tusers=['*'], tgroups=['*'])
118 # ... check that users and groups are in different namespaces
118 # ... check that users and groups are in different namespaces
119 print "# we don't get confused by users and groups with the same name"
119 print "# we don't get confused by users and groups with the same name"
120 testui(user='abc', group='def', tusers=['def'], tgroups=['abc'])
120 testui(user='abc', group='def', tusers=['def'], tgroups=['abc'])
121 # ... lists of user names work
121 # ... lists of user names work
122 print "# list of user names"
122 print "# list of user names"
123 testui(user='abc', group='def', tusers=['foo', 'xyz', 'abc', 'bleh'],
123 testui(user='abc', group='def', tusers=['foo', 'xyz', 'abc', 'bleh'],
124 tgroups=['bar', 'baz', 'qux'])
124 tgroups=['bar', 'baz', 'qux'])
125 # ... lists of group names work
125 # ... lists of group names work
126 print "# list of group names"
126 print "# list of group names"
127 testui(user='abc', group='def', tusers=['foo', 'xyz', 'bleh'],
127 testui(user='abc', group='def', tusers=['foo', 'xyz', 'bleh'],
128 tgroups=['bar', 'def', 'baz', 'qux'])
128 tgroups=['bar', 'def', 'baz', 'qux'])
129
129
130 print "# Can't figure out the name of the user running this process"
130 print "# Can't figure out the name of the user running this process"
131 testui(user='abc', group='def', cuser=None)
131 testui(user='abc', group='def', cuser=None)
132
132
133 print "# prints debug warnings"
133 print "# prints debug warnings"
134 u = testui(user='abc', group='def', cuser='foo', debug=True)
134 u = testui(user='abc', group='def', cuser='foo', debug=True)
135
135
136 print "# ui.readsections"
136 print "# ui.readconfig sections"
137 filename = 'foobar'
137 filename = 'foobar'
138 f = open(filename, 'w')
138 f = open(filename, 'w')
139 f.write('[foobar]\n')
139 f.write('[foobar]\n')
140 f.write('baz = quux\n')
140 f.write('baz = quux\n')
141 f.close()
141 f.close()
142 u.readsections(filename, 'foobar')
142 u.readconfig(filename, sections = ['foobar'])
143 print u.config('foobar', 'baz')
143 print u.config('foobar', 'baz')
144
144
145 print
145 print
146 print "# read trusted, untrusted, new ui, trusted"
146 print "# read trusted, untrusted, new ui, trusted"
147 u = ui.ui()
147 u = ui.ui()
148 u.setconfig('ui', 'debug', 'on')
148 u.setconfig('ui', 'debug', 'on')
149 u.readconfig(filename)
149 u.readconfig(filename)
150 u2 = ui.ui(parentui=u)
150 u2 = ui.ui(parentui=u)
151 def username(uid=None):
151 def username(uid=None):
152 return 'foo'
152 return 'foo'
153 util.username = username
153 util.username = username
154 u2.readconfig('.hg/hgrc')
154 u2.readconfig('.hg/hgrc')
155 print 'trusted:'
155 print 'trusted:'
156 print u2.config('foobar', 'baz')
156 print u2.config('foobar', 'baz')
157 print u2.config('paths', 'interpolated')
157 print u2.config('paths', 'interpolated')
158 print 'untrusted:'
158 print 'untrusted:'
159 print u2.config('foobar', 'baz', untrusted=True)
159 print u2.config('foobar', 'baz', untrusted=True)
160 print u2.config('paths', 'interpolated', untrusted=True)
160 print u2.config('paths', 'interpolated', untrusted=True)
161
161
162 print
162 print
163 print "# error handling"
163 print "# error handling"
164
164
165 def assertraises(f, exc=util.Abort):
165 def assertraises(f, exc=util.Abort):
166 try:
166 try:
167 f()
167 f()
168 except exc, inst:
168 except exc, inst:
169 print 'raised', inst.__class__.__name__
169 print 'raised', inst.__class__.__name__
170 else:
170 else:
171 print 'no exception?!'
171 print 'no exception?!'
172
172
173 print "# file doesn't exist"
173 print "# file doesn't exist"
174 os.unlink('.hg/hgrc')
174 os.unlink('.hg/hgrc')
175 assert not os.path.exists('.hg/hgrc')
175 assert not os.path.exists('.hg/hgrc')
176 testui(debug=True, silent=True)
176 testui(debug=True, silent=True)
177 testui(user='abc', group='def', debug=True, silent=True)
177 testui(user='abc', group='def', debug=True, silent=True)
178
178
179 print
179 print
180 print "# parse error"
180 print "# parse error"
181 f = open('.hg/hgrc', 'w')
181 f = open('.hg/hgrc', 'w')
182 f.write('foo = bar')
182 f.write('foo = bar')
183 f.close()
183 f.close()
184 testui(user='abc', group='def', silent=True)
184 testui(user='abc', group='def', silent=True)
185 assertraises(lambda: testui(debug=True, silent=True))
185 assertraises(lambda: testui(debug=True, silent=True))
186
186
187 print
187 print
188 print "# interpolation error"
188 print "# interpolation error"
189 f = open('.hg/hgrc', 'w')
189 f = open('.hg/hgrc', 'w')
190 f.write('[foo]\n')
190 f.write('[foo]\n')
191 f.write('bar = %(')
191 f.write('bar = %(')
192 f.close()
192 f.close()
193 u = testui(debug=True, silent=True)
193 u = testui(debug=True, silent=True)
194 print '# regular config:'
194 print '# regular config:'
195 print ' trusted',
195 print ' trusted',
196 assertraises(lambda: u.config('foo', 'bar'))
196 assertraises(lambda: u.config('foo', 'bar'))
197 print 'untrusted',
197 print 'untrusted',
198 assertraises(lambda: u.config('foo', 'bar', untrusted=True))
198 assertraises(lambda: u.config('foo', 'bar', untrusted=True))
199
199
200 u = testui(user='abc', group='def', debug=True, silent=True)
200 u = testui(user='abc', group='def', debug=True, silent=True)
201 print ' trusted ',
201 print ' trusted ',
202 print u.config('foo', 'bar')
202 print u.config('foo', 'bar')
203 print 'untrusted',
203 print 'untrusted',
204 assertraises(lambda: u.config('foo', 'bar', untrusted=True))
204 assertraises(lambda: u.config('foo', 'bar', untrusted=True))
205
205
206 print '# configitems:'
206 print '# configitems:'
207 print ' trusted ',
207 print ' trusted ',
208 print u.configitems('foo')
208 print u.configitems('foo')
209 print 'untrusted',
209 print 'untrusted',
210 assertraises(lambda: u.configitems('foo', untrusted=True))
210 assertraises(lambda: u.configitems('foo', untrusted=True))
211
211
@@ -1,211 +1,211 b''
1 # same user, same group
1 # same user, same group
2 trusted
2 trusted
3 global = /some/path
3 global = /some/path
4 interpolated = /some/path/another/path
4 interpolated = /some/path/another/path
5 local = /another/path
5 local = /another/path
6 untrusted
6 untrusted
7 . . global = /some/path
7 . . global = /some/path
8 . . interpolated = /some/path/another/path
8 . . interpolated = /some/path/another/path
9 . . local = /another/path
9 . . local = /another/path
10
10
11 # same user, different group
11 # same user, different group
12 trusted
12 trusted
13 global = /some/path
13 global = /some/path
14 interpolated = /some/path/another/path
14 interpolated = /some/path/another/path
15 local = /another/path
15 local = /another/path
16 untrusted
16 untrusted
17 . . global = /some/path
17 . . global = /some/path
18 . . interpolated = /some/path/another/path
18 . . interpolated = /some/path/another/path
19 . . local = /another/path
19 . . local = /another/path
20
20
21 # different user, same group
21 # different user, same group
22 Not trusting file .hg/hgrc from untrusted user abc, group bar
22 Not trusting file .hg/hgrc from untrusted user abc, group bar
23 trusted
23 trusted
24 global = /some/path
24 global = /some/path
25 untrusted
25 untrusted
26 . . global = /some/path
26 . . global = /some/path
27 . . interpolated = /some/path/another/path
27 . . interpolated = /some/path/another/path
28 . . local = /another/path
28 . . local = /another/path
29
29
30 # different user, same group, but we trust the group
30 # different user, same group, but we trust the group
31 trusted
31 trusted
32 global = /some/path
32 global = /some/path
33 interpolated = /some/path/another/path
33 interpolated = /some/path/another/path
34 local = /another/path
34 local = /another/path
35 untrusted
35 untrusted
36 . . global = /some/path
36 . . global = /some/path
37 . . interpolated = /some/path/another/path
37 . . interpolated = /some/path/another/path
38 . . local = /another/path
38 . . local = /another/path
39
39
40 # different user, different group
40 # different user, different group
41 Not trusting file .hg/hgrc from untrusted user abc, group def
41 Not trusting file .hg/hgrc from untrusted user abc, group def
42 trusted
42 trusted
43 global = /some/path
43 global = /some/path
44 untrusted
44 untrusted
45 . . global = /some/path
45 . . global = /some/path
46 . . interpolated = /some/path/another/path
46 . . interpolated = /some/path/another/path
47 . . local = /another/path
47 . . local = /another/path
48
48
49 # different user, different group, but we trust the user
49 # different user, different group, but we trust the user
50 trusted
50 trusted
51 global = /some/path
51 global = /some/path
52 interpolated = /some/path/another/path
52 interpolated = /some/path/another/path
53 local = /another/path
53 local = /another/path
54 untrusted
54 untrusted
55 . . global = /some/path
55 . . global = /some/path
56 . . interpolated = /some/path/another/path
56 . . interpolated = /some/path/another/path
57 . . local = /another/path
57 . . local = /another/path
58
58
59 # different user, different group, but we trust the group
59 # different user, different group, but we trust the group
60 trusted
60 trusted
61 global = /some/path
61 global = /some/path
62 interpolated = /some/path/another/path
62 interpolated = /some/path/another/path
63 local = /another/path
63 local = /another/path
64 untrusted
64 untrusted
65 . . global = /some/path
65 . . global = /some/path
66 . . interpolated = /some/path/another/path
66 . . interpolated = /some/path/another/path
67 . . local = /another/path
67 . . local = /another/path
68
68
69 # different user, different group, but we trust the user and the group
69 # different user, different group, but we trust the user and the group
70 trusted
70 trusted
71 global = /some/path
71 global = /some/path
72 interpolated = /some/path/another/path
72 interpolated = /some/path/another/path
73 local = /another/path
73 local = /another/path
74 untrusted
74 untrusted
75 . . global = /some/path
75 . . global = /some/path
76 . . interpolated = /some/path/another/path
76 . . interpolated = /some/path/another/path
77 . . local = /another/path
77 . . local = /another/path
78
78
79 # we trust all users
79 # we trust all users
80 # different user, different group
80 # different user, different group
81 trusted
81 trusted
82 global = /some/path
82 global = /some/path
83 interpolated = /some/path/another/path
83 interpolated = /some/path/another/path
84 local = /another/path
84 local = /another/path
85 untrusted
85 untrusted
86 . . global = /some/path
86 . . global = /some/path
87 . . interpolated = /some/path/another/path
87 . . interpolated = /some/path/another/path
88 . . local = /another/path
88 . . local = /another/path
89
89
90 # we trust all groups
90 # we trust all groups
91 # different user, different group
91 # different user, different group
92 trusted
92 trusted
93 global = /some/path
93 global = /some/path
94 interpolated = /some/path/another/path
94 interpolated = /some/path/another/path
95 local = /another/path
95 local = /another/path
96 untrusted
96 untrusted
97 . . global = /some/path
97 . . global = /some/path
98 . . interpolated = /some/path/another/path
98 . . interpolated = /some/path/another/path
99 . . local = /another/path
99 . . local = /another/path
100
100
101 # we trust all users and groups
101 # we trust all users and groups
102 # different user, different group
102 # different user, different group
103 trusted
103 trusted
104 global = /some/path
104 global = /some/path
105 interpolated = /some/path/another/path
105 interpolated = /some/path/another/path
106 local = /another/path
106 local = /another/path
107 untrusted
107 untrusted
108 . . global = /some/path
108 . . global = /some/path
109 . . interpolated = /some/path/another/path
109 . . interpolated = /some/path/another/path
110 . . local = /another/path
110 . . local = /another/path
111
111
112 # we don't get confused by users and groups with the same name
112 # we don't get confused by users and groups with the same name
113 # different user, different group
113 # different user, different group
114 Not trusting file .hg/hgrc from untrusted user abc, group def
114 Not trusting file .hg/hgrc from untrusted user abc, group def
115 trusted
115 trusted
116 global = /some/path
116 global = /some/path
117 untrusted
117 untrusted
118 . . global = /some/path
118 . . global = /some/path
119 . . interpolated = /some/path/another/path
119 . . interpolated = /some/path/another/path
120 . . local = /another/path
120 . . local = /another/path
121
121
122 # list of user names
122 # list of user names
123 # different user, different group, but we trust the user
123 # different user, different group, but we trust the user
124 trusted
124 trusted
125 global = /some/path
125 global = /some/path
126 interpolated = /some/path/another/path
126 interpolated = /some/path/another/path
127 local = /another/path
127 local = /another/path
128 untrusted
128 untrusted
129 . . global = /some/path
129 . . global = /some/path
130 . . interpolated = /some/path/another/path
130 . . interpolated = /some/path/another/path
131 . . local = /another/path
131 . . local = /another/path
132
132
133 # list of group names
133 # list of group names
134 # different user, different group, but we trust the group
134 # different user, different group, but we trust the group
135 trusted
135 trusted
136 global = /some/path
136 global = /some/path
137 interpolated = /some/path/another/path
137 interpolated = /some/path/another/path
138 local = /another/path
138 local = /another/path
139 untrusted
139 untrusted
140 . . global = /some/path
140 . . global = /some/path
141 . . interpolated = /some/path/another/path
141 . . interpolated = /some/path/another/path
142 . . local = /another/path
142 . . local = /another/path
143
143
144 # Can't figure out the name of the user running this process
144 # Can't figure out the name of the user running this process
145 # different user, different group
145 # different user, different group
146 Not trusting file .hg/hgrc from untrusted user abc, group def
146 Not trusting file .hg/hgrc from untrusted user abc, group def
147 trusted
147 trusted
148 global = /some/path
148 global = /some/path
149 untrusted
149 untrusted
150 . . global = /some/path
150 . . global = /some/path
151 . . interpolated = /some/path/another/path
151 . . interpolated = /some/path/another/path
152 . . local = /another/path
152 . . local = /another/path
153
153
154 # prints debug warnings
154 # prints debug warnings
155 # different user, different group
155 # different user, different group
156 Not trusting file .hg/hgrc from untrusted user abc, group def
156 Not trusting file .hg/hgrc from untrusted user abc, group def
157 trusted
157 trusted
158 Ignoring untrusted configuration option paths.interpolated = /some/path/another/path
158 Ignoring untrusted configuration option paths.interpolated = /some/path/another/path
159 Ignoring untrusted configuration option paths.local = /another/path
159 Ignoring untrusted configuration option paths.local = /another/path
160 global = /some/path
160 global = /some/path
161 untrusted
161 untrusted
162 . . global = /some/path
162 . . global = /some/path
163 .Ignoring untrusted configuration option paths.interpolated = /some/path/another/path
163 .Ignoring untrusted configuration option paths.interpolated = /some/path/another/path
164 . interpolated = /some/path/another/path
164 . interpolated = /some/path/another/path
165 .Ignoring untrusted configuration option paths.local = /another/path
165 .Ignoring untrusted configuration option paths.local = /another/path
166 . local = /another/path
166 . local = /another/path
167
167
168 # ui.readsections
168 # ui.readconfig sections
169 quux
169 quux
170
170
171 # read trusted, untrusted, new ui, trusted
171 # read trusted, untrusted, new ui, trusted
172 Not trusting file foobar from untrusted user abc, group def
172 Not trusting file foobar from untrusted user abc, group def
173 trusted:
173 trusted:
174 Ignoring untrusted configuration option foobar.baz = quux
174 Ignoring untrusted configuration option foobar.baz = quux
175 None
175 None
176 /some/path/another/path
176 /some/path/another/path
177 untrusted:
177 untrusted:
178 quux
178 quux
179 /some/path/another/path
179 /some/path/another/path
180
180
181 # error handling
181 # error handling
182 # file doesn't exist
182 # file doesn't exist
183 # same user, same group
183 # same user, same group
184 # different user, different group
184 # different user, different group
185
185
186 # parse error
186 # parse error
187 # different user, different group
187 # different user, different group
188 Not trusting file .hg/hgrc from untrusted user abc, group def
188 Not trusting file .hg/hgrc from untrusted user abc, group def
189 Ignored: Failed to parse .hg/hgrc
189 Ignored: Failed to parse .hg/hgrc
190 File contains no section headers.
190 File contains no section headers.
191 file: .hg/hgrc, line: 1
191 file: .hg/hgrc, line: 1
192 'foo = bar'
192 'foo = bar'
193 # same user, same group
193 # same user, same group
194 raised Abort
194 raised Abort
195
195
196 # interpolation error
196 # interpolation error
197 # same user, same group
197 # same user, same group
198 # regular config:
198 # regular config:
199 trusted raised Abort
199 trusted raised Abort
200 untrusted raised Abort
200 untrusted raised Abort
201 # different user, different group
201 # different user, different group
202 Not trusting file .hg/hgrc from untrusted user abc, group def
202 Not trusting file .hg/hgrc from untrusted user abc, group def
203 trusted Ignored: Error in configuration section [foo] parameter 'bar':
203 trusted Ignored: Error in configuration section [foo] parameter 'bar':
204 bad interpolation variable reference '%('
204 bad interpolation variable reference '%('
205 None
205 None
206 untrusted raised Abort
206 untrusted raised Abort
207 # configitems:
207 # configitems:
208 trusted Ignored: Error in configuration section [foo]:
208 trusted Ignored: Error in configuration section [foo]:
209 bad interpolation variable reference '%('
209 bad interpolation variable reference '%('
210 []
210 []
211 untrusted raised Abort
211 untrusted raised Abort
General Comments 0
You need to be logged in to leave comments. Login now