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.read |
|
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.read |
|
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.read |
|
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 |
|
|
103 | try: | |
101 |
|
|
104 | cdata.readfp(fp, filename) | |
102 |
|
|
105 | except ConfigParser.ParsingError, inst: | |
103 |
|
|
106 | msg = _("Failed to parse %s\n%s") % (filename, inst) | |
104 |
|
|
107 | if trusted: | |
105 |
|
|
108 | raise util.Abort(msg) | |
106 |
|
|
109 | self.warn(_("Ignored: %s\n") % msg) | |
107 |
|
110 | |||
108 |
|
|
111 | if trusted: | |
109 |
|
|
112 | updateconfig(cdata, self.cdata, sections) | |
110 |
|
|
113 | updateconfig(self.overlay, self.cdata, sections) | |
111 |
|
|
114 | updateconfig(cdata, self.ucdata, sections) | |
112 |
|
|
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 |
|
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 ' |
|
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 |
|
78 | |||
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.read |
|
142 | u.readconfig(filename, sections = ['foobar']) | |
143 | print u.config('foobar', 'baz') |
|
143 | print u.config('foobar', 'baz') | |
144 |
|
144 | |||
145 |
|
145 | |||
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 |
|
162 | |||
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 |
|
179 | |||
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 |
|
187 | |||
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