##// END OF EJS Templates
add --git option to commands supporting --patch (log, incoming, history, tip)...
Jim Correia -
r7762:fece056b default
parent child Browse files
Show More
@@ -1,410 +1,410 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 bug
11 that refer to bugs by Bugzilla ID are seen. The hook does not change bug
12 status.
12 status.
13
13
14 The hook updates the Bugzilla database directly. Only Bugzilla installations
14 The hook updates the Bugzilla database directly. Only Bugzilla installations
15 using MySQL are supported.
15 using MySQL are supported.
16
16
17 The hook relies on a Bugzilla script to send bug change notification emails.
17 The hook relies on a Bugzilla script to send bug change notification emails.
18 That script changes between Bugzilla versions; the 'processmail' script used
18 That script changes between Bugzilla versions; the 'processmail' script used
19 prior to 2.18 is replaced in 2.18 and subsequent versions by
19 prior to 2.18 is replaced in 2.18 and subsequent versions by
20 'config/sendbugmail.pl'. Note that these will be run by Mercurial as the user
20 'config/sendbugmail.pl'. Note that these will be run by Mercurial as the user
21 pushing the change; you will need to ensure the Bugzilla install file
21 pushing the change; you will need to ensure the Bugzilla install file
22 permissions are set appropriately.
22 permissions are set appropriately.
23
23
24 Configuring the extension:
24 Configuring the extension:
25
25
26 [bugzilla]
26 [bugzilla]
27 host Hostname of the MySQL server holding the Bugzilla database.
27 host Hostname of the MySQL server holding the Bugzilla database.
28 db Name of the Bugzilla database in MySQL. Default 'bugs'.
28 db Name of the Bugzilla database in MySQL. Default 'bugs'.
29 user Username to use to access MySQL server. Default 'bugs'.
29 user Username to use to access MySQL server. Default 'bugs'.
30 password Password to use to access MySQL server.
30 password Password to use to access MySQL server.
31 timeout Database connection timeout (seconds). Default 5.
31 timeout Database connection timeout (seconds). Default 5.
32 version Bugzilla version. Specify '3.0' for Bugzilla versions 3.0 and
32 version Bugzilla version. Specify '3.0' for Bugzilla versions 3.0 and
33 later, '2.18' for Bugzilla versions from 2.18 and '2.16' for
33 later, '2.18' for Bugzilla versions from 2.18 and '2.16' for
34 versions prior to 2.18.
34 versions prior to 2.18.
35 bzuser Fallback Bugzilla user name to record comments with, if
35 bzuser Fallback Bugzilla user name to record comments with, if
36 changeset committer cannot be found as a Bugzilla user.
36 changeset committer cannot be found as a Bugzilla user.
37 bzdir Bugzilla install directory. Used by default notify.
37 bzdir Bugzilla install directory. Used by default notify.
38 Default '/var/www/html/bugzilla'.
38 Default '/var/www/html/bugzilla'.
39 notify The command to run to get Bugzilla to send bug change
39 notify The command to run to get Bugzilla to send bug change
40 notification emails. Substitutes from a map with 3 keys,
40 notification emails. Substitutes from a map with 3 keys,
41 'bzdir', 'id' (bug id) and 'user' (committer bugzilla email).
41 'bzdir', 'id' (bug id) and 'user' (committer bugzilla email).
42 Default depends on version; from 2.18 it is
42 Default depends on version; from 2.18 it is
43 "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s".
43 "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s".
44 regexp Regular expression to match bug IDs in changeset commit message.
44 regexp Regular expression to match bug IDs in changeset commit message.
45 Must contain one "()" group. The default expression matches
45 Must contain one "()" group. The default expression matches
46 'Bug 1234', 'Bug no. 1234', 'Bug number 1234',
46 'Bug 1234', 'Bug no. 1234', 'Bug number 1234',
47 'Bugs 1234,5678', 'Bug 1234 and 5678' and variations thereof.
47 'Bugs 1234,5678', 'Bug 1234 and 5678' and variations thereof.
48 Matching is case insensitive.
48 Matching is case insensitive.
49 style The style file to use when formatting comments.
49 style The style file to use when formatting comments.
50 template Template to use when formatting comments. Overrides
50 template Template to use when formatting comments. Overrides
51 style if specified. In addition to the usual Mercurial
51 style if specified. In addition to the usual Mercurial
52 keywords, the extension specifies:
52 keywords, the extension specifies:
53 {bug} The Bugzilla bug ID.
53 {bug} The Bugzilla bug ID.
54 {root} The full pathname of the Mercurial repository.
54 {root} The full pathname of the Mercurial repository.
55 {webroot} Stripped pathname of the Mercurial repository.
55 {webroot} Stripped pathname of the Mercurial repository.
56 {hgweb} Base URL for browsing Mercurial repositories.
56 {hgweb} Base URL for browsing Mercurial repositories.
57 Default 'changeset {node|short} in repo {root} refers '
57 Default 'changeset {node|short} in repo {root} refers '
58 'to bug {bug}.\\ndetails:\\n\\t{desc|tabindent}'
58 'to bug {bug}.\\ndetails:\\n\\t{desc|tabindent}'
59 strip The number of slashes to strip from the front of {root}
59 strip The number of slashes to strip from the front of {root}
60 to produce {webroot}. Default 0.
60 to produce {webroot}. Default 0.
61 usermap Path of file containing Mercurial committer ID to Bugzilla user
61 usermap Path of file containing Mercurial committer ID to Bugzilla user
62 ID mappings. If specified, the file should contain one mapping
62 ID mappings. If specified, the file should contain one mapping
63 per line, "committer"="Bugzilla user". See also the
63 per line, "committer"="Bugzilla user". See also the
64 [usermap] section.
64 [usermap] section.
65
65
66 [usermap]
66 [usermap]
67 Any entries in this section specify mappings of Mercurial committer ID
67 Any entries in this section specify mappings of Mercurial committer ID
68 to Bugzilla user ID. See also [bugzilla].usermap.
68 to Bugzilla user ID. See also [bugzilla].usermap.
69 "committer"="Bugzilla user"
69 "committer"="Bugzilla user"
70
70
71 [web]
71 [web]
72 baseurl Base URL for browsing Mercurial repositories. Reference from
72 baseurl Base URL for browsing Mercurial repositories. Reference from
73 templates as {hgweb}.
73 templates as {hgweb}.
74
74
75 Activating the extension:
75 Activating the extension:
76
76
77 [extensions]
77 [extensions]
78 hgext.bugzilla =
78 hgext.bugzilla =
79
79
80 [hooks]
80 [hooks]
81 # run bugzilla hook on every change pulled or pushed in here
81 # run bugzilla hook on every change pulled or pushed in here
82 incoming.bugzilla = python:hgext.bugzilla.hook
82 incoming.bugzilla = python:hgext.bugzilla.hook
83
83
84 Example configuration:
84 Example configuration:
85
85
86 This example configuration is for a collection of Mercurial repositories
86 This example configuration is for a collection of Mercurial repositories
87 in /var/local/hg/repos/ used with a local Bugzilla 3.2 installation in
87 in /var/local/hg/repos/ used with a local Bugzilla 3.2 installation in
88 /opt/bugzilla-3.2.
88 /opt/bugzilla-3.2.
89
89
90 [bugzilla]
90 [bugzilla]
91 host=localhost
91 host=localhost
92 password=XYZZY
92 password=XYZZY
93 version=3.0
93 version=3.0
94 bzuser=unknown@domain.com
94 bzuser=unknown@domain.com
95 bzdir=/opt/bugzilla-3.2
95 bzdir=/opt/bugzilla-3.2
96 template=Changeset {node|short} in {root|basename}.\\n{hgweb}/{webroot}/rev/{node|short}\\n\\n{desc}\\n
96 template=Changeset {node|short} in {root|basename}.\\n{hgweb}/{webroot}/rev/{node|short}\\n\\n{desc}\\n
97 strip=5
97 strip=5
98
98
99 [web]
99 [web]
100 baseurl=http://dev.domain.com/hg
100 baseurl=http://dev.domain.com/hg
101
101
102 [usermap]
102 [usermap]
103 user@emaildomain.com=user.name@bugzilladomain.com
103 user@emaildomain.com=user.name@bugzilladomain.com
104
104
105 Commits add a comment to the Bugzilla bug record of the form:
105 Commits add a comment to the Bugzilla bug record of the form:
106
106
107 Changeset 3b16791d6642 in repository-name.
107 Changeset 3b16791d6642 in repository-name.
108 http://dev.domain.com/hg/repository-name/rev/3b16791d6642
108 http://dev.domain.com/hg/repository-name/rev/3b16791d6642
109
109
110 Changeset commit comment. Bug 1234.
110 Changeset commit comment. Bug 1234.
111 '''
111 '''
112
112
113 from mercurial.i18n import _
113 from mercurial.i18n import _
114 from mercurial.node import short
114 from mercurial.node import short
115 from mercurial import cmdutil, templater, util
115 from mercurial import cmdutil, templater, util
116 import re, time
116 import re, time
117
117
118 MySQLdb = None
118 MySQLdb = None
119
119
120 def buglist(ids):
120 def buglist(ids):
121 return '(' + ','.join(map(str, ids)) + ')'
121 return '(' + ','.join(map(str, ids)) + ')'
122
122
123 class bugzilla_2_16(object):
123 class bugzilla_2_16(object):
124 '''support for bugzilla version 2.16.'''
124 '''support for bugzilla version 2.16.'''
125
125
126 def __init__(self, ui):
126 def __init__(self, ui):
127 self.ui = ui
127 self.ui = ui
128 host = self.ui.config('bugzilla', 'host', 'localhost')
128 host = self.ui.config('bugzilla', 'host', 'localhost')
129 user = self.ui.config('bugzilla', 'user', 'bugs')
129 user = self.ui.config('bugzilla', 'user', 'bugs')
130 passwd = self.ui.config('bugzilla', 'password')
130 passwd = self.ui.config('bugzilla', 'password')
131 db = self.ui.config('bugzilla', 'db', 'bugs')
131 db = self.ui.config('bugzilla', 'db', 'bugs')
132 timeout = int(self.ui.config('bugzilla', 'timeout', 5))
132 timeout = int(self.ui.config('bugzilla', 'timeout', 5))
133 usermap = self.ui.config('bugzilla', 'usermap')
133 usermap = self.ui.config('bugzilla', 'usermap')
134 if usermap:
134 if usermap:
135 self.ui.readsections(usermap, 'usermap')
135 self.ui.readsections(usermap, 'usermap')
136 self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
136 self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
137 (host, db, user, '*' * len(passwd)))
137 (host, db, user, '*' * len(passwd)))
138 self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
138 self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
139 db=db, connect_timeout=timeout)
139 db=db, connect_timeout=timeout)
140 self.cursor = self.conn.cursor()
140 self.cursor = self.conn.cursor()
141 self.longdesc_id = self.get_longdesc_id()
141 self.longdesc_id = self.get_longdesc_id()
142 self.user_ids = {}
142 self.user_ids = {}
143 self.default_notify = "cd %(bzdir)s && ./processmail %(id)s %(user)s"
143 self.default_notify = "cd %(bzdir)s && ./processmail %(id)s %(user)s"
144
144
145 def run(self, *args, **kwargs):
145 def run(self, *args, **kwargs):
146 '''run a query.'''
146 '''run a query.'''
147 self.ui.note(_('query: %s %s\n') % (args, kwargs))
147 self.ui.note(_('query: %s %s\n') % (args, kwargs))
148 try:
148 try:
149 self.cursor.execute(*args, **kwargs)
149 self.cursor.execute(*args, **kwargs)
150 except MySQLdb.MySQLError:
150 except MySQLdb.MySQLError:
151 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
151 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
152 raise
152 raise
153
153
154 def get_longdesc_id(self):
154 def get_longdesc_id(self):
155 '''get identity of longdesc field'''
155 '''get identity of longdesc field'''
156 self.run('select fieldid from fielddefs where name = "longdesc"')
156 self.run('select fieldid from fielddefs where name = "longdesc"')
157 ids = self.cursor.fetchall()
157 ids = self.cursor.fetchall()
158 if len(ids) != 1:
158 if len(ids) != 1:
159 raise util.Abort(_('unknown database schema'))
159 raise util.Abort(_('unknown database schema'))
160 return ids[0][0]
160 return ids[0][0]
161
161
162 def filter_real_bug_ids(self, ids):
162 def filter_real_bug_ids(self, ids):
163 '''filter not-existing bug ids from list.'''
163 '''filter not-existing bug ids from list.'''
164 self.run('select bug_id from bugs where bug_id in %s' % buglist(ids))
164 self.run('select bug_id from bugs where bug_id in %s' % buglist(ids))
165 return util.sort([c[0] for c in self.cursor.fetchall()])
165 return util.sort([c[0] for c in self.cursor.fetchall()])
166
166
167 def filter_unknown_bug_ids(self, node, ids):
167 def filter_unknown_bug_ids(self, node, ids):
168 '''filter bug ids from list that already refer to this changeset.'''
168 '''filter bug ids from list that already refer to this changeset.'''
169
169
170 self.run('''select bug_id from longdescs where
170 self.run('''select bug_id from longdescs where
171 bug_id in %s and thetext like "%%%s%%"''' %
171 bug_id in %s and thetext like "%%%s%%"''' %
172 (buglist(ids), short(node)))
172 (buglist(ids), short(node)))
173 unknown = dict.fromkeys(ids)
173 unknown = dict.fromkeys(ids)
174 for (id,) in self.cursor.fetchall():
174 for (id,) in self.cursor.fetchall():
175 self.ui.status(_('bug %d already knows about changeset %s\n') %
175 self.ui.status(_('bug %d already knows about changeset %s\n') %
176 (id, short(node)))
176 (id, short(node)))
177 unknown.pop(id, None)
177 unknown.pop(id, None)
178 return util.sort(unknown.keys())
178 return util.sort(unknown.keys())
179
179
180 def notify(self, ids, committer):
180 def notify(self, ids, committer):
181 '''tell bugzilla to send mail.'''
181 '''tell bugzilla to send mail.'''
182
182
183 self.ui.status(_('telling bugzilla to send mail:\n'))
183 self.ui.status(_('telling bugzilla to send mail:\n'))
184 (user, userid) = self.get_bugzilla_user(committer)
184 (user, userid) = self.get_bugzilla_user(committer)
185 for id in ids:
185 for id in ids:
186 self.ui.status(_(' bug %s\n') % id)
186 self.ui.status(_(' bug %s\n') % id)
187 cmdfmt = self.ui.config('bugzilla', 'notify', self.default_notify)
187 cmdfmt = self.ui.config('bugzilla', 'notify', self.default_notify)
188 bzdir = self.ui.config('bugzilla', 'bzdir', '/var/www/html/bugzilla')
188 bzdir = self.ui.config('bugzilla', 'bzdir', '/var/www/html/bugzilla')
189 try:
189 try:
190 # Backwards-compatible with old notify string, which
190 # Backwards-compatible with old notify string, which
191 # took one string. This will throw with a new format
191 # took one string. This will throw with a new format
192 # string.
192 # string.
193 cmd = cmdfmt % id
193 cmd = cmdfmt % id
194 except TypeError:
194 except TypeError:
195 cmd = cmdfmt % {'bzdir': bzdir, 'id': id, 'user': user}
195 cmd = cmdfmt % {'bzdir': bzdir, 'id': id, 'user': user}
196 self.ui.note(_('running notify command %s\n') % cmd)
196 self.ui.note(_('running notify command %s\n') % cmd)
197 fp = util.popen('(%s) 2>&1' % cmd)
197 fp = util.popen('(%s) 2>&1' % cmd)
198 out = fp.read()
198 out = fp.read()
199 ret = fp.close()
199 ret = fp.close()
200 if ret:
200 if ret:
201 self.ui.warn(out)
201 self.ui.warn(out)
202 raise util.Abort(_('bugzilla notify command %s') %
202 raise util.Abort(_('bugzilla notify command %s') %
203 util.explain_exit(ret)[0])
203 util.explain_exit(ret)[0])
204 self.ui.status(_('done\n'))
204 self.ui.status(_('done\n'))
205
205
206 def get_user_id(self, user):
206 def get_user_id(self, user):
207 '''look up numeric bugzilla user id.'''
207 '''look up numeric bugzilla user id.'''
208 try:
208 try:
209 return self.user_ids[user]
209 return self.user_ids[user]
210 except KeyError:
210 except KeyError:
211 try:
211 try:
212 userid = int(user)
212 userid = int(user)
213 except ValueError:
213 except ValueError:
214 self.ui.note(_('looking up user %s\n') % user)
214 self.ui.note(_('looking up user %s\n') % user)
215 self.run('''select userid from profiles
215 self.run('''select userid from profiles
216 where login_name like %s''', user)
216 where login_name like %s''', user)
217 all = self.cursor.fetchall()
217 all = self.cursor.fetchall()
218 if len(all) != 1:
218 if len(all) != 1:
219 raise KeyError(user)
219 raise KeyError(user)
220 userid = int(all[0][0])
220 userid = int(all[0][0])
221 self.user_ids[user] = userid
221 self.user_ids[user] = userid
222 return userid
222 return userid
223
223
224 def map_committer(self, user):
224 def map_committer(self, user):
225 '''map name of committer to bugzilla user name.'''
225 '''map name of committer to bugzilla user name.'''
226 for committer, bzuser in self.ui.configitems('usermap'):
226 for committer, bzuser in self.ui.configitems('usermap'):
227 if committer.lower() == user.lower():
227 if committer.lower() == user.lower():
228 return bzuser
228 return bzuser
229 return user
229 return user
230
230
231 def get_bugzilla_user(self, committer):
231 def get_bugzilla_user(self, committer):
232 '''see if committer is a registered bugzilla user. Return
232 '''see if committer is a registered bugzilla user. Return
233 bugzilla username and userid if so. If not, return default
233 bugzilla username and userid if so. If not, return default
234 bugzilla username and userid.'''
234 bugzilla username and userid.'''
235 user = self.map_committer(committer)
235 user = self.map_committer(committer)
236 try:
236 try:
237 userid = self.get_user_id(user)
237 userid = self.get_user_id(user)
238 except KeyError:
238 except KeyError:
239 try:
239 try:
240 defaultuser = self.ui.config('bugzilla', 'bzuser')
240 defaultuser = self.ui.config('bugzilla', 'bzuser')
241 if not defaultuser:
241 if not defaultuser:
242 raise util.Abort(_('cannot find bugzilla user id for %s') %
242 raise util.Abort(_('cannot find bugzilla user id for %s') %
243 user)
243 user)
244 userid = self.get_user_id(defaultuser)
244 userid = self.get_user_id(defaultuser)
245 user = defaultuser
245 user = defaultuser
246 except KeyError:
246 except KeyError:
247 raise util.Abort(_('cannot find bugzilla user id for %s or %s') %
247 raise util.Abort(_('cannot find bugzilla user id for %s or %s') %
248 (user, defaultuser))
248 (user, defaultuser))
249 return (user, userid)
249 return (user, userid)
250
250
251 def add_comment(self, bugid, text, committer):
251 def add_comment(self, bugid, text, committer):
252 '''add comment to bug. try adding comment as committer of
252 '''add comment to bug. try adding comment as committer of
253 changeset, otherwise as default bugzilla user.'''
253 changeset, otherwise as default bugzilla user.'''
254 (user, userid) = self.get_bugzilla_user(committer)
254 (user, userid) = self.get_bugzilla_user(committer)
255 now = time.strftime('%Y-%m-%d %H:%M:%S')
255 now = time.strftime('%Y-%m-%d %H:%M:%S')
256 self.run('''insert into longdescs
256 self.run('''insert into longdescs
257 (bug_id, who, bug_when, thetext)
257 (bug_id, who, bug_when, thetext)
258 values (%s, %s, %s, %s)''',
258 values (%s, %s, %s, %s)''',
259 (bugid, userid, now, text))
259 (bugid, userid, now, text))
260 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
260 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
261 values (%s, %s, %s, %s)''',
261 values (%s, %s, %s, %s)''',
262 (bugid, userid, now, self.longdesc_id))
262 (bugid, userid, now, self.longdesc_id))
263 self.conn.commit()
263 self.conn.commit()
264
264
265 class bugzilla_2_18(bugzilla_2_16):
265 class bugzilla_2_18(bugzilla_2_16):
266 '''support for bugzilla 2.18 series.'''
266 '''support for bugzilla 2.18 series.'''
267
267
268 def __init__(self, ui):
268 def __init__(self, ui):
269 bugzilla_2_16.__init__(self, ui)
269 bugzilla_2_16.__init__(self, ui)
270 self.default_notify = "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s"
270 self.default_notify = "cd %(bzdir)s && perl -T contrib/sendbugmail.pl %(id)s %(user)s"
271
271
272 class bugzilla_3_0(bugzilla_2_18):
272 class bugzilla_3_0(bugzilla_2_18):
273 '''support for bugzilla 3.0 series.'''
273 '''support for bugzilla 3.0 series.'''
274
274
275 def __init__(self, ui):
275 def __init__(self, ui):
276 bugzilla_2_18.__init__(self, ui)
276 bugzilla_2_18.__init__(self, ui)
277
277
278 def get_longdesc_id(self):
278 def get_longdesc_id(self):
279 '''get identity of longdesc field'''
279 '''get identity of longdesc field'''
280 self.run('select id from fielddefs where name = "longdesc"')
280 self.run('select id from fielddefs where name = "longdesc"')
281 ids = self.cursor.fetchall()
281 ids = self.cursor.fetchall()
282 if len(ids) != 1:
282 if len(ids) != 1:
283 raise util.Abort(_('unknown database schema'))
283 raise util.Abort(_('unknown database schema'))
284 return ids[0][0]
284 return ids[0][0]
285
285
286 class bugzilla(object):
286 class bugzilla(object):
287 # supported versions of bugzilla. different versions have
287 # supported versions of bugzilla. different versions have
288 # different schemas.
288 # different schemas.
289 _versions = {
289 _versions = {
290 '2.16': bugzilla_2_16,
290 '2.16': bugzilla_2_16,
291 '2.18': bugzilla_2_18,
291 '2.18': bugzilla_2_18,
292 '3.0': bugzilla_3_0
292 '3.0': bugzilla_3_0
293 }
293 }
294
294
295 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
295 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
296 r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)')
296 r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)')
297
297
298 _bz = None
298 _bz = None
299
299
300 def __init__(self, ui, repo):
300 def __init__(self, ui, repo):
301 self.ui = ui
301 self.ui = ui
302 self.repo = repo
302 self.repo = repo
303
303
304 def bz(self):
304 def bz(self):
305 '''return object that knows how to talk to bugzilla version in
305 '''return object that knows how to talk to bugzilla version in
306 use.'''
306 use.'''
307
307
308 if bugzilla._bz is None:
308 if bugzilla._bz is None:
309 bzversion = self.ui.config('bugzilla', 'version')
309 bzversion = self.ui.config('bugzilla', 'version')
310 try:
310 try:
311 bzclass = bugzilla._versions[bzversion]
311 bzclass = bugzilla._versions[bzversion]
312 except KeyError:
312 except KeyError:
313 raise util.Abort(_('bugzilla version %s not supported') %
313 raise util.Abort(_('bugzilla version %s not supported') %
314 bzversion)
314 bzversion)
315 bugzilla._bz = bzclass(self.ui)
315 bugzilla._bz = bzclass(self.ui)
316 return bugzilla._bz
316 return bugzilla._bz
317
317
318 def __getattr__(self, key):
318 def __getattr__(self, key):
319 return getattr(self.bz(), key)
319 return getattr(self.bz(), key)
320
320
321 _bug_re = None
321 _bug_re = None
322 _split_re = None
322 _split_re = None
323
323
324 def find_bug_ids(self, ctx):
324 def find_bug_ids(self, ctx):
325 '''find valid bug ids that are referred to in changeset
325 '''find valid bug ids that are referred to in changeset
326 comments and that do not already have references to this
326 comments and that do not already have references to this
327 changeset.'''
327 changeset.'''
328
328
329 if bugzilla._bug_re is None:
329 if bugzilla._bug_re is None:
330 bugzilla._bug_re = re.compile(
330 bugzilla._bug_re = re.compile(
331 self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re),
331 self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re),
332 re.IGNORECASE)
332 re.IGNORECASE)
333 bugzilla._split_re = re.compile(r'\D+')
333 bugzilla._split_re = re.compile(r'\D+')
334 start = 0
334 start = 0
335 ids = {}
335 ids = {}
336 while True:
336 while True:
337 m = bugzilla._bug_re.search(ctx.description(), start)
337 m = bugzilla._bug_re.search(ctx.description(), start)
338 if not m:
338 if not m:
339 break
339 break
340 start = m.end()
340 start = m.end()
341 for id in bugzilla._split_re.split(m.group(1)):
341 for id in bugzilla._split_re.split(m.group(1)):
342 if not id: continue
342 if not id: continue
343 ids[int(id)] = 1
343 ids[int(id)] = 1
344 ids = ids.keys()
344 ids = ids.keys()
345 if ids:
345 if ids:
346 ids = self.filter_real_bug_ids(ids)
346 ids = self.filter_real_bug_ids(ids)
347 if ids:
347 if ids:
348 ids = self.filter_unknown_bug_ids(ctx.node(), ids)
348 ids = self.filter_unknown_bug_ids(ctx.node(), ids)
349 return ids
349 return ids
350
350
351 def update(self, bugid, ctx):
351 def update(self, bugid, ctx):
352 '''update bugzilla bug with reference to changeset.'''
352 '''update bugzilla bug with reference to changeset.'''
353
353
354 def webroot(root):
354 def webroot(root):
355 '''strip leading prefix of repo root and turn into
355 '''strip leading prefix of repo root and turn into
356 url-safe path.'''
356 url-safe path.'''
357 count = int(self.ui.config('bugzilla', 'strip', 0))
357 count = int(self.ui.config('bugzilla', 'strip', 0))
358 root = util.pconvert(root)
358 root = util.pconvert(root)
359 while count > 0:
359 while count > 0:
360 c = root.find('/')
360 c = root.find('/')
361 if c == -1:
361 if c == -1:
362 break
362 break
363 root = root[c+1:]
363 root = root[c+1:]
364 count -= 1
364 count -= 1
365 return root
365 return root
366
366
367 mapfile = self.ui.config('bugzilla', 'style')
367 mapfile = self.ui.config('bugzilla', 'style')
368 tmpl = self.ui.config('bugzilla', 'template')
368 tmpl = self.ui.config('bugzilla', 'template')
369 t = cmdutil.changeset_templater(self.ui, self.repo,
369 t = cmdutil.changeset_templater(self.ui, self.repo,
370 False, mapfile, False)
370 False, None, mapfile, False)
371 if not mapfile and not tmpl:
371 if not mapfile and not tmpl:
372 tmpl = _('changeset {node|short} in repo {root} refers '
372 tmpl = _('changeset {node|short} in repo {root} refers '
373 'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
373 'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
374 if tmpl:
374 if tmpl:
375 tmpl = templater.parsestring(tmpl, quoted=False)
375 tmpl = templater.parsestring(tmpl, quoted=False)
376 t.use_template(tmpl)
376 t.use_template(tmpl)
377 self.ui.pushbuffer()
377 self.ui.pushbuffer()
378 t.show(ctx, changes=ctx.changeset(),
378 t.show(ctx, changes=ctx.changeset(),
379 bug=str(bugid),
379 bug=str(bugid),
380 hgweb=self.ui.config('web', 'baseurl'),
380 hgweb=self.ui.config('web', 'baseurl'),
381 root=self.repo.root,
381 root=self.repo.root,
382 webroot=webroot(self.repo.root))
382 webroot=webroot(self.repo.root))
383 data = self.ui.popbuffer()
383 data = self.ui.popbuffer()
384 self.add_comment(bugid, data, util.email(ctx.user()))
384 self.add_comment(bugid, data, util.email(ctx.user()))
385
385
386 def hook(ui, repo, hooktype, node=None, **kwargs):
386 def hook(ui, repo, hooktype, node=None, **kwargs):
387 '''add comment to bugzilla for each changeset that refers to a
387 '''add comment to bugzilla for each changeset that refers to a
388 bugzilla bug id. only add a comment once per bug, so same change
388 bugzilla bug id. only add a comment once per bug, so same change
389 seen multiple times does not fill bug with duplicate data.'''
389 seen multiple times does not fill bug with duplicate data.'''
390 try:
390 try:
391 import MySQLdb as mysql
391 import MySQLdb as mysql
392 global MySQLdb
392 global MySQLdb
393 MySQLdb = mysql
393 MySQLdb = mysql
394 except ImportError, err:
394 except ImportError, err:
395 raise util.Abort(_('python mysql support not available: %s') % err)
395 raise util.Abort(_('python mysql support not available: %s') % err)
396
396
397 if node is None:
397 if node is None:
398 raise util.Abort(_('hook type %s does not pass a changeset id') %
398 raise util.Abort(_('hook type %s does not pass a changeset id') %
399 hooktype)
399 hooktype)
400 try:
400 try:
401 bz = bugzilla(ui, repo)
401 bz = bugzilla(ui, repo)
402 ctx = repo[node]
402 ctx = repo[node]
403 ids = bz.find_bug_ids(ctx)
403 ids = bz.find_bug_ids(ctx)
404 if ids:
404 if ids:
405 for id in ids:
405 for id in ids:
406 bz.update(id, ctx)
406 bz.update(id, ctx)
407 bz.notify(ids, util.email(ctx.user()))
407 bz.notify(ids, util.email(ctx.user()))
408 except MySQLdb.MySQLError, err:
408 except MySQLdb.MySQLError, err:
409 raise util.Abort(_('database error: %s') % err[1])
409 raise util.Abort(_('database error: %s') % err[1])
410
410
@@ -1,161 +1,161 b''
1 # churn.py - create a graph of revisions count grouped by template
1 # churn.py - create a graph of revisions count grouped by template
2 #
2 #
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8 '''command to show certain statistics about revision history'''
8 '''command to show certain statistics about revision history'''
9
9
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import patch, cmdutil, util, templater
11 from mercurial import patch, cmdutil, util, templater
12 import os, sys
12 import os, sys
13 import time, datetime
13 import time, datetime
14
14
15 def maketemplater(ui, repo, tmpl):
15 def maketemplater(ui, repo, tmpl):
16 tmpl = templater.parsestring(tmpl, quoted=False)
16 tmpl = templater.parsestring(tmpl, quoted=False)
17 try:
17 try:
18 t = cmdutil.changeset_templater(ui, repo, False, None, False)
18 t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
19 except SyntaxError, inst:
19 except SyntaxError, inst:
20 raise util.Abort(inst.args[0])
20 raise util.Abort(inst.args[0])
21 t.use_template(tmpl)
21 t.use_template(tmpl)
22 return t
22 return t
23
23
24 def changedlines(ui, repo, ctx1, ctx2):
24 def changedlines(ui, repo, ctx1, ctx2):
25 lines = 0
25 lines = 0
26 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node()))
26 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node()))
27 for l in diff.split('\n'):
27 for l in diff.split('\n'):
28 if (l.startswith("+") and not l.startswith("+++ ") or
28 if (l.startswith("+") and not l.startswith("+++ ") or
29 l.startswith("-") and not l.startswith("--- ")):
29 l.startswith("-") and not l.startswith("--- ")):
30 lines += 1
30 lines += 1
31 return lines
31 return lines
32
32
33 def countrate(ui, repo, amap, *pats, **opts):
33 def countrate(ui, repo, amap, *pats, **opts):
34 """Calculate stats"""
34 """Calculate stats"""
35 if opts.get('dateformat'):
35 if opts.get('dateformat'):
36 def getkey(ctx):
36 def getkey(ctx):
37 t, tz = ctx.date()
37 t, tz = ctx.date()
38 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
38 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
39 return date.strftime(opts['dateformat'])
39 return date.strftime(opts['dateformat'])
40 else:
40 else:
41 tmpl = opts.get('template', '{author|email}')
41 tmpl = opts.get('template', '{author|email}')
42 tmpl = maketemplater(ui, repo, tmpl)
42 tmpl = maketemplater(ui, repo, tmpl)
43 def getkey(ctx):
43 def getkey(ctx):
44 ui.pushbuffer()
44 ui.pushbuffer()
45 tmpl.show(ctx)
45 tmpl.show(ctx)
46 return ui.popbuffer()
46 return ui.popbuffer()
47
47
48 count = pct = 0
48 count = pct = 0
49 rate = {}
49 rate = {}
50 df = False
50 df = False
51 if opts.get('date'):
51 if opts.get('date'):
52 df = util.matchdate(opts['date'])
52 df = util.matchdate(opts['date'])
53
53
54 get = util.cachefunc(lambda r: repo[r].changeset())
54 get = util.cachefunc(lambda r: repo[r].changeset())
55 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
55 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
56 for st, rev, fns in changeiter:
56 for st, rev, fns in changeiter:
57 if not st == 'add':
57 if not st == 'add':
58 continue
58 continue
59 if df and not df(get(rev)[2][0]): # doesn't match date format
59 if df and not df(get(rev)[2][0]): # doesn't match date format
60 continue
60 continue
61
61
62 ctx = repo[rev]
62 ctx = repo[rev]
63 key = getkey(ctx)
63 key = getkey(ctx)
64 key = amap.get(key, key) # alias remap
64 key = amap.get(key, key) # alias remap
65 if opts.get('changesets'):
65 if opts.get('changesets'):
66 rate[key] = rate.get(key, 0) + 1
66 rate[key] = rate.get(key, 0) + 1
67 else:
67 else:
68 parents = ctx.parents()
68 parents = ctx.parents()
69 if len(parents) > 1:
69 if len(parents) > 1:
70 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
70 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
71 continue
71 continue
72
72
73 ctx1 = parents[0]
73 ctx1 = parents[0]
74 lines = changedlines(ui, repo, ctx1, ctx)
74 lines = changedlines(ui, repo, ctx1, ctx)
75 rate[key] = rate.get(key, 0) + lines
75 rate[key] = rate.get(key, 0) + lines
76
76
77 if opts.get('progress'):
77 if opts.get('progress'):
78 count += 1
78 count += 1
79 newpct = int(100.0 * count / max(len(repo), 1))
79 newpct = int(100.0 * count / max(len(repo), 1))
80 if pct < newpct:
80 if pct < newpct:
81 pct = newpct
81 pct = newpct
82 ui.write(_("\rgenerating stats: %d%%") % pct)
82 ui.write(_("\rgenerating stats: %d%%") % pct)
83 sys.stdout.flush()
83 sys.stdout.flush()
84
84
85 if opts.get('progress'):
85 if opts.get('progress'):
86 ui.write("\r")
86 ui.write("\r")
87 sys.stdout.flush()
87 sys.stdout.flush()
88
88
89 return rate
89 return rate
90
90
91
91
92 def churn(ui, repo, *pats, **opts):
92 def churn(ui, repo, *pats, **opts):
93 '''graph count of revisions grouped by template
93 '''graph count of revisions grouped by template
94
94
95 Will graph count of changed lines or revisions grouped by template or
95 Will graph count of changed lines or revisions grouped by template or
96 alternatively by date, if dateformat is used. In this case it will override
96 alternatively by date, if dateformat is used. In this case it will override
97 template.
97 template.
98
98
99 By default statistics are counted for number of changed lines.
99 By default statistics are counted for number of changed lines.
100
100
101 Examples:
101 Examples:
102
102
103 # display count of changed lines for every committer
103 # display count of changed lines for every committer
104 hg churn -t '{author|email}'
104 hg churn -t '{author|email}'
105
105
106 # display daily activity graph
106 # display daily activity graph
107 hg churn -f '%H' -s -c
107 hg churn -f '%H' -s -c
108
108
109 # display activity of developers by month
109 # display activity of developers by month
110 hg churn -f '%Y-%m' -s -c
110 hg churn -f '%Y-%m' -s -c
111
111
112 # display count of lines changed in every year
112 # display count of lines changed in every year
113 hg churn -f '%Y' -s
113 hg churn -f '%Y' -s
114
114
115 The map file format used to specify aliases is fairly simple:
115 The map file format used to specify aliases is fairly simple:
116
116
117 <alias email> <actual email>'''
117 <alias email> <actual email>'''
118 def pad(s, l):
118 def pad(s, l):
119 return (s + " " * l)[:l]
119 return (s + " " * l)[:l]
120
120
121 amap = {}
121 amap = {}
122 aliases = opts.get('aliases')
122 aliases = opts.get('aliases')
123 if aliases:
123 if aliases:
124 for l in open(aliases, "r"):
124 for l in open(aliases, "r"):
125 l = l.strip()
125 l = l.strip()
126 alias, actual = l.split()
126 alias, actual = l.split()
127 amap[alias] = actual
127 amap[alias] = actual
128
128
129 rate = countrate(ui, repo, amap, *pats, **opts).items()
129 rate = countrate(ui, repo, amap, *pats, **opts).items()
130 if not rate:
130 if not rate:
131 return
131 return
132
132
133 sortfn = ((not opts.get('sort')) and (lambda a, b: cmp(b[1], a[1])) or None)
133 sortfn = ((not opts.get('sort')) and (lambda a, b: cmp(b[1], a[1])) or None)
134 rate.sort(sortfn)
134 rate.sort(sortfn)
135
135
136 maxcount = float(max([v for k, v in rate]))
136 maxcount = float(max([v for k, v in rate]))
137 maxname = max([len(k) for k, v in rate])
137 maxname = max([len(k) for k, v in rate])
138
138
139 ttywidth = util.termwidth()
139 ttywidth = util.termwidth()
140 ui.debug(_("assuming %i character terminal\n") % ttywidth)
140 ui.debug(_("assuming %i character terminal\n") % ttywidth)
141 width = ttywidth - maxname - 2 - 6 - 2 - 2
141 width = ttywidth - maxname - 2 - 6 - 2 - 2
142
142
143 for date, count in rate:
143 for date, count in rate:
144 print "%s %6d %s" % (pad(date, maxname), count,
144 print "%s %6d %s" % (pad(date, maxname), count,
145 "*" * int(count * width / maxcount))
145 "*" * int(count * width / maxcount))
146
146
147
147
148 cmdtable = {
148 cmdtable = {
149 "churn":
149 "churn":
150 (churn,
150 (churn,
151 [('r', 'rev', [], _('count rate for the specified revision or range')),
151 [('r', 'rev', [], _('count rate for the specified revision or range')),
152 ('d', 'date', '', _('count rate for revs matching date spec')),
152 ('d', 'date', '', _('count rate for revs matching date spec')),
153 ('t', 'template', '{author|email}', _('template to group changesets')),
153 ('t', 'template', '{author|email}', _('template to group changesets')),
154 ('f', 'dateformat', '',
154 ('f', 'dateformat', '',
155 _('strftime-compatible format for grouping by date')),
155 _('strftime-compatible format for grouping by date')),
156 ('c', 'changesets', False, _('count rate by number of changesets')),
156 ('c', 'changesets', False, _('count rate by number of changesets')),
157 ('s', 'sort', False, _('sort by key (default: sort by count)')),
157 ('s', 'sort', False, _('sort by key (default: sort by count)')),
158 ('', 'aliases', '', _('file with email aliases')),
158 ('', 'aliases', '', _('file with email aliases')),
159 ('', 'progress', None, _('show progress'))],
159 ('', 'progress', None, _('show progress'))],
160 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [--progress] [FILE]")),
160 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [--progress] [FILE]")),
161 }
161 }
@@ -1,245 +1,246 b''
1 # Copyright (C) 2007-8 Brendan Cully <brendan@kublai.com>
1 # Copyright (C) 2007-8 Brendan Cully <brendan@kublai.com>
2 # Published under the GNU GPL
2 # Published under the GNU GPL
3
3
4 """CIA notification
4 """CIA notification
5
5
6 This is meant to be run as a changegroup or incoming hook.
6 This is meant to be run as a changegroup or incoming hook.
7 To configure it, set the following options in your hgrc:
7 To configure it, set the following options in your hgrc:
8
8
9 [cia]
9 [cia]
10 # your registered CIA user name
10 # your registered CIA user name
11 user = foo
11 user = foo
12 # the name of the project in CIA
12 # the name of the project in CIA
13 project = foo
13 project = foo
14 # the module (subproject) (optional)
14 # the module (subproject) (optional)
15 #module = foo
15 #module = foo
16 # Append a diffstat to the log message (optional)
16 # Append a diffstat to the log message (optional)
17 #diffstat = False
17 #diffstat = False
18 # Template to use for log messages (optional)
18 # Template to use for log messages (optional)
19 #template = {desc}\n{baseurl}/rev/{node}-- {diffstat}
19 #template = {desc}\n{baseurl}/rev/{node}-- {diffstat}
20 # Style to use (optional)
20 # Style to use (optional)
21 #style = foo
21 #style = foo
22 # The URL of the CIA notification service (optional)
22 # The URL of the CIA notification service (optional)
23 # You can use mailto: URLs to send by email, eg
23 # You can use mailto: URLs to send by email, eg
24 # mailto:cia@cia.vc
24 # mailto:cia@cia.vc
25 # Make sure to set email.from if you do this.
25 # Make sure to set email.from if you do this.
26 #url = http://cia.vc/
26 #url = http://cia.vc/
27 # print message instead of sending it (optional)
27 # print message instead of sending it (optional)
28 #test = False
28 #test = False
29
29
30 [hooks]
30 [hooks]
31 # one of these:
31 # one of these:
32 changegroup.cia = python:hgcia.hook
32 changegroup.cia = python:hgcia.hook
33 #incoming.cia = python:hgcia.hook
33 #incoming.cia = python:hgcia.hook
34
34
35 [web]
35 [web]
36 # If you want hyperlinks (optional)
36 # If you want hyperlinks (optional)
37 baseurl = http://server/path/to/repo
37 baseurl = http://server/path/to/repo
38 """
38 """
39
39
40 from mercurial.i18n import _
40 from mercurial.i18n import _
41 from mercurial.node import *
41 from mercurial.node import *
42 from mercurial import cmdutil, patch, templater, util, mail
42 from mercurial import cmdutil, patch, templater, util, mail
43 import email.Parser
43 import email.Parser
44
44
45 import xmlrpclib
45 import xmlrpclib
46 from xml.sax import saxutils
46 from xml.sax import saxutils
47
47
48 socket_timeout = 30 # seconds
48 socket_timeout = 30 # seconds
49 try:
49 try:
50 # set a timeout for the socket so you don't have to wait so looooong
50 # set a timeout for the socket so you don't have to wait so looooong
51 # when cia.vc is having problems. requires python >= 2.3:
51 # when cia.vc is having problems. requires python >= 2.3:
52 import socket
52 import socket
53 socket.setdefaulttimeout(socket_timeout)
53 socket.setdefaulttimeout(socket_timeout)
54 except:
54 except:
55 pass
55 pass
56
56
57 HGCIA_VERSION = '0.1'
57 HGCIA_VERSION = '0.1'
58 HGCIA_URL = 'http://hg.kublai.com/mercurial/hgcia'
58 HGCIA_URL = 'http://hg.kublai.com/mercurial/hgcia'
59
59
60
60
61 class ciamsg(object):
61 class ciamsg(object):
62 """ A CIA message """
62 """ A CIA message """
63 def __init__(self, cia, ctx):
63 def __init__(self, cia, ctx):
64 self.cia = cia
64 self.cia = cia
65 self.ctx = ctx
65 self.ctx = ctx
66 self.url = self.cia.url
66 self.url = self.cia.url
67
67
68 def fileelem(self, path, uri, action):
68 def fileelem(self, path, uri, action):
69 if uri:
69 if uri:
70 uri = ' uri=%s' % saxutils.quoteattr(uri)
70 uri = ' uri=%s' % saxutils.quoteattr(uri)
71 return '<file%s action=%s>%s</file>' % (
71 return '<file%s action=%s>%s</file>' % (
72 uri, saxutils.quoteattr(action), saxutils.escape(path))
72 uri, saxutils.quoteattr(action), saxutils.escape(path))
73
73
74 def fileelems(self):
74 def fileelems(self):
75 n = self.ctx.node()
75 n = self.ctx.node()
76 f = self.cia.repo.status(self.ctx.parents()[0].node(), n)
76 f = self.cia.repo.status(self.ctx.parents()[0].node(), n)
77 url = self.url or ''
77 url = self.url or ''
78 elems = []
78 elems = []
79 for path in f[0]:
79 for path in f[0]:
80 uri = '%s/diff/%s/%s' % (url, short(n), path)
80 uri = '%s/diff/%s/%s' % (url, short(n), path)
81 elems.append(self.fileelem(path, url and uri, 'modify'))
81 elems.append(self.fileelem(path, url and uri, 'modify'))
82 for path in f[1]:
82 for path in f[1]:
83 # TODO: copy/rename ?
83 # TODO: copy/rename ?
84 uri = '%s/file/%s/%s' % (url, short(n), path)
84 uri = '%s/file/%s/%s' % (url, short(n), path)
85 elems.append(self.fileelem(path, url and uri, 'add'))
85 elems.append(self.fileelem(path, url and uri, 'add'))
86 for path in f[2]:
86 for path in f[2]:
87 elems.append(self.fileelem(path, '', 'remove'))
87 elems.append(self.fileelem(path, '', 'remove'))
88
88
89 return '\n'.join(elems)
89 return '\n'.join(elems)
90
90
91 def sourceelem(self, project, module=None, branch=None):
91 def sourceelem(self, project, module=None, branch=None):
92 msg = ['<source>', '<project>%s</project>' % saxutils.escape(project)]
92 msg = ['<source>', '<project>%s</project>' % saxutils.escape(project)]
93 if module:
93 if module:
94 msg.append('<module>%s</module>' % saxutils.escape(module))
94 msg.append('<module>%s</module>' % saxutils.escape(module))
95 if branch:
95 if branch:
96 msg.append('<branch>%s</branch>' % saxutils.escape(branch))
96 msg.append('<branch>%s</branch>' % saxutils.escape(branch))
97 msg.append('</source>')
97 msg.append('</source>')
98
98
99 return '\n'.join(msg)
99 return '\n'.join(msg)
100
100
101 def diffstat(self):
101 def diffstat(self):
102 class patchbuf:
102 class patchbuf:
103 def __init__(self):
103 def __init__(self):
104 self.lines = []
104 self.lines = []
105 # diffstat is stupid
105 # diffstat is stupid
106 self.name = 'cia'
106 self.name = 'cia'
107 def write(self, data):
107 def write(self, data):
108 self.lines.append(data)
108 self.lines.append(data)
109 def close(self):
109 def close(self):
110 pass
110 pass
111
111
112 n = self.ctx.node()
112 n = self.ctx.node()
113 pbuf = patchbuf()
113 pbuf = patchbuf()
114 patch.export(self.cia.repo, [n], fp=pbuf)
114 patch.export(self.cia.repo, [n], fp=pbuf)
115 return patch.diffstat(pbuf.lines) or ''
115 return patch.diffstat(pbuf.lines) or ''
116
116
117 def logmsg(self):
117 def logmsg(self):
118 diffstat = self.cia.diffstat and self.diffstat() or ''
118 diffstat = self.cia.diffstat and self.diffstat() or ''
119 self.cia.ui.pushbuffer()
119 self.cia.ui.pushbuffer()
120 self.cia.templater.show(self.ctx, changes=self.ctx.changeset(),
120 self.cia.templater.show(self.ctx, changes=self.ctx.changeset(),
121 url=self.cia.url, diffstat=diffstat)
121 url=self.cia.url, diffstat=diffstat)
122 return self.cia.ui.popbuffer()
122 return self.cia.ui.popbuffer()
123
123
124 def xml(self):
124 def xml(self):
125 n = short(self.ctx.node())
125 n = short(self.ctx.node())
126 src = self.sourceelem(self.cia.project, module=self.cia.module,
126 src = self.sourceelem(self.cia.project, module=self.cia.module,
127 branch=self.ctx.branch())
127 branch=self.ctx.branch())
128 # unix timestamp
128 # unix timestamp
129 dt = self.ctx.date()
129 dt = self.ctx.date()
130 timestamp = dt[0]
130 timestamp = dt[0]
131
131
132 author = saxutils.escape(self.ctx.user())
132 author = saxutils.escape(self.ctx.user())
133 rev = '%d:%s' % (self.ctx.rev(), n)
133 rev = '%d:%s' % (self.ctx.rev(), n)
134 log = saxutils.escape(self.logmsg())
134 log = saxutils.escape(self.logmsg())
135
135
136 url = self.url and '<url>%s/rev/%s</url>' % (saxutils.escape(self.url),
136 url = self.url and '<url>%s/rev/%s</url>' % (saxutils.escape(self.url),
137 n) or ''
137 n) or ''
138
138
139 msg = """
139 msg = """
140 <message>
140 <message>
141 <generator>
141 <generator>
142 <name>Mercurial (hgcia)</name>
142 <name>Mercurial (hgcia)</name>
143 <version>%s</version>
143 <version>%s</version>
144 <url>%s</url>
144 <url>%s</url>
145 <user>%s</user>
145 <user>%s</user>
146 </generator>
146 </generator>
147 %s
147 %s
148 <body>
148 <body>
149 <commit>
149 <commit>
150 <author>%s</author>
150 <author>%s</author>
151 <version>%s</version>
151 <version>%s</version>
152 <log>%s</log>
152 <log>%s</log>
153 %s
153 %s
154 <files>%s</files>
154 <files>%s</files>
155 </commit>
155 </commit>
156 </body>
156 </body>
157 <timestamp>%d</timestamp>
157 <timestamp>%d</timestamp>
158 </message>
158 </message>
159 """ % \
159 """ % \
160 (HGCIA_VERSION, saxutils.escape(HGCIA_URL),
160 (HGCIA_VERSION, saxutils.escape(HGCIA_URL),
161 saxutils.escape(self.cia.user), src, author, rev, log, url,
161 saxutils.escape(self.cia.user), src, author, rev, log, url,
162 self.fileelems(), timestamp)
162 self.fileelems(), timestamp)
163
163
164 return msg
164 return msg
165
165
166
166
167 class hgcia(object):
167 class hgcia(object):
168 """ CIA notification class """
168 """ CIA notification class """
169
169
170 deftemplate = '{desc}'
170 deftemplate = '{desc}'
171 dstemplate = '{desc}\n-- \n{diffstat}'
171 dstemplate = '{desc}\n-- \n{diffstat}'
172
172
173 def __init__(self, ui, repo):
173 def __init__(self, ui, repo):
174 self.ui = ui
174 self.ui = ui
175 self.repo = repo
175 self.repo = repo
176
176
177 self.ciaurl = self.ui.config('cia', 'url', 'http://cia.vc')
177 self.ciaurl = self.ui.config('cia', 'url', 'http://cia.vc')
178 self.user = self.ui.config('cia', 'user')
178 self.user = self.ui.config('cia', 'user')
179 self.project = self.ui.config('cia', 'project')
179 self.project = self.ui.config('cia', 'project')
180 self.module = self.ui.config('cia', 'module')
180 self.module = self.ui.config('cia', 'module')
181 self.diffstat = self.ui.configbool('cia', 'diffstat')
181 self.diffstat = self.ui.configbool('cia', 'diffstat')
182 self.emailfrom = self.ui.config('email', 'from')
182 self.emailfrom = self.ui.config('email', 'from')
183 self.dryrun = self.ui.configbool('cia', 'test')
183 self.dryrun = self.ui.configbool('cia', 'test')
184 self.url = self.ui.config('web', 'baseurl')
184 self.url = self.ui.config('web', 'baseurl')
185
185
186 style = self.ui.config('cia', 'style')
186 style = self.ui.config('cia', 'style')
187 template = self.ui.config('cia', 'template')
187 template = self.ui.config('cia', 'template')
188 if not template:
188 if not template:
189 template = self.diffstat and self.dstemplate or self.deftemplate
189 template = self.diffstat and self.dstemplate or self.deftemplate
190 template = templater.parsestring(template, quoted=False)
190 template = templater.parsestring(template, quoted=False)
191 t = cmdutil.changeset_templater(self.ui, self.repo, False, style, False)
191 t = cmdutil.changeset_templater(self.ui, self.repo, False, None,
192 style, False)
192 t.use_template(template)
193 t.use_template(template)
193 self.templater = t
194 self.templater = t
194
195
195 def sendrpc(self, msg):
196 def sendrpc(self, msg):
196 srv = xmlrpclib.Server(self.ciaurl)
197 srv = xmlrpclib.Server(self.ciaurl)
197 srv.hub.deliver(msg)
198 srv.hub.deliver(msg)
198
199
199 def sendemail(self, address, data):
200 def sendemail(self, address, data):
200 p = email.Parser.Parser()
201 p = email.Parser.Parser()
201 msg = p.parsestr(data)
202 msg = p.parsestr(data)
202 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
203 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
203 msg['To'] = address
204 msg['To'] = address
204 msg['From'] = self.emailfrom
205 msg['From'] = self.emailfrom
205 msg['Subject'] = 'DeliverXML'
206 msg['Subject'] = 'DeliverXML'
206 msg['Content-type'] = 'text/xml'
207 msg['Content-type'] = 'text/xml'
207 msgtext = msg.as_string(0)
208 msgtext = msg.as_string(0)
208
209
209 self.ui.status(_('hgcia: sending update to %s\n') % address)
210 self.ui.status(_('hgcia: sending update to %s\n') % address)
210 mail.sendmail(self.ui, util.email(self.emailfrom),
211 mail.sendmail(self.ui, util.email(self.emailfrom),
211 [address], msgtext)
212 [address], msgtext)
212
213
213
214
214 def hook(ui, repo, hooktype, node=None, url=None, **kwargs):
215 def hook(ui, repo, hooktype, node=None, url=None, **kwargs):
215 """ send CIA notification """
216 """ send CIA notification """
216 def sendmsg(cia, ctx):
217 def sendmsg(cia, ctx):
217 msg = ciamsg(cia, ctx).xml()
218 msg = ciamsg(cia, ctx).xml()
218 if cia.dryrun:
219 if cia.dryrun:
219 ui.write(msg)
220 ui.write(msg)
220 elif cia.ciaurl.startswith('mailto:'):
221 elif cia.ciaurl.startswith('mailto:'):
221 if not cia.emailfrom:
222 if not cia.emailfrom:
222 raise util.Abort(_('email.from must be defined when '
223 raise util.Abort(_('email.from must be defined when '
223 'sending by email'))
224 'sending by email'))
224 cia.sendemail(cia.ciaurl[7:], msg)
225 cia.sendemail(cia.ciaurl[7:], msg)
225 else:
226 else:
226 cia.sendrpc(msg)
227 cia.sendrpc(msg)
227
228
228 n = bin(node)
229 n = bin(node)
229 cia = hgcia(ui, repo)
230 cia = hgcia(ui, repo)
230 if not cia.user:
231 if not cia.user:
231 ui.debug(_('cia: no user specified'))
232 ui.debug(_('cia: no user specified'))
232 return
233 return
233 if not cia.project:
234 if not cia.project:
234 ui.debug(_('cia: no project specified'))
235 ui.debug(_('cia: no project specified'))
235 return
236 return
236 if hooktype == 'changegroup':
237 if hooktype == 'changegroup':
237 start = repo.changelog.rev(n)
238 start = repo.changelog.rev(n)
238 end = len(repo.changelog)
239 end = len(repo.changelog)
239 for rev in xrange(start, end):
240 for rev in xrange(start, end):
240 n = repo.changelog.node(rev)
241 n = repo.changelog.node(rev)
241 ctx = repo.changectx(n)
242 ctx = repo.changectx(n)
242 sendmsg(cia, ctx)
243 sendmsg(cia, ctx)
243 else:
244 else:
244 ctx = repo.changectx(n)
245 ctx = repo.changectx(n)
245 sendmsg(cia, ctx)
246 sendmsg(cia, ctx)
@@ -1,532 +1,532 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
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 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a DSCM
10 # Keyword expansion hack against the grain of a DSCM
11 #
11 #
12 # There are many good reasons why this is not needed in a distributed
12 # There are many good reasons why this is not needed in a distributed
13 # SCM, still it may be useful in very small projects based on single
13 # SCM, still it may be useful in very small projects based on single
14 # files (like LaTeX packages), that are mostly addressed to an audience
14 # files (like LaTeX packages), that are mostly addressed to an audience
15 # not running a version control system.
15 # not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
19 #
19 #
20 # Keyword expansion is based on Mercurial's changeset template mappings.
20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 #
21 #
22 # Binary files are not touched.
22 # Binary files are not touched.
23 #
23 #
24 # Setup in hgrc:
24 # Setup in hgrc:
25 #
25 #
26 # [extensions]
26 # [extensions]
27 # # enable extension
27 # # enable extension
28 # hgext.keyword =
28 # hgext.keyword =
29 #
29 #
30 # Files to act upon/ignore are specified in the [keyword] section.
30 # Files to act upon/ignore are specified in the [keyword] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
32 #
32 #
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
34
34
35 '''keyword expansion in local repositories
35 '''keyword expansion in local repositories
36
36
37 This extension expands RCS/CVS-like or self-customized $Keywords$
37 This extension expands RCS/CVS-like or self-customized $Keywords$
38 in tracked text files selected by your configuration.
38 in tracked text files selected by your configuration.
39
39
40 Keywords are only expanded in local repositories and not stored in
40 Keywords are only expanded in local repositories and not stored in
41 the change history. The mechanism can be regarded as a convenience
41 the change history. The mechanism can be regarded as a convenience
42 for the current user or for archive distribution.
42 for the current user or for archive distribution.
43
43
44 Configuration is done in the [keyword] and [keywordmaps] sections
44 Configuration is done in the [keyword] and [keywordmaps] sections
45 of hgrc files.
45 of hgrc files.
46
46
47 Example:
47 Example:
48
48
49 [keyword]
49 [keyword]
50 # expand keywords in every python file except those matching "x*"
50 # expand keywords in every python file except those matching "x*"
51 **.py =
51 **.py =
52 x* = ignore
52 x* = ignore
53
53
54 Note: the more specific you are in your filename patterns
54 Note: the more specific you are in your filename patterns
55 the less you lose speed in huge repos.
55 the less you lose speed in huge repos.
56
56
57 For [keywordmaps] template mapping and expansion demonstration and
57 For [keywordmaps] template mapping and expansion demonstration and
58 control run "hg kwdemo".
58 control run "hg kwdemo".
59
59
60 An additional date template filter {date|utcdate} is provided.
60 An additional date template filter {date|utcdate} is provided.
61
61
62 The default template mappings (view with "hg kwdemo -d") can be replaced
62 The default template mappings (view with "hg kwdemo -d") can be replaced
63 with customized keywords and templates.
63 with customized keywords and templates.
64 Again, run "hg kwdemo" to control the results of your config changes.
64 Again, run "hg kwdemo" to control the results of your config changes.
65
65
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
67 the risk of inadvertedly storing expanded keywords in the change history.
67 the risk of inadvertedly storing expanded keywords in the change history.
68
68
69 To force expansion after enabling it, or a configuration change, run
69 To force expansion after enabling it, or a configuration change, run
70 "hg kwexpand".
70 "hg kwexpand".
71
71
72 Also, when committing with the record extension or using mq's qrecord, be aware
72 Also, when committing with the record extension or using mq's qrecord, be aware
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
74 question to update keyword expansions after all changes have been checked in.
74 question to update keyword expansions after all changes have been checked in.
75
75
76 Expansions spanning more than one line and incremental expansions,
76 Expansions spanning more than one line and incremental expansions,
77 like CVS' $Log$, are not supported. A keyword template map
77 like CVS' $Log$, are not supported. A keyword template map
78 "Log = {desc}" expands to the first line of the changeset description.
78 "Log = {desc}" expands to the first line of the changeset description.
79 '''
79 '''
80
80
81 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
81 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
82 from mercurial import patch, localrepo, templater, templatefilters, util
82 from mercurial import patch, localrepo, templater, templatefilters, util
83 from mercurial.hgweb import webcommands
83 from mercurial.hgweb import webcommands
84 from mercurial.node import nullid, hex
84 from mercurial.node import nullid, hex
85 from mercurial.i18n import _
85 from mercurial.i18n import _
86 import re, shutil, tempfile, time
86 import re, shutil, tempfile, time
87
87
88 commands.optionalrepo += ' kwdemo'
88 commands.optionalrepo += ' kwdemo'
89
89
90 # hg commands that do not act on keywords
90 # hg commands that do not act on keywords
91 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
91 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
92 ' log outgoing push rename rollback tip verify'
92 ' log outgoing push rename rollback tip verify'
93 ' convert email glog')
93 ' convert email glog')
94
94
95 # hg commands that trigger expansion only when writing to working dir,
95 # hg commands that trigger expansion only when writing to working dir,
96 # not when reading filelog, and unexpand when reading from working dir
96 # not when reading filelog, and unexpand when reading from working dir
97 restricted = 'merge record resolve qfold qimport qnew qpush qrefresh qrecord'
97 restricted = 'merge record resolve qfold qimport qnew qpush qrefresh qrecord'
98
98
99 def utcdate(date):
99 def utcdate(date):
100 '''Returns hgdate in cvs-like UTC format.'''
100 '''Returns hgdate in cvs-like UTC format.'''
101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
102
102
103 # make keyword tools accessible
103 # make keyword tools accessible
104 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
104 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
105
105
106
106
107 class kwtemplater(object):
107 class kwtemplater(object):
108 '''
108 '''
109 Sets up keyword templates, corresponding keyword regex, and
109 Sets up keyword templates, corresponding keyword regex, and
110 provides keyword substitution functions.
110 provides keyword substitution functions.
111 '''
111 '''
112 templates = {
112 templates = {
113 'Revision': '{node|short}',
113 'Revision': '{node|short}',
114 'Author': '{author|user}',
114 'Author': '{author|user}',
115 'Date': '{date|utcdate}',
115 'Date': '{date|utcdate}',
116 'RCSFile': '{file|basename},v',
116 'RCSFile': '{file|basename},v',
117 'Source': '{root}/{file},v',
117 'Source': '{root}/{file},v',
118 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
118 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
119 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
119 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
120 }
120 }
121
121
122 def __init__(self, ui, repo):
122 def __init__(self, ui, repo):
123 self.ui = ui
123 self.ui = ui
124 self.repo = repo
124 self.repo = repo
125 self.matcher = util.matcher(repo.root,
125 self.matcher = util.matcher(repo.root,
126 inc=kwtools['inc'], exc=kwtools['exc'])[1]
126 inc=kwtools['inc'], exc=kwtools['exc'])[1]
127 self.restrict = kwtools['hgcmd'] in restricted.split()
127 self.restrict = kwtools['hgcmd'] in restricted.split()
128
128
129 kwmaps = self.ui.configitems('keywordmaps')
129 kwmaps = self.ui.configitems('keywordmaps')
130 if kwmaps: # override default templates
130 if kwmaps: # override default templates
131 kwmaps = [(k, templater.parsestring(v, False))
131 kwmaps = [(k, templater.parsestring(v, False))
132 for (k, v) in kwmaps]
132 for (k, v) in kwmaps]
133 self.templates = dict(kwmaps)
133 self.templates = dict(kwmaps)
134 escaped = map(re.escape, self.templates.keys())
134 escaped = map(re.escape, self.templates.keys())
135 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
135 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
136 self.re_kw = re.compile(kwpat)
136 self.re_kw = re.compile(kwpat)
137
137
138 templatefilters.filters['utcdate'] = utcdate
138 templatefilters.filters['utcdate'] = utcdate
139 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
139 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
140 False, '', False)
140 False, None, '', False)
141
141
142 def substitute(self, data, path, ctx, subfunc):
142 def substitute(self, data, path, ctx, subfunc):
143 '''Replaces keywords in data with expanded template.'''
143 '''Replaces keywords in data with expanded template.'''
144 def kwsub(mobj):
144 def kwsub(mobj):
145 kw = mobj.group(1)
145 kw = mobj.group(1)
146 self.ct.use_template(self.templates[kw])
146 self.ct.use_template(self.templates[kw])
147 self.ui.pushbuffer()
147 self.ui.pushbuffer()
148 self.ct.show(ctx, root=self.repo.root, file=path)
148 self.ct.show(ctx, root=self.repo.root, file=path)
149 ekw = templatefilters.firstline(self.ui.popbuffer())
149 ekw = templatefilters.firstline(self.ui.popbuffer())
150 return '$%s: %s $' % (kw, ekw)
150 return '$%s: %s $' % (kw, ekw)
151 return subfunc(kwsub, data)
151 return subfunc(kwsub, data)
152
152
153 def expand(self, path, node, data):
153 def expand(self, path, node, data):
154 '''Returns data with keywords expanded.'''
154 '''Returns data with keywords expanded.'''
155 if not self.restrict and self.matcher(path) and not util.binary(data):
155 if not self.restrict and self.matcher(path) and not util.binary(data):
156 ctx = self.repo.filectx(path, fileid=node).changectx()
156 ctx = self.repo.filectx(path, fileid=node).changectx()
157 return self.substitute(data, path, ctx, self.re_kw.sub)
157 return self.substitute(data, path, ctx, self.re_kw.sub)
158 return data
158 return data
159
159
160 def iskwfile(self, path, flagfunc):
160 def iskwfile(self, path, flagfunc):
161 '''Returns true if path matches [keyword] pattern
161 '''Returns true if path matches [keyword] pattern
162 and is not a symbolic link.
162 and is not a symbolic link.
163 Caveat: localrepository._link fails on Windows.'''
163 Caveat: localrepository._link fails on Windows.'''
164 return self.matcher(path) and not 'l' in flagfunc(path)
164 return self.matcher(path) and not 'l' in flagfunc(path)
165
165
166 def overwrite(self, node, expand, files):
166 def overwrite(self, node, expand, files):
167 '''Overwrites selected files expanding/shrinking keywords.'''
167 '''Overwrites selected files expanding/shrinking keywords.'''
168 ctx = self.repo[node]
168 ctx = self.repo[node]
169 mf = ctx.manifest()
169 mf = ctx.manifest()
170 if node is not None: # commit
170 if node is not None: # commit
171 files = [f for f in ctx.files() if f in mf]
171 files = [f for f in ctx.files() if f in mf]
172 notify = self.ui.debug
172 notify = self.ui.debug
173 else: # kwexpand/kwshrink
173 else: # kwexpand/kwshrink
174 notify = self.ui.note
174 notify = self.ui.note
175 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
175 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
176 if candidates:
176 if candidates:
177 self.restrict = True # do not expand when reading
177 self.restrict = True # do not expand when reading
178 action = expand and 'expanding' or 'shrinking'
178 action = expand and 'expanding' or 'shrinking'
179 for f in candidates:
179 for f in candidates:
180 fp = self.repo.file(f)
180 fp = self.repo.file(f)
181 data = fp.read(mf[f])
181 data = fp.read(mf[f])
182 if util.binary(data):
182 if util.binary(data):
183 continue
183 continue
184 if expand:
184 if expand:
185 if node is None:
185 if node is None:
186 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
186 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
187 data, found = self.substitute(data, f, ctx,
187 data, found = self.substitute(data, f, ctx,
188 self.re_kw.subn)
188 self.re_kw.subn)
189 else:
189 else:
190 found = self.re_kw.search(data)
190 found = self.re_kw.search(data)
191 if found:
191 if found:
192 notify(_('overwriting %s %s keywords\n') % (f, action))
192 notify(_('overwriting %s %s keywords\n') % (f, action))
193 self.repo.wwrite(f, data, mf.flags(f))
193 self.repo.wwrite(f, data, mf.flags(f))
194 self.repo.dirstate.normal(f)
194 self.repo.dirstate.normal(f)
195 self.restrict = False
195 self.restrict = False
196
196
197 def shrinktext(self, text):
197 def shrinktext(self, text):
198 '''Unconditionally removes all keyword substitutions from text.'''
198 '''Unconditionally removes all keyword substitutions from text.'''
199 return self.re_kw.sub(r'$\1$', text)
199 return self.re_kw.sub(r'$\1$', text)
200
200
201 def shrink(self, fname, text):
201 def shrink(self, fname, text):
202 '''Returns text with all keyword substitutions removed.'''
202 '''Returns text with all keyword substitutions removed.'''
203 if self.matcher(fname) and not util.binary(text):
203 if self.matcher(fname) and not util.binary(text):
204 return self.shrinktext(text)
204 return self.shrinktext(text)
205 return text
205 return text
206
206
207 def shrinklines(self, fname, lines):
207 def shrinklines(self, fname, lines):
208 '''Returns lines with keyword substitutions removed.'''
208 '''Returns lines with keyword substitutions removed.'''
209 if self.matcher(fname):
209 if self.matcher(fname):
210 text = ''.join(lines)
210 text = ''.join(lines)
211 if not util.binary(text):
211 if not util.binary(text):
212 return self.shrinktext(text).splitlines(True)
212 return self.shrinktext(text).splitlines(True)
213 return lines
213 return lines
214
214
215 def wread(self, fname, data):
215 def wread(self, fname, data):
216 '''If in restricted mode returns data read from wdir with
216 '''If in restricted mode returns data read from wdir with
217 keyword substitutions removed.'''
217 keyword substitutions removed.'''
218 return self.restrict and self.shrink(fname, data) or data
218 return self.restrict and self.shrink(fname, data) or data
219
219
220 class kwfilelog(filelog.filelog):
220 class kwfilelog(filelog.filelog):
221 '''
221 '''
222 Subclass of filelog to hook into its read, add, cmp methods.
222 Subclass of filelog to hook into its read, add, cmp methods.
223 Keywords are "stored" unexpanded, and processed on reading.
223 Keywords are "stored" unexpanded, and processed on reading.
224 '''
224 '''
225 def __init__(self, opener, kwt, path):
225 def __init__(self, opener, kwt, path):
226 super(kwfilelog, self).__init__(opener, path)
226 super(kwfilelog, self).__init__(opener, path)
227 self.kwt = kwt
227 self.kwt = kwt
228 self.path = path
228 self.path = path
229
229
230 def read(self, node):
230 def read(self, node):
231 '''Expands keywords when reading filelog.'''
231 '''Expands keywords when reading filelog.'''
232 data = super(kwfilelog, self).read(node)
232 data = super(kwfilelog, self).read(node)
233 return self.kwt.expand(self.path, node, data)
233 return self.kwt.expand(self.path, node, data)
234
234
235 def add(self, text, meta, tr, link, p1=None, p2=None):
235 def add(self, text, meta, tr, link, p1=None, p2=None):
236 '''Removes keyword substitutions when adding to filelog.'''
236 '''Removes keyword substitutions when adding to filelog.'''
237 text = self.kwt.shrink(self.path, text)
237 text = self.kwt.shrink(self.path, text)
238 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
238 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
239
239
240 def cmp(self, node, text):
240 def cmp(self, node, text):
241 '''Removes keyword substitutions for comparison.'''
241 '''Removes keyword substitutions for comparison.'''
242 text = self.kwt.shrink(self.path, text)
242 text = self.kwt.shrink(self.path, text)
243 if self.renamed(node):
243 if self.renamed(node):
244 t2 = super(kwfilelog, self).read(node)
244 t2 = super(kwfilelog, self).read(node)
245 return t2 != text
245 return t2 != text
246 return revlog.revlog.cmp(self, node, text)
246 return revlog.revlog.cmp(self, node, text)
247
247
248 def _status(ui, repo, kwt, unknown, *pats, **opts):
248 def _status(ui, repo, kwt, unknown, *pats, **opts):
249 '''Bails out if [keyword] configuration is not active.
249 '''Bails out if [keyword] configuration is not active.
250 Returns status of working directory.'''
250 Returns status of working directory.'''
251 if kwt:
251 if kwt:
252 matcher = cmdutil.match(repo, pats, opts)
252 matcher = cmdutil.match(repo, pats, opts)
253 return repo.status(match=matcher, unknown=unknown, clean=True)
253 return repo.status(match=matcher, unknown=unknown, clean=True)
254 if ui.configitems('keyword'):
254 if ui.configitems('keyword'):
255 raise util.Abort(_('[keyword] patterns cannot match'))
255 raise util.Abort(_('[keyword] patterns cannot match'))
256 raise util.Abort(_('no [keyword] patterns configured'))
256 raise util.Abort(_('no [keyword] patterns configured'))
257
257
258 def _kwfwrite(ui, repo, expand, *pats, **opts):
258 def _kwfwrite(ui, repo, expand, *pats, **opts):
259 '''Selects files and passes them to kwtemplater.overwrite.'''
259 '''Selects files and passes them to kwtemplater.overwrite.'''
260 if repo.dirstate.parents()[1] != nullid:
260 if repo.dirstate.parents()[1] != nullid:
261 raise util.Abort(_('outstanding uncommitted merge'))
261 raise util.Abort(_('outstanding uncommitted merge'))
262 kwt = kwtools['templater']
262 kwt = kwtools['templater']
263 status = _status(ui, repo, kwt, False, *pats, **opts)
263 status = _status(ui, repo, kwt, False, *pats, **opts)
264 modified, added, removed, deleted = status[:4]
264 modified, added, removed, deleted = status[:4]
265 if modified or added or removed or deleted:
265 if modified or added or removed or deleted:
266 raise util.Abort(_('outstanding uncommitted changes'))
266 raise util.Abort(_('outstanding uncommitted changes'))
267 wlock = lock = None
267 wlock = lock = None
268 try:
268 try:
269 wlock = repo.wlock()
269 wlock = repo.wlock()
270 lock = repo.lock()
270 lock = repo.lock()
271 kwt.overwrite(None, expand, status[6])
271 kwt.overwrite(None, expand, status[6])
272 finally:
272 finally:
273 del wlock, lock
273 del wlock, lock
274
274
275
275
276 def demo(ui, repo, *args, **opts):
276 def demo(ui, repo, *args, **opts):
277 '''print [keywordmaps] configuration and an expansion example
277 '''print [keywordmaps] configuration and an expansion example
278
278
279 Show current, custom, or default keyword template maps
279 Show current, custom, or default keyword template maps
280 and their expansion.
280 and their expansion.
281
281
282 Extend current configuration by specifying maps as arguments
282 Extend current configuration by specifying maps as arguments
283 and optionally by reading from an additional hgrc file.
283 and optionally by reading from an additional hgrc file.
284
284
285 Override current keyword template maps with "default" option.
285 Override current keyword template maps with "default" option.
286 '''
286 '''
287 def demostatus(stat):
287 def demostatus(stat):
288 ui.status(_('\n\t%s\n') % stat)
288 ui.status(_('\n\t%s\n') % stat)
289
289
290 def demoitems(section, items):
290 def demoitems(section, items):
291 ui.write('[%s]\n' % section)
291 ui.write('[%s]\n' % section)
292 for k, v in items:
292 for k, v in items:
293 ui.write('%s = %s\n' % (k, v))
293 ui.write('%s = %s\n' % (k, v))
294
294
295 msg = 'hg keyword config and expansion example'
295 msg = 'hg keyword config and expansion example'
296 kwstatus = 'current'
296 kwstatus = 'current'
297 fn = 'demo.txt'
297 fn = 'demo.txt'
298 branchname = 'demobranch'
298 branchname = 'demobranch'
299 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
299 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
300 ui.note(_('creating temporary repo at %s\n') % tmpdir)
300 ui.note(_('creating temporary repo at %s\n') % tmpdir)
301 repo = localrepo.localrepository(ui, tmpdir, True)
301 repo = localrepo.localrepository(ui, tmpdir, True)
302 ui.setconfig('keyword', fn, '')
302 ui.setconfig('keyword', fn, '')
303 if args or opts.get('rcfile'):
303 if args or opts.get('rcfile'):
304 kwstatus = 'custom'
304 kwstatus = 'custom'
305 if opts.get('rcfile'):
305 if opts.get('rcfile'):
306 ui.readconfig(opts.get('rcfile'))
306 ui.readconfig(opts.get('rcfile'))
307 if opts.get('default'):
307 if opts.get('default'):
308 kwstatus = 'default'
308 kwstatus = 'default'
309 kwmaps = kwtemplater.templates
309 kwmaps = kwtemplater.templates
310 if ui.configitems('keywordmaps'):
310 if ui.configitems('keywordmaps'):
311 # override maps from optional rcfile
311 # override maps from optional rcfile
312 for k, v in kwmaps.iteritems():
312 for k, v in kwmaps.iteritems():
313 ui.setconfig('keywordmaps', k, v)
313 ui.setconfig('keywordmaps', k, v)
314 elif args:
314 elif args:
315 # simulate hgrc parsing
315 # simulate hgrc parsing
316 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
316 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
317 fp = repo.opener('hgrc', 'w')
317 fp = repo.opener('hgrc', 'w')
318 fp.writelines(rcmaps)
318 fp.writelines(rcmaps)
319 fp.close()
319 fp.close()
320 ui.readconfig(repo.join('hgrc'))
320 ui.readconfig(repo.join('hgrc'))
321 if not opts.get('default'):
321 if not opts.get('default'):
322 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
322 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
323 uisetup(ui)
323 uisetup(ui)
324 reposetup(ui, repo)
324 reposetup(ui, repo)
325 for k, v in ui.configitems('extensions'):
325 for k, v in ui.configitems('extensions'):
326 if k.endswith('keyword'):
326 if k.endswith('keyword'):
327 extension = '%s = %s' % (k, v)
327 extension = '%s = %s' % (k, v)
328 break
328 break
329 demostatus('config using %s keyword template maps' % kwstatus)
329 demostatus('config using %s keyword template maps' % kwstatus)
330 ui.write('[extensions]\n%s\n' % extension)
330 ui.write('[extensions]\n%s\n' % extension)
331 demoitems('keyword', ui.configitems('keyword'))
331 demoitems('keyword', ui.configitems('keyword'))
332 demoitems('keywordmaps', kwmaps.iteritems())
332 demoitems('keywordmaps', kwmaps.iteritems())
333 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
333 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
334 repo.wopener(fn, 'w').write(keywords)
334 repo.wopener(fn, 'w').write(keywords)
335 repo.add([fn])
335 repo.add([fn])
336 path = repo.wjoin(fn)
336 path = repo.wjoin(fn)
337 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
337 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
338 ui.note(keywords)
338 ui.note(keywords)
339 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
339 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
340 # silence branch command if not verbose
340 # silence branch command if not verbose
341 quiet = ui.quiet
341 quiet = ui.quiet
342 ui.quiet = not ui.verbose
342 ui.quiet = not ui.verbose
343 commands.branch(ui, repo, branchname)
343 commands.branch(ui, repo, branchname)
344 ui.quiet = quiet
344 ui.quiet = quiet
345 for name, cmd in ui.configitems('hooks'):
345 for name, cmd in ui.configitems('hooks'):
346 if name.split('.', 1)[0].find('commit') > -1:
346 if name.split('.', 1)[0].find('commit') > -1:
347 repo.ui.setconfig('hooks', name, '')
347 repo.ui.setconfig('hooks', name, '')
348 ui.note(_('unhooked all commit hooks\n'))
348 ui.note(_('unhooked all commit hooks\n'))
349 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
349 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
350 repo.commit(text=msg)
350 repo.commit(text=msg)
351 fmt = ui.verbose and ' in %s' % path or ''
351 fmt = ui.verbose and ' in %s' % path or ''
352 demostatus('%s keywords expanded%s' % (kwstatus, fmt))
352 demostatus('%s keywords expanded%s' % (kwstatus, fmt))
353 ui.write(repo.wread(fn))
353 ui.write(repo.wread(fn))
354 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
354 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
355 shutil.rmtree(tmpdir, ignore_errors=True)
355 shutil.rmtree(tmpdir, ignore_errors=True)
356
356
357 def expand(ui, repo, *pats, **opts):
357 def expand(ui, repo, *pats, **opts):
358 '''expand keywords in working directory
358 '''expand keywords in working directory
359
359
360 Run after (re)enabling keyword expansion.
360 Run after (re)enabling keyword expansion.
361
361
362 kwexpand refuses to run if given files contain local changes.
362 kwexpand refuses to run if given files contain local changes.
363 '''
363 '''
364 # 3rd argument sets expansion to True
364 # 3rd argument sets expansion to True
365 _kwfwrite(ui, repo, True, *pats, **opts)
365 _kwfwrite(ui, repo, True, *pats, **opts)
366
366
367 def files(ui, repo, *pats, **opts):
367 def files(ui, repo, *pats, **opts):
368 '''print files currently configured for keyword expansion
368 '''print files currently configured for keyword expansion
369
369
370 Crosscheck which files in working directory are potential targets for
370 Crosscheck which files in working directory are potential targets for
371 keyword expansion.
371 keyword expansion.
372 That is, files matched by [keyword] config patterns but not symlinks.
372 That is, files matched by [keyword] config patterns but not symlinks.
373 '''
373 '''
374 kwt = kwtools['templater']
374 kwt = kwtools['templater']
375 status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts)
375 status = _status(ui, repo, kwt, opts.get('untracked'), *pats, **opts)
376 modified, added, removed, deleted, unknown, ignored, clean = status
376 modified, added, removed, deleted, unknown, ignored, clean = status
377 files = util.sort(modified + added + clean + unknown)
377 files = util.sort(modified + added + clean + unknown)
378 wctx = repo[None]
378 wctx = repo[None]
379 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
379 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
380 cwd = pats and repo.getcwd() or ''
380 cwd = pats and repo.getcwd() or ''
381 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
381 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
382 if opts.get('all') or opts.get('ignore'):
382 if opts.get('all') or opts.get('ignore'):
383 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
383 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
384 for char, filenames in kwfstats:
384 for char, filenames in kwfstats:
385 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
385 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
386 for f in filenames:
386 for f in filenames:
387 ui.write(fmt % repo.pathto(f, cwd))
387 ui.write(fmt % repo.pathto(f, cwd))
388
388
389 def shrink(ui, repo, *pats, **opts):
389 def shrink(ui, repo, *pats, **opts):
390 '''revert expanded keywords in working directory
390 '''revert expanded keywords in working directory
391
391
392 Run before changing/disabling active keywords
392 Run before changing/disabling active keywords
393 or if you experience problems with "hg import" or "hg merge".
393 or if you experience problems with "hg import" or "hg merge".
394
394
395 kwshrink refuses to run if given files contain local changes.
395 kwshrink refuses to run if given files contain local changes.
396 '''
396 '''
397 # 3rd argument sets expansion to False
397 # 3rd argument sets expansion to False
398 _kwfwrite(ui, repo, False, *pats, **opts)
398 _kwfwrite(ui, repo, False, *pats, **opts)
399
399
400
400
401 def uisetup(ui):
401 def uisetup(ui):
402 '''Collects [keyword] config in kwtools.
402 '''Collects [keyword] config in kwtools.
403 Monkeypatches dispatch._parse if needed.'''
403 Monkeypatches dispatch._parse if needed.'''
404
404
405 for pat, opt in ui.configitems('keyword'):
405 for pat, opt in ui.configitems('keyword'):
406 if opt != 'ignore':
406 if opt != 'ignore':
407 kwtools['inc'].append(pat)
407 kwtools['inc'].append(pat)
408 else:
408 else:
409 kwtools['exc'].append(pat)
409 kwtools['exc'].append(pat)
410
410
411 if kwtools['inc']:
411 if kwtools['inc']:
412 def kwdispatch_parse(orig, ui, args):
412 def kwdispatch_parse(orig, ui, args):
413 '''Monkeypatch dispatch._parse to obtain running hg command.'''
413 '''Monkeypatch dispatch._parse to obtain running hg command.'''
414 cmd, func, args, options, cmdoptions = orig(ui, args)
414 cmd, func, args, options, cmdoptions = orig(ui, args)
415 kwtools['hgcmd'] = cmd
415 kwtools['hgcmd'] = cmd
416 return cmd, func, args, options, cmdoptions
416 return cmd, func, args, options, cmdoptions
417
417
418 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
418 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
419
419
420 def reposetup(ui, repo):
420 def reposetup(ui, repo):
421 '''Sets up repo as kwrepo for keyword substitution.
421 '''Sets up repo as kwrepo for keyword substitution.
422 Overrides file method to return kwfilelog instead of filelog
422 Overrides file method to return kwfilelog instead of filelog
423 if file matches user configuration.
423 if file matches user configuration.
424 Wraps commit to overwrite configured files with updated
424 Wraps commit to overwrite configured files with updated
425 keyword substitutions.
425 keyword substitutions.
426 Monkeypatches patch and webcommands.'''
426 Monkeypatches patch and webcommands.'''
427
427
428 if (not hasattr(repo, 'dirstate') or not kwtools['inc']
428 if (not hasattr(repo, 'dirstate') or not kwtools['inc']
429 or kwtools['hgcmd'] in nokwcommands.split()
429 or kwtools['hgcmd'] in nokwcommands.split()
430 or '.hg' in util.splitpath(repo.root)):
430 or '.hg' in util.splitpath(repo.root)):
431 return
431 return
432
432
433 kwtools['templater'] = kwt = kwtemplater(ui, repo)
433 kwtools['templater'] = kwt = kwtemplater(ui, repo)
434
434
435 class kwrepo(repo.__class__):
435 class kwrepo(repo.__class__):
436 def file(self, f):
436 def file(self, f):
437 if f[0] == '/':
437 if f[0] == '/':
438 f = f[1:]
438 f = f[1:]
439 return kwfilelog(self.sopener, kwt, f)
439 return kwfilelog(self.sopener, kwt, f)
440
440
441 def wread(self, filename):
441 def wread(self, filename):
442 data = super(kwrepo, self).wread(filename)
442 data = super(kwrepo, self).wread(filename)
443 return kwt.wread(filename, data)
443 return kwt.wread(filename, data)
444
444
445 def commit(self, files=None, text='', user=None, date=None,
445 def commit(self, files=None, text='', user=None, date=None,
446 match=None, force=False, force_editor=False,
446 match=None, force=False, force_editor=False,
447 p1=None, p2=None, extra={}, empty_ok=False):
447 p1=None, p2=None, extra={}, empty_ok=False):
448 wlock = lock = None
448 wlock = lock = None
449 _p1 = _p2 = None
449 _p1 = _p2 = None
450 try:
450 try:
451 wlock = self.wlock()
451 wlock = self.wlock()
452 lock = self.lock()
452 lock = self.lock()
453 # store and postpone commit hooks
453 # store and postpone commit hooks
454 commithooks = {}
454 commithooks = {}
455 for name, cmd in ui.configitems('hooks'):
455 for name, cmd in ui.configitems('hooks'):
456 if name.split('.', 1)[0] == 'commit':
456 if name.split('.', 1)[0] == 'commit':
457 commithooks[name] = cmd
457 commithooks[name] = cmd
458 ui.setconfig('hooks', name, None)
458 ui.setconfig('hooks', name, None)
459 if commithooks:
459 if commithooks:
460 # store parents for commit hook environment
460 # store parents for commit hook environment
461 if p1 is None:
461 if p1 is None:
462 _p1, _p2 = repo.dirstate.parents()
462 _p1, _p2 = repo.dirstate.parents()
463 else:
463 else:
464 _p1, _p2 = p1, p2 or nullid
464 _p1, _p2 = p1, p2 or nullid
465 _p1 = hex(_p1)
465 _p1 = hex(_p1)
466 if _p2 == nullid:
466 if _p2 == nullid:
467 _p2 = ''
467 _p2 = ''
468 else:
468 else:
469 _p2 = hex(_p2)
469 _p2 = hex(_p2)
470
470
471 n = super(kwrepo, self).commit(files, text, user, date, match,
471 n = super(kwrepo, self).commit(files, text, user, date, match,
472 force, force_editor, p1, p2,
472 force, force_editor, p1, p2,
473 extra, empty_ok)
473 extra, empty_ok)
474
474
475 # restore commit hooks
475 # restore commit hooks
476 for name, cmd in commithooks.iteritems():
476 for name, cmd in commithooks.iteritems():
477 ui.setconfig('hooks', name, cmd)
477 ui.setconfig('hooks', name, cmd)
478 if n is not None:
478 if n is not None:
479 kwt.overwrite(n, True, None)
479 kwt.overwrite(n, True, None)
480 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
480 repo.hook('commit', node=n, parent1=_p1, parent2=_p2)
481 return n
481 return n
482 finally:
482 finally:
483 del wlock, lock
483 del wlock, lock
484
484
485 # monkeypatches
485 # monkeypatches
486 def kwpatchfile_init(orig, self, ui, fname, opener, missing=False):
486 def kwpatchfile_init(orig, self, ui, fname, opener, missing=False):
487 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
487 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
488 rejects or conflicts due to expanded keywords in working dir.'''
488 rejects or conflicts due to expanded keywords in working dir.'''
489 orig(self, ui, fname, opener, missing)
489 orig(self, ui, fname, opener, missing)
490 # shrink keywords read from working dir
490 # shrink keywords read from working dir
491 self.lines = kwt.shrinklines(self.fname, self.lines)
491 self.lines = kwt.shrinklines(self.fname, self.lines)
492
492
493 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
493 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
494 opts=None):
494 opts=None):
495 '''Monkeypatch patch.diff to avoid expansion except when
495 '''Monkeypatch patch.diff to avoid expansion except when
496 comparing against working dir.'''
496 comparing against working dir.'''
497 if node2 is not None:
497 if node2 is not None:
498 kwt.matcher = util.never
498 kwt.matcher = util.never
499 elif node1 is not None and node1 != repo['.'].node():
499 elif node1 is not None and node1 != repo['.'].node():
500 kwt.restrict = True
500 kwt.restrict = True
501 return orig(repo, node1, node2, match, changes, opts)
501 return orig(repo, node1, node2, match, changes, opts)
502
502
503 def kwweb_skip(orig, web, req, tmpl):
503 def kwweb_skip(orig, web, req, tmpl):
504 '''Wraps webcommands.x turning off keyword expansion.'''
504 '''Wraps webcommands.x turning off keyword expansion.'''
505 kwt.matcher = util.never
505 kwt.matcher = util.never
506 return orig(web, req, tmpl)
506 return orig(web, req, tmpl)
507
507
508 repo.__class__ = kwrepo
508 repo.__class__ = kwrepo
509
509
510 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
510 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
511 extensions.wrapfunction(patch, 'diff', kw_diff)
511 extensions.wrapfunction(patch, 'diff', kw_diff)
512 for c in 'annotate changeset rev filediff diff'.split():
512 for c in 'annotate changeset rev filediff diff'.split():
513 extensions.wrapfunction(webcommands, c, kwweb_skip)
513 extensions.wrapfunction(webcommands, c, kwweb_skip)
514
514
515 cmdtable = {
515 cmdtable = {
516 'kwdemo':
516 'kwdemo':
517 (demo,
517 (demo,
518 [('d', 'default', None, _('show default keyword template maps')),
518 [('d', 'default', None, _('show default keyword template maps')),
519 ('f', 'rcfile', [], _('read maps from rcfile'))],
519 ('f', 'rcfile', [], _('read maps from rcfile'))],
520 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
520 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
521 'kwexpand': (expand, commands.walkopts,
521 'kwexpand': (expand, commands.walkopts,
522 _('hg kwexpand [OPTION]... [FILE]...')),
522 _('hg kwexpand [OPTION]... [FILE]...')),
523 'kwfiles':
523 'kwfiles':
524 (files,
524 (files,
525 [('a', 'all', None, _('show keyword status flags of all files')),
525 [('a', 'all', None, _('show keyword status flags of all files')),
526 ('i', 'ignore', None, _('show files excluded from expansion')),
526 ('i', 'ignore', None, _('show files excluded from expansion')),
527 ('u', 'untracked', None, _('additionally show untracked files')),
527 ('u', 'untracked', None, _('additionally show untracked files')),
528 ] + commands.walkopts,
528 ] + commands.walkopts,
529 _('hg kwfiles [OPTION]... [FILE]...')),
529 _('hg kwfiles [OPTION]... [FILE]...')),
530 'kwshrink': (shrink, commands.walkopts,
530 'kwshrink': (shrink, commands.walkopts,
531 _('hg kwshrink [OPTION]... [FILE]...')),
531 _('hg kwshrink [OPTION]... [FILE]...')),
532 }
532 }
@@ -1,291 +1,291 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 repo root.
62 glob patterns are matched against path to repo root.
63
63
64 if you like, you can put notify config file in repo that users can
64 if you like, you can put notify config file in repo that users can
65 push changes to, they can manage their own subscriptions.'''
65 push changes to, they can manage their own subscriptions.'''
66
66
67 from mercurial.i18n import _
67 from mercurial.i18n import _
68 from mercurial.node import bin, short
68 from mercurial.node import bin, short
69 from mercurial import patch, cmdutil, templater, util, mail
69 from mercurial import patch, cmdutil, templater, util, mail
70 import email.Parser, fnmatch, socket, time
70 import email.Parser, fnmatch, socket, time
71
71
72 # template for single changeset can include email headers.
72 # template for single changeset can include email headers.
73 single_template = '''
73 single_template = '''
74 Subject: changeset in {webroot}: {desc|firstline|strip}
74 Subject: changeset in {webroot}: {desc|firstline|strip}
75 From: {author}
75 From: {author}
76
76
77 changeset {node|short} in {root}
77 changeset {node|short} in {root}
78 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
78 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
79 description:
79 description:
80 \t{desc|tabindent|strip}
80 \t{desc|tabindent|strip}
81 '''.lstrip()
81 '''.lstrip()
82
82
83 # template for multiple changesets should not contain email headers,
83 # template for multiple changesets should not contain email headers,
84 # because only first set of headers will be used and result will look
84 # because only first set of headers will be used and result will look
85 # strange.
85 # strange.
86 multiple_template = '''
86 multiple_template = '''
87 changeset {node|short} in {root}
87 changeset {node|short} in {root}
88 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
88 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
89 summary: {desc|firstline}
89 summary: {desc|firstline}
90 '''
90 '''
91
91
92 deftemplates = {
92 deftemplates = {
93 'changegroup': multiple_template,
93 'changegroup': multiple_template,
94 }
94 }
95
95
96 class notifier(object):
96 class notifier(object):
97 '''email notification class.'''
97 '''email notification class.'''
98
98
99 def __init__(self, ui, repo, hooktype):
99 def __init__(self, ui, repo, hooktype):
100 self.ui = ui
100 self.ui = ui
101 cfg = self.ui.config('notify', 'config')
101 cfg = self.ui.config('notify', 'config')
102 if cfg:
102 if cfg:
103 self.ui.readsections(cfg, 'usersubs', 'reposubs')
103 self.ui.readsections(cfg, 'usersubs', 'reposubs')
104 self.repo = repo
104 self.repo = repo
105 self.stripcount = int(self.ui.config('notify', 'strip', 0))
105 self.stripcount = int(self.ui.config('notify', 'strip', 0))
106 self.root = self.strip(self.repo.root)
106 self.root = self.strip(self.repo.root)
107 self.domain = self.ui.config('notify', 'domain')
107 self.domain = self.ui.config('notify', 'domain')
108 self.test = self.ui.configbool('notify', 'test', True)
108 self.test = self.ui.configbool('notify', 'test', True)
109 self.charsets = mail._charsets(self.ui)
109 self.charsets = mail._charsets(self.ui)
110 self.subs = self.subscribers()
110 self.subs = self.subscribers()
111
111
112 mapfile = self.ui.config('notify', 'style')
112 mapfile = self.ui.config('notify', 'style')
113 template = (self.ui.config('notify', hooktype) or
113 template = (self.ui.config('notify', hooktype) or
114 self.ui.config('notify', 'template'))
114 self.ui.config('notify', 'template'))
115 self.t = cmdutil.changeset_templater(self.ui, self.repo,
115 self.t = cmdutil.changeset_templater(self.ui, self.repo,
116 False, mapfile, False)
116 False, None, mapfile, False)
117 if not mapfile and not template:
117 if not mapfile and not template:
118 template = deftemplates.get(hooktype) or single_template
118 template = deftemplates.get(hooktype) or single_template
119 if template:
119 if template:
120 template = templater.parsestring(template, quoted=False)
120 template = templater.parsestring(template, quoted=False)
121 self.t.use_template(template)
121 self.t.use_template(template)
122
122
123 def strip(self, path):
123 def strip(self, path):
124 '''strip leading slashes from local path, turn into web-safe path.'''
124 '''strip leading slashes from local path, turn into web-safe path.'''
125
125
126 path = util.pconvert(path)
126 path = util.pconvert(path)
127 count = self.stripcount
127 count = self.stripcount
128 while count > 0:
128 while count > 0:
129 c = path.find('/')
129 c = path.find('/')
130 if c == -1:
130 if c == -1:
131 break
131 break
132 path = path[c+1:]
132 path = path[c+1:]
133 count -= 1
133 count -= 1
134 return path
134 return path
135
135
136 def fixmail(self, addr):
136 def fixmail(self, addr):
137 '''try to clean up email addresses.'''
137 '''try to clean up email addresses.'''
138
138
139 addr = util.email(addr.strip())
139 addr = util.email(addr.strip())
140 if self.domain:
140 if self.domain:
141 a = addr.find('@localhost')
141 a = addr.find('@localhost')
142 if a != -1:
142 if a != -1:
143 addr = addr[:a]
143 addr = addr[:a]
144 if '@' not in addr:
144 if '@' not in addr:
145 return addr + '@' + self.domain
145 return addr + '@' + self.domain
146 return addr
146 return addr
147
147
148 def subscribers(self):
148 def subscribers(self):
149 '''return list of email addresses of subscribers to this repo.'''
149 '''return list of email addresses of subscribers to this repo.'''
150 subs = {}
150 subs = {}
151 for user, pats in self.ui.configitems('usersubs'):
151 for user, pats in self.ui.configitems('usersubs'):
152 for pat in pats.split(','):
152 for pat in pats.split(','):
153 if fnmatch.fnmatch(self.repo.root, pat.strip()):
153 if fnmatch.fnmatch(self.repo.root, pat.strip()):
154 subs[self.fixmail(user)] = 1
154 subs[self.fixmail(user)] = 1
155 for pat, users in self.ui.configitems('reposubs'):
155 for pat, users in self.ui.configitems('reposubs'):
156 if fnmatch.fnmatch(self.repo.root, pat):
156 if fnmatch.fnmatch(self.repo.root, pat):
157 for user in users.split(','):
157 for user in users.split(','):
158 subs[self.fixmail(user)] = 1
158 subs[self.fixmail(user)] = 1
159 subs = util.sort(subs)
159 subs = util.sort(subs)
160 return [mail.addressencode(self.ui, s, self.charsets, self.test)
160 return [mail.addressencode(self.ui, s, self.charsets, self.test)
161 for s in subs]
161 for s in subs]
162
162
163 def url(self, path=None):
163 def url(self, path=None):
164 return self.ui.config('web', 'baseurl') + (path or self.root)
164 return self.ui.config('web', 'baseurl') + (path or self.root)
165
165
166 def node(self, ctx):
166 def node(self, ctx):
167 '''format one changeset.'''
167 '''format one changeset.'''
168 self.t.show(ctx, changes=ctx.changeset(),
168 self.t.show(ctx, changes=ctx.changeset(),
169 baseurl=self.ui.config('web', 'baseurl'),
169 baseurl=self.ui.config('web', 'baseurl'),
170 root=self.repo.root, webroot=self.root)
170 root=self.repo.root, webroot=self.root)
171
171
172 def skipsource(self, source):
172 def skipsource(self, source):
173 '''true if incoming changes from this source should be skipped.'''
173 '''true if incoming changes from this source should be skipped.'''
174 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
174 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
175 return source not in ok_sources
175 return source not in ok_sources
176
176
177 def send(self, ctx, count, data):
177 def send(self, ctx, count, data):
178 '''send message.'''
178 '''send message.'''
179
179
180 p = email.Parser.Parser()
180 p = email.Parser.Parser()
181 msg = p.parsestr(data)
181 msg = p.parsestr(data)
182
182
183 # store sender and subject
183 # store sender and subject
184 sender, subject = msg['From'], msg['Subject']
184 sender, subject = msg['From'], msg['Subject']
185 del msg['From'], msg['Subject']
185 del msg['From'], msg['Subject']
186 # store remaining headers
186 # store remaining headers
187 headers = msg.items()
187 headers = msg.items()
188 # create fresh mime message from msg body
188 # create fresh mime message from msg body
189 text = msg.get_payload()
189 text = msg.get_payload()
190 # for notification prefer readability over data precision
190 # for notification prefer readability over data precision
191 msg = mail.mimeencode(self.ui, text, self.charsets, self.test)
191 msg = mail.mimeencode(self.ui, text, self.charsets, self.test)
192 # reinstate custom headers
192 # reinstate custom headers
193 for k, v in headers:
193 for k, v in headers:
194 msg[k] = v
194 msg[k] = v
195
195
196 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
196 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
197
197
198 # try to make subject line exist and be useful
198 # try to make subject line exist and be useful
199 if not subject:
199 if not subject:
200 if count > 1:
200 if count > 1:
201 subject = _('%s: %d new changesets') % (self.root, count)
201 subject = _('%s: %d new changesets') % (self.root, count)
202 else:
202 else:
203 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
203 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
204 subject = '%s: %s' % (self.root, s)
204 subject = '%s: %s' % (self.root, s)
205 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
205 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
206 if maxsubject and len(subject) > maxsubject:
206 if maxsubject and len(subject) > maxsubject:
207 subject = subject[:maxsubject-3] + '...'
207 subject = subject[:maxsubject-3] + '...'
208 msg['Subject'] = mail.headencode(self.ui, subject,
208 msg['Subject'] = mail.headencode(self.ui, subject,
209 self.charsets, self.test)
209 self.charsets, self.test)
210
210
211 # try to make message have proper sender
211 # try to make message have proper sender
212 if not sender:
212 if not sender:
213 sender = self.ui.config('email', 'from') or self.ui.username()
213 sender = self.ui.config('email', 'from') or self.ui.username()
214 if '@' not in sender or '@localhost' in sender:
214 if '@' not in sender or '@localhost' in sender:
215 sender = self.fixmail(sender)
215 sender = self.fixmail(sender)
216 msg['From'] = mail.addressencode(self.ui, sender,
216 msg['From'] = mail.addressencode(self.ui, sender,
217 self.charsets, self.test)
217 self.charsets, self.test)
218
218
219 msg['X-Hg-Notification'] = 'changeset %s' % ctx
219 msg['X-Hg-Notification'] = 'changeset %s' % ctx
220 if not msg['Message-Id']:
220 if not msg['Message-Id']:
221 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
221 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
222 (ctx, int(time.time()),
222 (ctx, int(time.time()),
223 hash(self.repo.root), socket.getfqdn()))
223 hash(self.repo.root), socket.getfqdn()))
224 msg['To'] = ', '.join(self.subs)
224 msg['To'] = ', '.join(self.subs)
225
225
226 msgtext = msg.as_string(0)
226 msgtext = msg.as_string(0)
227 if self.test:
227 if self.test:
228 self.ui.write(msgtext)
228 self.ui.write(msgtext)
229 if not msgtext.endswith('\n'):
229 if not msgtext.endswith('\n'):
230 self.ui.write('\n')
230 self.ui.write('\n')
231 else:
231 else:
232 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
232 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
233 (len(self.subs), count))
233 (len(self.subs), count))
234 mail.sendmail(self.ui, util.email(msg['From']),
234 mail.sendmail(self.ui, util.email(msg['From']),
235 self.subs, msgtext)
235 self.subs, msgtext)
236
236
237 def diff(self, ctx, ref=None):
237 def diff(self, ctx, ref=None):
238
238
239 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
239 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
240 prev = ctx.parents()[0].node()
240 prev = ctx.parents()[0].node()
241 ref = ref and ref.node() or ctx.node()
241 ref = ref and ref.node() or ctx.node()
242 chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
242 chunks = patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
243 difflines = ''.join(chunks).splitlines()
243 difflines = ''.join(chunks).splitlines()
244
244
245 if self.ui.configbool('notify', 'diffstat', True):
245 if self.ui.configbool('notify', 'diffstat', True):
246 s = patch.diffstat(difflines)
246 s = patch.diffstat(difflines)
247 # s may be nil, don't include the header if it is
247 # s may be nil, don't include the header if it is
248 if s:
248 if s:
249 self.ui.write('\ndiffstat:\n\n%s' % s)
249 self.ui.write('\ndiffstat:\n\n%s' % s)
250
250
251 if maxdiff == 0:
251 if maxdiff == 0:
252 return
252 return
253 elif maxdiff > 0 and len(difflines) > maxdiff:
253 elif maxdiff > 0 and len(difflines) > maxdiff:
254 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
254 msg = _('\ndiffs (truncated from %d to %d lines):\n\n')
255 self.ui.write(msg % (len(difflines), maxdiff))
255 self.ui.write(msg % (len(difflines), maxdiff))
256 difflines = difflines[:maxdiff]
256 difflines = difflines[:maxdiff]
257 elif difflines:
257 elif difflines:
258 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
258 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
259
259
260 self.ui.write("\n".join(difflines))
260 self.ui.write("\n".join(difflines))
261
261
262 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
262 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
263 '''send email notifications to interested subscribers.
263 '''send email notifications to interested subscribers.
264
264
265 if used as changegroup hook, send one email for all changesets in
265 if used as changegroup hook, send one email for all changesets in
266 changegroup. else send one email per changeset.'''
266 changegroup. else send one email per changeset.'''
267
267
268 n = notifier(ui, repo, hooktype)
268 n = notifier(ui, repo, hooktype)
269 ctx = repo[node]
269 ctx = repo[node]
270
270
271 if not n.subs:
271 if not n.subs:
272 ui.debug(_('notify: no subscribers to repo %s\n') % n.root)
272 ui.debug(_('notify: no subscribers to repo %s\n') % n.root)
273 return
273 return
274 if n.skipsource(source):
274 if n.skipsource(source):
275 ui.debug(_('notify: changes have source "%s" - skipping\n') % source)
275 ui.debug(_('notify: changes have source "%s" - skipping\n') % source)
276 return
276 return
277
277
278 ui.pushbuffer()
278 ui.pushbuffer()
279 if hooktype == 'changegroup':
279 if hooktype == 'changegroup':
280 start, end = ctx.rev(), len(repo)
280 start, end = ctx.rev(), len(repo)
281 count = end - start
281 count = end - start
282 for rev in xrange(start, end):
282 for rev in xrange(start, end):
283 n.node(repo[rev])
283 n.node(repo[rev])
284 n.diff(ctx, repo['tip'])
284 n.diff(ctx, repo['tip'])
285 else:
285 else:
286 count = 1
286 count = 1
287 n.node(ctx)
287 n.node(ctx)
288 n.diff(ctx)
288 n.diff(ctx)
289
289
290 data = ui.popbuffer()
290 data = ui.popbuffer()
291 n.send(ctx, count, data)
291 n.send(ctx, count, data)
@@ -1,1186 +1,1187 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in 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 node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, bisect, stat
10 import os, sys, bisect, stat
11 import mdiff, bdiff, util, templater, templatefilters, patch, errno, error
11 import mdiff, bdiff, util, templater, templatefilters, patch, errno, error
12 import match as _match
12 import match as _match
13
13
14 revrangesep = ':'
14 revrangesep = ':'
15
15
16 def findpossible(cmd, table, strict=False):
16 def findpossible(cmd, table, strict=False):
17 """
17 """
18 Return cmd -> (aliases, command table entry)
18 Return cmd -> (aliases, command table entry)
19 for each matching command.
19 for each matching command.
20 Return debug commands (or their aliases) only if no normal command matches.
20 Return debug commands (or their aliases) only if no normal command matches.
21 """
21 """
22 choice = {}
22 choice = {}
23 debugchoice = {}
23 debugchoice = {}
24 for e in table.keys():
24 for e in table.keys():
25 aliases = e.lstrip("^").split("|")
25 aliases = e.lstrip("^").split("|")
26 found = None
26 found = None
27 if cmd in aliases:
27 if cmd in aliases:
28 found = cmd
28 found = cmd
29 elif not strict:
29 elif not strict:
30 for a in aliases:
30 for a in aliases:
31 if a.startswith(cmd):
31 if a.startswith(cmd):
32 found = a
32 found = a
33 break
33 break
34 if found is not None:
34 if found is not None:
35 if aliases[0].startswith("debug") or found.startswith("debug"):
35 if aliases[0].startswith("debug") or found.startswith("debug"):
36 debugchoice[found] = (aliases, table[e])
36 debugchoice[found] = (aliases, table[e])
37 else:
37 else:
38 choice[found] = (aliases, table[e])
38 choice[found] = (aliases, table[e])
39
39
40 if not choice and debugchoice:
40 if not choice and debugchoice:
41 choice = debugchoice
41 choice = debugchoice
42
42
43 return choice
43 return choice
44
44
45 def findcmd(cmd, table, strict=True):
45 def findcmd(cmd, table, strict=True):
46 """Return (aliases, command table entry) for command string."""
46 """Return (aliases, command table entry) for command string."""
47 choice = findpossible(cmd, table, strict)
47 choice = findpossible(cmd, table, strict)
48
48
49 if cmd in choice:
49 if cmd in choice:
50 return choice[cmd]
50 return choice[cmd]
51
51
52 if len(choice) > 1:
52 if len(choice) > 1:
53 clist = choice.keys()
53 clist = choice.keys()
54 clist.sort()
54 clist.sort()
55 raise error.AmbiguousCommand(cmd, clist)
55 raise error.AmbiguousCommand(cmd, clist)
56
56
57 if choice:
57 if choice:
58 return choice.values()[0]
58 return choice.values()[0]
59
59
60 raise error.UnknownCommand(cmd)
60 raise error.UnknownCommand(cmd)
61
61
62 def bail_if_changed(repo):
62 def bail_if_changed(repo):
63 if repo.dirstate.parents()[1] != nullid:
63 if repo.dirstate.parents()[1] != nullid:
64 raise util.Abort(_('outstanding uncommitted merge'))
64 raise util.Abort(_('outstanding uncommitted merge'))
65 modified, added, removed, deleted = repo.status()[:4]
65 modified, added, removed, deleted = repo.status()[:4]
66 if modified or added or removed or deleted:
66 if modified or added or removed or deleted:
67 raise util.Abort(_("outstanding uncommitted changes"))
67 raise util.Abort(_("outstanding uncommitted changes"))
68
68
69 def logmessage(opts):
69 def logmessage(opts):
70 """ get the log message according to -m and -l option """
70 """ get the log message according to -m and -l option """
71 message = opts.get('message')
71 message = opts.get('message')
72 logfile = opts.get('logfile')
72 logfile = opts.get('logfile')
73
73
74 if message and logfile:
74 if message and logfile:
75 raise util.Abort(_('options --message and --logfile are mutually '
75 raise util.Abort(_('options --message and --logfile are mutually '
76 'exclusive'))
76 'exclusive'))
77 if not message and logfile:
77 if not message and logfile:
78 try:
78 try:
79 if logfile == '-':
79 if logfile == '-':
80 message = sys.stdin.read()
80 message = sys.stdin.read()
81 else:
81 else:
82 message = open(logfile).read()
82 message = open(logfile).read()
83 except IOError, inst:
83 except IOError, inst:
84 raise util.Abort(_("can't read commit message '%s': %s") %
84 raise util.Abort(_("can't read commit message '%s': %s") %
85 (logfile, inst.strerror))
85 (logfile, inst.strerror))
86 return message
86 return message
87
87
88 def loglimit(opts):
88 def loglimit(opts):
89 """get the log limit according to option -l/--limit"""
89 """get the log limit according to option -l/--limit"""
90 limit = opts.get('limit')
90 limit = opts.get('limit')
91 if limit:
91 if limit:
92 try:
92 try:
93 limit = int(limit)
93 limit = int(limit)
94 except ValueError:
94 except ValueError:
95 raise util.Abort(_('limit must be a positive integer'))
95 raise util.Abort(_('limit must be a positive integer'))
96 if limit <= 0: raise util.Abort(_('limit must be positive'))
96 if limit <= 0: raise util.Abort(_('limit must be positive'))
97 else:
97 else:
98 limit = sys.maxint
98 limit = sys.maxint
99 return limit
99 return limit
100
100
101 def setremoteconfig(ui, opts):
101 def setremoteconfig(ui, opts):
102 "copy remote options to ui tree"
102 "copy remote options to ui tree"
103 if opts.get('ssh'):
103 if opts.get('ssh'):
104 ui.setconfig("ui", "ssh", opts['ssh'])
104 ui.setconfig("ui", "ssh", opts['ssh'])
105 if opts.get('remotecmd'):
105 if opts.get('remotecmd'):
106 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
106 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
107
107
108 def revpair(repo, revs):
108 def revpair(repo, revs):
109 '''return pair of nodes, given list of revisions. second item can
109 '''return pair of nodes, given list of revisions. second item can
110 be None, meaning use working dir.'''
110 be None, meaning use working dir.'''
111
111
112 def revfix(repo, val, defval):
112 def revfix(repo, val, defval):
113 if not val and val != 0 and defval is not None:
113 if not val and val != 0 and defval is not None:
114 val = defval
114 val = defval
115 return repo.lookup(val)
115 return repo.lookup(val)
116
116
117 if not revs:
117 if not revs:
118 return repo.dirstate.parents()[0], None
118 return repo.dirstate.parents()[0], None
119 end = None
119 end = None
120 if len(revs) == 1:
120 if len(revs) == 1:
121 if revrangesep in revs[0]:
121 if revrangesep in revs[0]:
122 start, end = revs[0].split(revrangesep, 1)
122 start, end = revs[0].split(revrangesep, 1)
123 start = revfix(repo, start, 0)
123 start = revfix(repo, start, 0)
124 end = revfix(repo, end, len(repo) - 1)
124 end = revfix(repo, end, len(repo) - 1)
125 else:
125 else:
126 start = revfix(repo, revs[0], None)
126 start = revfix(repo, revs[0], None)
127 elif len(revs) == 2:
127 elif len(revs) == 2:
128 if revrangesep in revs[0] or revrangesep in revs[1]:
128 if revrangesep in revs[0] or revrangesep in revs[1]:
129 raise util.Abort(_('too many revisions specified'))
129 raise util.Abort(_('too many revisions specified'))
130 start = revfix(repo, revs[0], None)
130 start = revfix(repo, revs[0], None)
131 end = revfix(repo, revs[1], None)
131 end = revfix(repo, revs[1], None)
132 else:
132 else:
133 raise util.Abort(_('too many revisions specified'))
133 raise util.Abort(_('too many revisions specified'))
134 return start, end
134 return start, end
135
135
136 def revrange(repo, revs):
136 def revrange(repo, revs):
137 """Yield revision as strings from a list of revision specifications."""
137 """Yield revision as strings from a list of revision specifications."""
138
138
139 def revfix(repo, val, defval):
139 def revfix(repo, val, defval):
140 if not val and val != 0 and defval is not None:
140 if not val and val != 0 and defval is not None:
141 return defval
141 return defval
142 return repo.changelog.rev(repo.lookup(val))
142 return repo.changelog.rev(repo.lookup(val))
143
143
144 seen, l = {}, []
144 seen, l = {}, []
145 for spec in revs:
145 for spec in revs:
146 if revrangesep in spec:
146 if revrangesep in spec:
147 start, end = spec.split(revrangesep, 1)
147 start, end = spec.split(revrangesep, 1)
148 start = revfix(repo, start, 0)
148 start = revfix(repo, start, 0)
149 end = revfix(repo, end, len(repo) - 1)
149 end = revfix(repo, end, len(repo) - 1)
150 step = start > end and -1 or 1
150 step = start > end and -1 or 1
151 for rev in xrange(start, end+step, step):
151 for rev in xrange(start, end+step, step):
152 if rev in seen:
152 if rev in seen:
153 continue
153 continue
154 seen[rev] = 1
154 seen[rev] = 1
155 l.append(rev)
155 l.append(rev)
156 else:
156 else:
157 rev = revfix(repo, spec, None)
157 rev = revfix(repo, spec, None)
158 if rev in seen:
158 if rev in seen:
159 continue
159 continue
160 seen[rev] = 1
160 seen[rev] = 1
161 l.append(rev)
161 l.append(rev)
162
162
163 return l
163 return l
164
164
165 def make_filename(repo, pat, node,
165 def make_filename(repo, pat, node,
166 total=None, seqno=None, revwidth=None, pathname=None):
166 total=None, seqno=None, revwidth=None, pathname=None):
167 node_expander = {
167 node_expander = {
168 'H': lambda: hex(node),
168 'H': lambda: hex(node),
169 'R': lambda: str(repo.changelog.rev(node)),
169 'R': lambda: str(repo.changelog.rev(node)),
170 'h': lambda: short(node),
170 'h': lambda: short(node),
171 }
171 }
172 expander = {
172 expander = {
173 '%': lambda: '%',
173 '%': lambda: '%',
174 'b': lambda: os.path.basename(repo.root),
174 'b': lambda: os.path.basename(repo.root),
175 }
175 }
176
176
177 try:
177 try:
178 if node:
178 if node:
179 expander.update(node_expander)
179 expander.update(node_expander)
180 if node:
180 if node:
181 expander['r'] = (lambda:
181 expander['r'] = (lambda:
182 str(repo.changelog.rev(node)).zfill(revwidth or 0))
182 str(repo.changelog.rev(node)).zfill(revwidth or 0))
183 if total is not None:
183 if total is not None:
184 expander['N'] = lambda: str(total)
184 expander['N'] = lambda: str(total)
185 if seqno is not None:
185 if seqno is not None:
186 expander['n'] = lambda: str(seqno)
186 expander['n'] = lambda: str(seqno)
187 if total is not None and seqno is not None:
187 if total is not None and seqno is not None:
188 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
188 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
189 if pathname is not None:
189 if pathname is not None:
190 expander['s'] = lambda: os.path.basename(pathname)
190 expander['s'] = lambda: os.path.basename(pathname)
191 expander['d'] = lambda: os.path.dirname(pathname) or '.'
191 expander['d'] = lambda: os.path.dirname(pathname) or '.'
192 expander['p'] = lambda: pathname
192 expander['p'] = lambda: pathname
193
193
194 newname = []
194 newname = []
195 patlen = len(pat)
195 patlen = len(pat)
196 i = 0
196 i = 0
197 while i < patlen:
197 while i < patlen:
198 c = pat[i]
198 c = pat[i]
199 if c == '%':
199 if c == '%':
200 i += 1
200 i += 1
201 c = pat[i]
201 c = pat[i]
202 c = expander[c]()
202 c = expander[c]()
203 newname.append(c)
203 newname.append(c)
204 i += 1
204 i += 1
205 return ''.join(newname)
205 return ''.join(newname)
206 except KeyError, inst:
206 except KeyError, inst:
207 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
207 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
208 inst.args[0])
208 inst.args[0])
209
209
210 def make_file(repo, pat, node=None,
210 def make_file(repo, pat, node=None,
211 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
211 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
212
212
213 writable = 'w' in mode or 'a' in mode
213 writable = 'w' in mode or 'a' in mode
214
214
215 if not pat or pat == '-':
215 if not pat or pat == '-':
216 return writable and sys.stdout or sys.stdin
216 return writable and sys.stdout or sys.stdin
217 if hasattr(pat, 'write') and writable:
217 if hasattr(pat, 'write') and writable:
218 return pat
218 return pat
219 if hasattr(pat, 'read') and 'r' in mode:
219 if hasattr(pat, 'read') and 'r' in mode:
220 return pat
220 return pat
221 return open(make_filename(repo, pat, node, total, seqno, revwidth,
221 return open(make_filename(repo, pat, node, total, seqno, revwidth,
222 pathname),
222 pathname),
223 mode)
223 mode)
224
224
225 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
225 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
226 if not globbed and default == 'relpath':
226 if not globbed and default == 'relpath':
227 pats = util.expand_glob(pats or [])
227 pats = util.expand_glob(pats or [])
228 m = _match.match(repo.root, repo.getcwd(), pats,
228 m = _match.match(repo.root, repo.getcwd(), pats,
229 opts.get('include'), opts.get('exclude'), default)
229 opts.get('include'), opts.get('exclude'), default)
230 def badfn(f, msg):
230 def badfn(f, msg):
231 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
231 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
232 return False
232 return False
233 m.bad = badfn
233 m.bad = badfn
234 return m
234 return m
235
235
236 def matchall(repo):
236 def matchall(repo):
237 return _match.always(repo.root, repo.getcwd())
237 return _match.always(repo.root, repo.getcwd())
238
238
239 def matchfiles(repo, files):
239 def matchfiles(repo, files):
240 return _match.exact(repo.root, repo.getcwd(), files)
240 return _match.exact(repo.root, repo.getcwd(), files)
241
241
242 def findrenames(repo, added=None, removed=None, threshold=0.5):
242 def findrenames(repo, added=None, removed=None, threshold=0.5):
243 '''find renamed files -- yields (before, after, score) tuples'''
243 '''find renamed files -- yields (before, after, score) tuples'''
244 if added is None or removed is None:
244 if added is None or removed is None:
245 added, removed = repo.status()[1:3]
245 added, removed = repo.status()[1:3]
246 ctx = repo['.']
246 ctx = repo['.']
247 for a in added:
247 for a in added:
248 aa = repo.wread(a)
248 aa = repo.wread(a)
249 bestname, bestscore = None, threshold
249 bestname, bestscore = None, threshold
250 for r in removed:
250 for r in removed:
251 rr = ctx.filectx(r).data()
251 rr = ctx.filectx(r).data()
252
252
253 # bdiff.blocks() returns blocks of matching lines
253 # bdiff.blocks() returns blocks of matching lines
254 # count the number of bytes in each
254 # count the number of bytes in each
255 equal = 0
255 equal = 0
256 alines = mdiff.splitnewlines(aa)
256 alines = mdiff.splitnewlines(aa)
257 matches = bdiff.blocks(aa, rr)
257 matches = bdiff.blocks(aa, rr)
258 for x1,x2,y1,y2 in matches:
258 for x1,x2,y1,y2 in matches:
259 for line in alines[x1:x2]:
259 for line in alines[x1:x2]:
260 equal += len(line)
260 equal += len(line)
261
261
262 lengths = len(aa) + len(rr)
262 lengths = len(aa) + len(rr)
263 if lengths:
263 if lengths:
264 myscore = equal*2.0 / lengths
264 myscore = equal*2.0 / lengths
265 if myscore >= bestscore:
265 if myscore >= bestscore:
266 bestname, bestscore = r, myscore
266 bestname, bestscore = r, myscore
267 if bestname:
267 if bestname:
268 yield bestname, a, bestscore
268 yield bestname, a, bestscore
269
269
270 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
270 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
271 if dry_run is None:
271 if dry_run is None:
272 dry_run = opts.get('dry_run')
272 dry_run = opts.get('dry_run')
273 if similarity is None:
273 if similarity is None:
274 similarity = float(opts.get('similarity') or 0)
274 similarity = float(opts.get('similarity') or 0)
275 add, remove = [], []
275 add, remove = [], []
276 mapping = {}
276 mapping = {}
277 audit_path = util.path_auditor(repo.root)
277 audit_path = util.path_auditor(repo.root)
278 m = match(repo, pats, opts)
278 m = match(repo, pats, opts)
279 for abs in repo.walk(m):
279 for abs in repo.walk(m):
280 target = repo.wjoin(abs)
280 target = repo.wjoin(abs)
281 good = True
281 good = True
282 try:
282 try:
283 audit_path(abs)
283 audit_path(abs)
284 except:
284 except:
285 good = False
285 good = False
286 rel = m.rel(abs)
286 rel = m.rel(abs)
287 exact = m.exact(abs)
287 exact = m.exact(abs)
288 if good and abs not in repo.dirstate:
288 if good and abs not in repo.dirstate:
289 add.append(abs)
289 add.append(abs)
290 mapping[abs] = rel, m.exact(abs)
290 mapping[abs] = rel, m.exact(abs)
291 if repo.ui.verbose or not exact:
291 if repo.ui.verbose or not exact:
292 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
292 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
293 if repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
293 if repo.dirstate[abs] != 'r' and (not good or not util.lexists(target)
294 or (os.path.isdir(target) and not os.path.islink(target))):
294 or (os.path.isdir(target) and not os.path.islink(target))):
295 remove.append(abs)
295 remove.append(abs)
296 mapping[abs] = rel, exact
296 mapping[abs] = rel, exact
297 if repo.ui.verbose or not exact:
297 if repo.ui.verbose or not exact:
298 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
298 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
299 if not dry_run:
299 if not dry_run:
300 repo.remove(remove)
300 repo.remove(remove)
301 repo.add(add)
301 repo.add(add)
302 if similarity > 0:
302 if similarity > 0:
303 for old, new, score in findrenames(repo, add, remove, similarity):
303 for old, new, score in findrenames(repo, add, remove, similarity):
304 oldrel, oldexact = mapping[old]
304 oldrel, oldexact = mapping[old]
305 newrel, newexact = mapping[new]
305 newrel, newexact = mapping[new]
306 if repo.ui.verbose or not oldexact or not newexact:
306 if repo.ui.verbose or not oldexact or not newexact:
307 repo.ui.status(_('recording removal of %s as rename to %s '
307 repo.ui.status(_('recording removal of %s as rename to %s '
308 '(%d%% similar)\n') %
308 '(%d%% similar)\n') %
309 (oldrel, newrel, score * 100))
309 (oldrel, newrel, score * 100))
310 if not dry_run:
310 if not dry_run:
311 repo.copy(old, new)
311 repo.copy(old, new)
312
312
313 def copy(ui, repo, pats, opts, rename=False):
313 def copy(ui, repo, pats, opts, rename=False):
314 # called with the repo lock held
314 # called with the repo lock held
315 #
315 #
316 # hgsep => pathname that uses "/" to separate directories
316 # hgsep => pathname that uses "/" to separate directories
317 # ossep => pathname that uses os.sep to separate directories
317 # ossep => pathname that uses os.sep to separate directories
318 cwd = repo.getcwd()
318 cwd = repo.getcwd()
319 targets = {}
319 targets = {}
320 after = opts.get("after")
320 after = opts.get("after")
321 dryrun = opts.get("dry_run")
321 dryrun = opts.get("dry_run")
322
322
323 def walkpat(pat):
323 def walkpat(pat):
324 srcs = []
324 srcs = []
325 m = match(repo, [pat], opts, globbed=True)
325 m = match(repo, [pat], opts, globbed=True)
326 for abs in repo.walk(m):
326 for abs in repo.walk(m):
327 state = repo.dirstate[abs]
327 state = repo.dirstate[abs]
328 rel = m.rel(abs)
328 rel = m.rel(abs)
329 exact = m.exact(abs)
329 exact = m.exact(abs)
330 if state in '?r':
330 if state in '?r':
331 if exact and state == '?':
331 if exact and state == '?':
332 ui.warn(_('%s: not copying - file is not managed\n') % rel)
332 ui.warn(_('%s: not copying - file is not managed\n') % rel)
333 if exact and state == 'r':
333 if exact and state == 'r':
334 ui.warn(_('%s: not copying - file has been marked for'
334 ui.warn(_('%s: not copying - file has been marked for'
335 ' remove\n') % rel)
335 ' remove\n') % rel)
336 continue
336 continue
337 # abs: hgsep
337 # abs: hgsep
338 # rel: ossep
338 # rel: ossep
339 srcs.append((abs, rel, exact))
339 srcs.append((abs, rel, exact))
340 return srcs
340 return srcs
341
341
342 # abssrc: hgsep
342 # abssrc: hgsep
343 # relsrc: ossep
343 # relsrc: ossep
344 # otarget: ossep
344 # otarget: ossep
345 def copyfile(abssrc, relsrc, otarget, exact):
345 def copyfile(abssrc, relsrc, otarget, exact):
346 abstarget = util.canonpath(repo.root, cwd, otarget)
346 abstarget = util.canonpath(repo.root, cwd, otarget)
347 reltarget = repo.pathto(abstarget, cwd)
347 reltarget = repo.pathto(abstarget, cwd)
348 target = repo.wjoin(abstarget)
348 target = repo.wjoin(abstarget)
349 src = repo.wjoin(abssrc)
349 src = repo.wjoin(abssrc)
350 state = repo.dirstate[abstarget]
350 state = repo.dirstate[abstarget]
351
351
352 # check for collisions
352 # check for collisions
353 prevsrc = targets.get(abstarget)
353 prevsrc = targets.get(abstarget)
354 if prevsrc is not None:
354 if prevsrc is not None:
355 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
355 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
356 (reltarget, repo.pathto(abssrc, cwd),
356 (reltarget, repo.pathto(abssrc, cwd),
357 repo.pathto(prevsrc, cwd)))
357 repo.pathto(prevsrc, cwd)))
358 return
358 return
359
359
360 # check for overwrites
360 # check for overwrites
361 exists = os.path.exists(target)
361 exists = os.path.exists(target)
362 if (not after and exists or after and state in 'mn'):
362 if (not after and exists or after and state in 'mn'):
363 if not opts['force']:
363 if not opts['force']:
364 ui.warn(_('%s: not overwriting - file exists\n') %
364 ui.warn(_('%s: not overwriting - file exists\n') %
365 reltarget)
365 reltarget)
366 return
366 return
367
367
368 if after:
368 if after:
369 if not exists:
369 if not exists:
370 return
370 return
371 elif not dryrun:
371 elif not dryrun:
372 try:
372 try:
373 if exists:
373 if exists:
374 os.unlink(target)
374 os.unlink(target)
375 targetdir = os.path.dirname(target) or '.'
375 targetdir = os.path.dirname(target) or '.'
376 if not os.path.isdir(targetdir):
376 if not os.path.isdir(targetdir):
377 os.makedirs(targetdir)
377 os.makedirs(targetdir)
378 util.copyfile(src, target)
378 util.copyfile(src, target)
379 except IOError, inst:
379 except IOError, inst:
380 if inst.errno == errno.ENOENT:
380 if inst.errno == errno.ENOENT:
381 ui.warn(_('%s: deleted in working copy\n') % relsrc)
381 ui.warn(_('%s: deleted in working copy\n') % relsrc)
382 else:
382 else:
383 ui.warn(_('%s: cannot copy - %s\n') %
383 ui.warn(_('%s: cannot copy - %s\n') %
384 (relsrc, inst.strerror))
384 (relsrc, inst.strerror))
385 return True # report a failure
385 return True # report a failure
386
386
387 if ui.verbose or not exact:
387 if ui.verbose or not exact:
388 action = rename and "moving" or "copying"
388 action = rename and "moving" or "copying"
389 ui.status(_('%s %s to %s\n') % (action, relsrc, reltarget))
389 ui.status(_('%s %s to %s\n') % (action, relsrc, reltarget))
390
390
391 targets[abstarget] = abssrc
391 targets[abstarget] = abssrc
392
392
393 # fix up dirstate
393 # fix up dirstate
394 origsrc = repo.dirstate.copied(abssrc) or abssrc
394 origsrc = repo.dirstate.copied(abssrc) or abssrc
395 if abstarget == origsrc: # copying back a copy?
395 if abstarget == origsrc: # copying back a copy?
396 if state not in 'mn' and not dryrun:
396 if state not in 'mn' and not dryrun:
397 repo.dirstate.normallookup(abstarget)
397 repo.dirstate.normallookup(abstarget)
398 else:
398 else:
399 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
399 if repo.dirstate[origsrc] == 'a' and origsrc == abssrc:
400 if not ui.quiet:
400 if not ui.quiet:
401 ui.warn(_("%s has not been committed yet, so no copy "
401 ui.warn(_("%s has not been committed yet, so no copy "
402 "data will be stored for %s.\n")
402 "data will be stored for %s.\n")
403 % (repo.pathto(origsrc, cwd), reltarget))
403 % (repo.pathto(origsrc, cwd), reltarget))
404 if repo.dirstate[abstarget] in '?r' and not dryrun:
404 if repo.dirstate[abstarget] in '?r' and not dryrun:
405 repo.add([abstarget])
405 repo.add([abstarget])
406 elif not dryrun:
406 elif not dryrun:
407 repo.copy(origsrc, abstarget)
407 repo.copy(origsrc, abstarget)
408
408
409 if rename and not dryrun:
409 if rename and not dryrun:
410 repo.remove([abssrc], not after)
410 repo.remove([abssrc], not after)
411
411
412 # pat: ossep
412 # pat: ossep
413 # dest ossep
413 # dest ossep
414 # srcs: list of (hgsep, hgsep, ossep, bool)
414 # srcs: list of (hgsep, hgsep, ossep, bool)
415 # return: function that takes hgsep and returns ossep
415 # return: function that takes hgsep and returns ossep
416 def targetpathfn(pat, dest, srcs):
416 def targetpathfn(pat, dest, srcs):
417 if os.path.isdir(pat):
417 if os.path.isdir(pat):
418 abspfx = util.canonpath(repo.root, cwd, pat)
418 abspfx = util.canonpath(repo.root, cwd, pat)
419 abspfx = util.localpath(abspfx)
419 abspfx = util.localpath(abspfx)
420 if destdirexists:
420 if destdirexists:
421 striplen = len(os.path.split(abspfx)[0])
421 striplen = len(os.path.split(abspfx)[0])
422 else:
422 else:
423 striplen = len(abspfx)
423 striplen = len(abspfx)
424 if striplen:
424 if striplen:
425 striplen += len(os.sep)
425 striplen += len(os.sep)
426 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
426 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
427 elif destdirexists:
427 elif destdirexists:
428 res = lambda p: os.path.join(dest,
428 res = lambda p: os.path.join(dest,
429 os.path.basename(util.localpath(p)))
429 os.path.basename(util.localpath(p)))
430 else:
430 else:
431 res = lambda p: dest
431 res = lambda p: dest
432 return res
432 return res
433
433
434 # pat: ossep
434 # pat: ossep
435 # dest ossep
435 # dest ossep
436 # srcs: list of (hgsep, hgsep, ossep, bool)
436 # srcs: list of (hgsep, hgsep, ossep, bool)
437 # return: function that takes hgsep and returns ossep
437 # return: function that takes hgsep and returns ossep
438 def targetpathafterfn(pat, dest, srcs):
438 def targetpathafterfn(pat, dest, srcs):
439 if util.patkind(pat, None)[0]:
439 if util.patkind(pat, None)[0]:
440 # a mercurial pattern
440 # a mercurial pattern
441 res = lambda p: os.path.join(dest,
441 res = lambda p: os.path.join(dest,
442 os.path.basename(util.localpath(p)))
442 os.path.basename(util.localpath(p)))
443 else:
443 else:
444 abspfx = util.canonpath(repo.root, cwd, pat)
444 abspfx = util.canonpath(repo.root, cwd, pat)
445 if len(abspfx) < len(srcs[0][0]):
445 if len(abspfx) < len(srcs[0][0]):
446 # A directory. Either the target path contains the last
446 # A directory. Either the target path contains the last
447 # component of the source path or it does not.
447 # component of the source path or it does not.
448 def evalpath(striplen):
448 def evalpath(striplen):
449 score = 0
449 score = 0
450 for s in srcs:
450 for s in srcs:
451 t = os.path.join(dest, util.localpath(s[0])[striplen:])
451 t = os.path.join(dest, util.localpath(s[0])[striplen:])
452 if os.path.exists(t):
452 if os.path.exists(t):
453 score += 1
453 score += 1
454 return score
454 return score
455
455
456 abspfx = util.localpath(abspfx)
456 abspfx = util.localpath(abspfx)
457 striplen = len(abspfx)
457 striplen = len(abspfx)
458 if striplen:
458 if striplen:
459 striplen += len(os.sep)
459 striplen += len(os.sep)
460 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
460 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
461 score = evalpath(striplen)
461 score = evalpath(striplen)
462 striplen1 = len(os.path.split(abspfx)[0])
462 striplen1 = len(os.path.split(abspfx)[0])
463 if striplen1:
463 if striplen1:
464 striplen1 += len(os.sep)
464 striplen1 += len(os.sep)
465 if evalpath(striplen1) > score:
465 if evalpath(striplen1) > score:
466 striplen = striplen1
466 striplen = striplen1
467 res = lambda p: os.path.join(dest,
467 res = lambda p: os.path.join(dest,
468 util.localpath(p)[striplen:])
468 util.localpath(p)[striplen:])
469 else:
469 else:
470 # a file
470 # a file
471 if destdirexists:
471 if destdirexists:
472 res = lambda p: os.path.join(dest,
472 res = lambda p: os.path.join(dest,
473 os.path.basename(util.localpath(p)))
473 os.path.basename(util.localpath(p)))
474 else:
474 else:
475 res = lambda p: dest
475 res = lambda p: dest
476 return res
476 return res
477
477
478
478
479 pats = util.expand_glob(pats)
479 pats = util.expand_glob(pats)
480 if not pats:
480 if not pats:
481 raise util.Abort(_('no source or destination specified'))
481 raise util.Abort(_('no source or destination specified'))
482 if len(pats) == 1:
482 if len(pats) == 1:
483 raise util.Abort(_('no destination specified'))
483 raise util.Abort(_('no destination specified'))
484 dest = pats.pop()
484 dest = pats.pop()
485 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
485 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
486 if not destdirexists:
486 if not destdirexists:
487 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
487 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
488 raise util.Abort(_('with multiple sources, destination must be an '
488 raise util.Abort(_('with multiple sources, destination must be an '
489 'existing directory'))
489 'existing directory'))
490 if util.endswithsep(dest):
490 if util.endswithsep(dest):
491 raise util.Abort(_('destination %s is not a directory') % dest)
491 raise util.Abort(_('destination %s is not a directory') % dest)
492
492
493 tfn = targetpathfn
493 tfn = targetpathfn
494 if after:
494 if after:
495 tfn = targetpathafterfn
495 tfn = targetpathafterfn
496 copylist = []
496 copylist = []
497 for pat in pats:
497 for pat in pats:
498 srcs = walkpat(pat)
498 srcs = walkpat(pat)
499 if not srcs:
499 if not srcs:
500 continue
500 continue
501 copylist.append((tfn(pat, dest, srcs), srcs))
501 copylist.append((tfn(pat, dest, srcs), srcs))
502 if not copylist:
502 if not copylist:
503 raise util.Abort(_('no files to copy'))
503 raise util.Abort(_('no files to copy'))
504
504
505 errors = 0
505 errors = 0
506 for targetpath, srcs in copylist:
506 for targetpath, srcs in copylist:
507 for abssrc, relsrc, exact in srcs:
507 for abssrc, relsrc, exact in srcs:
508 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
508 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
509 errors += 1
509 errors += 1
510
510
511 if errors:
511 if errors:
512 ui.warn(_('(consider using --after)\n'))
512 ui.warn(_('(consider using --after)\n'))
513
513
514 return errors
514 return errors
515
515
516 def service(opts, parentfn=None, initfn=None, runfn=None):
516 def service(opts, parentfn=None, initfn=None, runfn=None):
517 '''Run a command as a service.'''
517 '''Run a command as a service.'''
518
518
519 if opts['daemon'] and not opts['daemon_pipefds']:
519 if opts['daemon'] and not opts['daemon_pipefds']:
520 rfd, wfd = os.pipe()
520 rfd, wfd = os.pipe()
521 args = sys.argv[:]
521 args = sys.argv[:]
522 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
522 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
523 # Don't pass --cwd to the child process, because we've already
523 # Don't pass --cwd to the child process, because we've already
524 # changed directory.
524 # changed directory.
525 for i in xrange(1,len(args)):
525 for i in xrange(1,len(args)):
526 if args[i].startswith('--cwd='):
526 if args[i].startswith('--cwd='):
527 del args[i]
527 del args[i]
528 break
528 break
529 elif args[i].startswith('--cwd'):
529 elif args[i].startswith('--cwd'):
530 del args[i:i+2]
530 del args[i:i+2]
531 break
531 break
532 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
532 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
533 args[0], args)
533 args[0], args)
534 os.close(wfd)
534 os.close(wfd)
535 os.read(rfd, 1)
535 os.read(rfd, 1)
536 if parentfn:
536 if parentfn:
537 return parentfn(pid)
537 return parentfn(pid)
538 else:
538 else:
539 os._exit(0)
539 os._exit(0)
540
540
541 if initfn:
541 if initfn:
542 initfn()
542 initfn()
543
543
544 if opts['pid_file']:
544 if opts['pid_file']:
545 fp = open(opts['pid_file'], 'w')
545 fp = open(opts['pid_file'], 'w')
546 fp.write(str(os.getpid()) + '\n')
546 fp.write(str(os.getpid()) + '\n')
547 fp.close()
547 fp.close()
548
548
549 if opts['daemon_pipefds']:
549 if opts['daemon_pipefds']:
550 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
550 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
551 os.close(rfd)
551 os.close(rfd)
552 try:
552 try:
553 os.setsid()
553 os.setsid()
554 except AttributeError:
554 except AttributeError:
555 pass
555 pass
556 os.write(wfd, 'y')
556 os.write(wfd, 'y')
557 os.close(wfd)
557 os.close(wfd)
558 sys.stdout.flush()
558 sys.stdout.flush()
559 sys.stderr.flush()
559 sys.stderr.flush()
560 fd = os.open(util.nulldev, os.O_RDWR)
560 fd = os.open(util.nulldev, os.O_RDWR)
561 if fd != 0: os.dup2(fd, 0)
561 if fd != 0: os.dup2(fd, 0)
562 if fd != 1: os.dup2(fd, 1)
562 if fd != 1: os.dup2(fd, 1)
563 if fd != 2: os.dup2(fd, 2)
563 if fd != 2: os.dup2(fd, 2)
564 if fd not in (0, 1, 2): os.close(fd)
564 if fd not in (0, 1, 2): os.close(fd)
565
565
566 if runfn:
566 if runfn:
567 return runfn()
567 return runfn()
568
568
569 class changeset_printer(object):
569 class changeset_printer(object):
570 '''show changeset information when templating not requested.'''
570 '''show changeset information when templating not requested.'''
571
571
572 def __init__(self, ui, repo, patch, buffered):
572 def __init__(self, ui, repo, patch, diffopts, buffered):
573 self.ui = ui
573 self.ui = ui
574 self.repo = repo
574 self.repo = repo
575 self.buffered = buffered
575 self.buffered = buffered
576 self.patch = patch
576 self.patch = patch
577 self.diffopts = diffopts
577 self.header = {}
578 self.header = {}
578 self.hunk = {}
579 self.hunk = {}
579 self.lastheader = None
580 self.lastheader = None
580
581
581 def flush(self, rev):
582 def flush(self, rev):
582 if rev in self.header:
583 if rev in self.header:
583 h = self.header[rev]
584 h = self.header[rev]
584 if h != self.lastheader:
585 if h != self.lastheader:
585 self.lastheader = h
586 self.lastheader = h
586 self.ui.write(h)
587 self.ui.write(h)
587 del self.header[rev]
588 del self.header[rev]
588 if rev in self.hunk:
589 if rev in self.hunk:
589 self.ui.write(self.hunk[rev])
590 self.ui.write(self.hunk[rev])
590 del self.hunk[rev]
591 del self.hunk[rev]
591 return 1
592 return 1
592 return 0
593 return 0
593
594
594 def show(self, ctx, copies=(), **props):
595 def show(self, ctx, copies=(), **props):
595 if self.buffered:
596 if self.buffered:
596 self.ui.pushbuffer()
597 self.ui.pushbuffer()
597 self._show(ctx, copies, props)
598 self._show(ctx, copies, props)
598 self.hunk[ctx.rev()] = self.ui.popbuffer()
599 self.hunk[ctx.rev()] = self.ui.popbuffer()
599 else:
600 else:
600 self._show(ctx, copies, props)
601 self._show(ctx, copies, props)
601
602
602 def _show(self, ctx, copies, props):
603 def _show(self, ctx, copies, props):
603 '''show a single changeset or file revision'''
604 '''show a single changeset or file revision'''
604 changenode = ctx.node()
605 changenode = ctx.node()
605 rev = ctx.rev()
606 rev = ctx.rev()
606
607
607 if self.ui.quiet:
608 if self.ui.quiet:
608 self.ui.write("%d:%s\n" % (rev, short(changenode)))
609 self.ui.write("%d:%s\n" % (rev, short(changenode)))
609 return
610 return
610
611
611 log = self.repo.changelog
612 log = self.repo.changelog
612 changes = log.read(changenode)
613 changes = log.read(changenode)
613 date = util.datestr(changes[2])
614 date = util.datestr(changes[2])
614 extra = changes[5]
615 extra = changes[5]
615 branch = extra.get("branch")
616 branch = extra.get("branch")
616
617
617 hexfunc = self.ui.debugflag and hex or short
618 hexfunc = self.ui.debugflag and hex or short
618
619
619 parents = [(p, hexfunc(log.node(p)))
620 parents = [(p, hexfunc(log.node(p)))
620 for p in self._meaningful_parentrevs(log, rev)]
621 for p in self._meaningful_parentrevs(log, rev)]
621
622
622 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
623 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
623
624
624 # don't show the default branch name
625 # don't show the default branch name
625 if branch != 'default':
626 if branch != 'default':
626 branch = util.tolocal(branch)
627 branch = util.tolocal(branch)
627 self.ui.write(_("branch: %s\n") % branch)
628 self.ui.write(_("branch: %s\n") % branch)
628 for tag in self.repo.nodetags(changenode):
629 for tag in self.repo.nodetags(changenode):
629 self.ui.write(_("tag: %s\n") % tag)
630 self.ui.write(_("tag: %s\n") % tag)
630 for parent in parents:
631 for parent in parents:
631 self.ui.write(_("parent: %d:%s\n") % parent)
632 self.ui.write(_("parent: %d:%s\n") % parent)
632
633
633 if self.ui.debugflag:
634 if self.ui.debugflag:
634 self.ui.write(_("manifest: %d:%s\n") %
635 self.ui.write(_("manifest: %d:%s\n") %
635 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
636 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
636 self.ui.write(_("user: %s\n") % changes[1])
637 self.ui.write(_("user: %s\n") % changes[1])
637 self.ui.write(_("date: %s\n") % date)
638 self.ui.write(_("date: %s\n") % date)
638
639
639 if self.ui.debugflag:
640 if self.ui.debugflag:
640 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
641 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
641 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
642 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
642 files):
643 files):
643 if value:
644 if value:
644 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
645 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
645 elif changes[3] and self.ui.verbose:
646 elif changes[3] and self.ui.verbose:
646 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
647 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
647 if copies and self.ui.verbose:
648 if copies and self.ui.verbose:
648 copies = ['%s (%s)' % c for c in copies]
649 copies = ['%s (%s)' % c for c in copies]
649 self.ui.write(_("copies: %s\n") % ' '.join(copies))
650 self.ui.write(_("copies: %s\n") % ' '.join(copies))
650
651
651 if extra and self.ui.debugflag:
652 if extra and self.ui.debugflag:
652 for key, value in util.sort(extra.items()):
653 for key, value in util.sort(extra.items()):
653 self.ui.write(_("extra: %s=%s\n")
654 self.ui.write(_("extra: %s=%s\n")
654 % (key, value.encode('string_escape')))
655 % (key, value.encode('string_escape')))
655
656
656 description = changes[4].strip()
657 description = changes[4].strip()
657 if description:
658 if description:
658 if self.ui.verbose:
659 if self.ui.verbose:
659 self.ui.write(_("description:\n"))
660 self.ui.write(_("description:\n"))
660 self.ui.write(description)
661 self.ui.write(description)
661 self.ui.write("\n\n")
662 self.ui.write("\n\n")
662 else:
663 else:
663 self.ui.write(_("summary: %s\n") %
664 self.ui.write(_("summary: %s\n") %
664 description.splitlines()[0])
665 description.splitlines()[0])
665 self.ui.write("\n")
666 self.ui.write("\n")
666
667
667 self.showpatch(changenode)
668 self.showpatch(changenode)
668
669
669 def showpatch(self, node):
670 def showpatch(self, node):
670 if self.patch:
671 if self.patch:
671 prev = self.repo.changelog.parents(node)[0]
672 prev = self.repo.changelog.parents(node)[0]
672 chunks = patch.diff(self.repo, prev, node, match=self.patch,
673 chunks = patch.diff(self.repo, prev, node, match=self.patch,
673 opts=patch.diffopts(self.ui))
674 opts=patch.diffopts(self.ui, self.diffopts))
674 for chunk in chunks:
675 for chunk in chunks:
675 self.ui.write(chunk)
676 self.ui.write(chunk)
676 self.ui.write("\n")
677 self.ui.write("\n")
677
678
678 def _meaningful_parentrevs(self, log, rev):
679 def _meaningful_parentrevs(self, log, rev):
679 """Return list of meaningful (or all if debug) parentrevs for rev.
680 """Return list of meaningful (or all if debug) parentrevs for rev.
680
681
681 For merges (two non-nullrev revisions) both parents are meaningful.
682 For merges (two non-nullrev revisions) both parents are meaningful.
682 Otherwise the first parent revision is considered meaningful if it
683 Otherwise the first parent revision is considered meaningful if it
683 is not the preceding revision.
684 is not the preceding revision.
684 """
685 """
685 parents = log.parentrevs(rev)
686 parents = log.parentrevs(rev)
686 if not self.ui.debugflag and parents[1] == nullrev:
687 if not self.ui.debugflag and parents[1] == nullrev:
687 if parents[0] >= rev - 1:
688 if parents[0] >= rev - 1:
688 parents = []
689 parents = []
689 else:
690 else:
690 parents = [parents[0]]
691 parents = [parents[0]]
691 return parents
692 return parents
692
693
693
694
694 class changeset_templater(changeset_printer):
695 class changeset_templater(changeset_printer):
695 '''format changeset information.'''
696 '''format changeset information.'''
696
697
697 def __init__(self, ui, repo, patch, mapfile, buffered):
698 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
698 changeset_printer.__init__(self, ui, repo, patch, buffered)
699 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
699 filters = templatefilters.filters.copy()
700 filters = templatefilters.filters.copy()
700 filters['formatnode'] = (ui.debugflag and (lambda x: x)
701 filters['formatnode'] = (ui.debugflag and (lambda x: x)
701 or (lambda x: x[:12]))
702 or (lambda x: x[:12]))
702 self.t = templater.templater(mapfile, filters,
703 self.t = templater.templater(mapfile, filters,
703 cache={
704 cache={
704 'parent': '{rev}:{node|formatnode} ',
705 'parent': '{rev}:{node|formatnode} ',
705 'manifest': '{rev}:{node|formatnode}',
706 'manifest': '{rev}:{node|formatnode}',
706 'filecopy': '{name} ({source})'})
707 'filecopy': '{name} ({source})'})
707
708
708 def use_template(self, t):
709 def use_template(self, t):
709 '''set template string to use'''
710 '''set template string to use'''
710 self.t.cache['changeset'] = t
711 self.t.cache['changeset'] = t
711
712
712 def _show(self, ctx, copies, props):
713 def _show(self, ctx, copies, props):
713 '''show a single changeset or file revision'''
714 '''show a single changeset or file revision'''
714 changenode = ctx.node()
715 changenode = ctx.node()
715 rev = ctx.rev()
716 rev = ctx.rev()
716
717
717 log = self.repo.changelog
718 log = self.repo.changelog
718 changes = log.read(changenode)
719 changes = log.read(changenode)
719
720
720 def showlist(name, values, plural=None, **args):
721 def showlist(name, values, plural=None, **args):
721 '''expand set of values.
722 '''expand set of values.
722 name is name of key in template map.
723 name is name of key in template map.
723 values is list of strings or dicts.
724 values is list of strings or dicts.
724 plural is plural of name, if not simply name + 's'.
725 plural is plural of name, if not simply name + 's'.
725
726
726 expansion works like this, given name 'foo'.
727 expansion works like this, given name 'foo'.
727
728
728 if values is empty, expand 'no_foos'.
729 if values is empty, expand 'no_foos'.
729
730
730 if 'foo' not in template map, return values as a string,
731 if 'foo' not in template map, return values as a string,
731 joined by space.
732 joined by space.
732
733
733 expand 'start_foos'.
734 expand 'start_foos'.
734
735
735 for each value, expand 'foo'. if 'last_foo' in template
736 for each value, expand 'foo'. if 'last_foo' in template
736 map, expand it instead of 'foo' for last key.
737 map, expand it instead of 'foo' for last key.
737
738
738 expand 'end_foos'.
739 expand 'end_foos'.
739 '''
740 '''
740 if plural: names = plural
741 if plural: names = plural
741 else: names = name + 's'
742 else: names = name + 's'
742 if not values:
743 if not values:
743 noname = 'no_' + names
744 noname = 'no_' + names
744 if noname in self.t:
745 if noname in self.t:
745 yield self.t(noname, **args)
746 yield self.t(noname, **args)
746 return
747 return
747 if name not in self.t:
748 if name not in self.t:
748 if isinstance(values[0], str):
749 if isinstance(values[0], str):
749 yield ' '.join(values)
750 yield ' '.join(values)
750 else:
751 else:
751 for v in values:
752 for v in values:
752 yield dict(v, **args)
753 yield dict(v, **args)
753 return
754 return
754 startname = 'start_' + names
755 startname = 'start_' + names
755 if startname in self.t:
756 if startname in self.t:
756 yield self.t(startname, **args)
757 yield self.t(startname, **args)
757 vargs = args.copy()
758 vargs = args.copy()
758 def one(v, tag=name):
759 def one(v, tag=name):
759 try:
760 try:
760 vargs.update(v)
761 vargs.update(v)
761 except (AttributeError, ValueError):
762 except (AttributeError, ValueError):
762 try:
763 try:
763 for a, b in v:
764 for a, b in v:
764 vargs[a] = b
765 vargs[a] = b
765 except ValueError:
766 except ValueError:
766 vargs[name] = v
767 vargs[name] = v
767 return self.t(tag, **vargs)
768 return self.t(tag, **vargs)
768 lastname = 'last_' + name
769 lastname = 'last_' + name
769 if lastname in self.t:
770 if lastname in self.t:
770 last = values.pop()
771 last = values.pop()
771 else:
772 else:
772 last = None
773 last = None
773 for v in values:
774 for v in values:
774 yield one(v)
775 yield one(v)
775 if last is not None:
776 if last is not None:
776 yield one(last, tag=lastname)
777 yield one(last, tag=lastname)
777 endname = 'end_' + names
778 endname = 'end_' + names
778 if endname in self.t:
779 if endname in self.t:
779 yield self.t(endname, **args)
780 yield self.t(endname, **args)
780
781
781 def showbranches(**args):
782 def showbranches(**args):
782 branch = changes[5].get("branch")
783 branch = changes[5].get("branch")
783 if branch != 'default':
784 if branch != 'default':
784 branch = util.tolocal(branch)
785 branch = util.tolocal(branch)
785 return showlist('branch', [branch], plural='branches', **args)
786 return showlist('branch', [branch], plural='branches', **args)
786
787
787 def showparents(**args):
788 def showparents(**args):
788 parents = [[('rev', p), ('node', hex(log.node(p)))]
789 parents = [[('rev', p), ('node', hex(log.node(p)))]
789 for p in self._meaningful_parentrevs(log, rev)]
790 for p in self._meaningful_parentrevs(log, rev)]
790 return showlist('parent', parents, **args)
791 return showlist('parent', parents, **args)
791
792
792 def showtags(**args):
793 def showtags(**args):
793 return showlist('tag', self.repo.nodetags(changenode), **args)
794 return showlist('tag', self.repo.nodetags(changenode), **args)
794
795
795 def showextras(**args):
796 def showextras(**args):
796 for key, value in util.sort(changes[5].items()):
797 for key, value in util.sort(changes[5].items()):
797 args = args.copy()
798 args = args.copy()
798 args.update(dict(key=key, value=value))
799 args.update(dict(key=key, value=value))
799 yield self.t('extra', **args)
800 yield self.t('extra', **args)
800
801
801 def showcopies(**args):
802 def showcopies(**args):
802 c = [{'name': x[0], 'source': x[1]} for x in copies]
803 c = [{'name': x[0], 'source': x[1]} for x in copies]
803 return showlist('file_copy', c, plural='file_copies', **args)
804 return showlist('file_copy', c, plural='file_copies', **args)
804
805
805 files = []
806 files = []
806 def getfiles():
807 def getfiles():
807 if not files:
808 if not files:
808 files[:] = self.repo.status(
809 files[:] = self.repo.status(
809 log.parents(changenode)[0], changenode)[:3]
810 log.parents(changenode)[0], changenode)[:3]
810 return files
811 return files
811 def showfiles(**args):
812 def showfiles(**args):
812 return showlist('file', changes[3], **args)
813 return showlist('file', changes[3], **args)
813 def showmods(**args):
814 def showmods(**args):
814 return showlist('file_mod', getfiles()[0], **args)
815 return showlist('file_mod', getfiles()[0], **args)
815 def showadds(**args):
816 def showadds(**args):
816 return showlist('file_add', getfiles()[1], **args)
817 return showlist('file_add', getfiles()[1], **args)
817 def showdels(**args):
818 def showdels(**args):
818 return showlist('file_del', getfiles()[2], **args)
819 return showlist('file_del', getfiles()[2], **args)
819 def showmanifest(**args):
820 def showmanifest(**args):
820 args = args.copy()
821 args = args.copy()
821 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
822 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
822 node=hex(changes[0])))
823 node=hex(changes[0])))
823 return self.t('manifest', **args)
824 return self.t('manifest', **args)
824
825
825 defprops = {
826 defprops = {
826 'author': changes[1],
827 'author': changes[1],
827 'branches': showbranches,
828 'branches': showbranches,
828 'date': changes[2],
829 'date': changes[2],
829 'desc': changes[4].strip(),
830 'desc': changes[4].strip(),
830 'file_adds': showadds,
831 'file_adds': showadds,
831 'file_dels': showdels,
832 'file_dels': showdels,
832 'file_mods': showmods,
833 'file_mods': showmods,
833 'files': showfiles,
834 'files': showfiles,
834 'file_copies': showcopies,
835 'file_copies': showcopies,
835 'manifest': showmanifest,
836 'manifest': showmanifest,
836 'node': hex(changenode),
837 'node': hex(changenode),
837 'parents': showparents,
838 'parents': showparents,
838 'rev': rev,
839 'rev': rev,
839 'tags': showtags,
840 'tags': showtags,
840 'extras': showextras,
841 'extras': showextras,
841 }
842 }
842 props = props.copy()
843 props = props.copy()
843 props.update(defprops)
844 props.update(defprops)
844
845
845 try:
846 try:
846 if self.ui.debugflag and 'header_debug' in self.t:
847 if self.ui.debugflag and 'header_debug' in self.t:
847 key = 'header_debug'
848 key = 'header_debug'
848 elif self.ui.quiet and 'header_quiet' in self.t:
849 elif self.ui.quiet and 'header_quiet' in self.t:
849 key = 'header_quiet'
850 key = 'header_quiet'
850 elif self.ui.verbose and 'header_verbose' in self.t:
851 elif self.ui.verbose and 'header_verbose' in self.t:
851 key = 'header_verbose'
852 key = 'header_verbose'
852 elif 'header' in self.t:
853 elif 'header' in self.t:
853 key = 'header'
854 key = 'header'
854 else:
855 else:
855 key = ''
856 key = ''
856 if key:
857 if key:
857 h = templater.stringify(self.t(key, **props))
858 h = templater.stringify(self.t(key, **props))
858 if self.buffered:
859 if self.buffered:
859 self.header[rev] = h
860 self.header[rev] = h
860 else:
861 else:
861 self.ui.write(h)
862 self.ui.write(h)
862 if self.ui.debugflag and 'changeset_debug' in self.t:
863 if self.ui.debugflag and 'changeset_debug' in self.t:
863 key = 'changeset_debug'
864 key = 'changeset_debug'
864 elif self.ui.quiet and 'changeset_quiet' in self.t:
865 elif self.ui.quiet and 'changeset_quiet' in self.t:
865 key = 'changeset_quiet'
866 key = 'changeset_quiet'
866 elif self.ui.verbose and 'changeset_verbose' in self.t:
867 elif self.ui.verbose and 'changeset_verbose' in self.t:
867 key = 'changeset_verbose'
868 key = 'changeset_verbose'
868 else:
869 else:
869 key = 'changeset'
870 key = 'changeset'
870 self.ui.write(templater.stringify(self.t(key, **props)))
871 self.ui.write(templater.stringify(self.t(key, **props)))
871 self.showpatch(changenode)
872 self.showpatch(changenode)
872 except KeyError, inst:
873 except KeyError, inst:
873 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
874 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
874 inst.args[0]))
875 inst.args[0]))
875 except SyntaxError, inst:
876 except SyntaxError, inst:
876 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
877 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
877
878
878 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
879 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
879 """show one changeset using template or regular display.
880 """show one changeset using template or regular display.
880
881
881 Display format will be the first non-empty hit of:
882 Display format will be the first non-empty hit of:
882 1. option 'template'
883 1. option 'template'
883 2. option 'style'
884 2. option 'style'
884 3. [ui] setting 'logtemplate'
885 3. [ui] setting 'logtemplate'
885 4. [ui] setting 'style'
886 4. [ui] setting 'style'
886 If all of these values are either the unset or the empty string,
887 If all of these values are either the unset or the empty string,
887 regular display via changeset_printer() is done.
888 regular display via changeset_printer() is done.
888 """
889 """
889 # options
890 # options
890 patch = False
891 patch = False
891 if opts.get('patch'):
892 if opts.get('patch'):
892 patch = matchfn or matchall(repo)
893 patch = matchfn or matchall(repo)
893
894
894 tmpl = opts.get('template')
895 tmpl = opts.get('template')
895 mapfile = None
896 mapfile = None
896 if tmpl:
897 if tmpl:
897 tmpl = templater.parsestring(tmpl, quoted=False)
898 tmpl = templater.parsestring(tmpl, quoted=False)
898 else:
899 else:
899 mapfile = opts.get('style')
900 mapfile = opts.get('style')
900 # ui settings
901 # ui settings
901 if not mapfile:
902 if not mapfile:
902 tmpl = ui.config('ui', 'logtemplate')
903 tmpl = ui.config('ui', 'logtemplate')
903 if tmpl:
904 if tmpl:
904 tmpl = templater.parsestring(tmpl)
905 tmpl = templater.parsestring(tmpl)
905 else:
906 else:
906 mapfile = ui.config('ui', 'style')
907 mapfile = ui.config('ui', 'style')
907
908
908 if tmpl or mapfile:
909 if tmpl or mapfile:
909 if mapfile:
910 if mapfile:
910 if not os.path.split(mapfile)[0]:
911 if not os.path.split(mapfile)[0]:
911 mapname = (templater.templatepath('map-cmdline.' + mapfile)
912 mapname = (templater.templatepath('map-cmdline.' + mapfile)
912 or templater.templatepath(mapfile))
913 or templater.templatepath(mapfile))
913 if mapname: mapfile = mapname
914 if mapname: mapfile = mapname
914 try:
915 try:
915 t = changeset_templater(ui, repo, patch, mapfile, buffered)
916 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
916 except SyntaxError, inst:
917 except SyntaxError, inst:
917 raise util.Abort(inst.args[0])
918 raise util.Abort(inst.args[0])
918 if tmpl: t.use_template(tmpl)
919 if tmpl: t.use_template(tmpl)
919 return t
920 return t
920 return changeset_printer(ui, repo, patch, buffered)
921 return changeset_printer(ui, repo, patch, opts, buffered)
921
922
922 def finddate(ui, repo, date):
923 def finddate(ui, repo, date):
923 """Find the tipmost changeset that matches the given date spec"""
924 """Find the tipmost changeset that matches the given date spec"""
924 df = util.matchdate(date)
925 df = util.matchdate(date)
925 get = util.cachefunc(lambda r: repo[r].changeset())
926 get = util.cachefunc(lambda r: repo[r].changeset())
926 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
927 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
927 results = {}
928 results = {}
928 for st, rev, fns in changeiter:
929 for st, rev, fns in changeiter:
929 if st == 'add':
930 if st == 'add':
930 d = get(rev)[2]
931 d = get(rev)[2]
931 if df(d[0]):
932 if df(d[0]):
932 results[rev] = d
933 results[rev] = d
933 elif st == 'iter':
934 elif st == 'iter':
934 if rev in results:
935 if rev in results:
935 ui.status(_("Found revision %s from %s\n") %
936 ui.status(_("Found revision %s from %s\n") %
936 (rev, util.datestr(results[rev])))
937 (rev, util.datestr(results[rev])))
937 return str(rev)
938 return str(rev)
938
939
939 raise util.Abort(_("revision matching date not found"))
940 raise util.Abort(_("revision matching date not found"))
940
941
941 def walkchangerevs(ui, repo, pats, change, opts):
942 def walkchangerevs(ui, repo, pats, change, opts):
942 '''Iterate over files and the revs they changed in.
943 '''Iterate over files and the revs they changed in.
943
944
944 Callers most commonly need to iterate backwards over the history
945 Callers most commonly need to iterate backwards over the history
945 it is interested in. Doing so has awful (quadratic-looking)
946 it is interested in. Doing so has awful (quadratic-looking)
946 performance, so we use iterators in a "windowed" way.
947 performance, so we use iterators in a "windowed" way.
947
948
948 We walk a window of revisions in the desired order. Within the
949 We walk a window of revisions in the desired order. Within the
949 window, we first walk forwards to gather data, then in the desired
950 window, we first walk forwards to gather data, then in the desired
950 order (usually backwards) to display it.
951 order (usually backwards) to display it.
951
952
952 This function returns an (iterator, matchfn) tuple. The iterator
953 This function returns an (iterator, matchfn) tuple. The iterator
953 yields 3-tuples. They will be of one of the following forms:
954 yields 3-tuples. They will be of one of the following forms:
954
955
955 "window", incrementing, lastrev: stepping through a window,
956 "window", incrementing, lastrev: stepping through a window,
956 positive if walking forwards through revs, last rev in the
957 positive if walking forwards through revs, last rev in the
957 sequence iterated over - use to reset state for the current window
958 sequence iterated over - use to reset state for the current window
958
959
959 "add", rev, fns: out-of-order traversal of the given file names
960 "add", rev, fns: out-of-order traversal of the given file names
960 fns, which changed during revision rev - use to gather data for
961 fns, which changed during revision rev - use to gather data for
961 possible display
962 possible display
962
963
963 "iter", rev, None: in-order traversal of the revs earlier iterated
964 "iter", rev, None: in-order traversal of the revs earlier iterated
964 over with "add" - use to display data'''
965 over with "add" - use to display data'''
965
966
966 def increasing_windows(start, end, windowsize=8, sizelimit=512):
967 def increasing_windows(start, end, windowsize=8, sizelimit=512):
967 if start < end:
968 if start < end:
968 while start < end:
969 while start < end:
969 yield start, min(windowsize, end-start)
970 yield start, min(windowsize, end-start)
970 start += windowsize
971 start += windowsize
971 if windowsize < sizelimit:
972 if windowsize < sizelimit:
972 windowsize *= 2
973 windowsize *= 2
973 else:
974 else:
974 while start > end:
975 while start > end:
975 yield start, min(windowsize, start-end-1)
976 yield start, min(windowsize, start-end-1)
976 start -= windowsize
977 start -= windowsize
977 if windowsize < sizelimit:
978 if windowsize < sizelimit:
978 windowsize *= 2
979 windowsize *= 2
979
980
980 m = match(repo, pats, opts)
981 m = match(repo, pats, opts)
981 follow = opts.get('follow') or opts.get('follow_first')
982 follow = opts.get('follow') or opts.get('follow_first')
982
983
983 if not len(repo):
984 if not len(repo):
984 return [], m
985 return [], m
985
986
986 if follow:
987 if follow:
987 defrange = '%s:0' % repo['.'].rev()
988 defrange = '%s:0' % repo['.'].rev()
988 else:
989 else:
989 defrange = '-1:0'
990 defrange = '-1:0'
990 revs = revrange(repo, opts['rev'] or [defrange])
991 revs = revrange(repo, opts['rev'] or [defrange])
991 wanted = {}
992 wanted = {}
992 slowpath = m.anypats() or opts.get('removed')
993 slowpath = m.anypats() or opts.get('removed')
993 fncache = {}
994 fncache = {}
994
995
995 if not slowpath and not m.files():
996 if not slowpath and not m.files():
996 # No files, no patterns. Display all revs.
997 # No files, no patterns. Display all revs.
997 wanted = dict.fromkeys(revs)
998 wanted = dict.fromkeys(revs)
998 copies = []
999 copies = []
999 if not slowpath:
1000 if not slowpath:
1000 # Only files, no patterns. Check the history of each file.
1001 # Only files, no patterns. Check the history of each file.
1001 def filerevgen(filelog, node):
1002 def filerevgen(filelog, node):
1002 cl_count = len(repo)
1003 cl_count = len(repo)
1003 if node is None:
1004 if node is None:
1004 last = len(filelog) - 1
1005 last = len(filelog) - 1
1005 else:
1006 else:
1006 last = filelog.rev(node)
1007 last = filelog.rev(node)
1007 for i, window in increasing_windows(last, nullrev):
1008 for i, window in increasing_windows(last, nullrev):
1008 revs = []
1009 revs = []
1009 for j in xrange(i - window, i + 1):
1010 for j in xrange(i - window, i + 1):
1010 n = filelog.node(j)
1011 n = filelog.node(j)
1011 revs.append((filelog.linkrev(j),
1012 revs.append((filelog.linkrev(j),
1012 follow and filelog.renamed(n)))
1013 follow and filelog.renamed(n)))
1013 revs.reverse()
1014 revs.reverse()
1014 for rev in revs:
1015 for rev in revs:
1015 # only yield rev for which we have the changelog, it can
1016 # only yield rev for which we have the changelog, it can
1016 # happen while doing "hg log" during a pull or commit
1017 # happen while doing "hg log" during a pull or commit
1017 if rev[0] < cl_count:
1018 if rev[0] < cl_count:
1018 yield rev
1019 yield rev
1019 def iterfiles():
1020 def iterfiles():
1020 for filename in m.files():
1021 for filename in m.files():
1021 yield filename, None
1022 yield filename, None
1022 for filename_node in copies:
1023 for filename_node in copies:
1023 yield filename_node
1024 yield filename_node
1024 minrev, maxrev = min(revs), max(revs)
1025 minrev, maxrev = min(revs), max(revs)
1025 for file_, node in iterfiles():
1026 for file_, node in iterfiles():
1026 filelog = repo.file(file_)
1027 filelog = repo.file(file_)
1027 if not len(filelog):
1028 if not len(filelog):
1028 if node is None:
1029 if node is None:
1029 # A zero count may be a directory or deleted file, so
1030 # A zero count may be a directory or deleted file, so
1030 # try to find matching entries on the slow path.
1031 # try to find matching entries on the slow path.
1031 if follow:
1032 if follow:
1032 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1033 raise util.Abort(_('cannot follow nonexistent file: "%s"') % file_)
1033 slowpath = True
1034 slowpath = True
1034 break
1035 break
1035 else:
1036 else:
1036 ui.warn(_('%s:%s copy source revision cannot be found!\n')
1037 ui.warn(_('%s:%s copy source revision cannot be found!\n')
1037 % (file_, short(node)))
1038 % (file_, short(node)))
1038 continue
1039 continue
1039 for rev, copied in filerevgen(filelog, node):
1040 for rev, copied in filerevgen(filelog, node):
1040 if rev <= maxrev:
1041 if rev <= maxrev:
1041 if rev < minrev:
1042 if rev < minrev:
1042 break
1043 break
1043 fncache.setdefault(rev, [])
1044 fncache.setdefault(rev, [])
1044 fncache[rev].append(file_)
1045 fncache[rev].append(file_)
1045 wanted[rev] = 1
1046 wanted[rev] = 1
1046 if follow and copied:
1047 if follow and copied:
1047 copies.append(copied)
1048 copies.append(copied)
1048 if slowpath:
1049 if slowpath:
1049 if follow:
1050 if follow:
1050 raise util.Abort(_('can only follow copies/renames for explicit '
1051 raise util.Abort(_('can only follow copies/renames for explicit '
1051 'file names'))
1052 'file names'))
1052
1053
1053 # The slow path checks files modified in every changeset.
1054 # The slow path checks files modified in every changeset.
1054 def changerevgen():
1055 def changerevgen():
1055 for i, window in increasing_windows(len(repo) - 1, nullrev):
1056 for i, window in increasing_windows(len(repo) - 1, nullrev):
1056 for j in xrange(i - window, i + 1):
1057 for j in xrange(i - window, i + 1):
1057 yield j, change(j)[3]
1058 yield j, change(j)[3]
1058
1059
1059 for rev, changefiles in changerevgen():
1060 for rev, changefiles in changerevgen():
1060 matches = filter(m, changefiles)
1061 matches = filter(m, changefiles)
1061 if matches:
1062 if matches:
1062 fncache[rev] = matches
1063 fncache[rev] = matches
1063 wanted[rev] = 1
1064 wanted[rev] = 1
1064
1065
1065 class followfilter:
1066 class followfilter:
1066 def __init__(self, onlyfirst=False):
1067 def __init__(self, onlyfirst=False):
1067 self.startrev = nullrev
1068 self.startrev = nullrev
1068 self.roots = []
1069 self.roots = []
1069 self.onlyfirst = onlyfirst
1070 self.onlyfirst = onlyfirst
1070
1071
1071 def match(self, rev):
1072 def match(self, rev):
1072 def realparents(rev):
1073 def realparents(rev):
1073 if self.onlyfirst:
1074 if self.onlyfirst:
1074 return repo.changelog.parentrevs(rev)[0:1]
1075 return repo.changelog.parentrevs(rev)[0:1]
1075 else:
1076 else:
1076 return filter(lambda x: x != nullrev,
1077 return filter(lambda x: x != nullrev,
1077 repo.changelog.parentrevs(rev))
1078 repo.changelog.parentrevs(rev))
1078
1079
1079 if self.startrev == nullrev:
1080 if self.startrev == nullrev:
1080 self.startrev = rev
1081 self.startrev = rev
1081 return True
1082 return True
1082
1083
1083 if rev > self.startrev:
1084 if rev > self.startrev:
1084 # forward: all descendants
1085 # forward: all descendants
1085 if not self.roots:
1086 if not self.roots:
1086 self.roots.append(self.startrev)
1087 self.roots.append(self.startrev)
1087 for parent in realparents(rev):
1088 for parent in realparents(rev):
1088 if parent in self.roots:
1089 if parent in self.roots:
1089 self.roots.append(rev)
1090 self.roots.append(rev)
1090 return True
1091 return True
1091 else:
1092 else:
1092 # backwards: all parents
1093 # backwards: all parents
1093 if not self.roots:
1094 if not self.roots:
1094 self.roots.extend(realparents(self.startrev))
1095 self.roots.extend(realparents(self.startrev))
1095 if rev in self.roots:
1096 if rev in self.roots:
1096 self.roots.remove(rev)
1097 self.roots.remove(rev)
1097 self.roots.extend(realparents(rev))
1098 self.roots.extend(realparents(rev))
1098 return True
1099 return True
1099
1100
1100 return False
1101 return False
1101
1102
1102 # it might be worthwhile to do this in the iterator if the rev range
1103 # it might be worthwhile to do this in the iterator if the rev range
1103 # is descending and the prune args are all within that range
1104 # is descending and the prune args are all within that range
1104 for rev in opts.get('prune', ()):
1105 for rev in opts.get('prune', ()):
1105 rev = repo.changelog.rev(repo.lookup(rev))
1106 rev = repo.changelog.rev(repo.lookup(rev))
1106 ff = followfilter()
1107 ff = followfilter()
1107 stop = min(revs[0], revs[-1])
1108 stop = min(revs[0], revs[-1])
1108 for x in xrange(rev, stop-1, -1):
1109 for x in xrange(rev, stop-1, -1):
1109 if ff.match(x) and x in wanted:
1110 if ff.match(x) and x in wanted:
1110 del wanted[x]
1111 del wanted[x]
1111
1112
1112 def iterate():
1113 def iterate():
1113 if follow and not m.files():
1114 if follow and not m.files():
1114 ff = followfilter(onlyfirst=opts.get('follow_first'))
1115 ff = followfilter(onlyfirst=opts.get('follow_first'))
1115 def want(rev):
1116 def want(rev):
1116 if ff.match(rev) and rev in wanted:
1117 if ff.match(rev) and rev in wanted:
1117 return True
1118 return True
1118 return False
1119 return False
1119 else:
1120 else:
1120 def want(rev):
1121 def want(rev):
1121 return rev in wanted
1122 return rev in wanted
1122
1123
1123 for i, window in increasing_windows(0, len(revs)):
1124 for i, window in increasing_windows(0, len(revs)):
1124 yield 'window', revs[0] < revs[-1], revs[-1]
1125 yield 'window', revs[0] < revs[-1], revs[-1]
1125 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1126 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1126 for rev in util.sort(list(nrevs)):
1127 for rev in util.sort(list(nrevs)):
1127 fns = fncache.get(rev)
1128 fns = fncache.get(rev)
1128 if not fns:
1129 if not fns:
1129 def fns_generator():
1130 def fns_generator():
1130 for f in change(rev)[3]:
1131 for f in change(rev)[3]:
1131 if m(f):
1132 if m(f):
1132 yield f
1133 yield f
1133 fns = fns_generator()
1134 fns = fns_generator()
1134 yield 'add', rev, fns
1135 yield 'add', rev, fns
1135 for rev in nrevs:
1136 for rev in nrevs:
1136 yield 'iter', rev, None
1137 yield 'iter', rev, None
1137 return iterate(), m
1138 return iterate(), m
1138
1139
1139 def commit(ui, repo, commitfunc, pats, opts):
1140 def commit(ui, repo, commitfunc, pats, opts):
1140 '''commit the specified files or all outstanding changes'''
1141 '''commit the specified files or all outstanding changes'''
1141 date = opts.get('date')
1142 date = opts.get('date')
1142 if date:
1143 if date:
1143 opts['date'] = util.parsedate(date)
1144 opts['date'] = util.parsedate(date)
1144 message = logmessage(opts)
1145 message = logmessage(opts)
1145
1146
1146 # extract addremove carefully -- this function can be called from a command
1147 # extract addremove carefully -- this function can be called from a command
1147 # that doesn't support addremove
1148 # that doesn't support addremove
1148 if opts.get('addremove'):
1149 if opts.get('addremove'):
1149 addremove(repo, pats, opts)
1150 addremove(repo, pats, opts)
1150
1151
1151 m = match(repo, pats, opts)
1152 m = match(repo, pats, opts)
1152 if pats:
1153 if pats:
1153 modified, added, removed = repo.status(match=m)[:3]
1154 modified, added, removed = repo.status(match=m)[:3]
1154 files = util.sort(modified + added + removed)
1155 files = util.sort(modified + added + removed)
1155
1156
1156 def is_dir(f):
1157 def is_dir(f):
1157 name = f + '/'
1158 name = f + '/'
1158 i = bisect.bisect(files, name)
1159 i = bisect.bisect(files, name)
1159 return i < len(files) and files[i].startswith(name)
1160 return i < len(files) and files[i].startswith(name)
1160
1161
1161 for f in m.files():
1162 for f in m.files():
1162 if f == '.':
1163 if f == '.':
1163 continue
1164 continue
1164 if f not in files:
1165 if f not in files:
1165 rf = repo.wjoin(f)
1166 rf = repo.wjoin(f)
1166 rel = repo.pathto(f)
1167 rel = repo.pathto(f)
1167 try:
1168 try:
1168 mode = os.lstat(rf)[stat.ST_MODE]
1169 mode = os.lstat(rf)[stat.ST_MODE]
1169 except OSError:
1170 except OSError:
1170 if is_dir(f): # deleted directory ?
1171 if is_dir(f): # deleted directory ?
1171 continue
1172 continue
1172 raise util.Abort(_("file %s not found!") % rel)
1173 raise util.Abort(_("file %s not found!") % rel)
1173 if stat.S_ISDIR(mode):
1174 if stat.S_ISDIR(mode):
1174 if not is_dir(f):
1175 if not is_dir(f):
1175 raise util.Abort(_("no match under directory %s!")
1176 raise util.Abort(_("no match under directory %s!")
1176 % rel)
1177 % rel)
1177 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
1178 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
1178 raise util.Abort(_("can't commit %s: "
1179 raise util.Abort(_("can't commit %s: "
1179 "unsupported file type!") % rel)
1180 "unsupported file type!") % rel)
1180 elif f not in repo.dirstate:
1181 elif f not in repo.dirstate:
1181 raise util.Abort(_("file %s not tracked!") % rel)
1182 raise util.Abort(_("file %s not tracked!") % rel)
1182 m = matchfiles(repo, files)
1183 m = matchfiles(repo, files)
1183 try:
1184 try:
1184 return commitfunc(ui, repo, message, m, opts)
1185 return commitfunc(ui, repo, message, m, opts)
1185 except ValueError, inst:
1186 except ValueError, inst:
1186 raise util.Abort(str(inst))
1187 raise util.Abort(str(inst))
@@ -1,3413 +1,3415 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing 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 node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _, gettext
9 from i18n import _, gettext
10 import os, re, sys
10 import os, re, sys
11 import hg, util, revlog, bundlerepo, extensions, copies, context, error
11 import hg, util, revlog, bundlerepo, extensions, copies, context, error
12 import difflib, patch, time, help, mdiff, tempfile, url
12 import difflib, patch, time, help, mdiff, tempfile, url
13 import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
13 import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
14 import merge as merge_
14 import merge as merge_
15
15
16 # Commands start here, listed alphabetically
16 # Commands start here, listed alphabetically
17
17
18 def add(ui, repo, *pats, **opts):
18 def add(ui, repo, *pats, **opts):
19 """add the specified files on the next commit
19 """add the specified files on the next commit
20
20
21 Schedule files to be version controlled and added to the repository.
21 Schedule files to be version controlled and added to the repository.
22
22
23 The files will be added to the repository at the next commit. To
23 The files will be added to the repository at the next commit. To
24 undo an add before that, see hg revert.
24 undo an add before that, see hg revert.
25
25
26 If no names are given, add all files in the repository.
26 If no names are given, add all files in the repository.
27 """
27 """
28
28
29 rejected = None
29 rejected = None
30 exacts = {}
30 exacts = {}
31 names = []
31 names = []
32 m = cmdutil.match(repo, pats, opts)
32 m = cmdutil.match(repo, pats, opts)
33 m.bad = lambda x,y: True
33 m.bad = lambda x,y: True
34 for abs in repo.walk(m):
34 for abs in repo.walk(m):
35 if m.exact(abs):
35 if m.exact(abs):
36 if ui.verbose:
36 if ui.verbose:
37 ui.status(_('adding %s\n') % m.rel(abs))
37 ui.status(_('adding %s\n') % m.rel(abs))
38 names.append(abs)
38 names.append(abs)
39 exacts[abs] = 1
39 exacts[abs] = 1
40 elif abs not in repo.dirstate:
40 elif abs not in repo.dirstate:
41 ui.status(_('adding %s\n') % m.rel(abs))
41 ui.status(_('adding %s\n') % m.rel(abs))
42 names.append(abs)
42 names.append(abs)
43 if not opts.get('dry_run'):
43 if not opts.get('dry_run'):
44 rejected = repo.add(names)
44 rejected = repo.add(names)
45 rejected = [p for p in rejected if p in exacts]
45 rejected = [p for p in rejected if p in exacts]
46 return rejected and 1 or 0
46 return rejected and 1 or 0
47
47
48 def addremove(ui, repo, *pats, **opts):
48 def addremove(ui, repo, *pats, **opts):
49 """add all new files, delete all missing files
49 """add all new files, delete all missing files
50
50
51 Add all new files and remove all missing files from the repository.
51 Add all new files and remove all missing files from the repository.
52
52
53 New files are ignored if they match any of the patterns in .hgignore. As
53 New files are ignored if they match any of the patterns in .hgignore. As
54 with add, these changes take effect at the next commit.
54 with add, these changes take effect at the next commit.
55
55
56 Use the -s option to detect renamed files. With a parameter > 0,
56 Use the -s option to detect renamed files. With a parameter > 0,
57 this compares every removed file with every added file and records
57 this compares every removed file with every added file and records
58 those similar enough as renames. This option takes a percentage
58 those similar enough as renames. This option takes a percentage
59 between 0 (disabled) and 100 (files must be identical) as its
59 between 0 (disabled) and 100 (files must be identical) as its
60 parameter. Detecting renamed files this way can be expensive.
60 parameter. Detecting renamed files this way can be expensive.
61 """
61 """
62 try:
62 try:
63 sim = float(opts.get('similarity') or 0)
63 sim = float(opts.get('similarity') or 0)
64 except ValueError:
64 except ValueError:
65 raise util.Abort(_('similarity must be a number'))
65 raise util.Abort(_('similarity must be a number'))
66 if sim < 0 or sim > 100:
66 if sim < 0 or sim > 100:
67 raise util.Abort(_('similarity must be between 0 and 100'))
67 raise util.Abort(_('similarity must be between 0 and 100'))
68 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
68 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
69
69
70 def annotate(ui, repo, *pats, **opts):
70 def annotate(ui, repo, *pats, **opts):
71 """show changeset information per file line
71 """show changeset information per file line
72
72
73 List changes in files, showing the revision id responsible for each line
73 List changes in files, showing the revision id responsible for each line
74
74
75 This command is useful to discover who did a change or when a change took
75 This command is useful to discover who did a change or when a change took
76 place.
76 place.
77
77
78 Without the -a option, annotate will avoid processing files it
78 Without the -a option, annotate will avoid processing files it
79 detects as binary. With -a, annotate will generate an annotation
79 detects as binary. With -a, annotate will generate an annotation
80 anyway, probably with undesirable results.
80 anyway, probably with undesirable results.
81 """
81 """
82 datefunc = ui.quiet and util.shortdate or util.datestr
82 datefunc = ui.quiet and util.shortdate or util.datestr
83 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
83 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
84
84
85 if not pats:
85 if not pats:
86 raise util.Abort(_('at least one file name or pattern required'))
86 raise util.Abort(_('at least one file name or pattern required'))
87
87
88 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
88 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
89 ('number', lambda x: str(x[0].rev())),
89 ('number', lambda x: str(x[0].rev())),
90 ('changeset', lambda x: short(x[0].node())),
90 ('changeset', lambda x: short(x[0].node())),
91 ('date', getdate),
91 ('date', getdate),
92 ('follow', lambda x: x[0].path()),
92 ('follow', lambda x: x[0].path()),
93 ]
93 ]
94
94
95 if (not opts.get('user') and not opts.get('changeset') and not opts.get('date')
95 if (not opts.get('user') and not opts.get('changeset') and not opts.get('date')
96 and not opts.get('follow')):
96 and not opts.get('follow')):
97 opts['number'] = 1
97 opts['number'] = 1
98
98
99 linenumber = opts.get('line_number') is not None
99 linenumber = opts.get('line_number') is not None
100 if (linenumber and (not opts.get('changeset')) and (not opts.get('number'))):
100 if (linenumber and (not opts.get('changeset')) and (not opts.get('number'))):
101 raise util.Abort(_('at least one of -n/-c is required for -l'))
101 raise util.Abort(_('at least one of -n/-c is required for -l'))
102
102
103 funcmap = [func for op, func in opmap if opts.get(op)]
103 funcmap = [func for op, func in opmap if opts.get(op)]
104 if linenumber:
104 if linenumber:
105 lastfunc = funcmap[-1]
105 lastfunc = funcmap[-1]
106 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
106 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
107
107
108 ctx = repo[opts.get('rev')]
108 ctx = repo[opts.get('rev')]
109
109
110 m = cmdutil.match(repo, pats, opts)
110 m = cmdutil.match(repo, pats, opts)
111 for abs in ctx.walk(m):
111 for abs in ctx.walk(m):
112 fctx = ctx[abs]
112 fctx = ctx[abs]
113 if not opts.get('text') and util.binary(fctx.data()):
113 if not opts.get('text') and util.binary(fctx.data()):
114 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
114 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
115 continue
115 continue
116
116
117 lines = fctx.annotate(follow=opts.get('follow'),
117 lines = fctx.annotate(follow=opts.get('follow'),
118 linenumber=linenumber)
118 linenumber=linenumber)
119 pieces = []
119 pieces = []
120
120
121 for f in funcmap:
121 for f in funcmap:
122 l = [f(n) for n, dummy in lines]
122 l = [f(n) for n, dummy in lines]
123 if l:
123 if l:
124 ml = max(map(len, l))
124 ml = max(map(len, l))
125 pieces.append(["%*s" % (ml, x) for x in l])
125 pieces.append(["%*s" % (ml, x) for x in l])
126
126
127 if pieces:
127 if pieces:
128 for p, l in zip(zip(*pieces), lines):
128 for p, l in zip(zip(*pieces), lines):
129 ui.write("%s: %s" % (" ".join(p), l[1]))
129 ui.write("%s: %s" % (" ".join(p), l[1]))
130
130
131 def archive(ui, repo, dest, **opts):
131 def archive(ui, repo, dest, **opts):
132 '''create unversioned archive of a repository revision
132 '''create unversioned archive of a repository revision
133
133
134 By default, the revision used is the parent of the working
134 By default, the revision used is the parent of the working
135 directory; use "-r" to specify a different revision.
135 directory; use "-r" to specify a different revision.
136
136
137 To specify the type of archive to create, use "-t". Valid
137 To specify the type of archive to create, use "-t". Valid
138 types are:
138 types are:
139
139
140 "files" (default): a directory full of files
140 "files" (default): a directory full of files
141 "tar": tar archive, uncompressed
141 "tar": tar archive, uncompressed
142 "tbz2": tar archive, compressed using bzip2
142 "tbz2": tar archive, compressed using bzip2
143 "tgz": tar archive, compressed using gzip
143 "tgz": tar archive, compressed using gzip
144 "uzip": zip archive, uncompressed
144 "uzip": zip archive, uncompressed
145 "zip": zip archive, compressed using deflate
145 "zip": zip archive, compressed using deflate
146
146
147 The exact name of the destination archive or directory is given
147 The exact name of the destination archive or directory is given
148 using a format string; see "hg help export" for details.
148 using a format string; see "hg help export" for details.
149
149
150 Each member added to an archive file has a directory prefix
150 Each member added to an archive file has a directory prefix
151 prepended. Use "-p" to specify a format string for the prefix.
151 prepended. Use "-p" to specify a format string for the prefix.
152 The default is the basename of the archive, with suffixes removed.
152 The default is the basename of the archive, with suffixes removed.
153 '''
153 '''
154
154
155 ctx = repo[opts.get('rev')]
155 ctx = repo[opts.get('rev')]
156 if not ctx:
156 if not ctx:
157 raise util.Abort(_('no working directory: please specify a revision'))
157 raise util.Abort(_('no working directory: please specify a revision'))
158 node = ctx.node()
158 node = ctx.node()
159 dest = cmdutil.make_filename(repo, dest, node)
159 dest = cmdutil.make_filename(repo, dest, node)
160 if os.path.realpath(dest) == repo.root:
160 if os.path.realpath(dest) == repo.root:
161 raise util.Abort(_('repository root cannot be destination'))
161 raise util.Abort(_('repository root cannot be destination'))
162 matchfn = cmdutil.match(repo, [], opts)
162 matchfn = cmdutil.match(repo, [], opts)
163 kind = opts.get('type') or 'files'
163 kind = opts.get('type') or 'files'
164 prefix = opts.get('prefix')
164 prefix = opts.get('prefix')
165 if dest == '-':
165 if dest == '-':
166 if kind == 'files':
166 if kind == 'files':
167 raise util.Abort(_('cannot archive plain files to stdout'))
167 raise util.Abort(_('cannot archive plain files to stdout'))
168 dest = sys.stdout
168 dest = sys.stdout
169 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
169 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
170 prefix = cmdutil.make_filename(repo, prefix, node)
170 prefix = cmdutil.make_filename(repo, prefix, node)
171 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
171 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
172 matchfn, prefix)
172 matchfn, prefix)
173
173
174 def backout(ui, repo, node=None, rev=None, **opts):
174 def backout(ui, repo, node=None, rev=None, **opts):
175 '''reverse effect of earlier changeset
175 '''reverse effect of earlier changeset
176
176
177 Commit the backed out changes as a new changeset. The new
177 Commit the backed out changes as a new changeset. The new
178 changeset is a child of the backed out changeset.
178 changeset is a child of the backed out changeset.
179
179
180 If you back out a changeset other than the tip, a new head is
180 If you back out a changeset other than the tip, a new head is
181 created. This head will be the new tip and you should merge this
181 created. This head will be the new tip and you should merge this
182 backout changeset with another head (current one by default).
182 backout changeset with another head (current one by default).
183
183
184 The --merge option remembers the parent of the working directory
184 The --merge option remembers the parent of the working directory
185 before starting the backout, then merges the new head with that
185 before starting the backout, then merges the new head with that
186 changeset afterwards. This saves you from doing the merge by
186 changeset afterwards. This saves you from doing the merge by
187 hand. The result of this merge is not committed, as for a normal
187 hand. The result of this merge is not committed, as for a normal
188 merge.
188 merge.
189
189
190 See \'hg help dates\' for a list of formats valid for -d/--date.
190 See \'hg help dates\' for a list of formats valid for -d/--date.
191 '''
191 '''
192 if rev and node:
192 if rev and node:
193 raise util.Abort(_("please specify just one revision"))
193 raise util.Abort(_("please specify just one revision"))
194
194
195 if not rev:
195 if not rev:
196 rev = node
196 rev = node
197
197
198 if not rev:
198 if not rev:
199 raise util.Abort(_("please specify a revision to backout"))
199 raise util.Abort(_("please specify a revision to backout"))
200
200
201 date = opts.get('date')
201 date = opts.get('date')
202 if date:
202 if date:
203 opts['date'] = util.parsedate(date)
203 opts['date'] = util.parsedate(date)
204
204
205 cmdutil.bail_if_changed(repo)
205 cmdutil.bail_if_changed(repo)
206 node = repo.lookup(rev)
206 node = repo.lookup(rev)
207
207
208 op1, op2 = repo.dirstate.parents()
208 op1, op2 = repo.dirstate.parents()
209 a = repo.changelog.ancestor(op1, node)
209 a = repo.changelog.ancestor(op1, node)
210 if a != node:
210 if a != node:
211 raise util.Abort(_('cannot back out change on a different branch'))
211 raise util.Abort(_('cannot back out change on a different branch'))
212
212
213 p1, p2 = repo.changelog.parents(node)
213 p1, p2 = repo.changelog.parents(node)
214 if p1 == nullid:
214 if p1 == nullid:
215 raise util.Abort(_('cannot back out a change with no parents'))
215 raise util.Abort(_('cannot back out a change with no parents'))
216 if p2 != nullid:
216 if p2 != nullid:
217 if not opts.get('parent'):
217 if not opts.get('parent'):
218 raise util.Abort(_('cannot back out a merge changeset without '
218 raise util.Abort(_('cannot back out a merge changeset without '
219 '--parent'))
219 '--parent'))
220 p = repo.lookup(opts['parent'])
220 p = repo.lookup(opts['parent'])
221 if p not in (p1, p2):
221 if p not in (p1, p2):
222 raise util.Abort(_('%s is not a parent of %s') %
222 raise util.Abort(_('%s is not a parent of %s') %
223 (short(p), short(node)))
223 (short(p), short(node)))
224 parent = p
224 parent = p
225 else:
225 else:
226 if opts.get('parent'):
226 if opts.get('parent'):
227 raise util.Abort(_('cannot use --parent on non-merge changeset'))
227 raise util.Abort(_('cannot use --parent on non-merge changeset'))
228 parent = p1
228 parent = p1
229
229
230 # the backout should appear on the same branch
230 # the backout should appear on the same branch
231 branch = repo.dirstate.branch()
231 branch = repo.dirstate.branch()
232 hg.clean(repo, node, show_stats=False)
232 hg.clean(repo, node, show_stats=False)
233 repo.dirstate.setbranch(branch)
233 repo.dirstate.setbranch(branch)
234 revert_opts = opts.copy()
234 revert_opts = opts.copy()
235 revert_opts['date'] = None
235 revert_opts['date'] = None
236 revert_opts['all'] = True
236 revert_opts['all'] = True
237 revert_opts['rev'] = hex(parent)
237 revert_opts['rev'] = hex(parent)
238 revert_opts['no_backup'] = None
238 revert_opts['no_backup'] = None
239 revert(ui, repo, **revert_opts)
239 revert(ui, repo, **revert_opts)
240 commit_opts = opts.copy()
240 commit_opts = opts.copy()
241 commit_opts['addremove'] = False
241 commit_opts['addremove'] = False
242 if not commit_opts['message'] and not commit_opts['logfile']:
242 if not commit_opts['message'] and not commit_opts['logfile']:
243 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
243 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
244 commit_opts['force_editor'] = True
244 commit_opts['force_editor'] = True
245 commit(ui, repo, **commit_opts)
245 commit(ui, repo, **commit_opts)
246 def nice(node):
246 def nice(node):
247 return '%d:%s' % (repo.changelog.rev(node), short(node))
247 return '%d:%s' % (repo.changelog.rev(node), short(node))
248 ui.status(_('changeset %s backs out changeset %s\n') %
248 ui.status(_('changeset %s backs out changeset %s\n') %
249 (nice(repo.changelog.tip()), nice(node)))
249 (nice(repo.changelog.tip()), nice(node)))
250 if op1 != node:
250 if op1 != node:
251 hg.clean(repo, op1, show_stats=False)
251 hg.clean(repo, op1, show_stats=False)
252 if opts.get('merge'):
252 if opts.get('merge'):
253 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
253 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
254 hg.merge(repo, hex(repo.changelog.tip()))
254 hg.merge(repo, hex(repo.changelog.tip()))
255 else:
255 else:
256 ui.status(_('the backout changeset is a new head - '
256 ui.status(_('the backout changeset is a new head - '
257 'do not forget to merge\n'))
257 'do not forget to merge\n'))
258 ui.status(_('(use "backout --merge" '
258 ui.status(_('(use "backout --merge" '
259 'if you want to auto-merge)\n'))
259 'if you want to auto-merge)\n'))
260
260
261 def bisect(ui, repo, rev=None, extra=None, command=None,
261 def bisect(ui, repo, rev=None, extra=None, command=None,
262 reset=None, good=None, bad=None, skip=None, noupdate=None):
262 reset=None, good=None, bad=None, skip=None, noupdate=None):
263 """subdivision search of changesets
263 """subdivision search of changesets
264
264
265 This command helps to find changesets which introduce problems.
265 This command helps to find changesets which introduce problems.
266 To use, mark the earliest changeset you know exhibits the problem
266 To use, mark the earliest changeset you know exhibits the problem
267 as bad, then mark the latest changeset which is free from the
267 as bad, then mark the latest changeset which is free from the
268 problem as good. Bisect will update your working directory to a
268 problem as good. Bisect will update your working directory to a
269 revision for testing (unless the --noupdate option is specified).
269 revision for testing (unless the --noupdate option is specified).
270 Once you have performed tests, mark the working directory as bad
270 Once you have performed tests, mark the working directory as bad
271 or good and bisect will either update to another candidate changeset
271 or good and bisect will either update to another candidate changeset
272 or announce that it has found the bad revision.
272 or announce that it has found the bad revision.
273
273
274 As a shortcut, you can also use the revision argument to mark a
274 As a shortcut, you can also use the revision argument to mark a
275 revision as good or bad without checking it out first.
275 revision as good or bad without checking it out first.
276
276
277 If you supply a command it will be used for automatic bisection. Its exit
277 If you supply a command it will be used for automatic bisection. Its exit
278 status will be used as flag to mark revision as bad or good. In case exit
278 status will be used as flag to mark revision as bad or good. In case exit
279 status is 0 the revision is marked as good, 125 - skipped, 127 (command not
279 status is 0 the revision is marked as good, 125 - skipped, 127 (command not
280 found) - bisection will be aborted and any other status bigger than 0 will
280 found) - bisection will be aborted and any other status bigger than 0 will
281 mark revision as bad.
281 mark revision as bad.
282 """
282 """
283 def print_result(nodes, good):
283 def print_result(nodes, good):
284 displayer = cmdutil.show_changeset(ui, repo, {})
284 displayer = cmdutil.show_changeset(ui, repo, {})
285 transition = (good and "good" or "bad")
285 transition = (good and "good" or "bad")
286 if len(nodes) == 1:
286 if len(nodes) == 1:
287 # narrowed it down to a single revision
287 # narrowed it down to a single revision
288 ui.write(_("The first %s revision is:\n") % transition)
288 ui.write(_("The first %s revision is:\n") % transition)
289 displayer.show(repo[nodes[0]])
289 displayer.show(repo[nodes[0]])
290 else:
290 else:
291 # multiple possible revisions
291 # multiple possible revisions
292 ui.write(_("Due to skipped revisions, the first "
292 ui.write(_("Due to skipped revisions, the first "
293 "%s revision could be any of:\n") % transition)
293 "%s revision could be any of:\n") % transition)
294 for n in nodes:
294 for n in nodes:
295 displayer.show(repo[n])
295 displayer.show(repo[n])
296
296
297 def check_state(state, interactive=True):
297 def check_state(state, interactive=True):
298 if not state['good'] or not state['bad']:
298 if not state['good'] or not state['bad']:
299 if (good or bad or skip or reset) and interactive:
299 if (good or bad or skip or reset) and interactive:
300 return
300 return
301 if not state['good']:
301 if not state['good']:
302 raise util.Abort(_('cannot bisect (no known good revisions)'))
302 raise util.Abort(_('cannot bisect (no known good revisions)'))
303 else:
303 else:
304 raise util.Abort(_('cannot bisect (no known bad revisions)'))
304 raise util.Abort(_('cannot bisect (no known bad revisions)'))
305 return True
305 return True
306
306
307 # backward compatibility
307 # backward compatibility
308 if rev in "good bad reset init".split():
308 if rev in "good bad reset init".split():
309 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
309 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
310 cmd, rev, extra = rev, extra, None
310 cmd, rev, extra = rev, extra, None
311 if cmd == "good":
311 if cmd == "good":
312 good = True
312 good = True
313 elif cmd == "bad":
313 elif cmd == "bad":
314 bad = True
314 bad = True
315 else:
315 else:
316 reset = True
316 reset = True
317 elif extra or good + bad + skip + reset + bool(command) > 1:
317 elif extra or good + bad + skip + reset + bool(command) > 1:
318 raise util.Abort(_('incompatible arguments'))
318 raise util.Abort(_('incompatible arguments'))
319
319
320 if reset:
320 if reset:
321 p = repo.join("bisect.state")
321 p = repo.join("bisect.state")
322 if os.path.exists(p):
322 if os.path.exists(p):
323 os.unlink(p)
323 os.unlink(p)
324 return
324 return
325
325
326 state = hbisect.load_state(repo)
326 state = hbisect.load_state(repo)
327
327
328 if command:
328 if command:
329 commandpath = util.find_exe(command)
329 commandpath = util.find_exe(command)
330 changesets = 1
330 changesets = 1
331 try:
331 try:
332 while changesets:
332 while changesets:
333 # update state
333 # update state
334 status = os.spawnl(os.P_WAIT, commandpath)
334 status = os.spawnl(os.P_WAIT, commandpath)
335 if status == 125:
335 if status == 125:
336 transition = "skip"
336 transition = "skip"
337 elif status == 0:
337 elif status == 0:
338 transition = "good"
338 transition = "good"
339 # status < 0 means process was killed
339 # status < 0 means process was killed
340 elif status == 127:
340 elif status == 127:
341 raise util.Abort(_("failed to execute %s") % command)
341 raise util.Abort(_("failed to execute %s") % command)
342 elif status < 0:
342 elif status < 0:
343 raise util.Abort(_("%s killed") % command)
343 raise util.Abort(_("%s killed") % command)
344 else:
344 else:
345 transition = "bad"
345 transition = "bad"
346 node = repo.lookup(rev or '.')
346 node = repo.lookup(rev or '.')
347 state[transition].append(node)
347 state[transition].append(node)
348 ui.note(_('Changeset %s: %s\n') % (short(node), transition))
348 ui.note(_('Changeset %s: %s\n') % (short(node), transition))
349 check_state(state, interactive=False)
349 check_state(state, interactive=False)
350 # bisect
350 # bisect
351 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
351 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
352 # update to next check
352 # update to next check
353 cmdutil.bail_if_changed(repo)
353 cmdutil.bail_if_changed(repo)
354 hg.clean(repo, nodes[0], show_stats=False)
354 hg.clean(repo, nodes[0], show_stats=False)
355 finally:
355 finally:
356 hbisect.save_state(repo, state)
356 hbisect.save_state(repo, state)
357 return print_result(nodes, not status)
357 return print_result(nodes, not status)
358
358
359 # update state
359 # update state
360 node = repo.lookup(rev or '.')
360 node = repo.lookup(rev or '.')
361 if good:
361 if good:
362 state['good'].append(node)
362 state['good'].append(node)
363 elif bad:
363 elif bad:
364 state['bad'].append(node)
364 state['bad'].append(node)
365 elif skip:
365 elif skip:
366 state['skip'].append(node)
366 state['skip'].append(node)
367
367
368 hbisect.save_state(repo, state)
368 hbisect.save_state(repo, state)
369
369
370 if not check_state(state):
370 if not check_state(state):
371 return
371 return
372
372
373 # actually bisect
373 # actually bisect
374 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
374 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
375 if changesets == 0:
375 if changesets == 0:
376 print_result(nodes, good)
376 print_result(nodes, good)
377 else:
377 else:
378 assert len(nodes) == 1 # only a single node can be tested next
378 assert len(nodes) == 1 # only a single node can be tested next
379 node = nodes[0]
379 node = nodes[0]
380 # compute the approximate number of remaining tests
380 # compute the approximate number of remaining tests
381 tests, size = 0, 2
381 tests, size = 0, 2
382 while size <= changesets:
382 while size <= changesets:
383 tests, size = tests + 1, size * 2
383 tests, size = tests + 1, size * 2
384 rev = repo.changelog.rev(node)
384 rev = repo.changelog.rev(node)
385 ui.write(_("Testing changeset %s:%s "
385 ui.write(_("Testing changeset %s:%s "
386 "(%s changesets remaining, ~%s tests)\n")
386 "(%s changesets remaining, ~%s tests)\n")
387 % (rev, short(node), changesets, tests))
387 % (rev, short(node), changesets, tests))
388 if not noupdate:
388 if not noupdate:
389 cmdutil.bail_if_changed(repo)
389 cmdutil.bail_if_changed(repo)
390 return hg.clean(repo, node)
390 return hg.clean(repo, node)
391
391
392 def branch(ui, repo, label=None, **opts):
392 def branch(ui, repo, label=None, **opts):
393 """set or show the current branch name
393 """set or show the current branch name
394
394
395 With no argument, show the current branch name. With one argument,
395 With no argument, show the current branch name. With one argument,
396 set the working directory branch name (the branch does not exist in
396 set the working directory branch name (the branch does not exist in
397 the repository until the next commit).
397 the repository until the next commit).
398
398
399 Unless --force is specified, branch will not let you set a
399 Unless --force is specified, branch will not let you set a
400 branch name that shadows an existing branch.
400 branch name that shadows an existing branch.
401
401
402 Use --clean to reset the working directory branch to that of the
402 Use --clean to reset the working directory branch to that of the
403 parent of the working directory, negating a previous branch change.
403 parent of the working directory, negating a previous branch change.
404
404
405 Use the command 'hg update' to switch to an existing branch.
405 Use the command 'hg update' to switch to an existing branch.
406 """
406 """
407
407
408 if opts.get('clean'):
408 if opts.get('clean'):
409 label = repo[None].parents()[0].branch()
409 label = repo[None].parents()[0].branch()
410 repo.dirstate.setbranch(label)
410 repo.dirstate.setbranch(label)
411 ui.status(_('reset working directory to branch %s\n') % label)
411 ui.status(_('reset working directory to branch %s\n') % label)
412 elif label:
412 elif label:
413 if not opts.get('force') and label in repo.branchtags():
413 if not opts.get('force') and label in repo.branchtags():
414 if label not in [p.branch() for p in repo.parents()]:
414 if label not in [p.branch() for p in repo.parents()]:
415 raise util.Abort(_('a branch of the same name already exists'
415 raise util.Abort(_('a branch of the same name already exists'
416 ' (use --force to override)'))
416 ' (use --force to override)'))
417 repo.dirstate.setbranch(util.fromlocal(label))
417 repo.dirstate.setbranch(util.fromlocal(label))
418 ui.status(_('marked working directory as branch %s\n') % label)
418 ui.status(_('marked working directory as branch %s\n') % label)
419 else:
419 else:
420 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
420 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
421
421
422 def branches(ui, repo, active=False):
422 def branches(ui, repo, active=False):
423 """list repository named branches
423 """list repository named branches
424
424
425 List the repository's named branches, indicating which ones are
425 List the repository's named branches, indicating which ones are
426 inactive. If active is specified, only show active branches.
426 inactive. If active is specified, only show active branches.
427
427
428 A branch is considered active if it contains repository heads.
428 A branch is considered active if it contains repository heads.
429
429
430 Use the command 'hg update' to switch to an existing branch.
430 Use the command 'hg update' to switch to an existing branch.
431 """
431 """
432 hexfunc = ui.debugflag and hex or short
432 hexfunc = ui.debugflag and hex or short
433 activebranches = [util.tolocal(repo[n].branch())
433 activebranches = [util.tolocal(repo[n].branch())
434 for n in repo.heads(closed=False)]
434 for n in repo.heads(closed=False)]
435 branches = util.sort([(tag in activebranches, repo.changelog.rev(node), tag)
435 branches = util.sort([(tag in activebranches, repo.changelog.rev(node), tag)
436 for tag, node in repo.branchtags().items()])
436 for tag, node in repo.branchtags().items()])
437 branches.reverse()
437 branches.reverse()
438
438
439 for isactive, node, tag in branches:
439 for isactive, node, tag in branches:
440 if (not active) or isactive:
440 if (not active) or isactive:
441 if ui.quiet:
441 if ui.quiet:
442 ui.write("%s\n" % tag)
442 ui.write("%s\n" % tag)
443 else:
443 else:
444 hn = repo.lookup(node)
444 hn = repo.lookup(node)
445 if isactive:
445 if isactive:
446 notice = ''
446 notice = ''
447 elif hn not in repo.branchheads(tag, closed=False):
447 elif hn not in repo.branchheads(tag, closed=False):
448 notice = ' (closed)'
448 notice = ' (closed)'
449 else:
449 else:
450 notice = ' (inactive)'
450 notice = ' (inactive)'
451 rev = str(node).rjust(31 - util.colwidth(tag))
451 rev = str(node).rjust(31 - util.colwidth(tag))
452 data = tag, rev, hexfunc(hn), notice
452 data = tag, rev, hexfunc(hn), notice
453 ui.write("%s %s:%s%s\n" % data)
453 ui.write("%s %s:%s%s\n" % data)
454
454
455 def bundle(ui, repo, fname, dest=None, **opts):
455 def bundle(ui, repo, fname, dest=None, **opts):
456 """create a changegroup file
456 """create a changegroup file
457
457
458 Generate a compressed changegroup file collecting changesets not
458 Generate a compressed changegroup file collecting changesets not
459 found in the other repository.
459 found in the other repository.
460
460
461 If no destination repository is specified the destination is
461 If no destination repository is specified the destination is
462 assumed to have all the nodes specified by one or more --base
462 assumed to have all the nodes specified by one or more --base
463 parameters. To create a bundle containing all changesets, use
463 parameters. To create a bundle containing all changesets, use
464 --all (or --base null). To change the compression method applied,
464 --all (or --base null). To change the compression method applied,
465 use the -t option (by default, bundles are compressed using bz2).
465 use the -t option (by default, bundles are compressed using bz2).
466
466
467 The bundle file can then be transferred using conventional means and
467 The bundle file can then be transferred using conventional means and
468 applied to another repository with the unbundle or pull command.
468 applied to another repository with the unbundle or pull command.
469 This is useful when direct push and pull are not available or when
469 This is useful when direct push and pull are not available or when
470 exporting an entire repository is undesirable.
470 exporting an entire repository is undesirable.
471
471
472 Applying bundles preserves all changeset contents including
472 Applying bundles preserves all changeset contents including
473 permissions, copy/rename information, and revision history.
473 permissions, copy/rename information, and revision history.
474 """
474 """
475 revs = opts.get('rev') or None
475 revs = opts.get('rev') or None
476 if revs:
476 if revs:
477 revs = [repo.lookup(rev) for rev in revs]
477 revs = [repo.lookup(rev) for rev in revs]
478 if opts.get('all'):
478 if opts.get('all'):
479 base = ['null']
479 base = ['null']
480 else:
480 else:
481 base = opts.get('base')
481 base = opts.get('base')
482 if base:
482 if base:
483 if dest:
483 if dest:
484 raise util.Abort(_("--base is incompatible with specifiying "
484 raise util.Abort(_("--base is incompatible with specifiying "
485 "a destination"))
485 "a destination"))
486 base = [repo.lookup(rev) for rev in base]
486 base = [repo.lookup(rev) for rev in base]
487 # create the right base
487 # create the right base
488 # XXX: nodesbetween / changegroup* should be "fixed" instead
488 # XXX: nodesbetween / changegroup* should be "fixed" instead
489 o = []
489 o = []
490 has = {nullid: None}
490 has = {nullid: None}
491 for n in base:
491 for n in base:
492 has.update(repo.changelog.reachable(n))
492 has.update(repo.changelog.reachable(n))
493 if revs:
493 if revs:
494 visit = list(revs)
494 visit = list(revs)
495 else:
495 else:
496 visit = repo.changelog.heads()
496 visit = repo.changelog.heads()
497 seen = {}
497 seen = {}
498 while visit:
498 while visit:
499 n = visit.pop(0)
499 n = visit.pop(0)
500 parents = [p for p in repo.changelog.parents(n) if p not in has]
500 parents = [p for p in repo.changelog.parents(n) if p not in has]
501 if len(parents) == 0:
501 if len(parents) == 0:
502 o.insert(0, n)
502 o.insert(0, n)
503 else:
503 else:
504 for p in parents:
504 for p in parents:
505 if p not in seen:
505 if p not in seen:
506 seen[p] = 1
506 seen[p] = 1
507 visit.append(p)
507 visit.append(p)
508 else:
508 else:
509 cmdutil.setremoteconfig(ui, opts)
509 cmdutil.setremoteconfig(ui, opts)
510 dest, revs, checkout = hg.parseurl(
510 dest, revs, checkout = hg.parseurl(
511 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
511 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
512 other = hg.repository(ui, dest)
512 other = hg.repository(ui, dest)
513 o = repo.findoutgoing(other, force=opts.get('force'))
513 o = repo.findoutgoing(other, force=opts.get('force'))
514
514
515 if revs:
515 if revs:
516 cg = repo.changegroupsubset(o, revs, 'bundle')
516 cg = repo.changegroupsubset(o, revs, 'bundle')
517 else:
517 else:
518 cg = repo.changegroup(o, 'bundle')
518 cg = repo.changegroup(o, 'bundle')
519
519
520 bundletype = opts.get('type', 'bzip2').lower()
520 bundletype = opts.get('type', 'bzip2').lower()
521 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
521 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
522 bundletype = btypes.get(bundletype)
522 bundletype = btypes.get(bundletype)
523 if bundletype not in changegroup.bundletypes:
523 if bundletype not in changegroup.bundletypes:
524 raise util.Abort(_('unknown bundle type specified with --type'))
524 raise util.Abort(_('unknown bundle type specified with --type'))
525
525
526 changegroup.writebundle(cg, fname, bundletype)
526 changegroup.writebundle(cg, fname, bundletype)
527
527
528 def cat(ui, repo, file1, *pats, **opts):
528 def cat(ui, repo, file1, *pats, **opts):
529 """output the current or given revision of files
529 """output the current or given revision of files
530
530
531 Print the specified files as they were at the given revision.
531 Print the specified files as they were at the given revision.
532 If no revision is given, the parent of the working directory is used,
532 If no revision is given, the parent of the working directory is used,
533 or tip if no revision is checked out.
533 or tip if no revision is checked out.
534
534
535 Output may be to a file, in which case the name of the file is
535 Output may be to a file, in which case the name of the file is
536 given using a format string. The formatting rules are the same as
536 given using a format string. The formatting rules are the same as
537 for the export command, with the following additions:
537 for the export command, with the following additions:
538
538
539 %s basename of file being printed
539 %s basename of file being printed
540 %d dirname of file being printed, or '.' if in repo root
540 %d dirname of file being printed, or '.' if in repo root
541 %p root-relative path name of file being printed
541 %p root-relative path name of file being printed
542 """
542 """
543 ctx = repo[opts.get('rev')]
543 ctx = repo[opts.get('rev')]
544 err = 1
544 err = 1
545 m = cmdutil.match(repo, (file1,) + pats, opts)
545 m = cmdutil.match(repo, (file1,) + pats, opts)
546 for abs in ctx.walk(m):
546 for abs in ctx.walk(m):
547 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
547 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
548 data = ctx[abs].data()
548 data = ctx[abs].data()
549 if opts.get('decode'):
549 if opts.get('decode'):
550 data = repo.wwritedata(abs, data)
550 data = repo.wwritedata(abs, data)
551 fp.write(data)
551 fp.write(data)
552 err = 0
552 err = 0
553 return err
553 return err
554
554
555 def clone(ui, source, dest=None, **opts):
555 def clone(ui, source, dest=None, **opts):
556 """make a copy of an existing repository
556 """make a copy of an existing repository
557
557
558 Create a copy of an existing repository in a new directory.
558 Create a copy of an existing repository in a new directory.
559
559
560 If no destination directory name is specified, it defaults to the
560 If no destination directory name is specified, it defaults to the
561 basename of the source.
561 basename of the source.
562
562
563 The location of the source is added to the new repository's
563 The location of the source is added to the new repository's
564 .hg/hgrc file, as the default to be used for future pulls.
564 .hg/hgrc file, as the default to be used for future pulls.
565
565
566 For efficiency, hardlinks are used for cloning whenever the source
566 For efficiency, hardlinks are used for cloning whenever the source
567 and destination are on the same filesystem (note this applies only
567 and destination are on the same filesystem (note this applies only
568 to the repository data, not to the checked out files). Some
568 to the repository data, not to the checked out files). Some
569 filesystems, such as AFS, implement hardlinking incorrectly, but
569 filesystems, such as AFS, implement hardlinking incorrectly, but
570 do not report errors. In these cases, use the --pull option to
570 do not report errors. In these cases, use the --pull option to
571 avoid hardlinking.
571 avoid hardlinking.
572
572
573 In some cases, you can clone repositories and checked out files
573 In some cases, you can clone repositories and checked out files
574 using full hardlinks with
574 using full hardlinks with
575
575
576 $ cp -al REPO REPOCLONE
576 $ cp -al REPO REPOCLONE
577
577
578 This is the fastest way to clone, but it is not always safe. The
578 This is the fastest way to clone, but it is not always safe. The
579 operation is not atomic (making sure REPO is not modified during
579 operation is not atomic (making sure REPO is not modified during
580 the operation is up to you) and you have to make sure your editor
580 the operation is up to you) and you have to make sure your editor
581 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
581 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
582 this is not compatible with certain extensions that place their
582 this is not compatible with certain extensions that place their
583 metadata under the .hg directory, such as mq.
583 metadata under the .hg directory, such as mq.
584
584
585 If you use the -r option to clone up to a specific revision, no
585 If you use the -r option to clone up to a specific revision, no
586 subsequent revisions will be present in the cloned repository.
586 subsequent revisions will be present in the cloned repository.
587 This option implies --pull, even on local repositories.
587 This option implies --pull, even on local repositories.
588
588
589 If the -U option is used, the new clone will contain only a repository
589 If the -U option is used, the new clone will contain only a repository
590 (.hg) and no working copy (the working copy parent is the null revision).
590 (.hg) and no working copy (the working copy parent is the null revision).
591
591
592 See 'hg help urls' for valid source format details.
592 See 'hg help urls' for valid source format details.
593
593
594 It is possible to specify an ssh:// URL as the destination, but no
594 It is possible to specify an ssh:// URL as the destination, but no
595 .hg/hgrc and working directory will be created on the remote side.
595 .hg/hgrc and working directory will be created on the remote side.
596 Look at the help text for urls for important details about ssh:// URLs.
596 Look at the help text for urls for important details about ssh:// URLs.
597 """
597 """
598 cmdutil.setremoteconfig(ui, opts)
598 cmdutil.setremoteconfig(ui, opts)
599 hg.clone(ui, source, dest,
599 hg.clone(ui, source, dest,
600 pull=opts.get('pull'),
600 pull=opts.get('pull'),
601 stream=opts.get('uncompressed'),
601 stream=opts.get('uncompressed'),
602 rev=opts.get('rev'),
602 rev=opts.get('rev'),
603 update=not opts.get('noupdate'))
603 update=not opts.get('noupdate'))
604
604
605 def commit(ui, repo, *pats, **opts):
605 def commit(ui, repo, *pats, **opts):
606 """commit the specified files or all outstanding changes
606 """commit the specified files or all outstanding changes
607
607
608 Commit changes to the given files into the repository.
608 Commit changes to the given files into the repository.
609
609
610 If a list of files is omitted, all changes reported by "hg status"
610 If a list of files is omitted, all changes reported by "hg status"
611 will be committed.
611 will be committed.
612
612
613 If you are committing the result of a merge, do not provide any
613 If you are committing the result of a merge, do not provide any
614 file names or -I/-X filters.
614 file names or -I/-X filters.
615
615
616 If no commit message is specified, the configured editor is started to
616 If no commit message is specified, the configured editor is started to
617 enter a message.
617 enter a message.
618
618
619 See 'hg help dates' for a list of formats valid for -d/--date.
619 See 'hg help dates' for a list of formats valid for -d/--date.
620 """
620 """
621 extra = {}
621 extra = {}
622 if opts.get('close_branch'):
622 if opts.get('close_branch'):
623 extra['close'] = 1
623 extra['close'] = 1
624 def commitfunc(ui, repo, message, match, opts):
624 def commitfunc(ui, repo, message, match, opts):
625 return repo.commit(match.files(), message, opts.get('user'),
625 return repo.commit(match.files(), message, opts.get('user'),
626 opts.get('date'), match, force_editor=opts.get('force_editor'),
626 opts.get('date'), match, force_editor=opts.get('force_editor'),
627 extra=extra)
627 extra=extra)
628
628
629 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
629 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
630 if not node:
630 if not node:
631 return
631 return
632 cl = repo.changelog
632 cl = repo.changelog
633 rev = cl.rev(node)
633 rev = cl.rev(node)
634 parents = cl.parentrevs(rev)
634 parents = cl.parentrevs(rev)
635 if rev - 1 in parents:
635 if rev - 1 in parents:
636 # one of the parents was the old tip
636 # one of the parents was the old tip
637 pass
637 pass
638 elif (parents == (nullrev, nullrev) or
638 elif (parents == (nullrev, nullrev) or
639 len(cl.heads(cl.node(parents[0]))) > 1 and
639 len(cl.heads(cl.node(parents[0]))) > 1 and
640 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
640 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
641 ui.status(_('created new head\n'))
641 ui.status(_('created new head\n'))
642
642
643 if ui.debugflag:
643 if ui.debugflag:
644 ui.write(_('committed changeset %d:%s\n') % (rev,hex(node)))
644 ui.write(_('committed changeset %d:%s\n') % (rev,hex(node)))
645 elif ui.verbose:
645 elif ui.verbose:
646 ui.write(_('committed changeset %d:%s\n') % (rev,short(node)))
646 ui.write(_('committed changeset %d:%s\n') % (rev,short(node)))
647
647
648 ms = merge_.mergestate(repo)
648 ms = merge_.mergestate(repo)
649 ms.reset(node)
649 ms.reset(node)
650
650
651 def copy(ui, repo, *pats, **opts):
651 def copy(ui, repo, *pats, **opts):
652 """mark files as copied for the next commit
652 """mark files as copied for the next commit
653
653
654 Mark dest as having copies of source files. If dest is a
654 Mark dest as having copies of source files. If dest is a
655 directory, copies are put in that directory. If dest is a file,
655 directory, copies are put in that directory. If dest is a file,
656 there can only be one source.
656 there can only be one source.
657
657
658 By default, this command copies the contents of files as they
658 By default, this command copies the contents of files as they
659 stand in the working directory. If invoked with --after, the
659 stand in the working directory. If invoked with --after, the
660 operation is recorded, but no copying is performed.
660 operation is recorded, but no copying is performed.
661
661
662 This command takes effect in the next commit. To undo a copy
662 This command takes effect in the next commit. To undo a copy
663 before that, see hg revert.
663 before that, see hg revert.
664 """
664 """
665 wlock = repo.wlock(False)
665 wlock = repo.wlock(False)
666 try:
666 try:
667 return cmdutil.copy(ui, repo, pats, opts)
667 return cmdutil.copy(ui, repo, pats, opts)
668 finally:
668 finally:
669 del wlock
669 del wlock
670
670
671 def debugancestor(ui, repo, *args):
671 def debugancestor(ui, repo, *args):
672 """find the ancestor revision of two revisions in a given index"""
672 """find the ancestor revision of two revisions in a given index"""
673 if len(args) == 3:
673 if len(args) == 3:
674 index, rev1, rev2 = args
674 index, rev1, rev2 = args
675 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
675 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
676 lookup = r.lookup
676 lookup = r.lookup
677 elif len(args) == 2:
677 elif len(args) == 2:
678 if not repo:
678 if not repo:
679 raise util.Abort(_("There is no Mercurial repository here "
679 raise util.Abort(_("There is no Mercurial repository here "
680 "(.hg not found)"))
680 "(.hg not found)"))
681 rev1, rev2 = args
681 rev1, rev2 = args
682 r = repo.changelog
682 r = repo.changelog
683 lookup = repo.lookup
683 lookup = repo.lookup
684 else:
684 else:
685 raise util.Abort(_('either two or three arguments required'))
685 raise util.Abort(_('either two or three arguments required'))
686 a = r.ancestor(lookup(rev1), lookup(rev2))
686 a = r.ancestor(lookup(rev1), lookup(rev2))
687 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
687 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
688
688
689 def debugcomplete(ui, cmd='', **opts):
689 def debugcomplete(ui, cmd='', **opts):
690 """returns the completion list associated with the given command"""
690 """returns the completion list associated with the given command"""
691
691
692 if opts.get('options'):
692 if opts.get('options'):
693 options = []
693 options = []
694 otables = [globalopts]
694 otables = [globalopts]
695 if cmd:
695 if cmd:
696 aliases, entry = cmdutil.findcmd(cmd, table, False)
696 aliases, entry = cmdutil.findcmd(cmd, table, False)
697 otables.append(entry[1])
697 otables.append(entry[1])
698 for t in otables:
698 for t in otables:
699 for o in t:
699 for o in t:
700 if o[0]:
700 if o[0]:
701 options.append('-%s' % o[0])
701 options.append('-%s' % o[0])
702 options.append('--%s' % o[1])
702 options.append('--%s' % o[1])
703 ui.write("%s\n" % "\n".join(options))
703 ui.write("%s\n" % "\n".join(options))
704 return
704 return
705
705
706 cmdlist = cmdutil.findpossible(cmd, table)
706 cmdlist = cmdutil.findpossible(cmd, table)
707 if ui.verbose:
707 if ui.verbose:
708 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
708 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
709 ui.write("%s\n" % "\n".join(util.sort(cmdlist)))
709 ui.write("%s\n" % "\n".join(util.sort(cmdlist)))
710
710
711 def debugfsinfo(ui, path = "."):
711 def debugfsinfo(ui, path = "."):
712 file('.debugfsinfo', 'w').write('')
712 file('.debugfsinfo', 'w').write('')
713 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
713 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
714 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
714 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
715 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
715 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
716 and 'yes' or 'no'))
716 and 'yes' or 'no'))
717 os.unlink('.debugfsinfo')
717 os.unlink('.debugfsinfo')
718
718
719 def debugrebuildstate(ui, repo, rev="tip"):
719 def debugrebuildstate(ui, repo, rev="tip"):
720 """rebuild the dirstate as it would look like for the given revision"""
720 """rebuild the dirstate as it would look like for the given revision"""
721 ctx = repo[rev]
721 ctx = repo[rev]
722 wlock = repo.wlock()
722 wlock = repo.wlock()
723 try:
723 try:
724 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
724 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
725 finally:
725 finally:
726 del wlock
726 del wlock
727
727
728 def debugcheckstate(ui, repo):
728 def debugcheckstate(ui, repo):
729 """validate the correctness of the current dirstate"""
729 """validate the correctness of the current dirstate"""
730 parent1, parent2 = repo.dirstate.parents()
730 parent1, parent2 = repo.dirstate.parents()
731 m1 = repo[parent1].manifest()
731 m1 = repo[parent1].manifest()
732 m2 = repo[parent2].manifest()
732 m2 = repo[parent2].manifest()
733 errors = 0
733 errors = 0
734 for f in repo.dirstate:
734 for f in repo.dirstate:
735 state = repo.dirstate[f]
735 state = repo.dirstate[f]
736 if state in "nr" and f not in m1:
736 if state in "nr" and f not in m1:
737 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
737 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
738 errors += 1
738 errors += 1
739 if state in "a" and f in m1:
739 if state in "a" and f in m1:
740 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
740 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
741 errors += 1
741 errors += 1
742 if state in "m" and f not in m1 and f not in m2:
742 if state in "m" and f not in m1 and f not in m2:
743 ui.warn(_("%s in state %s, but not in either manifest\n") %
743 ui.warn(_("%s in state %s, but not in either manifest\n") %
744 (f, state))
744 (f, state))
745 errors += 1
745 errors += 1
746 for f in m1:
746 for f in m1:
747 state = repo.dirstate[f]
747 state = repo.dirstate[f]
748 if state not in "nrm":
748 if state not in "nrm":
749 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
749 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
750 errors += 1
750 errors += 1
751 if errors:
751 if errors:
752 error = _(".hg/dirstate inconsistent with current parent's manifest")
752 error = _(".hg/dirstate inconsistent with current parent's manifest")
753 raise util.Abort(error)
753 raise util.Abort(error)
754
754
755 def showconfig(ui, repo, *values, **opts):
755 def showconfig(ui, repo, *values, **opts):
756 """show combined config settings from all hgrc files
756 """show combined config settings from all hgrc files
757
757
758 With no args, print names and values of all config items.
758 With no args, print names and values of all config items.
759
759
760 With one arg of the form section.name, print just the value of
760 With one arg of the form section.name, print just the value of
761 that config item.
761 that config item.
762
762
763 With multiple args, print names and values of all config items
763 With multiple args, print names and values of all config items
764 with matching section names."""
764 with matching section names."""
765
765
766 untrusted = bool(opts.get('untrusted'))
766 untrusted = bool(opts.get('untrusted'))
767 if values:
767 if values:
768 if len([v for v in values if '.' in v]) > 1:
768 if len([v for v in values if '.' in v]) > 1:
769 raise util.Abort(_('only one config item permitted'))
769 raise util.Abort(_('only one config item permitted'))
770 for section, name, value in ui.walkconfig(untrusted=untrusted):
770 for section, name, value in ui.walkconfig(untrusted=untrusted):
771 sectname = section + '.' + name
771 sectname = section + '.' + name
772 if values:
772 if values:
773 for v in values:
773 for v in values:
774 if v == section:
774 if v == section:
775 ui.write('%s=%s\n' % (sectname, value))
775 ui.write('%s=%s\n' % (sectname, value))
776 elif v == sectname:
776 elif v == sectname:
777 ui.write(value, '\n')
777 ui.write(value, '\n')
778 else:
778 else:
779 ui.write('%s=%s\n' % (sectname, value))
779 ui.write('%s=%s\n' % (sectname, value))
780
780
781 def debugsetparents(ui, repo, rev1, rev2=None):
781 def debugsetparents(ui, repo, rev1, rev2=None):
782 """manually set the parents of the current working directory
782 """manually set the parents of the current working directory
783
783
784 This is useful for writing repository conversion tools, but should
784 This is useful for writing repository conversion tools, but should
785 be used with care.
785 be used with care.
786 """
786 """
787
787
788 if not rev2:
788 if not rev2:
789 rev2 = hex(nullid)
789 rev2 = hex(nullid)
790
790
791 wlock = repo.wlock()
791 wlock = repo.wlock()
792 try:
792 try:
793 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
793 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
794 finally:
794 finally:
795 del wlock
795 del wlock
796
796
797 def debugstate(ui, repo, nodates=None):
797 def debugstate(ui, repo, nodates=None):
798 """show the contents of the current dirstate"""
798 """show the contents of the current dirstate"""
799 timestr = ""
799 timestr = ""
800 showdate = not nodates
800 showdate = not nodates
801 for file_, ent in util.sort(repo.dirstate._map.iteritems()):
801 for file_, ent in util.sort(repo.dirstate._map.iteritems()):
802 if showdate:
802 if showdate:
803 if ent[3] == -1:
803 if ent[3] == -1:
804 # Pad or slice to locale representation
804 # Pad or slice to locale representation
805 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
805 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
806 timestr = 'unset'
806 timestr = 'unset'
807 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
807 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
808 else:
808 else:
809 timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
809 timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
810 if ent[1] & 020000:
810 if ent[1] & 020000:
811 mode = 'lnk'
811 mode = 'lnk'
812 else:
812 else:
813 mode = '%3o' % (ent[1] & 0777)
813 mode = '%3o' % (ent[1] & 0777)
814 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
814 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
815 for f in repo.dirstate.copies():
815 for f in repo.dirstate.copies():
816 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
816 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
817
817
818 def debugdata(ui, file_, rev):
818 def debugdata(ui, file_, rev):
819 """dump the contents of a data file revision"""
819 """dump the contents of a data file revision"""
820 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
820 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
821 try:
821 try:
822 ui.write(r.revision(r.lookup(rev)))
822 ui.write(r.revision(r.lookup(rev)))
823 except KeyError:
823 except KeyError:
824 raise util.Abort(_('invalid revision identifier %s') % rev)
824 raise util.Abort(_('invalid revision identifier %s') % rev)
825
825
826 def debugdate(ui, date, range=None, **opts):
826 def debugdate(ui, date, range=None, **opts):
827 """parse and display a date"""
827 """parse and display a date"""
828 if opts["extended"]:
828 if opts["extended"]:
829 d = util.parsedate(date, util.extendeddateformats)
829 d = util.parsedate(date, util.extendeddateformats)
830 else:
830 else:
831 d = util.parsedate(date)
831 d = util.parsedate(date)
832 ui.write("internal: %s %s\n" % d)
832 ui.write("internal: %s %s\n" % d)
833 ui.write("standard: %s\n" % util.datestr(d))
833 ui.write("standard: %s\n" % util.datestr(d))
834 if range:
834 if range:
835 m = util.matchdate(range)
835 m = util.matchdate(range)
836 ui.write("match: %s\n" % m(d[0]))
836 ui.write("match: %s\n" % m(d[0]))
837
837
838 def debugindex(ui, file_):
838 def debugindex(ui, file_):
839 """dump the contents of an index file"""
839 """dump the contents of an index file"""
840 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
840 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
841 ui.write(" rev offset length base linkrev"
841 ui.write(" rev offset length base linkrev"
842 " nodeid p1 p2\n")
842 " nodeid p1 p2\n")
843 for i in r:
843 for i in r:
844 node = r.node(i)
844 node = r.node(i)
845 try:
845 try:
846 pp = r.parents(node)
846 pp = r.parents(node)
847 except:
847 except:
848 pp = [nullid, nullid]
848 pp = [nullid, nullid]
849 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
849 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
850 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
850 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
851 short(node), short(pp[0]), short(pp[1])))
851 short(node), short(pp[0]), short(pp[1])))
852
852
853 def debugindexdot(ui, file_):
853 def debugindexdot(ui, file_):
854 """dump an index DAG as a .dot file"""
854 """dump an index DAG as a .dot file"""
855 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
855 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
856 ui.write("digraph G {\n")
856 ui.write("digraph G {\n")
857 for i in r:
857 for i in r:
858 node = r.node(i)
858 node = r.node(i)
859 pp = r.parents(node)
859 pp = r.parents(node)
860 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
860 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
861 if pp[1] != nullid:
861 if pp[1] != nullid:
862 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
862 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
863 ui.write("}\n")
863 ui.write("}\n")
864
864
865 def debuginstall(ui):
865 def debuginstall(ui):
866 '''test Mercurial installation'''
866 '''test Mercurial installation'''
867
867
868 def writetemp(contents):
868 def writetemp(contents):
869 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
869 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
870 f = os.fdopen(fd, "wb")
870 f = os.fdopen(fd, "wb")
871 f.write(contents)
871 f.write(contents)
872 f.close()
872 f.close()
873 return name
873 return name
874
874
875 problems = 0
875 problems = 0
876
876
877 # encoding
877 # encoding
878 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
878 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
879 try:
879 try:
880 util.fromlocal("test")
880 util.fromlocal("test")
881 except util.Abort, inst:
881 except util.Abort, inst:
882 ui.write(" %s\n" % inst)
882 ui.write(" %s\n" % inst)
883 ui.write(_(" (check that your locale is properly set)\n"))
883 ui.write(_(" (check that your locale is properly set)\n"))
884 problems += 1
884 problems += 1
885
885
886 # compiled modules
886 # compiled modules
887 ui.status(_("Checking extensions...\n"))
887 ui.status(_("Checking extensions...\n"))
888 try:
888 try:
889 import bdiff, mpatch, base85
889 import bdiff, mpatch, base85
890 except Exception, inst:
890 except Exception, inst:
891 ui.write(" %s\n" % inst)
891 ui.write(" %s\n" % inst)
892 ui.write(_(" One or more extensions could not be found"))
892 ui.write(_(" One or more extensions could not be found"))
893 ui.write(_(" (check that you compiled the extensions)\n"))
893 ui.write(_(" (check that you compiled the extensions)\n"))
894 problems += 1
894 problems += 1
895
895
896 # templates
896 # templates
897 ui.status(_("Checking templates...\n"))
897 ui.status(_("Checking templates...\n"))
898 try:
898 try:
899 import templater
899 import templater
900 t = templater.templater(templater.templatepath("map-cmdline.default"))
900 t = templater.templater(templater.templatepath("map-cmdline.default"))
901 except Exception, inst:
901 except Exception, inst:
902 ui.write(" %s\n" % inst)
902 ui.write(" %s\n" % inst)
903 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
903 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
904 problems += 1
904 problems += 1
905
905
906 # patch
906 # patch
907 ui.status(_("Checking patch...\n"))
907 ui.status(_("Checking patch...\n"))
908 patchproblems = 0
908 patchproblems = 0
909 a = "1\n2\n3\n4\n"
909 a = "1\n2\n3\n4\n"
910 b = "1\n2\n3\ninsert\n4\n"
910 b = "1\n2\n3\ninsert\n4\n"
911 fa = writetemp(a)
911 fa = writetemp(a)
912 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
912 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
913 os.path.basename(fa))
913 os.path.basename(fa))
914 fd = writetemp(d)
914 fd = writetemp(d)
915
915
916 files = {}
916 files = {}
917 try:
917 try:
918 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
918 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
919 except util.Abort, e:
919 except util.Abort, e:
920 ui.write(_(" patch call failed:\n"))
920 ui.write(_(" patch call failed:\n"))
921 ui.write(" " + str(e) + "\n")
921 ui.write(" " + str(e) + "\n")
922 patchproblems += 1
922 patchproblems += 1
923 else:
923 else:
924 if list(files) != [os.path.basename(fa)]:
924 if list(files) != [os.path.basename(fa)]:
925 ui.write(_(" unexpected patch output!\n"))
925 ui.write(_(" unexpected patch output!\n"))
926 patchproblems += 1
926 patchproblems += 1
927 a = file(fa).read()
927 a = file(fa).read()
928 if a != b:
928 if a != b:
929 ui.write(_(" patch test failed!\n"))
929 ui.write(_(" patch test failed!\n"))
930 patchproblems += 1
930 patchproblems += 1
931
931
932 if patchproblems:
932 if patchproblems:
933 if ui.config('ui', 'patch'):
933 if ui.config('ui', 'patch'):
934 ui.write(_(" (Current patch tool may be incompatible with patch,"
934 ui.write(_(" (Current patch tool may be incompatible with patch,"
935 " or misconfigured. Please check your .hgrc file)\n"))
935 " or misconfigured. Please check your .hgrc file)\n"))
936 else:
936 else:
937 ui.write(_(" Internal patcher failure, please report this error"
937 ui.write(_(" Internal patcher failure, please report this error"
938 " to http://www.selenic.com/mercurial/bts\n"))
938 " to http://www.selenic.com/mercurial/bts\n"))
939 problems += patchproblems
939 problems += patchproblems
940
940
941 os.unlink(fa)
941 os.unlink(fa)
942 os.unlink(fd)
942 os.unlink(fd)
943
943
944 # editor
944 # editor
945 ui.status(_("Checking commit editor...\n"))
945 ui.status(_("Checking commit editor...\n"))
946 editor = ui.geteditor()
946 editor = ui.geteditor()
947 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
947 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
948 if not cmdpath:
948 if not cmdpath:
949 if editor == 'vi':
949 if editor == 'vi':
950 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
950 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
951 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
951 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
952 else:
952 else:
953 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
953 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
954 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
954 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
955 problems += 1
955 problems += 1
956
956
957 # check username
957 # check username
958 ui.status(_("Checking username...\n"))
958 ui.status(_("Checking username...\n"))
959 user = os.environ.get("HGUSER")
959 user = os.environ.get("HGUSER")
960 if user is None:
960 if user is None:
961 user = ui.config("ui", "username")
961 user = ui.config("ui", "username")
962 if user is None:
962 if user is None:
963 user = os.environ.get("EMAIL")
963 user = os.environ.get("EMAIL")
964 if not user:
964 if not user:
965 ui.warn(" ")
965 ui.warn(" ")
966 ui.username()
966 ui.username()
967 ui.write(_(" (specify a username in your .hgrc file)\n"))
967 ui.write(_(" (specify a username in your .hgrc file)\n"))
968
968
969 if not problems:
969 if not problems:
970 ui.status(_("No problems detected\n"))
970 ui.status(_("No problems detected\n"))
971 else:
971 else:
972 ui.write(_("%s problems detected,"
972 ui.write(_("%s problems detected,"
973 " please check your install!\n") % problems)
973 " please check your install!\n") % problems)
974
974
975 return problems
975 return problems
976
976
977 def debugrename(ui, repo, file1, *pats, **opts):
977 def debugrename(ui, repo, file1, *pats, **opts):
978 """dump rename information"""
978 """dump rename information"""
979
979
980 ctx = repo[opts.get('rev')]
980 ctx = repo[opts.get('rev')]
981 m = cmdutil.match(repo, (file1,) + pats, opts)
981 m = cmdutil.match(repo, (file1,) + pats, opts)
982 for abs in ctx.walk(m):
982 for abs in ctx.walk(m):
983 fctx = ctx[abs]
983 fctx = ctx[abs]
984 o = fctx.filelog().renamed(fctx.filenode())
984 o = fctx.filelog().renamed(fctx.filenode())
985 rel = m.rel(abs)
985 rel = m.rel(abs)
986 if o:
986 if o:
987 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
987 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
988 else:
988 else:
989 ui.write(_("%s not renamed\n") % rel)
989 ui.write(_("%s not renamed\n") % rel)
990
990
991 def debugwalk(ui, repo, *pats, **opts):
991 def debugwalk(ui, repo, *pats, **opts):
992 """show how files match on given patterns"""
992 """show how files match on given patterns"""
993 m = cmdutil.match(repo, pats, opts)
993 m = cmdutil.match(repo, pats, opts)
994 items = list(repo.walk(m))
994 items = list(repo.walk(m))
995 if not items:
995 if not items:
996 return
996 return
997 fmt = 'f %%-%ds %%-%ds %%s' % (
997 fmt = 'f %%-%ds %%-%ds %%s' % (
998 max([len(abs) for abs in items]),
998 max([len(abs) for abs in items]),
999 max([len(m.rel(abs)) for abs in items]))
999 max([len(m.rel(abs)) for abs in items]))
1000 for abs in items:
1000 for abs in items:
1001 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1001 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1002 ui.write("%s\n" % line.rstrip())
1002 ui.write("%s\n" % line.rstrip())
1003
1003
1004 def diff(ui, repo, *pats, **opts):
1004 def diff(ui, repo, *pats, **opts):
1005 """diff repository (or selected files)
1005 """diff repository (or selected files)
1006
1006
1007 Show differences between revisions for the specified files.
1007 Show differences between revisions for the specified files.
1008
1008
1009 Differences between files are shown using the unified diff format.
1009 Differences between files are shown using the unified diff format.
1010
1010
1011 NOTE: diff may generate unexpected results for merges, as it will
1011 NOTE: diff may generate unexpected results for merges, as it will
1012 default to comparing against the working directory's first parent
1012 default to comparing against the working directory's first parent
1013 changeset if no revisions are specified.
1013 changeset if no revisions are specified.
1014
1014
1015 When two revision arguments are given, then changes are shown
1015 When two revision arguments are given, then changes are shown
1016 between those revisions. If only one revision is specified then
1016 between those revisions. If only one revision is specified then
1017 that revision is compared to the working directory, and, when no
1017 that revision is compared to the working directory, and, when no
1018 revisions are specified, the working directory files are compared
1018 revisions are specified, the working directory files are compared
1019 to its parent.
1019 to its parent.
1020
1020
1021 Without the -a option, diff will avoid generating diffs of files
1021 Without the -a option, diff will avoid generating diffs of files
1022 it detects as binary. With -a, diff will generate a diff anyway,
1022 it detects as binary. With -a, diff will generate a diff anyway,
1023 probably with undesirable results.
1023 probably with undesirable results.
1024
1024
1025 Use the --git option to generate diffs in the git extended diff
1025 Use the --git option to generate diffs in the git extended diff
1026 format. Read the diffs help topic for more information.
1026 format. Read the diffs help topic for more information.
1027 """
1027 """
1028
1028
1029 revs = opts.get('rev')
1029 revs = opts.get('rev')
1030 change = opts.get('change')
1030 change = opts.get('change')
1031
1031
1032 if revs and change:
1032 if revs and change:
1033 msg = _('cannot specify --rev and --change at the same time')
1033 msg = _('cannot specify --rev and --change at the same time')
1034 raise util.Abort(msg)
1034 raise util.Abort(msg)
1035 elif change:
1035 elif change:
1036 node2 = repo.lookup(change)
1036 node2 = repo.lookup(change)
1037 node1 = repo[node2].parents()[0].node()
1037 node1 = repo[node2].parents()[0].node()
1038 else:
1038 else:
1039 node1, node2 = cmdutil.revpair(repo, revs)
1039 node1, node2 = cmdutil.revpair(repo, revs)
1040
1040
1041 m = cmdutil.match(repo, pats, opts)
1041 m = cmdutil.match(repo, pats, opts)
1042 it = patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts))
1042 it = patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts))
1043 for chunk in it:
1043 for chunk in it:
1044 repo.ui.write(chunk)
1044 repo.ui.write(chunk)
1045
1045
1046 def export(ui, repo, *changesets, **opts):
1046 def export(ui, repo, *changesets, **opts):
1047 """dump the header and diffs for one or more changesets
1047 """dump the header and diffs for one or more changesets
1048
1048
1049 Print the changeset header and diffs for one or more revisions.
1049 Print the changeset header and diffs for one or more revisions.
1050
1050
1051 The information shown in the changeset header is: author,
1051 The information shown in the changeset header is: author,
1052 changeset hash, parent(s) and commit comment.
1052 changeset hash, parent(s) and commit comment.
1053
1053
1054 NOTE: export may generate unexpected diff output for merge changesets,
1054 NOTE: export may generate unexpected diff output for merge changesets,
1055 as it will compare the merge changeset against its first parent only.
1055 as it will compare the merge changeset against its first parent only.
1056
1056
1057 Output may be to a file, in which case the name of the file is
1057 Output may be to a file, in which case the name of the file is
1058 given using a format string. The formatting rules are as follows:
1058 given using a format string. The formatting rules are as follows:
1059
1059
1060 %% literal "%" character
1060 %% literal "%" character
1061 %H changeset hash (40 bytes of hexadecimal)
1061 %H changeset hash (40 bytes of hexadecimal)
1062 %N number of patches being generated
1062 %N number of patches being generated
1063 %R changeset revision number
1063 %R changeset revision number
1064 %b basename of the exporting repository
1064 %b basename of the exporting repository
1065 %h short-form changeset hash (12 bytes of hexadecimal)
1065 %h short-form changeset hash (12 bytes of hexadecimal)
1066 %n zero-padded sequence number, starting at 1
1066 %n zero-padded sequence number, starting at 1
1067 %r zero-padded changeset revision number
1067 %r zero-padded changeset revision number
1068
1068
1069 Without the -a option, export will avoid generating diffs of files
1069 Without the -a option, export will avoid generating diffs of files
1070 it detects as binary. With -a, export will generate a diff anyway,
1070 it detects as binary. With -a, export will generate a diff anyway,
1071 probably with undesirable results.
1071 probably with undesirable results.
1072
1072
1073 Use the --git option to generate diffs in the git extended diff
1073 Use the --git option to generate diffs in the git extended diff
1074 format. Read the diffs help topic for more information.
1074 format. Read the diffs help topic for more information.
1075
1075
1076 With the --switch-parent option, the diff will be against the second
1076 With the --switch-parent option, the diff will be against the second
1077 parent. It can be useful to review a merge.
1077 parent. It can be useful to review a merge.
1078 """
1078 """
1079 if not changesets:
1079 if not changesets:
1080 raise util.Abort(_("export requires at least one changeset"))
1080 raise util.Abort(_("export requires at least one changeset"))
1081 revs = cmdutil.revrange(repo, changesets)
1081 revs = cmdutil.revrange(repo, changesets)
1082 if len(revs) > 1:
1082 if len(revs) > 1:
1083 ui.note(_('exporting patches:\n'))
1083 ui.note(_('exporting patches:\n'))
1084 else:
1084 else:
1085 ui.note(_('exporting patch:\n'))
1085 ui.note(_('exporting patch:\n'))
1086 patch.export(repo, revs, template=opts.get('output'),
1086 patch.export(repo, revs, template=opts.get('output'),
1087 switch_parent=opts.get('switch_parent'),
1087 switch_parent=opts.get('switch_parent'),
1088 opts=patch.diffopts(ui, opts))
1088 opts=patch.diffopts(ui, opts))
1089
1089
1090 def grep(ui, repo, pattern, *pats, **opts):
1090 def grep(ui, repo, pattern, *pats, **opts):
1091 """search for a pattern in specified files and revisions
1091 """search for a pattern in specified files and revisions
1092
1092
1093 Search revisions of files for a regular expression.
1093 Search revisions of files for a regular expression.
1094
1094
1095 This command behaves differently than Unix grep. It only accepts
1095 This command behaves differently than Unix grep. It only accepts
1096 Python/Perl regexps. It searches repository history, not the
1096 Python/Perl regexps. It searches repository history, not the
1097 working directory. It always prints the revision number in which
1097 working directory. It always prints the revision number in which
1098 a match appears.
1098 a match appears.
1099
1099
1100 By default, grep only prints output for the first revision of a
1100 By default, grep only prints output for the first revision of a
1101 file in which it finds a match. To get it to print every revision
1101 file in which it finds a match. To get it to print every revision
1102 that contains a change in match status ("-" for a match that
1102 that contains a change in match status ("-" for a match that
1103 becomes a non-match, or "+" for a non-match that becomes a match),
1103 becomes a non-match, or "+" for a non-match that becomes a match),
1104 use the --all flag.
1104 use the --all flag.
1105 """
1105 """
1106 reflags = 0
1106 reflags = 0
1107 if opts.get('ignore_case'):
1107 if opts.get('ignore_case'):
1108 reflags |= re.I
1108 reflags |= re.I
1109 try:
1109 try:
1110 regexp = re.compile(pattern, reflags)
1110 regexp = re.compile(pattern, reflags)
1111 except Exception, inst:
1111 except Exception, inst:
1112 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1112 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1113 return None
1113 return None
1114 sep, eol = ':', '\n'
1114 sep, eol = ':', '\n'
1115 if opts.get('print0'):
1115 if opts.get('print0'):
1116 sep = eol = '\0'
1116 sep = eol = '\0'
1117
1117
1118 fcache = {}
1118 fcache = {}
1119 def getfile(fn):
1119 def getfile(fn):
1120 if fn not in fcache:
1120 if fn not in fcache:
1121 fcache[fn] = repo.file(fn)
1121 fcache[fn] = repo.file(fn)
1122 return fcache[fn]
1122 return fcache[fn]
1123
1123
1124 def matchlines(body):
1124 def matchlines(body):
1125 begin = 0
1125 begin = 0
1126 linenum = 0
1126 linenum = 0
1127 while True:
1127 while True:
1128 match = regexp.search(body, begin)
1128 match = regexp.search(body, begin)
1129 if not match:
1129 if not match:
1130 break
1130 break
1131 mstart, mend = match.span()
1131 mstart, mend = match.span()
1132 linenum += body.count('\n', begin, mstart) + 1
1132 linenum += body.count('\n', begin, mstart) + 1
1133 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1133 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1134 begin = body.find('\n', mend) + 1 or len(body)
1134 begin = body.find('\n', mend) + 1 or len(body)
1135 lend = begin - 1
1135 lend = begin - 1
1136 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1136 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1137
1137
1138 class linestate(object):
1138 class linestate(object):
1139 def __init__(self, line, linenum, colstart, colend):
1139 def __init__(self, line, linenum, colstart, colend):
1140 self.line = line
1140 self.line = line
1141 self.linenum = linenum
1141 self.linenum = linenum
1142 self.colstart = colstart
1142 self.colstart = colstart
1143 self.colend = colend
1143 self.colend = colend
1144
1144
1145 def __hash__(self):
1145 def __hash__(self):
1146 return hash((self.linenum, self.line))
1146 return hash((self.linenum, self.line))
1147
1147
1148 def __eq__(self, other):
1148 def __eq__(self, other):
1149 return self.line == other.line
1149 return self.line == other.line
1150
1150
1151 matches = {}
1151 matches = {}
1152 copies = {}
1152 copies = {}
1153 def grepbody(fn, rev, body):
1153 def grepbody(fn, rev, body):
1154 matches[rev].setdefault(fn, [])
1154 matches[rev].setdefault(fn, [])
1155 m = matches[rev][fn]
1155 m = matches[rev][fn]
1156 for lnum, cstart, cend, line in matchlines(body):
1156 for lnum, cstart, cend, line in matchlines(body):
1157 s = linestate(line, lnum, cstart, cend)
1157 s = linestate(line, lnum, cstart, cend)
1158 m.append(s)
1158 m.append(s)
1159
1159
1160 def difflinestates(a, b):
1160 def difflinestates(a, b):
1161 sm = difflib.SequenceMatcher(None, a, b)
1161 sm = difflib.SequenceMatcher(None, a, b)
1162 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1162 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1163 if tag == 'insert':
1163 if tag == 'insert':
1164 for i in xrange(blo, bhi):
1164 for i in xrange(blo, bhi):
1165 yield ('+', b[i])
1165 yield ('+', b[i])
1166 elif tag == 'delete':
1166 elif tag == 'delete':
1167 for i in xrange(alo, ahi):
1167 for i in xrange(alo, ahi):
1168 yield ('-', a[i])
1168 yield ('-', a[i])
1169 elif tag == 'replace':
1169 elif tag == 'replace':
1170 for i in xrange(alo, ahi):
1170 for i in xrange(alo, ahi):
1171 yield ('-', a[i])
1171 yield ('-', a[i])
1172 for i in xrange(blo, bhi):
1172 for i in xrange(blo, bhi):
1173 yield ('+', b[i])
1173 yield ('+', b[i])
1174
1174
1175 prev = {}
1175 prev = {}
1176 def display(fn, rev, states, prevstates):
1176 def display(fn, rev, states, prevstates):
1177 datefunc = ui.quiet and util.shortdate or util.datestr
1177 datefunc = ui.quiet and util.shortdate or util.datestr
1178 found = False
1178 found = False
1179 filerevmatches = {}
1179 filerevmatches = {}
1180 r = prev.get(fn, -1)
1180 r = prev.get(fn, -1)
1181 if opts.get('all'):
1181 if opts.get('all'):
1182 iter = difflinestates(states, prevstates)
1182 iter = difflinestates(states, prevstates)
1183 else:
1183 else:
1184 iter = [('', l) for l in prevstates]
1184 iter = [('', l) for l in prevstates]
1185 for change, l in iter:
1185 for change, l in iter:
1186 cols = [fn, str(r)]
1186 cols = [fn, str(r)]
1187 if opts.get('line_number'):
1187 if opts.get('line_number'):
1188 cols.append(str(l.linenum))
1188 cols.append(str(l.linenum))
1189 if opts.get('all'):
1189 if opts.get('all'):
1190 cols.append(change)
1190 cols.append(change)
1191 if opts.get('user'):
1191 if opts.get('user'):
1192 cols.append(ui.shortuser(get(r)[1]))
1192 cols.append(ui.shortuser(get(r)[1]))
1193 if opts.get('date'):
1193 if opts.get('date'):
1194 cols.append(datefunc(get(r)[2]))
1194 cols.append(datefunc(get(r)[2]))
1195 if opts.get('files_with_matches'):
1195 if opts.get('files_with_matches'):
1196 c = (fn, r)
1196 c = (fn, r)
1197 if c in filerevmatches:
1197 if c in filerevmatches:
1198 continue
1198 continue
1199 filerevmatches[c] = 1
1199 filerevmatches[c] = 1
1200 else:
1200 else:
1201 cols.append(l.line)
1201 cols.append(l.line)
1202 ui.write(sep.join(cols), eol)
1202 ui.write(sep.join(cols), eol)
1203 found = True
1203 found = True
1204 return found
1204 return found
1205
1205
1206 fstate = {}
1206 fstate = {}
1207 skip = {}
1207 skip = {}
1208 get = util.cachefunc(lambda r: repo[r].changeset())
1208 get = util.cachefunc(lambda r: repo[r].changeset())
1209 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1209 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1210 found = False
1210 found = False
1211 follow = opts.get('follow')
1211 follow = opts.get('follow')
1212 for st, rev, fns in changeiter:
1212 for st, rev, fns in changeiter:
1213 if st == 'window':
1213 if st == 'window':
1214 matches.clear()
1214 matches.clear()
1215 elif st == 'add':
1215 elif st == 'add':
1216 ctx = repo[rev]
1216 ctx = repo[rev]
1217 matches[rev] = {}
1217 matches[rev] = {}
1218 for fn in fns:
1218 for fn in fns:
1219 if fn in skip:
1219 if fn in skip:
1220 continue
1220 continue
1221 try:
1221 try:
1222 grepbody(fn, rev, getfile(fn).read(ctx.filenode(fn)))
1222 grepbody(fn, rev, getfile(fn).read(ctx.filenode(fn)))
1223 fstate.setdefault(fn, [])
1223 fstate.setdefault(fn, [])
1224 if follow:
1224 if follow:
1225 copied = getfile(fn).renamed(ctx.filenode(fn))
1225 copied = getfile(fn).renamed(ctx.filenode(fn))
1226 if copied:
1226 if copied:
1227 copies.setdefault(rev, {})[fn] = copied[0]
1227 copies.setdefault(rev, {})[fn] = copied[0]
1228 except error.LookupError:
1228 except error.LookupError:
1229 pass
1229 pass
1230 elif st == 'iter':
1230 elif st == 'iter':
1231 for fn, m in util.sort(matches[rev].items()):
1231 for fn, m in util.sort(matches[rev].items()):
1232 copy = copies.get(rev, {}).get(fn)
1232 copy = copies.get(rev, {}).get(fn)
1233 if fn in skip:
1233 if fn in skip:
1234 if copy:
1234 if copy:
1235 skip[copy] = True
1235 skip[copy] = True
1236 continue
1236 continue
1237 if fn in prev or fstate[fn]:
1237 if fn in prev or fstate[fn]:
1238 r = display(fn, rev, m, fstate[fn])
1238 r = display(fn, rev, m, fstate[fn])
1239 found = found or r
1239 found = found or r
1240 if r and not opts.get('all'):
1240 if r and not opts.get('all'):
1241 skip[fn] = True
1241 skip[fn] = True
1242 if copy:
1242 if copy:
1243 skip[copy] = True
1243 skip[copy] = True
1244 fstate[fn] = m
1244 fstate[fn] = m
1245 if copy:
1245 if copy:
1246 fstate[copy] = m
1246 fstate[copy] = m
1247 prev[fn] = rev
1247 prev[fn] = rev
1248
1248
1249 for fn, state in util.sort(fstate.items()):
1249 for fn, state in util.sort(fstate.items()):
1250 if fn in skip:
1250 if fn in skip:
1251 continue
1251 continue
1252 if fn not in copies.get(prev[fn], {}):
1252 if fn not in copies.get(prev[fn], {}):
1253 found = display(fn, rev, {}, state) or found
1253 found = display(fn, rev, {}, state) or found
1254 return (not found and 1) or 0
1254 return (not found and 1) or 0
1255
1255
1256 def heads(ui, repo, *branchrevs, **opts):
1256 def heads(ui, repo, *branchrevs, **opts):
1257 """show current repository heads or show branch heads
1257 """show current repository heads or show branch heads
1258
1258
1259 With no arguments, show all repository head changesets.
1259 With no arguments, show all repository head changesets.
1260
1260
1261 If branch or revisions names are given this will show the heads of
1261 If branch or revisions names are given this will show the heads of
1262 the specified branches or the branches those revisions are tagged
1262 the specified branches or the branches those revisions are tagged
1263 with.
1263 with.
1264
1264
1265 Repository "heads" are changesets that don't have child
1265 Repository "heads" are changesets that don't have child
1266 changesets. They are where development generally takes place and
1266 changesets. They are where development generally takes place and
1267 are the usual targets for update and merge operations.
1267 are the usual targets for update and merge operations.
1268
1268
1269 Branch heads are changesets that have a given branch tag, but have
1269 Branch heads are changesets that have a given branch tag, but have
1270 no child changesets with that tag. They are usually where
1270 no child changesets with that tag. They are usually where
1271 development on the given branch takes place.
1271 development on the given branch takes place.
1272 """
1272 """
1273 if opts.get('rev'):
1273 if opts.get('rev'):
1274 start = repo.lookup(opts['rev'])
1274 start = repo.lookup(opts['rev'])
1275 else:
1275 else:
1276 start = None
1276 start = None
1277 closed = not opts.get('active')
1277 closed = not opts.get('active')
1278 if not branchrevs:
1278 if not branchrevs:
1279 # Assume we're looking repo-wide heads if no revs were specified.
1279 # Assume we're looking repo-wide heads if no revs were specified.
1280 heads = repo.heads(start, closed=closed)
1280 heads = repo.heads(start, closed=closed)
1281 else:
1281 else:
1282 heads = []
1282 heads = []
1283 visitedset = util.set()
1283 visitedset = util.set()
1284 for branchrev in branchrevs:
1284 for branchrev in branchrevs:
1285 branch = repo[branchrev].branch()
1285 branch = repo[branchrev].branch()
1286 if branch in visitedset:
1286 if branch in visitedset:
1287 continue
1287 continue
1288 visitedset.add(branch)
1288 visitedset.add(branch)
1289 bheads = repo.branchheads(branch, start, closed=closed)
1289 bheads = repo.branchheads(branch, start, closed=closed)
1290 if not bheads:
1290 if not bheads:
1291 if branch != branchrev:
1291 if branch != branchrev:
1292 ui.warn(_("no changes on branch %s containing %s are "
1292 ui.warn(_("no changes on branch %s containing %s are "
1293 "reachable from %s\n")
1293 "reachable from %s\n")
1294 % (branch, branchrev, opts.get('rev')))
1294 % (branch, branchrev, opts.get('rev')))
1295 else:
1295 else:
1296 ui.warn(_("no changes on branch %s are reachable from %s\n")
1296 ui.warn(_("no changes on branch %s are reachable from %s\n")
1297 % (branch, opts.get('rev')))
1297 % (branch, opts.get('rev')))
1298 heads.extend(bheads)
1298 heads.extend(bheads)
1299 if not heads:
1299 if not heads:
1300 return 1
1300 return 1
1301 displayer = cmdutil.show_changeset(ui, repo, opts)
1301 displayer = cmdutil.show_changeset(ui, repo, opts)
1302 for n in heads:
1302 for n in heads:
1303 displayer.show(repo[n])
1303 displayer.show(repo[n])
1304
1304
1305 def help_(ui, name=None, with_version=False):
1305 def help_(ui, name=None, with_version=False):
1306 """show help for a given topic or a help overview
1306 """show help for a given topic or a help overview
1307
1307
1308 With no arguments, print a list of commands and short help.
1308 With no arguments, print a list of commands and short help.
1309
1309
1310 Given a topic, extension, or command name, print help for that topic."""
1310 Given a topic, extension, or command name, print help for that topic."""
1311 option_lists = []
1311 option_lists = []
1312
1312
1313 def addglobalopts(aliases):
1313 def addglobalopts(aliases):
1314 if ui.verbose:
1314 if ui.verbose:
1315 option_lists.append((_("global options:"), globalopts))
1315 option_lists.append((_("global options:"), globalopts))
1316 if name == 'shortlist':
1316 if name == 'shortlist':
1317 option_lists.append((_('use "hg help" for the full list '
1317 option_lists.append((_('use "hg help" for the full list '
1318 'of commands'), ()))
1318 'of commands'), ()))
1319 else:
1319 else:
1320 if name == 'shortlist':
1320 if name == 'shortlist':
1321 msg = _('use "hg help" for the full list of commands '
1321 msg = _('use "hg help" for the full list of commands '
1322 'or "hg -v" for details')
1322 'or "hg -v" for details')
1323 elif aliases:
1323 elif aliases:
1324 msg = _('use "hg -v help%s" to show aliases and '
1324 msg = _('use "hg -v help%s" to show aliases and '
1325 'global options') % (name and " " + name or "")
1325 'global options') % (name and " " + name or "")
1326 else:
1326 else:
1327 msg = _('use "hg -v help %s" to show global options') % name
1327 msg = _('use "hg -v help %s" to show global options') % name
1328 option_lists.append((msg, ()))
1328 option_lists.append((msg, ()))
1329
1329
1330 def helpcmd(name):
1330 def helpcmd(name):
1331 if with_version:
1331 if with_version:
1332 version_(ui)
1332 version_(ui)
1333 ui.write('\n')
1333 ui.write('\n')
1334
1334
1335 try:
1335 try:
1336 aliases, i = cmdutil.findcmd(name, table, False)
1336 aliases, i = cmdutil.findcmd(name, table, False)
1337 except error.AmbiguousCommand, inst:
1337 except error.AmbiguousCommand, inst:
1338 select = lambda c: c.lstrip('^').startswith(inst.args[0])
1338 select = lambda c: c.lstrip('^').startswith(inst.args[0])
1339 helplist(_('list of commands:\n\n'), select)
1339 helplist(_('list of commands:\n\n'), select)
1340 return
1340 return
1341
1341
1342 # synopsis
1342 # synopsis
1343 if len(i) > 2:
1343 if len(i) > 2:
1344 if i[2].startswith('hg'):
1344 if i[2].startswith('hg'):
1345 ui.write("%s\n" % i[2])
1345 ui.write("%s\n" % i[2])
1346 else:
1346 else:
1347 ui.write('hg %s %s\n' % (aliases[0], i[2]))
1347 ui.write('hg %s %s\n' % (aliases[0], i[2]))
1348 else:
1348 else:
1349 ui.write('hg %s\n' % aliases[0])
1349 ui.write('hg %s\n' % aliases[0])
1350
1350
1351 # aliases
1351 # aliases
1352 if not ui.quiet and len(aliases) > 1:
1352 if not ui.quiet and len(aliases) > 1:
1353 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1353 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1354
1354
1355 # description
1355 # description
1356 doc = gettext(i[0].__doc__)
1356 doc = gettext(i[0].__doc__)
1357 if not doc:
1357 if not doc:
1358 doc = _("(no help text available)")
1358 doc = _("(no help text available)")
1359 if ui.quiet:
1359 if ui.quiet:
1360 doc = doc.splitlines(0)[0]
1360 doc = doc.splitlines(0)[0]
1361 ui.write("\n%s\n" % doc.rstrip())
1361 ui.write("\n%s\n" % doc.rstrip())
1362
1362
1363 if not ui.quiet:
1363 if not ui.quiet:
1364 # options
1364 # options
1365 if i[1]:
1365 if i[1]:
1366 option_lists.append((_("options:\n"), i[1]))
1366 option_lists.append((_("options:\n"), i[1]))
1367
1367
1368 addglobalopts(False)
1368 addglobalopts(False)
1369
1369
1370 def helplist(header, select=None):
1370 def helplist(header, select=None):
1371 h = {}
1371 h = {}
1372 cmds = {}
1372 cmds = {}
1373 for c, e in table.iteritems():
1373 for c, e in table.iteritems():
1374 f = c.split("|", 1)[0]
1374 f = c.split("|", 1)[0]
1375 if select and not select(f):
1375 if select and not select(f):
1376 continue
1376 continue
1377 if (not select and name != 'shortlist' and
1377 if (not select and name != 'shortlist' and
1378 e[0].__module__ != __name__):
1378 e[0].__module__ != __name__):
1379 continue
1379 continue
1380 if name == "shortlist" and not f.startswith("^"):
1380 if name == "shortlist" and not f.startswith("^"):
1381 continue
1381 continue
1382 f = f.lstrip("^")
1382 f = f.lstrip("^")
1383 if not ui.debugflag and f.startswith("debug"):
1383 if not ui.debugflag and f.startswith("debug"):
1384 continue
1384 continue
1385 doc = gettext(e[0].__doc__)
1385 doc = gettext(e[0].__doc__)
1386 if not doc:
1386 if not doc:
1387 doc = _("(no help text available)")
1387 doc = _("(no help text available)")
1388 h[f] = doc.splitlines(0)[0].rstrip()
1388 h[f] = doc.splitlines(0)[0].rstrip()
1389 cmds[f] = c.lstrip("^")
1389 cmds[f] = c.lstrip("^")
1390
1390
1391 if not h:
1391 if not h:
1392 ui.status(_('no commands defined\n'))
1392 ui.status(_('no commands defined\n'))
1393 return
1393 return
1394
1394
1395 ui.status(header)
1395 ui.status(header)
1396 fns = util.sort(h)
1396 fns = util.sort(h)
1397 m = max(map(len, fns))
1397 m = max(map(len, fns))
1398 for f in fns:
1398 for f in fns:
1399 if ui.verbose:
1399 if ui.verbose:
1400 commands = cmds[f].replace("|",", ")
1400 commands = cmds[f].replace("|",", ")
1401 ui.write(" %s:\n %s\n"%(commands, h[f]))
1401 ui.write(" %s:\n %s\n"%(commands, h[f]))
1402 else:
1402 else:
1403 ui.write(' %-*s %s\n' % (m, f, h[f]))
1403 ui.write(' %-*s %s\n' % (m, f, h[f]))
1404
1404
1405 exts = list(extensions.extensions())
1405 exts = list(extensions.extensions())
1406 if exts and name != 'shortlist':
1406 if exts and name != 'shortlist':
1407 ui.write(_('\nenabled extensions:\n\n'))
1407 ui.write(_('\nenabled extensions:\n\n'))
1408 maxlength = 0
1408 maxlength = 0
1409 exthelps = []
1409 exthelps = []
1410 for ename, ext in exts:
1410 for ename, ext in exts:
1411 doc = (ext.__doc__ or _('(no help text available)'))
1411 doc = (ext.__doc__ or _('(no help text available)'))
1412 ename = ename.split('.')[-1]
1412 ename = ename.split('.')[-1]
1413 maxlength = max(len(ename), maxlength)
1413 maxlength = max(len(ename), maxlength)
1414 exthelps.append((ename, doc.splitlines(0)[0].strip()))
1414 exthelps.append((ename, doc.splitlines(0)[0].strip()))
1415 for ename, text in exthelps:
1415 for ename, text in exthelps:
1416 ui.write(_(' %s %s\n') % (ename.ljust(maxlength), text))
1416 ui.write(_(' %s %s\n') % (ename.ljust(maxlength), text))
1417
1417
1418 if not ui.quiet:
1418 if not ui.quiet:
1419 addglobalopts(True)
1419 addglobalopts(True)
1420
1420
1421 def helptopic(name):
1421 def helptopic(name):
1422 for names, header, doc in help.helptable:
1422 for names, header, doc in help.helptable:
1423 if name in names:
1423 if name in names:
1424 break
1424 break
1425 else:
1425 else:
1426 raise error.UnknownCommand(name)
1426 raise error.UnknownCommand(name)
1427
1427
1428 # description
1428 # description
1429 if not doc:
1429 if not doc:
1430 doc = _("(no help text available)")
1430 doc = _("(no help text available)")
1431 if callable(doc):
1431 if callable(doc):
1432 doc = doc()
1432 doc = doc()
1433
1433
1434 ui.write("%s\n" % header)
1434 ui.write("%s\n" % header)
1435 ui.write("%s\n" % doc.rstrip())
1435 ui.write("%s\n" % doc.rstrip())
1436
1436
1437 def helpext(name):
1437 def helpext(name):
1438 try:
1438 try:
1439 mod = extensions.find(name)
1439 mod = extensions.find(name)
1440 except KeyError:
1440 except KeyError:
1441 raise error.UnknownCommand(name)
1441 raise error.UnknownCommand(name)
1442
1442
1443 doc = gettext(mod.__doc__) or _('no help text available')
1443 doc = gettext(mod.__doc__) or _('no help text available')
1444 doc = doc.splitlines(0)
1444 doc = doc.splitlines(0)
1445 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1445 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1446 for d in doc[1:]:
1446 for d in doc[1:]:
1447 ui.write(d, '\n')
1447 ui.write(d, '\n')
1448
1448
1449 ui.status('\n')
1449 ui.status('\n')
1450
1450
1451 try:
1451 try:
1452 ct = mod.cmdtable
1452 ct = mod.cmdtable
1453 except AttributeError:
1453 except AttributeError:
1454 ct = {}
1454 ct = {}
1455
1455
1456 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1456 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1457 helplist(_('list of commands:\n\n'), modcmds.has_key)
1457 helplist(_('list of commands:\n\n'), modcmds.has_key)
1458
1458
1459 if name and name != 'shortlist':
1459 if name and name != 'shortlist':
1460 i = None
1460 i = None
1461 for f in (helptopic, helpcmd, helpext):
1461 for f in (helptopic, helpcmd, helpext):
1462 try:
1462 try:
1463 f(name)
1463 f(name)
1464 i = None
1464 i = None
1465 break
1465 break
1466 except error.UnknownCommand, inst:
1466 except error.UnknownCommand, inst:
1467 i = inst
1467 i = inst
1468 if i:
1468 if i:
1469 raise i
1469 raise i
1470
1470
1471 else:
1471 else:
1472 # program name
1472 # program name
1473 if ui.verbose or with_version:
1473 if ui.verbose or with_version:
1474 version_(ui)
1474 version_(ui)
1475 else:
1475 else:
1476 ui.status(_("Mercurial Distributed SCM\n"))
1476 ui.status(_("Mercurial Distributed SCM\n"))
1477 ui.status('\n')
1477 ui.status('\n')
1478
1478
1479 # list of commands
1479 # list of commands
1480 if name == "shortlist":
1480 if name == "shortlist":
1481 header = _('basic commands:\n\n')
1481 header = _('basic commands:\n\n')
1482 else:
1482 else:
1483 header = _('list of commands:\n\n')
1483 header = _('list of commands:\n\n')
1484
1484
1485 helplist(header)
1485 helplist(header)
1486
1486
1487 # list all option lists
1487 # list all option lists
1488 opt_output = []
1488 opt_output = []
1489 for title, options in option_lists:
1489 for title, options in option_lists:
1490 opt_output.append(("\n%s" % title, None))
1490 opt_output.append(("\n%s" % title, None))
1491 for shortopt, longopt, default, desc in options:
1491 for shortopt, longopt, default, desc in options:
1492 if "DEPRECATED" in desc and not ui.verbose: continue
1492 if "DEPRECATED" in desc and not ui.verbose: continue
1493 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1493 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1494 longopt and " --%s" % longopt),
1494 longopt and " --%s" % longopt),
1495 "%s%s" % (desc,
1495 "%s%s" % (desc,
1496 default
1496 default
1497 and _(" (default: %s)") % default
1497 and _(" (default: %s)") % default
1498 or "")))
1498 or "")))
1499
1499
1500 if not name:
1500 if not name:
1501 ui.write(_("\nadditional help topics:\n\n"))
1501 ui.write(_("\nadditional help topics:\n\n"))
1502 topics = []
1502 topics = []
1503 for names, header, doc in help.helptable:
1503 for names, header, doc in help.helptable:
1504 names = [(-len(name), name) for name in names]
1504 names = [(-len(name), name) for name in names]
1505 names.sort()
1505 names.sort()
1506 topics.append((names[0][1], header))
1506 topics.append((names[0][1], header))
1507 topics_len = max([len(s[0]) for s in topics])
1507 topics_len = max([len(s[0]) for s in topics])
1508 for t, desc in topics:
1508 for t, desc in topics:
1509 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1509 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1510
1510
1511 if opt_output:
1511 if opt_output:
1512 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1512 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1513 for first, second in opt_output:
1513 for first, second in opt_output:
1514 if second:
1514 if second:
1515 ui.write(" %-*s %s\n" % (opts_len, first, second))
1515 ui.write(" %-*s %s\n" % (opts_len, first, second))
1516 else:
1516 else:
1517 ui.write("%s\n" % first)
1517 ui.write("%s\n" % first)
1518
1518
1519 def identify(ui, repo, source=None,
1519 def identify(ui, repo, source=None,
1520 rev=None, num=None, id=None, branch=None, tags=None):
1520 rev=None, num=None, id=None, branch=None, tags=None):
1521 """identify the working copy or specified revision
1521 """identify the working copy or specified revision
1522
1522
1523 With no revision, print a summary of the current state of the repo.
1523 With no revision, print a summary of the current state of the repo.
1524
1524
1525 With a path, do a lookup in another repository.
1525 With a path, do a lookup in another repository.
1526
1526
1527 This summary identifies the repository state using one or two parent
1527 This summary identifies the repository state using one or two parent
1528 hash identifiers, followed by a "+" if there are uncommitted changes
1528 hash identifiers, followed by a "+" if there are uncommitted changes
1529 in the working directory, a list of tags for this revision and a branch
1529 in the working directory, a list of tags for this revision and a branch
1530 name for non-default branches.
1530 name for non-default branches.
1531 """
1531 """
1532
1532
1533 if not repo and not source:
1533 if not repo and not source:
1534 raise util.Abort(_("There is no Mercurial repository here "
1534 raise util.Abort(_("There is no Mercurial repository here "
1535 "(.hg not found)"))
1535 "(.hg not found)"))
1536
1536
1537 hexfunc = ui.debugflag and hex or short
1537 hexfunc = ui.debugflag and hex or short
1538 default = not (num or id or branch or tags)
1538 default = not (num or id or branch or tags)
1539 output = []
1539 output = []
1540
1540
1541 revs = []
1541 revs = []
1542 if source:
1542 if source:
1543 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1543 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1544 repo = hg.repository(ui, source)
1544 repo = hg.repository(ui, source)
1545
1545
1546 if not repo.local():
1546 if not repo.local():
1547 if not rev and revs:
1547 if not rev and revs:
1548 rev = revs[0]
1548 rev = revs[0]
1549 if not rev:
1549 if not rev:
1550 rev = "tip"
1550 rev = "tip"
1551 if num or branch or tags:
1551 if num or branch or tags:
1552 raise util.Abort(
1552 raise util.Abort(
1553 "can't query remote revision number, branch, or tags")
1553 "can't query remote revision number, branch, or tags")
1554 output = [hexfunc(repo.lookup(rev))]
1554 output = [hexfunc(repo.lookup(rev))]
1555 elif not rev:
1555 elif not rev:
1556 ctx = repo[None]
1556 ctx = repo[None]
1557 parents = ctx.parents()
1557 parents = ctx.parents()
1558 changed = False
1558 changed = False
1559 if default or id or num:
1559 if default or id or num:
1560 changed = ctx.files() + ctx.deleted()
1560 changed = ctx.files() + ctx.deleted()
1561 if default or id:
1561 if default or id:
1562 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1562 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1563 (changed) and "+" or "")]
1563 (changed) and "+" or "")]
1564 if num:
1564 if num:
1565 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1565 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1566 (changed) and "+" or ""))
1566 (changed) and "+" or ""))
1567 else:
1567 else:
1568 ctx = repo[rev]
1568 ctx = repo[rev]
1569 if default or id:
1569 if default or id:
1570 output = [hexfunc(ctx.node())]
1570 output = [hexfunc(ctx.node())]
1571 if num:
1571 if num:
1572 output.append(str(ctx.rev()))
1572 output.append(str(ctx.rev()))
1573
1573
1574 if repo.local() and default and not ui.quiet:
1574 if repo.local() and default and not ui.quiet:
1575 b = util.tolocal(ctx.branch())
1575 b = util.tolocal(ctx.branch())
1576 if b != 'default':
1576 if b != 'default':
1577 output.append("(%s)" % b)
1577 output.append("(%s)" % b)
1578
1578
1579 # multiple tags for a single parent separated by '/'
1579 # multiple tags for a single parent separated by '/'
1580 t = "/".join(ctx.tags())
1580 t = "/".join(ctx.tags())
1581 if t:
1581 if t:
1582 output.append(t)
1582 output.append(t)
1583
1583
1584 if branch:
1584 if branch:
1585 output.append(util.tolocal(ctx.branch()))
1585 output.append(util.tolocal(ctx.branch()))
1586
1586
1587 if tags:
1587 if tags:
1588 output.extend(ctx.tags())
1588 output.extend(ctx.tags())
1589
1589
1590 ui.write("%s\n" % ' '.join(output))
1590 ui.write("%s\n" % ' '.join(output))
1591
1591
1592 def import_(ui, repo, patch1, *patches, **opts):
1592 def import_(ui, repo, patch1, *patches, **opts):
1593 """import an ordered set of patches
1593 """import an ordered set of patches
1594
1594
1595 Import a list of patches and commit them individually.
1595 Import a list of patches and commit them individually.
1596
1596
1597 If there are outstanding changes in the working directory, import
1597 If there are outstanding changes in the working directory, import
1598 will abort unless given the -f flag.
1598 will abort unless given the -f flag.
1599
1599
1600 You can import a patch straight from a mail message. Even patches
1600 You can import a patch straight from a mail message. Even patches
1601 as attachments work (body part must be type text/plain or
1601 as attachments work (body part must be type text/plain or
1602 text/x-patch to be used). From and Subject headers of email
1602 text/x-patch to be used). From and Subject headers of email
1603 message are used as default committer and commit message. All
1603 message are used as default committer and commit message. All
1604 text/plain body parts before first diff are added to commit
1604 text/plain body parts before first diff are added to commit
1605 message.
1605 message.
1606
1606
1607 If the imported patch was generated by hg export, user and description
1607 If the imported patch was generated by hg export, user and description
1608 from patch override values from message headers and body. Values
1608 from patch override values from message headers and body. Values
1609 given on command line with -m and -u override these.
1609 given on command line with -m and -u override these.
1610
1610
1611 If --exact is specified, import will set the working directory
1611 If --exact is specified, import will set the working directory
1612 to the parent of each patch before applying it, and will abort
1612 to the parent of each patch before applying it, and will abort
1613 if the resulting changeset has a different ID than the one
1613 if the resulting changeset has a different ID than the one
1614 recorded in the patch. This may happen due to character set
1614 recorded in the patch. This may happen due to character set
1615 problems or other deficiencies in the text patch format.
1615 problems or other deficiencies in the text patch format.
1616
1616
1617 With --similarity, hg will attempt to discover renames and copies
1617 With --similarity, hg will attempt to discover renames and copies
1618 in the patch in the same way as 'addremove'.
1618 in the patch in the same way as 'addremove'.
1619
1619
1620 To read a patch from standard input, use patch name "-".
1620 To read a patch from standard input, use patch name "-".
1621 See 'hg help dates' for a list of formats valid for -d/--date.
1621 See 'hg help dates' for a list of formats valid for -d/--date.
1622 """
1622 """
1623 patches = (patch1,) + patches
1623 patches = (patch1,) + patches
1624
1624
1625 date = opts.get('date')
1625 date = opts.get('date')
1626 if date:
1626 if date:
1627 opts['date'] = util.parsedate(date)
1627 opts['date'] = util.parsedate(date)
1628
1628
1629 try:
1629 try:
1630 sim = float(opts.get('similarity') or 0)
1630 sim = float(opts.get('similarity') or 0)
1631 except ValueError:
1631 except ValueError:
1632 raise util.Abort(_('similarity must be a number'))
1632 raise util.Abort(_('similarity must be a number'))
1633 if sim < 0 or sim > 100:
1633 if sim < 0 or sim > 100:
1634 raise util.Abort(_('similarity must be between 0 and 100'))
1634 raise util.Abort(_('similarity must be between 0 and 100'))
1635
1635
1636 if opts.get('exact') or not opts.get('force'):
1636 if opts.get('exact') or not opts.get('force'):
1637 cmdutil.bail_if_changed(repo)
1637 cmdutil.bail_if_changed(repo)
1638
1638
1639 d = opts["base"]
1639 d = opts["base"]
1640 strip = opts["strip"]
1640 strip = opts["strip"]
1641 wlock = lock = None
1641 wlock = lock = None
1642 try:
1642 try:
1643 wlock = repo.wlock()
1643 wlock = repo.wlock()
1644 lock = repo.lock()
1644 lock = repo.lock()
1645 for p in patches:
1645 for p in patches:
1646 pf = os.path.join(d, p)
1646 pf = os.path.join(d, p)
1647
1647
1648 if pf == '-':
1648 if pf == '-':
1649 ui.status(_("applying patch from stdin\n"))
1649 ui.status(_("applying patch from stdin\n"))
1650 pf = sys.stdin
1650 pf = sys.stdin
1651 else:
1651 else:
1652 ui.status(_("applying %s\n") % p)
1652 ui.status(_("applying %s\n") % p)
1653 pf = url.open(ui, pf)
1653 pf = url.open(ui, pf)
1654 data = patch.extract(ui, pf)
1654 data = patch.extract(ui, pf)
1655 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1655 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1656
1656
1657 if tmpname is None:
1657 if tmpname is None:
1658 raise util.Abort(_('no diffs found'))
1658 raise util.Abort(_('no diffs found'))
1659
1659
1660 try:
1660 try:
1661 cmdline_message = cmdutil.logmessage(opts)
1661 cmdline_message = cmdutil.logmessage(opts)
1662 if cmdline_message:
1662 if cmdline_message:
1663 # pickup the cmdline msg
1663 # pickup the cmdline msg
1664 message = cmdline_message
1664 message = cmdline_message
1665 elif message:
1665 elif message:
1666 # pickup the patch msg
1666 # pickup the patch msg
1667 message = message.strip()
1667 message = message.strip()
1668 else:
1668 else:
1669 # launch the editor
1669 # launch the editor
1670 message = None
1670 message = None
1671 ui.debug(_('message:\n%s\n') % message)
1671 ui.debug(_('message:\n%s\n') % message)
1672
1672
1673 wp = repo.parents()
1673 wp = repo.parents()
1674 if opts.get('exact'):
1674 if opts.get('exact'):
1675 if not nodeid or not p1:
1675 if not nodeid or not p1:
1676 raise util.Abort(_('not a mercurial patch'))
1676 raise util.Abort(_('not a mercurial patch'))
1677 p1 = repo.lookup(p1)
1677 p1 = repo.lookup(p1)
1678 p2 = repo.lookup(p2 or hex(nullid))
1678 p2 = repo.lookup(p2 or hex(nullid))
1679
1679
1680 if p1 != wp[0].node():
1680 if p1 != wp[0].node():
1681 hg.clean(repo, p1)
1681 hg.clean(repo, p1)
1682 repo.dirstate.setparents(p1, p2)
1682 repo.dirstate.setparents(p1, p2)
1683 elif p2:
1683 elif p2:
1684 try:
1684 try:
1685 p1 = repo.lookup(p1)
1685 p1 = repo.lookup(p1)
1686 p2 = repo.lookup(p2)
1686 p2 = repo.lookup(p2)
1687 if p1 == wp[0].node():
1687 if p1 == wp[0].node():
1688 repo.dirstate.setparents(p1, p2)
1688 repo.dirstate.setparents(p1, p2)
1689 except error.RepoError:
1689 except error.RepoError:
1690 pass
1690 pass
1691 if opts.get('exact') or opts.get('import_branch'):
1691 if opts.get('exact') or opts.get('import_branch'):
1692 repo.dirstate.setbranch(branch or 'default')
1692 repo.dirstate.setbranch(branch or 'default')
1693
1693
1694 files = {}
1694 files = {}
1695 try:
1695 try:
1696 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1696 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1697 files=files)
1697 files=files)
1698 finally:
1698 finally:
1699 files = patch.updatedir(ui, repo, files, similarity=sim/100.)
1699 files = patch.updatedir(ui, repo, files, similarity=sim/100.)
1700 if not opts.get('no_commit'):
1700 if not opts.get('no_commit'):
1701 n = repo.commit(files, message, opts.get('user') or user,
1701 n = repo.commit(files, message, opts.get('user') or user,
1702 opts.get('date') or date)
1702 opts.get('date') or date)
1703 if opts.get('exact'):
1703 if opts.get('exact'):
1704 if hex(n) != nodeid:
1704 if hex(n) != nodeid:
1705 repo.rollback()
1705 repo.rollback()
1706 raise util.Abort(_('patch is damaged'
1706 raise util.Abort(_('patch is damaged'
1707 ' or loses information'))
1707 ' or loses information'))
1708 # Force a dirstate write so that the next transaction
1708 # Force a dirstate write so that the next transaction
1709 # backups an up-do-date file.
1709 # backups an up-do-date file.
1710 repo.dirstate.write()
1710 repo.dirstate.write()
1711 finally:
1711 finally:
1712 os.unlink(tmpname)
1712 os.unlink(tmpname)
1713 finally:
1713 finally:
1714 del lock, wlock
1714 del lock, wlock
1715
1715
1716 def incoming(ui, repo, source="default", **opts):
1716 def incoming(ui, repo, source="default", **opts):
1717 """show new changesets found in source
1717 """show new changesets found in source
1718
1718
1719 Show new changesets found in the specified path/URL or the default
1719 Show new changesets found in the specified path/URL or the default
1720 pull location. These are the changesets that would be pulled if a pull
1720 pull location. These are the changesets that would be pulled if a pull
1721 was requested.
1721 was requested.
1722
1722
1723 For remote repository, using --bundle avoids downloading the changesets
1723 For remote repository, using --bundle avoids downloading the changesets
1724 twice if the incoming is followed by a pull.
1724 twice if the incoming is followed by a pull.
1725
1725
1726 See pull for valid source format details.
1726 See pull for valid source format details.
1727 """
1727 """
1728 limit = cmdutil.loglimit(opts)
1728 limit = cmdutil.loglimit(opts)
1729 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
1729 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
1730 cmdutil.setremoteconfig(ui, opts)
1730 cmdutil.setremoteconfig(ui, opts)
1731
1731
1732 other = hg.repository(ui, source)
1732 other = hg.repository(ui, source)
1733 ui.status(_('comparing with %s\n') % url.hidepassword(source))
1733 ui.status(_('comparing with %s\n') % url.hidepassword(source))
1734 if revs:
1734 if revs:
1735 revs = [other.lookup(rev) for rev in revs]
1735 revs = [other.lookup(rev) for rev in revs]
1736 common, incoming, rheads = repo.findcommonincoming(other, heads=revs,
1736 common, incoming, rheads = repo.findcommonincoming(other, heads=revs,
1737 force=opts["force"])
1737 force=opts["force"])
1738 if not incoming:
1738 if not incoming:
1739 try:
1739 try:
1740 os.unlink(opts["bundle"])
1740 os.unlink(opts["bundle"])
1741 except:
1741 except:
1742 pass
1742 pass
1743 ui.status(_("no changes found\n"))
1743 ui.status(_("no changes found\n"))
1744 return 1
1744 return 1
1745
1745
1746 cleanup = None
1746 cleanup = None
1747 try:
1747 try:
1748 fname = opts["bundle"]
1748 fname = opts["bundle"]
1749 if fname or not other.local():
1749 if fname or not other.local():
1750 # create a bundle (uncompressed if other repo is not local)
1750 # create a bundle (uncompressed if other repo is not local)
1751
1751
1752 if revs is None and other.capable('changegroupsubset'):
1752 if revs is None and other.capable('changegroupsubset'):
1753 revs = rheads
1753 revs = rheads
1754
1754
1755 if revs is None:
1755 if revs is None:
1756 cg = other.changegroup(incoming, "incoming")
1756 cg = other.changegroup(incoming, "incoming")
1757 else:
1757 else:
1758 cg = other.changegroupsubset(incoming, revs, 'incoming')
1758 cg = other.changegroupsubset(incoming, revs, 'incoming')
1759 bundletype = other.local() and "HG10BZ" or "HG10UN"
1759 bundletype = other.local() and "HG10BZ" or "HG10UN"
1760 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1760 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1761 # keep written bundle?
1761 # keep written bundle?
1762 if opts["bundle"]:
1762 if opts["bundle"]:
1763 cleanup = None
1763 cleanup = None
1764 if not other.local():
1764 if not other.local():
1765 # use the created uncompressed bundlerepo
1765 # use the created uncompressed bundlerepo
1766 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1766 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1767
1767
1768 o = other.changelog.nodesbetween(incoming, revs)[0]
1768 o = other.changelog.nodesbetween(incoming, revs)[0]
1769 if opts.get('newest_first'):
1769 if opts.get('newest_first'):
1770 o.reverse()
1770 o.reverse()
1771 displayer = cmdutil.show_changeset(ui, other, opts)
1771 displayer = cmdutil.show_changeset(ui, other, opts)
1772 count = 0
1772 count = 0
1773 for n in o:
1773 for n in o:
1774 if count >= limit:
1774 if count >= limit:
1775 break
1775 break
1776 parents = [p for p in other.changelog.parents(n) if p != nullid]
1776 parents = [p for p in other.changelog.parents(n) if p != nullid]
1777 if opts.get('no_merges') and len(parents) == 2:
1777 if opts.get('no_merges') and len(parents) == 2:
1778 continue
1778 continue
1779 count += 1
1779 count += 1
1780 displayer.show(other[n])
1780 displayer.show(other[n])
1781 finally:
1781 finally:
1782 if hasattr(other, 'close'):
1782 if hasattr(other, 'close'):
1783 other.close()
1783 other.close()
1784 if cleanup:
1784 if cleanup:
1785 os.unlink(cleanup)
1785 os.unlink(cleanup)
1786
1786
1787 def init(ui, dest=".", **opts):
1787 def init(ui, dest=".", **opts):
1788 """create a new repository in the given directory
1788 """create a new repository in the given directory
1789
1789
1790 Initialize a new repository in the given directory. If the given
1790 Initialize a new repository in the given directory. If the given
1791 directory does not exist, it is created.
1791 directory does not exist, it is created.
1792
1792
1793 If no directory is given, the current directory is used.
1793 If no directory is given, the current directory is used.
1794
1794
1795 It is possible to specify an ssh:// URL as the destination.
1795 It is possible to specify an ssh:// URL as the destination.
1796 See 'hg help urls' for more information.
1796 See 'hg help urls' for more information.
1797 """
1797 """
1798 cmdutil.setremoteconfig(ui, opts)
1798 cmdutil.setremoteconfig(ui, opts)
1799 hg.repository(ui, dest, create=1)
1799 hg.repository(ui, dest, create=1)
1800
1800
1801 def locate(ui, repo, *pats, **opts):
1801 def locate(ui, repo, *pats, **opts):
1802 """locate files matching specific patterns
1802 """locate files matching specific patterns
1803
1803
1804 Print all files under Mercurial control whose names match the
1804 Print all files under Mercurial control whose names match the
1805 given patterns.
1805 given patterns.
1806
1806
1807 This command searches the entire repository by default. To search
1807 This command searches the entire repository by default. To search
1808 just the current directory and its subdirectories, use
1808 just the current directory and its subdirectories, use
1809 "--include .".
1809 "--include .".
1810
1810
1811 If no patterns are given to match, this command prints all file
1811 If no patterns are given to match, this command prints all file
1812 names.
1812 names.
1813
1813
1814 If you want to feed the output of this command into the "xargs"
1814 If you want to feed the output of this command into the "xargs"
1815 command, use the "-0" option to both this command and "xargs".
1815 command, use the "-0" option to both this command and "xargs".
1816 This will avoid the problem of "xargs" treating single filenames
1816 This will avoid the problem of "xargs" treating single filenames
1817 that contain white space as multiple filenames.
1817 that contain white space as multiple filenames.
1818 """
1818 """
1819 end = opts.get('print0') and '\0' or '\n'
1819 end = opts.get('print0') and '\0' or '\n'
1820 rev = opts.get('rev') or None
1820 rev = opts.get('rev') or None
1821
1821
1822 ret = 1
1822 ret = 1
1823 m = cmdutil.match(repo, pats, opts, default='relglob')
1823 m = cmdutil.match(repo, pats, opts, default='relglob')
1824 m.bad = lambda x,y: False
1824 m.bad = lambda x,y: False
1825 for abs in repo[rev].walk(m):
1825 for abs in repo[rev].walk(m):
1826 if not rev and abs not in repo.dirstate:
1826 if not rev and abs not in repo.dirstate:
1827 continue
1827 continue
1828 if opts.get('fullpath'):
1828 if opts.get('fullpath'):
1829 ui.write(repo.wjoin(abs), end)
1829 ui.write(repo.wjoin(abs), end)
1830 else:
1830 else:
1831 ui.write(((pats and m.rel(abs)) or abs), end)
1831 ui.write(((pats and m.rel(abs)) or abs), end)
1832 ret = 0
1832 ret = 0
1833
1833
1834 return ret
1834 return ret
1835
1835
1836 def log(ui, repo, *pats, **opts):
1836 def log(ui, repo, *pats, **opts):
1837 """show revision history of entire repository or files
1837 """show revision history of entire repository or files
1838
1838
1839 Print the revision history of the specified files or the entire
1839 Print the revision history of the specified files or the entire
1840 project.
1840 project.
1841
1841
1842 File history is shown without following rename or copy history of
1842 File history is shown without following rename or copy history of
1843 files. Use -f/--follow with a file name to follow history across
1843 files. Use -f/--follow with a file name to follow history across
1844 renames and copies. --follow without a file name will only show
1844 renames and copies. --follow without a file name will only show
1845 ancestors or descendants of the starting revision. --follow-first
1845 ancestors or descendants of the starting revision. --follow-first
1846 only follows the first parent of merge revisions.
1846 only follows the first parent of merge revisions.
1847
1847
1848 If no revision range is specified, the default is tip:0 unless
1848 If no revision range is specified, the default is tip:0 unless
1849 --follow is set, in which case the working directory parent is
1849 --follow is set, in which case the working directory parent is
1850 used as the starting revision.
1850 used as the starting revision.
1851
1851
1852 See 'hg help dates' for a list of formats valid for -d/--date.
1852 See 'hg help dates' for a list of formats valid for -d/--date.
1853
1853
1854 By default this command outputs: changeset id and hash, tags,
1854 By default this command outputs: changeset id and hash, tags,
1855 non-trivial parents, user, date and time, and a summary for each
1855 non-trivial parents, user, date and time, and a summary for each
1856 commit. When the -v/--verbose switch is used, the list of changed
1856 commit. When the -v/--verbose switch is used, the list of changed
1857 files and full commit message is shown.
1857 files and full commit message is shown.
1858
1858
1859 NOTE: log -p may generate unexpected diff output for merge
1859 NOTE: log -p may generate unexpected diff output for merge
1860 changesets, as it will compare the merge changeset against its
1860 changesets, as it will compare the merge changeset against its
1861 first parent only. Also, the files: list will only reflect files
1861 first parent only. Also, the files: list will only reflect files
1862 that are different from BOTH parents.
1862 that are different from BOTH parents.
1863
1863
1864 """
1864 """
1865
1865
1866 get = util.cachefunc(lambda r: repo[r].changeset())
1866 get = util.cachefunc(lambda r: repo[r].changeset())
1867 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1867 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1868
1868
1869 limit = cmdutil.loglimit(opts)
1869 limit = cmdutil.loglimit(opts)
1870 count = 0
1870 count = 0
1871
1871
1872 if opts.get('copies') and opts.get('rev'):
1872 if opts.get('copies') and opts.get('rev'):
1873 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
1873 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
1874 else:
1874 else:
1875 endrev = len(repo)
1875 endrev = len(repo)
1876 rcache = {}
1876 rcache = {}
1877 ncache = {}
1877 ncache = {}
1878 def getrenamed(fn, rev):
1878 def getrenamed(fn, rev):
1879 '''looks up all renames for a file (up to endrev) the first
1879 '''looks up all renames for a file (up to endrev) the first
1880 time the file is given. It indexes on the changerev and only
1880 time the file is given. It indexes on the changerev and only
1881 parses the manifest if linkrev != changerev.
1881 parses the manifest if linkrev != changerev.
1882 Returns rename info for fn at changerev rev.'''
1882 Returns rename info for fn at changerev rev.'''
1883 if fn not in rcache:
1883 if fn not in rcache:
1884 rcache[fn] = {}
1884 rcache[fn] = {}
1885 ncache[fn] = {}
1885 ncache[fn] = {}
1886 fl = repo.file(fn)
1886 fl = repo.file(fn)
1887 for i in fl:
1887 for i in fl:
1888 node = fl.node(i)
1888 node = fl.node(i)
1889 lr = fl.linkrev(i)
1889 lr = fl.linkrev(i)
1890 renamed = fl.renamed(node)
1890 renamed = fl.renamed(node)
1891 rcache[fn][lr] = renamed
1891 rcache[fn][lr] = renamed
1892 if renamed:
1892 if renamed:
1893 ncache[fn][node] = renamed
1893 ncache[fn][node] = renamed
1894 if lr >= endrev:
1894 if lr >= endrev:
1895 break
1895 break
1896 if rev in rcache[fn]:
1896 if rev in rcache[fn]:
1897 return rcache[fn][rev]
1897 return rcache[fn][rev]
1898
1898
1899 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1899 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1900 # filectx logic.
1900 # filectx logic.
1901
1901
1902 try:
1902 try:
1903 return repo[rev][fn].renamed()
1903 return repo[rev][fn].renamed()
1904 except error.LookupError:
1904 except error.LookupError:
1905 pass
1905 pass
1906 return None
1906 return None
1907
1907
1908 df = False
1908 df = False
1909 if opts["date"]:
1909 if opts["date"]:
1910 df = util.matchdate(opts["date"])
1910 df = util.matchdate(opts["date"])
1911
1911
1912 only_branches = opts.get('only_branch')
1912 only_branches = opts.get('only_branch')
1913
1913
1914 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1914 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1915 for st, rev, fns in changeiter:
1915 for st, rev, fns in changeiter:
1916 if st == 'add':
1916 if st == 'add':
1917 parents = [p for p in repo.changelog.parentrevs(rev)
1917 parents = [p for p in repo.changelog.parentrevs(rev)
1918 if p != nullrev]
1918 if p != nullrev]
1919 if opts.get('no_merges') and len(parents) == 2:
1919 if opts.get('no_merges') and len(parents) == 2:
1920 continue
1920 continue
1921 if opts.get('only_merges') and len(parents) != 2:
1921 if opts.get('only_merges') and len(parents) != 2:
1922 continue
1922 continue
1923
1923
1924 if only_branches:
1924 if only_branches:
1925 revbranch = get(rev)[5]['branch']
1925 revbranch = get(rev)[5]['branch']
1926 if revbranch not in only_branches:
1926 if revbranch not in only_branches:
1927 continue
1927 continue
1928
1928
1929 if df:
1929 if df:
1930 changes = get(rev)
1930 changes = get(rev)
1931 if not df(changes[2][0]):
1931 if not df(changes[2][0]):
1932 continue
1932 continue
1933
1933
1934 if opts.get('keyword'):
1934 if opts.get('keyword'):
1935 changes = get(rev)
1935 changes = get(rev)
1936 miss = 0
1936 miss = 0
1937 for k in [kw.lower() for kw in opts['keyword']]:
1937 for k in [kw.lower() for kw in opts['keyword']]:
1938 if not (k in changes[1].lower() or
1938 if not (k in changes[1].lower() or
1939 k in changes[4].lower() or
1939 k in changes[4].lower() or
1940 k in " ".join(changes[3]).lower()):
1940 k in " ".join(changes[3]).lower()):
1941 miss = 1
1941 miss = 1
1942 break
1942 break
1943 if miss:
1943 if miss:
1944 continue
1944 continue
1945
1945
1946 if opts['user']:
1946 if opts['user']:
1947 changes = get(rev)
1947 changes = get(rev)
1948 miss = 0
1948 miss = 0
1949 for k in opts['user']:
1949 for k in opts['user']:
1950 if k != changes[1]:
1950 if k != changes[1]:
1951 miss = 1
1951 miss = 1
1952 break
1952 break
1953 if miss:
1953 if miss:
1954 continue
1954 continue
1955
1955
1956 copies = []
1956 copies = []
1957 if opts.get('copies') and rev:
1957 if opts.get('copies') and rev:
1958 for fn in get(rev)[3]:
1958 for fn in get(rev)[3]:
1959 rename = getrenamed(fn, rev)
1959 rename = getrenamed(fn, rev)
1960 if rename:
1960 if rename:
1961 copies.append((fn, rename[0]))
1961 copies.append((fn, rename[0]))
1962 displayer.show(context.changectx(repo, rev), copies=copies)
1962 displayer.show(context.changectx(repo, rev), copies=copies)
1963 elif st == 'iter':
1963 elif st == 'iter':
1964 if count == limit: break
1964 if count == limit: break
1965 if displayer.flush(rev):
1965 if displayer.flush(rev):
1966 count += 1
1966 count += 1
1967
1967
1968 def manifest(ui, repo, node=None, rev=None):
1968 def manifest(ui, repo, node=None, rev=None):
1969 """output the current or given revision of the project manifest
1969 """output the current or given revision of the project manifest
1970
1970
1971 Print a list of version controlled files for the given revision.
1971 Print a list of version controlled files for the given revision.
1972 If no revision is given, the parent of the working directory is used,
1972 If no revision is given, the parent of the working directory is used,
1973 or tip if no revision is checked out.
1973 or tip if no revision is checked out.
1974
1974
1975 The manifest is the list of files being version controlled. If no revision
1975 The manifest is the list of files being version controlled. If no revision
1976 is given then the first parent of the working directory is used.
1976 is given then the first parent of the working directory is used.
1977
1977
1978 With -v flag, print file permissions, symlink and executable bits. With
1978 With -v flag, print file permissions, symlink and executable bits. With
1979 --debug flag, print file revision hashes.
1979 --debug flag, print file revision hashes.
1980 """
1980 """
1981
1981
1982 if rev and node:
1982 if rev and node:
1983 raise util.Abort(_("please specify just one revision"))
1983 raise util.Abort(_("please specify just one revision"))
1984
1984
1985 if not node:
1985 if not node:
1986 node = rev
1986 node = rev
1987
1987
1988 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
1988 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
1989 ctx = repo[node]
1989 ctx = repo[node]
1990 for f in ctx:
1990 for f in ctx:
1991 if ui.debugflag:
1991 if ui.debugflag:
1992 ui.write("%40s " % hex(ctx.manifest()[f]))
1992 ui.write("%40s " % hex(ctx.manifest()[f]))
1993 if ui.verbose:
1993 if ui.verbose:
1994 ui.write(decor[ctx.flags(f)])
1994 ui.write(decor[ctx.flags(f)])
1995 ui.write("%s\n" % f)
1995 ui.write("%s\n" % f)
1996
1996
1997 def merge(ui, repo, node=None, force=None, rev=None):
1997 def merge(ui, repo, node=None, force=None, rev=None):
1998 """merge working directory with another revision
1998 """merge working directory with another revision
1999
1999
2000 Merge the contents of the current working directory and the
2000 Merge the contents of the current working directory and the
2001 requested revision. Files that changed between either parent are
2001 requested revision. Files that changed between either parent are
2002 marked as changed for the next commit and a commit must be
2002 marked as changed for the next commit and a commit must be
2003 performed before any further updates are allowed.
2003 performed before any further updates are allowed.
2004
2004
2005 If no revision is specified, the working directory's parent is a
2005 If no revision is specified, the working directory's parent is a
2006 head revision, and the current branch contains exactly one other head,
2006 head revision, and the current branch contains exactly one other head,
2007 the other head is merged with by default. Otherwise, an explicit
2007 the other head is merged with by default. Otherwise, an explicit
2008 revision to merge with must be provided.
2008 revision to merge with must be provided.
2009 """
2009 """
2010
2010
2011 if rev and node:
2011 if rev and node:
2012 raise util.Abort(_("please specify just one revision"))
2012 raise util.Abort(_("please specify just one revision"))
2013 if not node:
2013 if not node:
2014 node = rev
2014 node = rev
2015
2015
2016 if not node:
2016 if not node:
2017 branch = repo.changectx(None).branch()
2017 branch = repo.changectx(None).branch()
2018 bheads = repo.branchheads(branch)
2018 bheads = repo.branchheads(branch)
2019 if len(bheads) > 2:
2019 if len(bheads) > 2:
2020 raise util.Abort(_("branch '%s' has %d heads - "
2020 raise util.Abort(_("branch '%s' has %d heads - "
2021 "please merge with an explicit rev") %
2021 "please merge with an explicit rev") %
2022 (branch, len(bheads)))
2022 (branch, len(bheads)))
2023
2023
2024 parent = repo.dirstate.parents()[0]
2024 parent = repo.dirstate.parents()[0]
2025 if len(bheads) == 1:
2025 if len(bheads) == 1:
2026 if len(repo.heads()) > 1:
2026 if len(repo.heads()) > 1:
2027 raise util.Abort(_("branch '%s' has one head - "
2027 raise util.Abort(_("branch '%s' has one head - "
2028 "please merge with an explicit rev") %
2028 "please merge with an explicit rev") %
2029 branch)
2029 branch)
2030 msg = _('there is nothing to merge')
2030 msg = _('there is nothing to merge')
2031 if parent != repo.lookup(repo[None].branch()):
2031 if parent != repo.lookup(repo[None].branch()):
2032 msg = _('%s - use "hg update" instead') % msg
2032 msg = _('%s - use "hg update" instead') % msg
2033 raise util.Abort(msg)
2033 raise util.Abort(msg)
2034
2034
2035 if parent not in bheads:
2035 if parent not in bheads:
2036 raise util.Abort(_('working dir not at a head rev - '
2036 raise util.Abort(_('working dir not at a head rev - '
2037 'use "hg update" or merge with an explicit rev'))
2037 'use "hg update" or merge with an explicit rev'))
2038 node = parent == bheads[0] and bheads[-1] or bheads[0]
2038 node = parent == bheads[0] and bheads[-1] or bheads[0]
2039 return hg.merge(repo, node, force=force)
2039 return hg.merge(repo, node, force=force)
2040
2040
2041 def outgoing(ui, repo, dest=None, **opts):
2041 def outgoing(ui, repo, dest=None, **opts):
2042 """show changesets not found in destination
2042 """show changesets not found in destination
2043
2043
2044 Show changesets not found in the specified destination repository or
2044 Show changesets not found in the specified destination repository or
2045 the default push location. These are the changesets that would be pushed
2045 the default push location. These are the changesets that would be pushed
2046 if a push was requested.
2046 if a push was requested.
2047
2047
2048 See pull for valid destination format details.
2048 See pull for valid destination format details.
2049 """
2049 """
2050 limit = cmdutil.loglimit(opts)
2050 limit = cmdutil.loglimit(opts)
2051 dest, revs, checkout = hg.parseurl(
2051 dest, revs, checkout = hg.parseurl(
2052 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
2052 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
2053 cmdutil.setremoteconfig(ui, opts)
2053 cmdutil.setremoteconfig(ui, opts)
2054 if revs:
2054 if revs:
2055 revs = [repo.lookup(rev) for rev in revs]
2055 revs = [repo.lookup(rev) for rev in revs]
2056
2056
2057 other = hg.repository(ui, dest)
2057 other = hg.repository(ui, dest)
2058 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2058 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2059 o = repo.findoutgoing(other, force=opts.get('force'))
2059 o = repo.findoutgoing(other, force=opts.get('force'))
2060 if not o:
2060 if not o:
2061 ui.status(_("no changes found\n"))
2061 ui.status(_("no changes found\n"))
2062 return 1
2062 return 1
2063 o = repo.changelog.nodesbetween(o, revs)[0]
2063 o = repo.changelog.nodesbetween(o, revs)[0]
2064 if opts.get('newest_first'):
2064 if opts.get('newest_first'):
2065 o.reverse()
2065 o.reverse()
2066 displayer = cmdutil.show_changeset(ui, repo, opts)
2066 displayer = cmdutil.show_changeset(ui, repo, opts)
2067 count = 0
2067 count = 0
2068 for n in o:
2068 for n in o:
2069 if count >= limit:
2069 if count >= limit:
2070 break
2070 break
2071 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2071 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2072 if opts.get('no_merges') and len(parents) == 2:
2072 if opts.get('no_merges') and len(parents) == 2:
2073 continue
2073 continue
2074 count += 1
2074 count += 1
2075 displayer.show(repo[n])
2075 displayer.show(repo[n])
2076
2076
2077 def parents(ui, repo, file_=None, **opts):
2077 def parents(ui, repo, file_=None, **opts):
2078 """show the parents of the working dir or revision
2078 """show the parents of the working dir or revision
2079
2079
2080 Print the working directory's parent revisions. If a
2080 Print the working directory's parent revisions. If a
2081 revision is given via --rev, the parent of that revision
2081 revision is given via --rev, the parent of that revision
2082 will be printed. If a file argument is given, revision in
2082 will be printed. If a file argument is given, revision in
2083 which the file was last changed (before the working directory
2083 which the file was last changed (before the working directory
2084 revision or the argument to --rev if given) is printed.
2084 revision or the argument to --rev if given) is printed.
2085 """
2085 """
2086 rev = opts.get('rev')
2086 rev = opts.get('rev')
2087 if rev:
2087 if rev:
2088 ctx = repo[rev]
2088 ctx = repo[rev]
2089 else:
2089 else:
2090 ctx = repo[None]
2090 ctx = repo[None]
2091
2091
2092 if file_:
2092 if file_:
2093 m = cmdutil.match(repo, (file_,), opts)
2093 m = cmdutil.match(repo, (file_,), opts)
2094 if m.anypats() or len(m.files()) != 1:
2094 if m.anypats() or len(m.files()) != 1:
2095 raise util.Abort(_('can only specify an explicit file name'))
2095 raise util.Abort(_('can only specify an explicit file name'))
2096 file_ = m.files()[0]
2096 file_ = m.files()[0]
2097 filenodes = []
2097 filenodes = []
2098 for cp in ctx.parents():
2098 for cp in ctx.parents():
2099 if not cp:
2099 if not cp:
2100 continue
2100 continue
2101 try:
2101 try:
2102 filenodes.append(cp.filenode(file_))
2102 filenodes.append(cp.filenode(file_))
2103 except error.LookupError:
2103 except error.LookupError:
2104 pass
2104 pass
2105 if not filenodes:
2105 if not filenodes:
2106 raise util.Abort(_("'%s' not found in manifest!") % file_)
2106 raise util.Abort(_("'%s' not found in manifest!") % file_)
2107 fl = repo.file(file_)
2107 fl = repo.file(file_)
2108 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2108 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2109 else:
2109 else:
2110 p = [cp.node() for cp in ctx.parents()]
2110 p = [cp.node() for cp in ctx.parents()]
2111
2111
2112 displayer = cmdutil.show_changeset(ui, repo, opts)
2112 displayer = cmdutil.show_changeset(ui, repo, opts)
2113 for n in p:
2113 for n in p:
2114 if n != nullid:
2114 if n != nullid:
2115 displayer.show(repo[n])
2115 displayer.show(repo[n])
2116
2116
2117 def paths(ui, repo, search=None):
2117 def paths(ui, repo, search=None):
2118 """show aliases for remote repositories
2118 """show aliases for remote repositories
2119
2119
2120 Show definition of symbolic path name NAME. If no name is given, show
2120 Show definition of symbolic path name NAME. If no name is given, show
2121 definition of available names.
2121 definition of available names.
2122
2122
2123 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2123 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2124 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2124 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2125
2125
2126 See 'hg help urls' for more information.
2126 See 'hg help urls' for more information.
2127 """
2127 """
2128 if search:
2128 if search:
2129 for name, path in ui.configitems("paths"):
2129 for name, path in ui.configitems("paths"):
2130 if name == search:
2130 if name == search:
2131 ui.write("%s\n" % url.hidepassword(path))
2131 ui.write("%s\n" % url.hidepassword(path))
2132 return
2132 return
2133 ui.warn(_("not found!\n"))
2133 ui.warn(_("not found!\n"))
2134 return 1
2134 return 1
2135 else:
2135 else:
2136 for name, path in ui.configitems("paths"):
2136 for name, path in ui.configitems("paths"):
2137 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2137 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2138
2138
2139 def postincoming(ui, repo, modheads, optupdate, checkout):
2139 def postincoming(ui, repo, modheads, optupdate, checkout):
2140 if modheads == 0:
2140 if modheads == 0:
2141 return
2141 return
2142 if optupdate:
2142 if optupdate:
2143 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2143 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2144 return hg.update(repo, checkout)
2144 return hg.update(repo, checkout)
2145 else:
2145 else:
2146 ui.status(_("not updating, since new heads added\n"))
2146 ui.status(_("not updating, since new heads added\n"))
2147 if modheads > 1:
2147 if modheads > 1:
2148 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2148 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2149 else:
2149 else:
2150 ui.status(_("(run 'hg update' to get a working copy)\n"))
2150 ui.status(_("(run 'hg update' to get a working copy)\n"))
2151
2151
2152 def pull(ui, repo, source="default", **opts):
2152 def pull(ui, repo, source="default", **opts):
2153 """pull changes from the specified source
2153 """pull changes from the specified source
2154
2154
2155 Pull changes from a remote repository to a local one.
2155 Pull changes from a remote repository to a local one.
2156
2156
2157 This finds all changes from the repository at the specified path
2157 This finds all changes from the repository at the specified path
2158 or URL and adds them to the local repository. By default, this
2158 or URL and adds them to the local repository. By default, this
2159 does not update the copy of the project in the working directory.
2159 does not update the copy of the project in the working directory.
2160
2160
2161 If SOURCE is omitted, the 'default' path will be used.
2161 If SOURCE is omitted, the 'default' path will be used.
2162 See 'hg help urls' for more information.
2162 See 'hg help urls' for more information.
2163 """
2163 """
2164 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
2164 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
2165 cmdutil.setremoteconfig(ui, opts)
2165 cmdutil.setremoteconfig(ui, opts)
2166
2166
2167 other = hg.repository(ui, source)
2167 other = hg.repository(ui, source)
2168 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2168 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2169 if revs:
2169 if revs:
2170 try:
2170 try:
2171 revs = [other.lookup(rev) for rev in revs]
2171 revs = [other.lookup(rev) for rev in revs]
2172 except error.CapabilityError:
2172 except error.CapabilityError:
2173 err = _("Other repository doesn't support revision lookup, "
2173 err = _("Other repository doesn't support revision lookup, "
2174 "so a rev cannot be specified.")
2174 "so a rev cannot be specified.")
2175 raise util.Abort(err)
2175 raise util.Abort(err)
2176
2176
2177 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2177 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2178 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2178 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2179
2179
2180 def push(ui, repo, dest=None, **opts):
2180 def push(ui, repo, dest=None, **opts):
2181 """push changes to the specified destination
2181 """push changes to the specified destination
2182
2182
2183 Push changes from the local repository to the given destination.
2183 Push changes from the local repository to the given destination.
2184
2184
2185 This is the symmetrical operation for pull. It helps to move
2185 This is the symmetrical operation for pull. It helps to move
2186 changes from the current repository to a different one. If the
2186 changes from the current repository to a different one. If the
2187 destination is local this is identical to a pull in that directory
2187 destination is local this is identical to a pull in that directory
2188 from the current one.
2188 from the current one.
2189
2189
2190 By default, push will refuse to run if it detects the result would
2190 By default, push will refuse to run if it detects the result would
2191 increase the number of remote heads. This generally indicates the
2191 increase the number of remote heads. This generally indicates the
2192 the client has forgotten to pull and merge before pushing.
2192 the client has forgotten to pull and merge before pushing.
2193
2193
2194 If -r is used, the named changeset and all its ancestors will be pushed
2194 If -r is used, the named changeset and all its ancestors will be pushed
2195 to the remote repository.
2195 to the remote repository.
2196
2196
2197 Look at the help text for urls for important details about ssh:// URLs.
2197 Look at the help text for urls for important details about ssh:// URLs.
2198 If DESTINATION is omitted, a default path will be used.
2198 If DESTINATION is omitted, a default path will be used.
2199 See 'hg help urls' for more information.
2199 See 'hg help urls' for more information.
2200 """
2200 """
2201 dest, revs, checkout = hg.parseurl(
2201 dest, revs, checkout = hg.parseurl(
2202 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
2202 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
2203 cmdutil.setremoteconfig(ui, opts)
2203 cmdutil.setremoteconfig(ui, opts)
2204
2204
2205 other = hg.repository(ui, dest)
2205 other = hg.repository(ui, dest)
2206 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2206 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2207 if revs:
2207 if revs:
2208 revs = [repo.lookup(rev) for rev in revs]
2208 revs = [repo.lookup(rev) for rev in revs]
2209 r = repo.push(other, opts.get('force'), revs=revs)
2209 r = repo.push(other, opts.get('force'), revs=revs)
2210 return r == 0
2210 return r == 0
2211
2211
2212 def rawcommit(ui, repo, *pats, **opts):
2212 def rawcommit(ui, repo, *pats, **opts):
2213 """raw commit interface (DEPRECATED)
2213 """raw commit interface (DEPRECATED)
2214
2214
2215 (DEPRECATED)
2215 (DEPRECATED)
2216 Lowlevel commit, for use in helper scripts.
2216 Lowlevel commit, for use in helper scripts.
2217
2217
2218 This command is not intended to be used by normal users, as it is
2218 This command is not intended to be used by normal users, as it is
2219 primarily useful for importing from other SCMs.
2219 primarily useful for importing from other SCMs.
2220
2220
2221 This command is now deprecated and will be removed in a future
2221 This command is now deprecated and will be removed in a future
2222 release, please use debugsetparents and commit instead.
2222 release, please use debugsetparents and commit instead.
2223 """
2223 """
2224
2224
2225 ui.warn(_("(the rawcommit command is deprecated)\n"))
2225 ui.warn(_("(the rawcommit command is deprecated)\n"))
2226
2226
2227 message = cmdutil.logmessage(opts)
2227 message = cmdutil.logmessage(opts)
2228
2228
2229 files = cmdutil.match(repo, pats, opts).files()
2229 files = cmdutil.match(repo, pats, opts).files()
2230 if opts.get('files'):
2230 if opts.get('files'):
2231 files += open(opts['files']).read().splitlines()
2231 files += open(opts['files']).read().splitlines()
2232
2232
2233 parents = [repo.lookup(p) for p in opts['parent']]
2233 parents = [repo.lookup(p) for p in opts['parent']]
2234
2234
2235 try:
2235 try:
2236 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2236 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2237 except ValueError, inst:
2237 except ValueError, inst:
2238 raise util.Abort(str(inst))
2238 raise util.Abort(str(inst))
2239
2239
2240 def recover(ui, repo):
2240 def recover(ui, repo):
2241 """roll back an interrupted transaction
2241 """roll back an interrupted transaction
2242
2242
2243 Recover from an interrupted commit or pull.
2243 Recover from an interrupted commit or pull.
2244
2244
2245 This command tries to fix the repository status after an interrupted
2245 This command tries to fix the repository status after an interrupted
2246 operation. It should only be necessary when Mercurial suggests it.
2246 operation. It should only be necessary when Mercurial suggests it.
2247 """
2247 """
2248 if repo.recover():
2248 if repo.recover():
2249 return hg.verify(repo)
2249 return hg.verify(repo)
2250 return 1
2250 return 1
2251
2251
2252 def remove(ui, repo, *pats, **opts):
2252 def remove(ui, repo, *pats, **opts):
2253 """remove the specified files on the next commit
2253 """remove the specified files on the next commit
2254
2254
2255 Schedule the indicated files for removal from the repository.
2255 Schedule the indicated files for removal from the repository.
2256
2256
2257 This only removes files from the current branch, not from the entire
2257 This only removes files from the current branch, not from the entire
2258 project history. -A can be used to remove only files that have already
2258 project history. -A can be used to remove only files that have already
2259 been deleted, -f can be used to force deletion, and -Af can be used
2259 been deleted, -f can be used to force deletion, and -Af can be used
2260 to remove files from the next revision without deleting them.
2260 to remove files from the next revision without deleting them.
2261
2261
2262 The following table details the behavior of remove for different file
2262 The following table details the behavior of remove for different file
2263 states (columns) and option combinations (rows). The file states are
2263 states (columns) and option combinations (rows). The file states are
2264 Added, Clean, Modified and Missing (as reported by hg status). The
2264 Added, Clean, Modified and Missing (as reported by hg status). The
2265 actions are Warn, Remove (from branch) and Delete (from disk).
2265 actions are Warn, Remove (from branch) and Delete (from disk).
2266
2266
2267 A C M !
2267 A C M !
2268 none W RD W R
2268 none W RD W R
2269 -f R RD RD R
2269 -f R RD RD R
2270 -A W W W R
2270 -A W W W R
2271 -Af R R R R
2271 -Af R R R R
2272
2272
2273 This command schedules the files to be removed at the next commit.
2273 This command schedules the files to be removed at the next commit.
2274 To undo a remove before that, see hg revert.
2274 To undo a remove before that, see hg revert.
2275 """
2275 """
2276
2276
2277 after, force = opts.get('after'), opts.get('force')
2277 after, force = opts.get('after'), opts.get('force')
2278 if not pats and not after:
2278 if not pats and not after:
2279 raise util.Abort(_('no files specified'))
2279 raise util.Abort(_('no files specified'))
2280
2280
2281 m = cmdutil.match(repo, pats, opts)
2281 m = cmdutil.match(repo, pats, opts)
2282 s = repo.status(match=m, clean=True)
2282 s = repo.status(match=m, clean=True)
2283 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2283 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2284
2284
2285 def warn(files, reason):
2285 def warn(files, reason):
2286 for f in files:
2286 for f in files:
2287 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2287 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2288 % (m.rel(f), reason))
2288 % (m.rel(f), reason))
2289
2289
2290 if force:
2290 if force:
2291 remove, forget = modified + deleted + clean, added
2291 remove, forget = modified + deleted + clean, added
2292 elif after:
2292 elif after:
2293 remove, forget = deleted, []
2293 remove, forget = deleted, []
2294 warn(modified + added + clean, _('still exists'))
2294 warn(modified + added + clean, _('still exists'))
2295 else:
2295 else:
2296 remove, forget = deleted + clean, []
2296 remove, forget = deleted + clean, []
2297 warn(modified, _('is modified'))
2297 warn(modified, _('is modified'))
2298 warn(added, _('has been marked for add'))
2298 warn(added, _('has been marked for add'))
2299
2299
2300 for f in util.sort(remove + forget):
2300 for f in util.sort(remove + forget):
2301 if ui.verbose or not m.exact(f):
2301 if ui.verbose or not m.exact(f):
2302 ui.status(_('removing %s\n') % m.rel(f))
2302 ui.status(_('removing %s\n') % m.rel(f))
2303
2303
2304 repo.forget(forget)
2304 repo.forget(forget)
2305 repo.remove(remove, unlink=not after)
2305 repo.remove(remove, unlink=not after)
2306
2306
2307 def rename(ui, repo, *pats, **opts):
2307 def rename(ui, repo, *pats, **opts):
2308 """rename files; equivalent of copy + remove
2308 """rename files; equivalent of copy + remove
2309
2309
2310 Mark dest as copies of sources; mark sources for deletion. If
2310 Mark dest as copies of sources; mark sources for deletion. If
2311 dest is a directory, copies are put in that directory. If dest is
2311 dest is a directory, copies are put in that directory. If dest is
2312 a file, there can only be one source.
2312 a file, there can only be one source.
2313
2313
2314 By default, this command copies the contents of files as they
2314 By default, this command copies the contents of files as they
2315 stand in the working directory. If invoked with --after, the
2315 stand in the working directory. If invoked with --after, the
2316 operation is recorded, but no copying is performed.
2316 operation is recorded, but no copying is performed.
2317
2317
2318 This command takes effect in the next commit. To undo a rename
2318 This command takes effect in the next commit. To undo a rename
2319 before that, see hg revert.
2319 before that, see hg revert.
2320 """
2320 """
2321 wlock = repo.wlock(False)
2321 wlock = repo.wlock(False)
2322 try:
2322 try:
2323 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2323 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2324 finally:
2324 finally:
2325 del wlock
2325 del wlock
2326
2326
2327 def resolve(ui, repo, *pats, **opts):
2327 def resolve(ui, repo, *pats, **opts):
2328 """retry file merges from a merge or update
2328 """retry file merges from a merge or update
2329
2329
2330 This command will cleanly retry unresolved file merges using file
2330 This command will cleanly retry unresolved file merges using file
2331 revisions preserved from the last update or merge. To attempt to
2331 revisions preserved from the last update or merge. To attempt to
2332 resolve all unresolved files, use the -a switch.
2332 resolve all unresolved files, use the -a switch.
2333
2333
2334 This command will also allow listing resolved files and manually
2334 This command will also allow listing resolved files and manually
2335 marking and unmarking files as resolved.
2335 marking and unmarking files as resolved.
2336
2336
2337 The codes used to show the status of files are:
2337 The codes used to show the status of files are:
2338 U = unresolved
2338 U = unresolved
2339 R = resolved
2339 R = resolved
2340 """
2340 """
2341
2341
2342 all, mark, unmark, show = [opts.get(o) for o in 'all mark unmark list'.split()]
2342 all, mark, unmark, show = [opts.get(o) for o in 'all mark unmark list'.split()]
2343
2343
2344 if (show and (mark or unmark)) or (mark and unmark):
2344 if (show and (mark or unmark)) or (mark and unmark):
2345 raise util.Abort(_("too many options specified"))
2345 raise util.Abort(_("too many options specified"))
2346 if pats and all:
2346 if pats and all:
2347 raise util.Abort(_("can't specify --all and patterns"))
2347 raise util.Abort(_("can't specify --all and patterns"))
2348 if not (all or pats or show or mark or unmark):
2348 if not (all or pats or show or mark or unmark):
2349 raise util.Abort(_('no files or directories specified; '
2349 raise util.Abort(_('no files or directories specified; '
2350 'use --all to remerge all files'))
2350 'use --all to remerge all files'))
2351
2351
2352 ms = merge_.mergestate(repo)
2352 ms = merge_.mergestate(repo)
2353 m = cmdutil.match(repo, pats, opts)
2353 m = cmdutil.match(repo, pats, opts)
2354
2354
2355 for f in ms:
2355 for f in ms:
2356 if m(f):
2356 if m(f):
2357 if show:
2357 if show:
2358 ui.write("%s %s\n" % (ms[f].upper(), f))
2358 ui.write("%s %s\n" % (ms[f].upper(), f))
2359 elif mark:
2359 elif mark:
2360 ms.mark(f, "r")
2360 ms.mark(f, "r")
2361 elif unmark:
2361 elif unmark:
2362 ms.mark(f, "u")
2362 ms.mark(f, "u")
2363 else:
2363 else:
2364 wctx = repo[None]
2364 wctx = repo[None]
2365 mctx = wctx.parents()[-1]
2365 mctx = wctx.parents()[-1]
2366 ms.resolve(f, wctx, mctx)
2366 ms.resolve(f, wctx, mctx)
2367
2367
2368 def revert(ui, repo, *pats, **opts):
2368 def revert(ui, repo, *pats, **opts):
2369 """restore individual files or dirs to an earlier state
2369 """restore individual files or dirs to an earlier state
2370
2370
2371 (use update -r to check out earlier revisions, revert does not
2371 (use update -r to check out earlier revisions, revert does not
2372 change the working dir parents)
2372 change the working dir parents)
2373
2373
2374 With no revision specified, revert the named files or directories
2374 With no revision specified, revert the named files or directories
2375 to the contents they had in the parent of the working directory.
2375 to the contents they had in the parent of the working directory.
2376 This restores the contents of the affected files to an unmodified
2376 This restores the contents of the affected files to an unmodified
2377 state and unschedules adds, removes, copies, and renames. If the
2377 state and unschedules adds, removes, copies, and renames. If the
2378 working directory has two parents, you must explicitly specify the
2378 working directory has two parents, you must explicitly specify the
2379 revision to revert to.
2379 revision to revert to.
2380
2380
2381 Using the -r option, revert the given files or directories to their
2381 Using the -r option, revert the given files or directories to their
2382 contents as of a specific revision. This can be helpful to "roll
2382 contents as of a specific revision. This can be helpful to "roll
2383 back" some or all of an earlier change.
2383 back" some or all of an earlier change.
2384 See 'hg help dates' for a list of formats valid for -d/--date.
2384 See 'hg help dates' for a list of formats valid for -d/--date.
2385
2385
2386 Revert modifies the working directory. It does not commit any
2386 Revert modifies the working directory. It does not commit any
2387 changes, or change the parent of the working directory. If you
2387 changes, or change the parent of the working directory. If you
2388 revert to a revision other than the parent of the working
2388 revert to a revision other than the parent of the working
2389 directory, the reverted files will thus appear modified
2389 directory, the reverted files will thus appear modified
2390 afterwards.
2390 afterwards.
2391
2391
2392 If a file has been deleted, it is restored. If the executable
2392 If a file has been deleted, it is restored. If the executable
2393 mode of a file was changed, it is reset.
2393 mode of a file was changed, it is reset.
2394
2394
2395 If names are given, all files matching the names are reverted.
2395 If names are given, all files matching the names are reverted.
2396 If no arguments are given, no files are reverted.
2396 If no arguments are given, no files are reverted.
2397
2397
2398 Modified files are saved with a .orig suffix before reverting.
2398 Modified files are saved with a .orig suffix before reverting.
2399 To disable these backups, use --no-backup.
2399 To disable these backups, use --no-backup.
2400 """
2400 """
2401
2401
2402 if opts["date"]:
2402 if opts["date"]:
2403 if opts["rev"]:
2403 if opts["rev"]:
2404 raise util.Abort(_("you can't specify a revision and a date"))
2404 raise util.Abort(_("you can't specify a revision and a date"))
2405 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2405 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2406
2406
2407 if not pats and not opts.get('all'):
2407 if not pats and not opts.get('all'):
2408 raise util.Abort(_('no files or directories specified; '
2408 raise util.Abort(_('no files or directories specified; '
2409 'use --all to revert the whole repo'))
2409 'use --all to revert the whole repo'))
2410
2410
2411 parent, p2 = repo.dirstate.parents()
2411 parent, p2 = repo.dirstate.parents()
2412 if not opts.get('rev') and p2 != nullid:
2412 if not opts.get('rev') and p2 != nullid:
2413 raise util.Abort(_('uncommitted merge - please provide a '
2413 raise util.Abort(_('uncommitted merge - please provide a '
2414 'specific revision'))
2414 'specific revision'))
2415 ctx = repo[opts.get('rev')]
2415 ctx = repo[opts.get('rev')]
2416 node = ctx.node()
2416 node = ctx.node()
2417 mf = ctx.manifest()
2417 mf = ctx.manifest()
2418 if node == parent:
2418 if node == parent:
2419 pmf = mf
2419 pmf = mf
2420 else:
2420 else:
2421 pmf = None
2421 pmf = None
2422
2422
2423 # need all matching names in dirstate and manifest of target rev,
2423 # need all matching names in dirstate and manifest of target rev,
2424 # so have to walk both. do not print errors if files exist in one
2424 # so have to walk both. do not print errors if files exist in one
2425 # but not other.
2425 # but not other.
2426
2426
2427 names = {}
2427 names = {}
2428
2428
2429 wlock = repo.wlock()
2429 wlock = repo.wlock()
2430 try:
2430 try:
2431 # walk dirstate.
2431 # walk dirstate.
2432 files = []
2432 files = []
2433
2433
2434 m = cmdutil.match(repo, pats, opts)
2434 m = cmdutil.match(repo, pats, opts)
2435 m.bad = lambda x,y: False
2435 m.bad = lambda x,y: False
2436 for abs in repo.walk(m):
2436 for abs in repo.walk(m):
2437 names[abs] = m.rel(abs), m.exact(abs)
2437 names[abs] = m.rel(abs), m.exact(abs)
2438
2438
2439 # walk target manifest.
2439 # walk target manifest.
2440
2440
2441 def badfn(path, msg):
2441 def badfn(path, msg):
2442 if path in names:
2442 if path in names:
2443 return False
2443 return False
2444 path_ = path + '/'
2444 path_ = path + '/'
2445 for f in names:
2445 for f in names:
2446 if f.startswith(path_):
2446 if f.startswith(path_):
2447 return False
2447 return False
2448 repo.ui.warn("%s: %s\n" % (m.rel(path), msg))
2448 repo.ui.warn("%s: %s\n" % (m.rel(path), msg))
2449 return False
2449 return False
2450
2450
2451 m = cmdutil.match(repo, pats, opts)
2451 m = cmdutil.match(repo, pats, opts)
2452 m.bad = badfn
2452 m.bad = badfn
2453 for abs in repo[node].walk(m):
2453 for abs in repo[node].walk(m):
2454 if abs not in names:
2454 if abs not in names:
2455 names[abs] = m.rel(abs), m.exact(abs)
2455 names[abs] = m.rel(abs), m.exact(abs)
2456
2456
2457 m = cmdutil.matchfiles(repo, names)
2457 m = cmdutil.matchfiles(repo, names)
2458 changes = repo.status(match=m)[:4]
2458 changes = repo.status(match=m)[:4]
2459 modified, added, removed, deleted = map(dict.fromkeys, changes)
2459 modified, added, removed, deleted = map(dict.fromkeys, changes)
2460
2460
2461 # if f is a rename, also revert the source
2461 # if f is a rename, also revert the source
2462 cwd = repo.getcwd()
2462 cwd = repo.getcwd()
2463 for f in added:
2463 for f in added:
2464 src = repo.dirstate.copied(f)
2464 src = repo.dirstate.copied(f)
2465 if src and src not in names and repo.dirstate[src] == 'r':
2465 if src and src not in names and repo.dirstate[src] == 'r':
2466 removed[src] = None
2466 removed[src] = None
2467 names[src] = (repo.pathto(src, cwd), True)
2467 names[src] = (repo.pathto(src, cwd), True)
2468
2468
2469 def removeforget(abs):
2469 def removeforget(abs):
2470 if repo.dirstate[abs] == 'a':
2470 if repo.dirstate[abs] == 'a':
2471 return _('forgetting %s\n')
2471 return _('forgetting %s\n')
2472 return _('removing %s\n')
2472 return _('removing %s\n')
2473
2473
2474 revert = ([], _('reverting %s\n'))
2474 revert = ([], _('reverting %s\n'))
2475 add = ([], _('adding %s\n'))
2475 add = ([], _('adding %s\n'))
2476 remove = ([], removeforget)
2476 remove = ([], removeforget)
2477 undelete = ([], _('undeleting %s\n'))
2477 undelete = ([], _('undeleting %s\n'))
2478
2478
2479 disptable = (
2479 disptable = (
2480 # dispatch table:
2480 # dispatch table:
2481 # file state
2481 # file state
2482 # action if in target manifest
2482 # action if in target manifest
2483 # action if not in target manifest
2483 # action if not in target manifest
2484 # make backup if in target manifest
2484 # make backup if in target manifest
2485 # make backup if not in target manifest
2485 # make backup if not in target manifest
2486 (modified, revert, remove, True, True),
2486 (modified, revert, remove, True, True),
2487 (added, revert, remove, True, False),
2487 (added, revert, remove, True, False),
2488 (removed, undelete, None, False, False),
2488 (removed, undelete, None, False, False),
2489 (deleted, revert, remove, False, False),
2489 (deleted, revert, remove, False, False),
2490 )
2490 )
2491
2491
2492 for abs, (rel, exact) in util.sort(names.items()):
2492 for abs, (rel, exact) in util.sort(names.items()):
2493 mfentry = mf.get(abs)
2493 mfentry = mf.get(abs)
2494 target = repo.wjoin(abs)
2494 target = repo.wjoin(abs)
2495 def handle(xlist, dobackup):
2495 def handle(xlist, dobackup):
2496 xlist[0].append(abs)
2496 xlist[0].append(abs)
2497 if dobackup and not opts.get('no_backup') and util.lexists(target):
2497 if dobackup and not opts.get('no_backup') and util.lexists(target):
2498 bakname = "%s.orig" % rel
2498 bakname = "%s.orig" % rel
2499 ui.note(_('saving current version of %s as %s\n') %
2499 ui.note(_('saving current version of %s as %s\n') %
2500 (rel, bakname))
2500 (rel, bakname))
2501 if not opts.get('dry_run'):
2501 if not opts.get('dry_run'):
2502 util.copyfile(target, bakname)
2502 util.copyfile(target, bakname)
2503 if ui.verbose or not exact:
2503 if ui.verbose or not exact:
2504 msg = xlist[1]
2504 msg = xlist[1]
2505 if not isinstance(msg, basestring):
2505 if not isinstance(msg, basestring):
2506 msg = msg(abs)
2506 msg = msg(abs)
2507 ui.status(msg % rel)
2507 ui.status(msg % rel)
2508 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2508 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2509 if abs not in table: continue
2509 if abs not in table: continue
2510 # file has changed in dirstate
2510 # file has changed in dirstate
2511 if mfentry:
2511 if mfentry:
2512 handle(hitlist, backuphit)
2512 handle(hitlist, backuphit)
2513 elif misslist is not None:
2513 elif misslist is not None:
2514 handle(misslist, backupmiss)
2514 handle(misslist, backupmiss)
2515 break
2515 break
2516 else:
2516 else:
2517 if abs not in repo.dirstate:
2517 if abs not in repo.dirstate:
2518 if mfentry:
2518 if mfentry:
2519 handle(add, True)
2519 handle(add, True)
2520 elif exact:
2520 elif exact:
2521 ui.warn(_('file not managed: %s\n') % rel)
2521 ui.warn(_('file not managed: %s\n') % rel)
2522 continue
2522 continue
2523 # file has not changed in dirstate
2523 # file has not changed in dirstate
2524 if node == parent:
2524 if node == parent:
2525 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2525 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2526 continue
2526 continue
2527 if pmf is None:
2527 if pmf is None:
2528 # only need parent manifest in this unlikely case,
2528 # only need parent manifest in this unlikely case,
2529 # so do not read by default
2529 # so do not read by default
2530 pmf = repo[parent].manifest()
2530 pmf = repo[parent].manifest()
2531 if abs in pmf:
2531 if abs in pmf:
2532 if mfentry:
2532 if mfentry:
2533 # if version of file is same in parent and target
2533 # if version of file is same in parent and target
2534 # manifests, do nothing
2534 # manifests, do nothing
2535 if (pmf[abs] != mfentry or
2535 if (pmf[abs] != mfentry or
2536 pmf.flags(abs) != mf.flags(abs)):
2536 pmf.flags(abs) != mf.flags(abs)):
2537 handle(revert, False)
2537 handle(revert, False)
2538 else:
2538 else:
2539 handle(remove, False)
2539 handle(remove, False)
2540
2540
2541 if not opts.get('dry_run'):
2541 if not opts.get('dry_run'):
2542 def checkout(f):
2542 def checkout(f):
2543 fc = ctx[f]
2543 fc = ctx[f]
2544 repo.wwrite(f, fc.data(), fc.flags())
2544 repo.wwrite(f, fc.data(), fc.flags())
2545
2545
2546 audit_path = util.path_auditor(repo.root)
2546 audit_path = util.path_auditor(repo.root)
2547 for f in remove[0]:
2547 for f in remove[0]:
2548 if repo.dirstate[f] == 'a':
2548 if repo.dirstate[f] == 'a':
2549 repo.dirstate.forget(f)
2549 repo.dirstate.forget(f)
2550 continue
2550 continue
2551 audit_path(f)
2551 audit_path(f)
2552 try:
2552 try:
2553 util.unlink(repo.wjoin(f))
2553 util.unlink(repo.wjoin(f))
2554 except OSError:
2554 except OSError:
2555 pass
2555 pass
2556 repo.dirstate.remove(f)
2556 repo.dirstate.remove(f)
2557
2557
2558 normal = None
2558 normal = None
2559 if node == parent:
2559 if node == parent:
2560 # We're reverting to our parent. If possible, we'd like status
2560 # We're reverting to our parent. If possible, we'd like status
2561 # to report the file as clean. We have to use normallookup for
2561 # to report the file as clean. We have to use normallookup for
2562 # merges to avoid losing information about merged/dirty files.
2562 # merges to avoid losing information about merged/dirty files.
2563 if p2 != nullid:
2563 if p2 != nullid:
2564 normal = repo.dirstate.normallookup
2564 normal = repo.dirstate.normallookup
2565 else:
2565 else:
2566 normal = repo.dirstate.normal
2566 normal = repo.dirstate.normal
2567 for f in revert[0]:
2567 for f in revert[0]:
2568 checkout(f)
2568 checkout(f)
2569 if normal:
2569 if normal:
2570 normal(f)
2570 normal(f)
2571
2571
2572 for f in add[0]:
2572 for f in add[0]:
2573 checkout(f)
2573 checkout(f)
2574 repo.dirstate.add(f)
2574 repo.dirstate.add(f)
2575
2575
2576 normal = repo.dirstate.normallookup
2576 normal = repo.dirstate.normallookup
2577 if node == parent and p2 == nullid:
2577 if node == parent and p2 == nullid:
2578 normal = repo.dirstate.normal
2578 normal = repo.dirstate.normal
2579 for f in undelete[0]:
2579 for f in undelete[0]:
2580 checkout(f)
2580 checkout(f)
2581 normal(f)
2581 normal(f)
2582
2582
2583 finally:
2583 finally:
2584 del wlock
2584 del wlock
2585
2585
2586 def rollback(ui, repo):
2586 def rollback(ui, repo):
2587 """roll back the last transaction
2587 """roll back the last transaction
2588
2588
2589 This command should be used with care. There is only one level of
2589 This command should be used with care. There is only one level of
2590 rollback, and there is no way to undo a rollback. It will also
2590 rollback, and there is no way to undo a rollback. It will also
2591 restore the dirstate at the time of the last transaction, losing
2591 restore the dirstate at the time of the last transaction, losing
2592 any dirstate changes since that time.
2592 any dirstate changes since that time.
2593
2593
2594 Transactions are used to encapsulate the effects of all commands
2594 Transactions are used to encapsulate the effects of all commands
2595 that create new changesets or propagate existing changesets into a
2595 that create new changesets or propagate existing changesets into a
2596 repository. For example, the following commands are transactional,
2596 repository. For example, the following commands are transactional,
2597 and their effects can be rolled back:
2597 and their effects can be rolled back:
2598
2598
2599 commit
2599 commit
2600 import
2600 import
2601 pull
2601 pull
2602 push (with this repository as destination)
2602 push (with this repository as destination)
2603 unbundle
2603 unbundle
2604
2604
2605 This command is not intended for use on public repositories. Once
2605 This command is not intended for use on public repositories. Once
2606 changes are visible for pull by other users, rolling a transaction
2606 changes are visible for pull by other users, rolling a transaction
2607 back locally is ineffective (someone else may already have pulled
2607 back locally is ineffective (someone else may already have pulled
2608 the changes). Furthermore, a race is possible with readers of the
2608 the changes). Furthermore, a race is possible with readers of the
2609 repository; for example an in-progress pull from the repository
2609 repository; for example an in-progress pull from the repository
2610 may fail if a rollback is performed.
2610 may fail if a rollback is performed.
2611 """
2611 """
2612 repo.rollback()
2612 repo.rollback()
2613
2613
2614 def root(ui, repo):
2614 def root(ui, repo):
2615 """print the root (top) of the current working dir
2615 """print the root (top) of the current working dir
2616
2616
2617 Print the root directory of the current repository.
2617 Print the root directory of the current repository.
2618 """
2618 """
2619 ui.write(repo.root + "\n")
2619 ui.write(repo.root + "\n")
2620
2620
2621 def serve(ui, repo, **opts):
2621 def serve(ui, repo, **opts):
2622 """export the repository via HTTP
2622 """export the repository via HTTP
2623
2623
2624 Start a local HTTP repository browser and pull server.
2624 Start a local HTTP repository browser and pull server.
2625
2625
2626 By default, the server logs accesses to stdout and errors to
2626 By default, the server logs accesses to stdout and errors to
2627 stderr. Use the "-A" and "-E" options to log to files.
2627 stderr. Use the "-A" and "-E" options to log to files.
2628 """
2628 """
2629
2629
2630 if opts["stdio"]:
2630 if opts["stdio"]:
2631 if repo is None:
2631 if repo is None:
2632 raise error.RepoError(_("There is no Mercurial repository here"
2632 raise error.RepoError(_("There is no Mercurial repository here"
2633 " (.hg not found)"))
2633 " (.hg not found)"))
2634 s = sshserver.sshserver(ui, repo)
2634 s = sshserver.sshserver(ui, repo)
2635 s.serve_forever()
2635 s.serve_forever()
2636
2636
2637 parentui = ui.parentui or ui
2637 parentui = ui.parentui or ui
2638 optlist = ("name templates style address port prefix ipv6"
2638 optlist = ("name templates style address port prefix ipv6"
2639 " accesslog errorlog webdir_conf certificate")
2639 " accesslog errorlog webdir_conf certificate")
2640 for o in optlist.split():
2640 for o in optlist.split():
2641 if opts[o]:
2641 if opts[o]:
2642 parentui.setconfig("web", o, str(opts[o]))
2642 parentui.setconfig("web", o, str(opts[o]))
2643 if (repo is not None) and (repo.ui != parentui):
2643 if (repo is not None) and (repo.ui != parentui):
2644 repo.ui.setconfig("web", o, str(opts[o]))
2644 repo.ui.setconfig("web", o, str(opts[o]))
2645
2645
2646 if repo is None and not ui.config("web", "webdir_conf"):
2646 if repo is None and not ui.config("web", "webdir_conf"):
2647 raise error.RepoError(_("There is no Mercurial repository here"
2647 raise error.RepoError(_("There is no Mercurial repository here"
2648 " (.hg not found)"))
2648 " (.hg not found)"))
2649
2649
2650 class service:
2650 class service:
2651 def init(self):
2651 def init(self):
2652 util.set_signal_handler()
2652 util.set_signal_handler()
2653 self.httpd = hgweb.server.create_server(parentui, repo)
2653 self.httpd = hgweb.server.create_server(parentui, repo)
2654
2654
2655 if not ui.verbose: return
2655 if not ui.verbose: return
2656
2656
2657 if self.httpd.prefix:
2657 if self.httpd.prefix:
2658 prefix = self.httpd.prefix.strip('/') + '/'
2658 prefix = self.httpd.prefix.strip('/') + '/'
2659 else:
2659 else:
2660 prefix = ''
2660 prefix = ''
2661
2661
2662 port = ':%d' % self.httpd.port
2662 port = ':%d' % self.httpd.port
2663 if port == ':80':
2663 if port == ':80':
2664 port = ''
2664 port = ''
2665
2665
2666 bindaddr = self.httpd.addr
2666 bindaddr = self.httpd.addr
2667 if bindaddr == '0.0.0.0':
2667 if bindaddr == '0.0.0.0':
2668 bindaddr = '*'
2668 bindaddr = '*'
2669 elif ':' in bindaddr: # IPv6
2669 elif ':' in bindaddr: # IPv6
2670 bindaddr = '[%s]' % bindaddr
2670 bindaddr = '[%s]' % bindaddr
2671
2671
2672 fqaddr = self.httpd.fqaddr
2672 fqaddr = self.httpd.fqaddr
2673 if ':' in fqaddr:
2673 if ':' in fqaddr:
2674 fqaddr = '[%s]' % fqaddr
2674 fqaddr = '[%s]' % fqaddr
2675 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2675 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2676 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2676 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2677
2677
2678 def run(self):
2678 def run(self):
2679 self.httpd.serve_forever()
2679 self.httpd.serve_forever()
2680
2680
2681 service = service()
2681 service = service()
2682
2682
2683 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2683 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2684
2684
2685 def status(ui, repo, *pats, **opts):
2685 def status(ui, repo, *pats, **opts):
2686 """show changed files in the working directory
2686 """show changed files in the working directory
2687
2687
2688 Show status of files in the repository. If names are given, only
2688 Show status of files in the repository. If names are given, only
2689 files that match are shown. Files that are clean or ignored or
2689 files that match are shown. Files that are clean or ignored or
2690 source of a copy/move operation, are not listed unless -c (clean),
2690 source of a copy/move operation, are not listed unless -c (clean),
2691 -i (ignored), -C (copies) or -A is given. Unless options described
2691 -i (ignored), -C (copies) or -A is given. Unless options described
2692 with "show only ..." are given, the options -mardu are used.
2692 with "show only ..." are given, the options -mardu are used.
2693
2693
2694 Option -q/--quiet hides untracked (unknown and ignored) files
2694 Option -q/--quiet hides untracked (unknown and ignored) files
2695 unless explicitly requested with -u/--unknown or -i/-ignored.
2695 unless explicitly requested with -u/--unknown or -i/-ignored.
2696
2696
2697 NOTE: status may appear to disagree with diff if permissions have
2697 NOTE: status may appear to disagree with diff if permissions have
2698 changed or a merge has occurred. The standard diff format does not
2698 changed or a merge has occurred. The standard diff format does not
2699 report permission changes and diff only reports changes relative
2699 report permission changes and diff only reports changes relative
2700 to one merge parent.
2700 to one merge parent.
2701
2701
2702 If one revision is given, it is used as the base revision.
2702 If one revision is given, it is used as the base revision.
2703 If two revisions are given, the difference between them is shown.
2703 If two revisions are given, the difference between them is shown.
2704
2704
2705 The codes used to show the status of files are:
2705 The codes used to show the status of files are:
2706 M = modified
2706 M = modified
2707 A = added
2707 A = added
2708 R = removed
2708 R = removed
2709 C = clean
2709 C = clean
2710 ! = deleted, but still tracked
2710 ! = deleted, but still tracked
2711 ? = not tracked
2711 ? = not tracked
2712 I = ignored
2712 I = ignored
2713 = the previous added file was copied from here
2713 = the previous added file was copied from here
2714 """
2714 """
2715
2715
2716 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2716 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2717 cwd = (pats and repo.getcwd()) or ''
2717 cwd = (pats and repo.getcwd()) or ''
2718 end = opts.get('print0') and '\0' or '\n'
2718 end = opts.get('print0') and '\0' or '\n'
2719 copy = {}
2719 copy = {}
2720 states = 'modified added removed deleted unknown ignored clean'.split()
2720 states = 'modified added removed deleted unknown ignored clean'.split()
2721 show = [k for k in states if opts.get(k)]
2721 show = [k for k in states if opts.get(k)]
2722 if opts.get('all'):
2722 if opts.get('all'):
2723 show += ui.quiet and (states[:4] + ['clean']) or states
2723 show += ui.quiet and (states[:4] + ['clean']) or states
2724 if not show:
2724 if not show:
2725 show = ui.quiet and states[:4] or states[:5]
2725 show = ui.quiet and states[:4] or states[:5]
2726
2726
2727 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2727 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2728 'ignored' in show, 'clean' in show, 'unknown' in show)
2728 'ignored' in show, 'clean' in show, 'unknown' in show)
2729 changestates = zip(states, 'MAR!?IC', stat)
2729 changestates = zip(states, 'MAR!?IC', stat)
2730
2730
2731 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
2731 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
2732 ctxn = repo[nullid]
2732 ctxn = repo[nullid]
2733 ctx1 = repo[node1]
2733 ctx1 = repo[node1]
2734 ctx2 = repo[node2]
2734 ctx2 = repo[node2]
2735 added = stat[1]
2735 added = stat[1]
2736 if node2 is None:
2736 if node2 is None:
2737 added = stat[0] + stat[1] # merged?
2737 added = stat[0] + stat[1] # merged?
2738
2738
2739 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
2739 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
2740 if k in added:
2740 if k in added:
2741 copy[k] = v
2741 copy[k] = v
2742 elif v in added:
2742 elif v in added:
2743 copy[v] = k
2743 copy[v] = k
2744
2744
2745 for state, char, files in changestates:
2745 for state, char, files in changestates:
2746 if state in show:
2746 if state in show:
2747 format = "%s %%s%s" % (char, end)
2747 format = "%s %%s%s" % (char, end)
2748 if opts.get('no_status'):
2748 if opts.get('no_status'):
2749 format = "%%s%s" % end
2749 format = "%%s%s" % end
2750
2750
2751 for f in files:
2751 for f in files:
2752 ui.write(format % repo.pathto(f, cwd))
2752 ui.write(format % repo.pathto(f, cwd))
2753 if f in copy:
2753 if f in copy:
2754 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2754 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2755
2755
2756 def tag(ui, repo, name1, *names, **opts):
2756 def tag(ui, repo, name1, *names, **opts):
2757 """add one or more tags for the current or given revision
2757 """add one or more tags for the current or given revision
2758
2758
2759 Name a particular revision using <name>.
2759 Name a particular revision using <name>.
2760
2760
2761 Tags are used to name particular revisions of the repository and are
2761 Tags are used to name particular revisions of the repository and are
2762 very useful to compare different revisions, to go back to significant
2762 very useful to compare different revisions, to go back to significant
2763 earlier versions or to mark branch points as releases, etc.
2763 earlier versions or to mark branch points as releases, etc.
2764
2764
2765 If no revision is given, the parent of the working directory is used,
2765 If no revision is given, the parent of the working directory is used,
2766 or tip if no revision is checked out.
2766 or tip if no revision is checked out.
2767
2767
2768 To facilitate version control, distribution, and merging of tags,
2768 To facilitate version control, distribution, and merging of tags,
2769 they are stored as a file named ".hgtags" which is managed
2769 they are stored as a file named ".hgtags" which is managed
2770 similarly to other project files and can be hand-edited if
2770 similarly to other project files and can be hand-edited if
2771 necessary. The file '.hg/localtags' is used for local tags (not
2771 necessary. The file '.hg/localtags' is used for local tags (not
2772 shared among repositories).
2772 shared among repositories).
2773
2773
2774 See 'hg help dates' for a list of formats valid for -d/--date.
2774 See 'hg help dates' for a list of formats valid for -d/--date.
2775 """
2775 """
2776
2776
2777 rev_ = "."
2777 rev_ = "."
2778 names = (name1,) + names
2778 names = (name1,) + names
2779 if len(names) != len(dict.fromkeys(names)):
2779 if len(names) != len(dict.fromkeys(names)):
2780 raise util.Abort(_('tag names must be unique'))
2780 raise util.Abort(_('tag names must be unique'))
2781 for n in names:
2781 for n in names:
2782 if n in ['tip', '.', 'null']:
2782 if n in ['tip', '.', 'null']:
2783 raise util.Abort(_('the name \'%s\' is reserved') % n)
2783 raise util.Abort(_('the name \'%s\' is reserved') % n)
2784 if opts.get('rev') and opts.get('remove'):
2784 if opts.get('rev') and opts.get('remove'):
2785 raise util.Abort(_("--rev and --remove are incompatible"))
2785 raise util.Abort(_("--rev and --remove are incompatible"))
2786 if opts.get('rev'):
2786 if opts.get('rev'):
2787 rev_ = opts['rev']
2787 rev_ = opts['rev']
2788 message = opts.get('message')
2788 message = opts.get('message')
2789 if opts.get('remove'):
2789 if opts.get('remove'):
2790 expectedtype = opts.get('local') and 'local' or 'global'
2790 expectedtype = opts.get('local') and 'local' or 'global'
2791 for n in names:
2791 for n in names:
2792 if not repo.tagtype(n):
2792 if not repo.tagtype(n):
2793 raise util.Abort(_('tag \'%s\' does not exist') % n)
2793 raise util.Abort(_('tag \'%s\' does not exist') % n)
2794 if repo.tagtype(n) != expectedtype:
2794 if repo.tagtype(n) != expectedtype:
2795 raise util.Abort(_('tag \'%s\' is not a %s tag') %
2795 raise util.Abort(_('tag \'%s\' is not a %s tag') %
2796 (n, expectedtype))
2796 (n, expectedtype))
2797 rev_ = nullid
2797 rev_ = nullid
2798 if not message:
2798 if not message:
2799 message = _('Removed tag %s') % ', '.join(names)
2799 message = _('Removed tag %s') % ', '.join(names)
2800 elif not opts.get('force'):
2800 elif not opts.get('force'):
2801 for n in names:
2801 for n in names:
2802 if n in repo.tags():
2802 if n in repo.tags():
2803 raise util.Abort(_('tag \'%s\' already exists '
2803 raise util.Abort(_('tag \'%s\' already exists '
2804 '(use -f to force)') % n)
2804 '(use -f to force)') % n)
2805 if not rev_ and repo.dirstate.parents()[1] != nullid:
2805 if not rev_ and repo.dirstate.parents()[1] != nullid:
2806 raise util.Abort(_('uncommitted merge - please provide a '
2806 raise util.Abort(_('uncommitted merge - please provide a '
2807 'specific revision'))
2807 'specific revision'))
2808 r = repo[rev_].node()
2808 r = repo[rev_].node()
2809
2809
2810 if not message:
2810 if not message:
2811 message = (_('Added tag %s for changeset %s') %
2811 message = (_('Added tag %s for changeset %s') %
2812 (', '.join(names), short(r)))
2812 (', '.join(names), short(r)))
2813
2813
2814 date = opts.get('date')
2814 date = opts.get('date')
2815 if date:
2815 if date:
2816 date = util.parsedate(date)
2816 date = util.parsedate(date)
2817
2817
2818 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
2818 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
2819
2819
2820 def tags(ui, repo):
2820 def tags(ui, repo):
2821 """list repository tags
2821 """list repository tags
2822
2822
2823 This lists both regular and local tags. When the -v/--verbose switch
2823 This lists both regular and local tags. When the -v/--verbose switch
2824 is used, a third column "local" is printed for local tags.
2824 is used, a third column "local" is printed for local tags.
2825 """
2825 """
2826
2826
2827 l = repo.tagslist()
2827 l = repo.tagslist()
2828 l.reverse()
2828 l.reverse()
2829 hexfunc = ui.debugflag and hex or short
2829 hexfunc = ui.debugflag and hex or short
2830 tagtype = ""
2830 tagtype = ""
2831
2831
2832 for t, n in l:
2832 for t, n in l:
2833 if ui.quiet:
2833 if ui.quiet:
2834 ui.write("%s\n" % t)
2834 ui.write("%s\n" % t)
2835 continue
2835 continue
2836
2836
2837 try:
2837 try:
2838 hn = hexfunc(n)
2838 hn = hexfunc(n)
2839 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2839 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2840 except error.LookupError:
2840 except error.LookupError:
2841 r = " ?:%s" % hn
2841 r = " ?:%s" % hn
2842 else:
2842 else:
2843 spaces = " " * (30 - util.colwidth(t))
2843 spaces = " " * (30 - util.colwidth(t))
2844 if ui.verbose:
2844 if ui.verbose:
2845 if repo.tagtype(t) == 'local':
2845 if repo.tagtype(t) == 'local':
2846 tagtype = " local"
2846 tagtype = " local"
2847 else:
2847 else:
2848 tagtype = ""
2848 tagtype = ""
2849 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2849 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2850
2850
2851 def tip(ui, repo, **opts):
2851 def tip(ui, repo, **opts):
2852 """show the tip revision
2852 """show the tip revision
2853
2853
2854 The tip revision (usually just called the tip) is the most
2854 The tip revision (usually just called the tip) is the most
2855 recently added changeset in the repository, the most recently
2855 recently added changeset in the repository, the most recently
2856 changed head.
2856 changed head.
2857
2857
2858 If you have just made a commit, that commit will be the tip. If
2858 If you have just made a commit, that commit will be the tip. If
2859 you have just pulled changes from another repository, the tip of
2859 you have just pulled changes from another repository, the tip of
2860 that repository becomes the current tip. The "tip" tag is special
2860 that repository becomes the current tip. The "tip" tag is special
2861 and cannot be renamed or assigned to a different changeset.
2861 and cannot be renamed or assigned to a different changeset.
2862 """
2862 """
2863 cmdutil.show_changeset(ui, repo, opts).show(repo[len(repo) - 1])
2863 cmdutil.show_changeset(ui, repo, opts).show(repo[len(repo) - 1])
2864
2864
2865 def unbundle(ui, repo, fname1, *fnames, **opts):
2865 def unbundle(ui, repo, fname1, *fnames, **opts):
2866 """apply one or more changegroup files
2866 """apply one or more changegroup files
2867
2867
2868 Apply one or more compressed changegroup files generated by the
2868 Apply one or more compressed changegroup files generated by the
2869 bundle command.
2869 bundle command.
2870 """
2870 """
2871 fnames = (fname1,) + fnames
2871 fnames = (fname1,) + fnames
2872
2872
2873 lock = None
2873 lock = None
2874 try:
2874 try:
2875 lock = repo.lock()
2875 lock = repo.lock()
2876 for fname in fnames:
2876 for fname in fnames:
2877 f = url.open(ui, fname)
2877 f = url.open(ui, fname)
2878 gen = changegroup.readbundle(f, fname)
2878 gen = changegroup.readbundle(f, fname)
2879 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2879 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2880 finally:
2880 finally:
2881 del lock
2881 del lock
2882
2882
2883 return postincoming(ui, repo, modheads, opts.get('update'), None)
2883 return postincoming(ui, repo, modheads, opts.get('update'), None)
2884
2884
2885 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2885 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2886 """update working directory
2886 """update working directory
2887
2887
2888 Update the repository's working directory to the specified revision,
2888 Update the repository's working directory to the specified revision,
2889 or the tip of the current branch if none is specified. Use null as
2889 or the tip of the current branch if none is specified. Use null as
2890 the revision to remove the working copy (like 'hg clone -U').
2890 the revision to remove the working copy (like 'hg clone -U').
2891
2891
2892 When the working dir contains no uncommitted changes, it will be
2892 When the working dir contains no uncommitted changes, it will be
2893 replaced by the state of the requested revision from the repo. When
2893 replaced by the state of the requested revision from the repo. When
2894 the requested revision is on a different branch, the working dir
2894 the requested revision is on a different branch, the working dir
2895 will additionally be switched to that branch.
2895 will additionally be switched to that branch.
2896
2896
2897 When there are uncommitted changes, use option -C to discard them,
2897 When there are uncommitted changes, use option -C to discard them,
2898 forcibly replacing the state of the working dir with the requested
2898 forcibly replacing the state of the working dir with the requested
2899 revision.
2899 revision.
2900
2900
2901 When there are uncommitted changes and option -C is not used, and
2901 When there are uncommitted changes and option -C is not used, and
2902 the parent revision and requested revision are on the same branch,
2902 the parent revision and requested revision are on the same branch,
2903 and one of them is an ancestor of the other, then the new working
2903 and one of them is an ancestor of the other, then the new working
2904 directory will contain the requested revision merged with the
2904 directory will contain the requested revision merged with the
2905 uncommitted changes. Otherwise, the update will fail with a
2905 uncommitted changes. Otherwise, the update will fail with a
2906 suggestion to use 'merge' or 'update -C' instead.
2906 suggestion to use 'merge' or 'update -C' instead.
2907
2907
2908 If you want to update just one file to an older revision, use revert.
2908 If you want to update just one file to an older revision, use revert.
2909
2909
2910 See 'hg help dates' for a list of formats valid for --date.
2910 See 'hg help dates' for a list of formats valid for --date.
2911 """
2911 """
2912 if rev and node:
2912 if rev and node:
2913 raise util.Abort(_("please specify just one revision"))
2913 raise util.Abort(_("please specify just one revision"))
2914
2914
2915 if not rev:
2915 if not rev:
2916 rev = node
2916 rev = node
2917
2917
2918 if date:
2918 if date:
2919 if rev:
2919 if rev:
2920 raise util.Abort(_("you can't specify a revision and a date"))
2920 raise util.Abort(_("you can't specify a revision and a date"))
2921 rev = cmdutil.finddate(ui, repo, date)
2921 rev = cmdutil.finddate(ui, repo, date)
2922
2922
2923 if clean:
2923 if clean:
2924 return hg.clean(repo, rev)
2924 return hg.clean(repo, rev)
2925 else:
2925 else:
2926 return hg.update(repo, rev)
2926 return hg.update(repo, rev)
2927
2927
2928 def verify(ui, repo):
2928 def verify(ui, repo):
2929 """verify the integrity of the repository
2929 """verify the integrity of the repository
2930
2930
2931 Verify the integrity of the current repository.
2931 Verify the integrity of the current repository.
2932
2932
2933 This will perform an extensive check of the repository's
2933 This will perform an extensive check of the repository's
2934 integrity, validating the hashes and checksums of each entry in
2934 integrity, validating the hashes and checksums of each entry in
2935 the changelog, manifest, and tracked files, as well as the
2935 the changelog, manifest, and tracked files, as well as the
2936 integrity of their crosslinks and indices.
2936 integrity of their crosslinks and indices.
2937 """
2937 """
2938 return hg.verify(repo)
2938 return hg.verify(repo)
2939
2939
2940 def version_(ui):
2940 def version_(ui):
2941 """output version and copyright information"""
2941 """output version and copyright information"""
2942 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2942 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2943 % util.version())
2943 % util.version())
2944 ui.status(_(
2944 ui.status(_(
2945 "\nCopyright (C) 2005-2008 Matt Mackall <mpm@selenic.com> and others\n"
2945 "\nCopyright (C) 2005-2008 Matt Mackall <mpm@selenic.com> and others\n"
2946 "This is free software; see the source for copying conditions. "
2946 "This is free software; see the source for copying conditions. "
2947 "There is NO\nwarranty; "
2947 "There is NO\nwarranty; "
2948 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2948 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2949 ))
2949 ))
2950
2950
2951 # Command options and aliases are listed here, alphabetically
2951 # Command options and aliases are listed here, alphabetically
2952
2952
2953 globalopts = [
2953 globalopts = [
2954 ('R', 'repository', '',
2954 ('R', 'repository', '',
2955 _('repository root directory or symbolic path name')),
2955 _('repository root directory or symbolic path name')),
2956 ('', 'cwd', '', _('change working directory')),
2956 ('', 'cwd', '', _('change working directory')),
2957 ('y', 'noninteractive', None,
2957 ('y', 'noninteractive', None,
2958 _('do not prompt, assume \'yes\' for any required answers')),
2958 _('do not prompt, assume \'yes\' for any required answers')),
2959 ('q', 'quiet', None, _('suppress output')),
2959 ('q', 'quiet', None, _('suppress output')),
2960 ('v', 'verbose', None, _('enable additional output')),
2960 ('v', 'verbose', None, _('enable additional output')),
2961 ('', 'config', [], _('set/override config option')),
2961 ('', 'config', [], _('set/override config option')),
2962 ('', 'debug', None, _('enable debugging output')),
2962 ('', 'debug', None, _('enable debugging output')),
2963 ('', 'debugger', None, _('start debugger')),
2963 ('', 'debugger', None, _('start debugger')),
2964 ('', 'encoding', util._encoding, _('set the charset encoding')),
2964 ('', 'encoding', util._encoding, _('set the charset encoding')),
2965 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2965 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2966 ('', 'lsprof', None, _('print improved command execution profile')),
2966 ('', 'lsprof', None, _('print improved command execution profile')),
2967 ('', 'traceback', None, _('print traceback on exception')),
2967 ('', 'traceback', None, _('print traceback on exception')),
2968 ('', 'time', None, _('time how long the command takes')),
2968 ('', 'time', None, _('time how long the command takes')),
2969 ('', 'profile', None, _('print command execution profile')),
2969 ('', 'profile', None, _('print command execution profile')),
2970 ('', 'version', None, _('output version information and exit')),
2970 ('', 'version', None, _('output version information and exit')),
2971 ('h', 'help', None, _('display help and exit')),
2971 ('h', 'help', None, _('display help and exit')),
2972 ]
2972 ]
2973
2973
2974 dryrunopts = [('n', 'dry-run', None,
2974 dryrunopts = [('n', 'dry-run', None,
2975 _('do not perform actions, just print output'))]
2975 _('do not perform actions, just print output'))]
2976
2976
2977 remoteopts = [
2977 remoteopts = [
2978 ('e', 'ssh', '', _('specify ssh command to use')),
2978 ('e', 'ssh', '', _('specify ssh command to use')),
2979 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2979 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2980 ]
2980 ]
2981
2981
2982 walkopts = [
2982 walkopts = [
2983 ('I', 'include', [], _('include names matching the given patterns')),
2983 ('I', 'include', [], _('include names matching the given patterns')),
2984 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2984 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2985 ]
2985 ]
2986
2986
2987 commitopts = [
2987 commitopts = [
2988 ('m', 'message', '', _('use <text> as commit message')),
2988 ('m', 'message', '', _('use <text> as commit message')),
2989 ('l', 'logfile', '', _('read commit message from <file>')),
2989 ('l', 'logfile', '', _('read commit message from <file>')),
2990 ]
2990 ]
2991
2991
2992 commitopts2 = [
2992 commitopts2 = [
2993 ('d', 'date', '', _('record datecode as commit date')),
2993 ('d', 'date', '', _('record datecode as commit date')),
2994 ('u', 'user', '', _('record user as committer')),
2994 ('u', 'user', '', _('record user as committer')),
2995 ]
2995 ]
2996
2996
2997 templateopts = [
2997 templateopts = [
2998 ('', 'style', '', _('display using template map file')),
2998 ('', 'style', '', _('display using template map file')),
2999 ('', 'template', '', _('display with template')),
2999 ('', 'template', '', _('display with template')),
3000 ]
3000 ]
3001
3001
3002 logopts = [
3002 logopts = [
3003 ('p', 'patch', None, _('show patch')),
3003 ('p', 'patch', None, _('show patch')),
3004 ('', 'git', None, _('use git extended diff format')),
3004 ('l', 'limit', '', _('limit number of changes displayed')),
3005 ('l', 'limit', '', _('limit number of changes displayed')),
3005 ('M', 'no-merges', None, _('do not show merges')),
3006 ('M', 'no-merges', None, _('do not show merges')),
3006 ] + templateopts
3007 ] + templateopts
3007
3008
3008 diffopts = [
3009 diffopts = [
3009 ('a', 'text', None, _('treat all files as text')),
3010 ('a', 'text', None, _('treat all files as text')),
3010 ('g', 'git', None, _('use git extended diff format')),
3011 ('g', 'git', None, _('use git extended diff format')),
3011 ('', 'nodates', None, _("don't include dates in diff headers"))
3012 ('', 'nodates', None, _("don't include dates in diff headers"))
3012 ]
3013 ]
3013
3014
3014 diffopts2 = [
3015 diffopts2 = [
3015 ('p', 'show-function', None, _('show which function each change is in')),
3016 ('p', 'show-function', None, _('show which function each change is in')),
3016 ('w', 'ignore-all-space', None,
3017 ('w', 'ignore-all-space', None,
3017 _('ignore white space when comparing lines')),
3018 _('ignore white space when comparing lines')),
3018 ('b', 'ignore-space-change', None,
3019 ('b', 'ignore-space-change', None,
3019 _('ignore changes in the amount of white space')),
3020 _('ignore changes in the amount of white space')),
3020 ('B', 'ignore-blank-lines', None,
3021 ('B', 'ignore-blank-lines', None,
3021 _('ignore changes whose lines are all blank')),
3022 _('ignore changes whose lines are all blank')),
3022 ('U', 'unified', '', _('number of lines of context to show'))
3023 ('U', 'unified', '', _('number of lines of context to show'))
3023 ]
3024 ]
3024
3025
3025 similarityopts = [
3026 similarityopts = [
3026 ('s', 'similarity', '',
3027 ('s', 'similarity', '',
3027 _('guess renamed files by similarity (0<=s<=100)'))
3028 _('guess renamed files by similarity (0<=s<=100)'))
3028 ]
3029 ]
3029
3030
3030 table = {
3031 table = {
3031 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3032 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3032 "addremove":
3033 "addremove":
3033 (addremove, similarityopts + walkopts + dryrunopts,
3034 (addremove, similarityopts + walkopts + dryrunopts,
3034 _('[OPTION]... [FILE]...')),
3035 _('[OPTION]... [FILE]...')),
3035 "^annotate|blame":
3036 "^annotate|blame":
3036 (annotate,
3037 (annotate,
3037 [('r', 'rev', '', _('annotate the specified revision')),
3038 [('r', 'rev', '', _('annotate the specified revision')),
3038 ('f', 'follow', None, _('follow file copies and renames')),
3039 ('f', 'follow', None, _('follow file copies and renames')),
3039 ('a', 'text', None, _('treat all files as text')),
3040 ('a', 'text', None, _('treat all files as text')),
3040 ('u', 'user', None, _('list the author (long with -v)')),
3041 ('u', 'user', None, _('list the author (long with -v)')),
3041 ('d', 'date', None, _('list the date (short with -q)')),
3042 ('d', 'date', None, _('list the date (short with -q)')),
3042 ('n', 'number', None, _('list the revision number (default)')),
3043 ('n', 'number', None, _('list the revision number (default)')),
3043 ('c', 'changeset', None, _('list the changeset')),
3044 ('c', 'changeset', None, _('list the changeset')),
3044 ('l', 'line-number', None,
3045 ('l', 'line-number', None,
3045 _('show line number at the first appearance'))
3046 _('show line number at the first appearance'))
3046 ] + walkopts,
3047 ] + walkopts,
3047 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3048 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3048 "archive":
3049 "archive":
3049 (archive,
3050 (archive,
3050 [('', 'no-decode', None, _('do not pass files through decoders')),
3051 [('', 'no-decode', None, _('do not pass files through decoders')),
3051 ('p', 'prefix', '', _('directory prefix for files in archive')),
3052 ('p', 'prefix', '', _('directory prefix for files in archive')),
3052 ('r', 'rev', '', _('revision to distribute')),
3053 ('r', 'rev', '', _('revision to distribute')),
3053 ('t', 'type', '', _('type of distribution to create')),
3054 ('t', 'type', '', _('type of distribution to create')),
3054 ] + walkopts,
3055 ] + walkopts,
3055 _('[OPTION]... DEST')),
3056 _('[OPTION]... DEST')),
3056 "backout":
3057 "backout":
3057 (backout,
3058 (backout,
3058 [('', 'merge', None,
3059 [('', 'merge', None,
3059 _('merge with old dirstate parent after backout')),
3060 _('merge with old dirstate parent after backout')),
3060 ('', 'parent', '', _('parent to choose when backing out merge')),
3061 ('', 'parent', '', _('parent to choose when backing out merge')),
3061 ('r', 'rev', '', _('revision to backout')),
3062 ('r', 'rev', '', _('revision to backout')),
3062 ] + walkopts + commitopts + commitopts2,
3063 ] + walkopts + commitopts + commitopts2,
3063 _('[OPTION]... [-r] REV')),
3064 _('[OPTION]... [-r] REV')),
3064 "bisect":
3065 "bisect":
3065 (bisect,
3066 (bisect,
3066 [('r', 'reset', False, _('reset bisect state')),
3067 [('r', 'reset', False, _('reset bisect state')),
3067 ('g', 'good', False, _('mark changeset good')),
3068 ('g', 'good', False, _('mark changeset good')),
3068 ('b', 'bad', False, _('mark changeset bad')),
3069 ('b', 'bad', False, _('mark changeset bad')),
3069 ('s', 'skip', False, _('skip testing changeset')),
3070 ('s', 'skip', False, _('skip testing changeset')),
3070 ('c', 'command', '', _('use command to check changeset state')),
3071 ('c', 'command', '', _('use command to check changeset state')),
3071 ('U', 'noupdate', False, _('do not update to target'))],
3072 ('U', 'noupdate', False, _('do not update to target'))],
3072 _("[-gbsr] [-c CMD] [REV]")),
3073 _("[-gbsr] [-c CMD] [REV]")),
3073 "branch":
3074 "branch":
3074 (branch,
3075 (branch,
3075 [('f', 'force', None,
3076 [('f', 'force', None,
3076 _('set branch name even if it shadows an existing branch')),
3077 _('set branch name even if it shadows an existing branch')),
3077 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3078 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3078 _('[-fC] [NAME]')),
3079 _('[-fC] [NAME]')),
3079 "branches":
3080 "branches":
3080 (branches,
3081 (branches,
3081 [('a', 'active', False,
3082 [('a', 'active', False,
3082 _('show only branches that have unmerged heads'))],
3083 _('show only branches that have unmerged heads'))],
3083 _('[-a]')),
3084 _('[-a]')),
3084 "bundle":
3085 "bundle":
3085 (bundle,
3086 (bundle,
3086 [('f', 'force', None,
3087 [('f', 'force', None,
3087 _('run even when remote repository is unrelated')),
3088 _('run even when remote repository is unrelated')),
3088 ('r', 'rev', [],
3089 ('r', 'rev', [],
3089 _('a changeset up to which you would like to bundle')),
3090 _('a changeset up to which you would like to bundle')),
3090 ('', 'base', [],
3091 ('', 'base', [],
3091 _('a base changeset to specify instead of a destination')),
3092 _('a base changeset to specify instead of a destination')),
3092 ('a', 'all', None, _('bundle all changesets in the repository')),
3093 ('a', 'all', None, _('bundle all changesets in the repository')),
3093 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3094 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3094 ] + remoteopts,
3095 ] + remoteopts,
3095 _('[-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3096 _('[-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3096 "cat":
3097 "cat":
3097 (cat,
3098 (cat,
3098 [('o', 'output', '', _('print output to file with formatted name')),
3099 [('o', 'output', '', _('print output to file with formatted name')),
3099 ('r', 'rev', '', _('print the given revision')),
3100 ('r', 'rev', '', _('print the given revision')),
3100 ('', 'decode', None, _('apply any matching decode filter')),
3101 ('', 'decode', None, _('apply any matching decode filter')),
3101 ] + walkopts,
3102 ] + walkopts,
3102 _('[OPTION]... FILE...')),
3103 _('[OPTION]... FILE...')),
3103 "^clone":
3104 "^clone":
3104 (clone,
3105 (clone,
3105 [('U', 'noupdate', None,
3106 [('U', 'noupdate', None,
3106 _('the clone will only contain a repository (no working copy)')),
3107 _('the clone will only contain a repository (no working copy)')),
3107 ('r', 'rev', [],
3108 ('r', 'rev', [],
3108 _('a changeset you would like to have after cloning')),
3109 _('a changeset you would like to have after cloning')),
3109 ('', 'pull', None, _('use pull protocol to copy metadata')),
3110 ('', 'pull', None, _('use pull protocol to copy metadata')),
3110 ('', 'uncompressed', None,
3111 ('', 'uncompressed', None,
3111 _('use uncompressed transfer (fast over LAN)')),
3112 _('use uncompressed transfer (fast over LAN)')),
3112 ] + remoteopts,
3113 ] + remoteopts,
3113 _('[OPTION]... SOURCE [DEST]')),
3114 _('[OPTION]... SOURCE [DEST]')),
3114 "^commit|ci":
3115 "^commit|ci":
3115 (commit,
3116 (commit,
3116 [('A', 'addremove', None,
3117 [('A', 'addremove', None,
3117 _('mark new/missing files as added/removed before committing')),
3118 _('mark new/missing files as added/removed before committing')),
3118 ('', 'close-branch', None,
3119 ('', 'close-branch', None,
3119 _('mark a branch as closed, hiding it from the branch list')),
3120 _('mark a branch as closed, hiding it from the branch list')),
3120 ] + walkopts + commitopts + commitopts2,
3121 ] + walkopts + commitopts + commitopts2,
3121 _('[OPTION]... [FILE]...')),
3122 _('[OPTION]... [FILE]...')),
3122 "copy|cp":
3123 "copy|cp":
3123 (copy,
3124 (copy,
3124 [('A', 'after', None, _('record a copy that has already occurred')),
3125 [('A', 'after', None, _('record a copy that has already occurred')),
3125 ('f', 'force', None,
3126 ('f', 'force', None,
3126 _('forcibly copy over an existing managed file')),
3127 _('forcibly copy over an existing managed file')),
3127 ] + walkopts + dryrunopts,
3128 ] + walkopts + dryrunopts,
3128 _('[OPTION]... [SOURCE]... DEST')),
3129 _('[OPTION]... [SOURCE]... DEST')),
3129 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
3130 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
3130 "debugcheckstate": (debugcheckstate, []),
3131 "debugcheckstate": (debugcheckstate, []),
3131 "debugcomplete":
3132 "debugcomplete":
3132 (debugcomplete,
3133 (debugcomplete,
3133 [('o', 'options', None, _('show the command options'))],
3134 [('o', 'options', None, _('show the command options'))],
3134 _('[-o] CMD')),
3135 _('[-o] CMD')),
3135 "debugdate":
3136 "debugdate":
3136 (debugdate,
3137 (debugdate,
3137 [('e', 'extended', None, _('try extended date formats'))],
3138 [('e', 'extended', None, _('try extended date formats'))],
3138 _('[-e] DATE [RANGE]')),
3139 _('[-e] DATE [RANGE]')),
3139 "debugdata": (debugdata, [], _('FILE REV')),
3140 "debugdata": (debugdata, [], _('FILE REV')),
3140 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
3141 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
3141 "debugindex": (debugindex, [], _('FILE')),
3142 "debugindex": (debugindex, [], _('FILE')),
3142 "debugindexdot": (debugindexdot, [], _('FILE')),
3143 "debugindexdot": (debugindexdot, [], _('FILE')),
3143 "debuginstall": (debuginstall, []),
3144 "debuginstall": (debuginstall, []),
3144 "debugrawcommit|rawcommit":
3145 "debugrawcommit|rawcommit":
3145 (rawcommit,
3146 (rawcommit,
3146 [('p', 'parent', [], _('parent')),
3147 [('p', 'parent', [], _('parent')),
3147 ('F', 'files', '', _('file list'))
3148 ('F', 'files', '', _('file list'))
3148 ] + commitopts + commitopts2,
3149 ] + commitopts + commitopts2,
3149 _('[OPTION]... [FILE]...')),
3150 _('[OPTION]... [FILE]...')),
3150 "debugrebuildstate":
3151 "debugrebuildstate":
3151 (debugrebuildstate,
3152 (debugrebuildstate,
3152 [('r', 'rev', '', _('revision to rebuild to'))],
3153 [('r', 'rev', '', _('revision to rebuild to'))],
3153 _('[-r REV] [REV]')),
3154 _('[-r REV] [REV]')),
3154 "debugrename":
3155 "debugrename":
3155 (debugrename,
3156 (debugrename,
3156 [('r', 'rev', '', _('revision to debug'))],
3157 [('r', 'rev', '', _('revision to debug'))],
3157 _('[-r REV] FILE')),
3158 _('[-r REV] FILE')),
3158 "debugsetparents":
3159 "debugsetparents":
3159 (debugsetparents, [], _('REV1 [REV2]')),
3160 (debugsetparents, [], _('REV1 [REV2]')),
3160 "debugstate":
3161 "debugstate":
3161 (debugstate,
3162 (debugstate,
3162 [('', 'nodates', None, _('do not display the saved mtime'))],
3163 [('', 'nodates', None, _('do not display the saved mtime'))],
3163 _('[OPTION]...')),
3164 _('[OPTION]...')),
3164 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
3165 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
3165 "^diff":
3166 "^diff":
3166 (diff,
3167 (diff,
3167 [('r', 'rev', [], _('revision')),
3168 [('r', 'rev', [], _('revision')),
3168 ('c', 'change', '', _('change made by revision'))
3169 ('c', 'change', '', _('change made by revision'))
3169 ] + diffopts + diffopts2 + walkopts,
3170 ] + diffopts + diffopts2 + walkopts,
3170 _('[OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3171 _('[OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3171 "^export":
3172 "^export":
3172 (export,
3173 (export,
3173 [('o', 'output', '', _('print output to file with formatted name')),
3174 [('o', 'output', '', _('print output to file with formatted name')),
3174 ('', 'switch-parent', None, _('diff against the second parent'))
3175 ('', 'switch-parent', None, _('diff against the second parent'))
3175 ] + diffopts,
3176 ] + diffopts,
3176 _('[OPTION]... [-o OUTFILESPEC] REV...')),
3177 _('[OPTION]... [-o OUTFILESPEC] REV...')),
3177 "grep":
3178 "grep":
3178 (grep,
3179 (grep,
3179 [('0', 'print0', None, _('end fields with NUL')),
3180 [('0', 'print0', None, _('end fields with NUL')),
3180 ('', 'all', None, _('print all revisions that match')),
3181 ('', 'all', None, _('print all revisions that match')),
3181 ('f', 'follow', None,
3182 ('f', 'follow', None,
3182 _('follow changeset history, or file history across copies and renames')),
3183 _('follow changeset history, or file history across copies and renames')),
3183 ('i', 'ignore-case', None, _('ignore case when matching')),
3184 ('i', 'ignore-case', None, _('ignore case when matching')),
3184 ('l', 'files-with-matches', None,
3185 ('l', 'files-with-matches', None,
3185 _('print only filenames and revs that match')),
3186 _('print only filenames and revs that match')),
3186 ('n', 'line-number', None, _('print matching line numbers')),
3187 ('n', 'line-number', None, _('print matching line numbers')),
3187 ('r', 'rev', [], _('search in given revision range')),
3188 ('r', 'rev', [], _('search in given revision range')),
3188 ('u', 'user', None, _('list the author (long with -v)')),
3189 ('u', 'user', None, _('list the author (long with -v)')),
3189 ('d', 'date', None, _('list the date (short with -q)')),
3190 ('d', 'date', None, _('list the date (short with -q)')),
3190 ] + walkopts,
3191 ] + walkopts,
3191 _('[OPTION]... PATTERN [FILE]...')),
3192 _('[OPTION]... PATTERN [FILE]...')),
3192 "heads":
3193 "heads":
3193 (heads,
3194 (heads,
3194 [('r', 'rev', '', _('show only heads which are descendants of rev')),
3195 [('r', 'rev', '', _('show only heads which are descendants of rev')),
3195 ('a', 'active', False,
3196 ('a', 'active', False,
3196 _('show only the active heads from open branches')),
3197 _('show only the active heads from open branches')),
3197 ] + templateopts,
3198 ] + templateopts,
3198 _('[-r REV] [REV]...')),
3199 _('[-r REV] [REV]...')),
3199 "help": (help_, [], _('[TOPIC]')),
3200 "help": (help_, [], _('[TOPIC]')),
3200 "identify|id":
3201 "identify|id":
3201 (identify,
3202 (identify,
3202 [('r', 'rev', '', _('identify the specified rev')),
3203 [('r', 'rev', '', _('identify the specified rev')),
3203 ('n', 'num', None, _('show local revision number')),
3204 ('n', 'num', None, _('show local revision number')),
3204 ('i', 'id', None, _('show global revision id')),
3205 ('i', 'id', None, _('show global revision id')),
3205 ('b', 'branch', None, _('show branch')),
3206 ('b', 'branch', None, _('show branch')),
3206 ('t', 'tags', None, _('show tags'))],
3207 ('t', 'tags', None, _('show tags'))],
3207 _('[-nibt] [-r REV] [SOURCE]')),
3208 _('[-nibt] [-r REV] [SOURCE]')),
3208 "import|patch":
3209 "import|patch":
3209 (import_,
3210 (import_,
3210 [('p', 'strip', 1,
3211 [('p', 'strip', 1,
3211 _('directory strip option for patch. This has the same\n'
3212 _('directory strip option for patch. This has the same\n'
3212 'meaning as the corresponding patch option')),
3213 'meaning as the corresponding patch option')),
3213 ('b', 'base', '', _('base path')),
3214 ('b', 'base', '', _('base path')),
3214 ('f', 'force', None,
3215 ('f', 'force', None,
3215 _('skip check for outstanding uncommitted changes')),
3216 _('skip check for outstanding uncommitted changes')),
3216 ('', 'no-commit', None, _("don't commit, just update the working directory")),
3217 ('', 'no-commit', None, _("don't commit, just update the working directory")),
3217 ('', 'exact', None,
3218 ('', 'exact', None,
3218 _('apply patch to the nodes from which it was generated')),
3219 _('apply patch to the nodes from which it was generated')),
3219 ('', 'import-branch', None,
3220 ('', 'import-branch', None,
3220 _('Use any branch information in patch (implied by --exact)'))] +
3221 _('Use any branch information in patch (implied by --exact)'))] +
3221 commitopts + commitopts2 + similarityopts,
3222 commitopts + commitopts2 + similarityopts,
3222 _('[OPTION]... PATCH...')),
3223 _('[OPTION]... PATCH...')),
3223 "incoming|in":
3224 "incoming|in":
3224 (incoming,
3225 (incoming,
3225 [('f', 'force', None,
3226 [('f', 'force', None,
3226 _('run even when remote repository is unrelated')),
3227 _('run even when remote repository is unrelated')),
3227 ('n', 'newest-first', None, _('show newest record first')),
3228 ('n', 'newest-first', None, _('show newest record first')),
3228 ('', 'bundle', '', _('file to store the bundles into')),
3229 ('', 'bundle', '', _('file to store the bundles into')),
3229 ('r', 'rev', [],
3230 ('r', 'rev', [],
3230 _('a specific revision up to which you would like to pull')),
3231 _('a specific revision up to which you would like to pull')),
3231 ] + logopts + remoteopts,
3232 ] + logopts + remoteopts,
3232 _('[-p] [-n] [-M] [-f] [-r REV]...'
3233 _('[-p] [-n] [-M] [-f] [-r REV]...'
3233 ' [--bundle FILENAME] [SOURCE]')),
3234 ' [--bundle FILENAME] [SOURCE]')),
3234 "^init":
3235 "^init":
3235 (init,
3236 (init,
3236 remoteopts,
3237 remoteopts,
3237 _('[-e CMD] [--remotecmd CMD] [DEST]')),
3238 _('[-e CMD] [--remotecmd CMD] [DEST]')),
3238 "locate":
3239 "locate":
3239 (locate,
3240 (locate,
3240 [('r', 'rev', '', _('search the repository as it stood at rev')),
3241 [('r', 'rev', '', _('search the repository as it stood at rev')),
3241 ('0', 'print0', None,
3242 ('0', 'print0', None,
3242 _('end filenames with NUL, for use with xargs')),
3243 _('end filenames with NUL, for use with xargs')),
3243 ('f', 'fullpath', None,
3244 ('f', 'fullpath', None,
3244 _('print complete paths from the filesystem root')),
3245 _('print complete paths from the filesystem root')),
3245 ] + walkopts,
3246 ] + walkopts,
3246 _('[OPTION]... [PATTERN]...')),
3247 _('[OPTION]... [PATTERN]...')),
3247 "^log|history":
3248 "^log|history":
3248 (log,
3249 (log,
3249 [('f', 'follow', None,
3250 [('f', 'follow', None,
3250 _('follow changeset history, or file history across copies and renames')),
3251 _('follow changeset history, or file history across copies and renames')),
3251 ('', 'follow-first', None,
3252 ('', 'follow-first', None,
3252 _('only follow the first parent of merge changesets')),
3253 _('only follow the first parent of merge changesets')),
3253 ('d', 'date', '', _('show revs matching date spec')),
3254 ('d', 'date', '', _('show revs matching date spec')),
3254 ('C', 'copies', None, _('show copied files')),
3255 ('C', 'copies', None, _('show copied files')),
3255 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3256 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3256 ('r', 'rev', [], _('show the specified revision or range')),
3257 ('r', 'rev', [], _('show the specified revision or range')),
3257 ('', 'removed', None, _('include revs where files were removed')),
3258 ('', 'removed', None, _('include revs where files were removed')),
3258 ('m', 'only-merges', None, _('show only merges')),
3259 ('m', 'only-merges', None, _('show only merges')),
3259 ('u', 'user', [], _('revs committed by user')),
3260 ('u', 'user', [], _('revs committed by user')),
3260 ('b', 'only-branch', [],
3261 ('b', 'only-branch', [],
3261 _('show only changesets within the given named branch')),
3262 _('show only changesets within the given named branch')),
3262 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3263 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3263 ] + logopts + walkopts,
3264 ] + logopts + walkopts,
3264 _('[OPTION]... [FILE]')),
3265 _('[OPTION]... [FILE]')),
3265 "manifest":
3266 "manifest":
3266 (manifest,
3267 (manifest,
3267 [('r', 'rev', '', _('revision to display'))],
3268 [('r', 'rev', '', _('revision to display'))],
3268 _('[-r REV]')),
3269 _('[-r REV]')),
3269 "^merge":
3270 "^merge":
3270 (merge,
3271 (merge,
3271 [('f', 'force', None, _('force a merge with outstanding changes')),
3272 [('f', 'force', None, _('force a merge with outstanding changes')),
3272 ('r', 'rev', '', _('revision to merge')),
3273 ('r', 'rev', '', _('revision to merge')),
3273 ],
3274 ],
3274 _('[-f] [[-r] REV]')),
3275 _('[-f] [[-r] REV]')),
3275 "outgoing|out":
3276 "outgoing|out":
3276 (outgoing,
3277 (outgoing,
3277 [('f', 'force', None,
3278 [('f', 'force', None,
3278 _('run even when remote repository is unrelated')),
3279 _('run even when remote repository is unrelated')),
3279 ('r', 'rev', [],
3280 ('r', 'rev', [],
3280 _('a specific revision up to which you would like to push')),
3281 _('a specific revision up to which you would like to push')),
3281 ('n', 'newest-first', None, _('show newest record first')),
3282 ('n', 'newest-first', None, _('show newest record first')),
3282 ] + logopts + remoteopts,
3283 ] + logopts + remoteopts,
3283 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3284 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3284 "^parents":
3285 "^parents":
3285 (parents,
3286 (parents,
3286 [('r', 'rev', '', _('show parents from the specified rev')),
3287 [('r', 'rev', '', _('show parents from the specified rev')),
3287 ] + templateopts,
3288 ] + templateopts,
3288 _('hg parents [-r REV] [FILE]')),
3289 _('hg parents [-r REV] [FILE]')),
3289 "paths": (paths, [], _('[NAME]')),
3290 "paths": (paths, [], _('[NAME]')),
3290 "^pull":
3291 "^pull":
3291 (pull,
3292 (pull,
3292 [('u', 'update', None,
3293 [('u', 'update', None,
3293 _('update to new tip if changesets were pulled')),
3294 _('update to new tip if changesets were pulled')),
3294 ('f', 'force', None,
3295 ('f', 'force', None,
3295 _('run even when remote repository is unrelated')),
3296 _('run even when remote repository is unrelated')),
3296 ('r', 'rev', [],
3297 ('r', 'rev', [],
3297 _('a specific revision up to which you would like to pull')),
3298 _('a specific revision up to which you would like to pull')),
3298 ] + remoteopts,
3299 ] + remoteopts,
3299 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3300 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3300 "^push":
3301 "^push":
3301 (push,
3302 (push,
3302 [('f', 'force', None, _('force push')),
3303 [('f', 'force', None, _('force push')),
3303 ('r', 'rev', [],
3304 ('r', 'rev', [],
3304 _('a specific revision up to which you would like to push')),
3305 _('a specific revision up to which you would like to push')),
3305 ] + remoteopts,
3306 ] + remoteopts,
3306 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3307 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3307 "recover": (recover, []),
3308 "recover": (recover, []),
3308 "^remove|rm":
3309 "^remove|rm":
3309 (remove,
3310 (remove,
3310 [('A', 'after', None, _('record delete for missing files')),
3311 [('A', 'after', None, _('record delete for missing files')),
3311 ('f', 'force', None,
3312 ('f', 'force', None,
3312 _('remove (and delete) file even if added or modified')),
3313 _('remove (and delete) file even if added or modified')),
3313 ] + walkopts,
3314 ] + walkopts,
3314 _('[OPTION]... FILE...')),
3315 _('[OPTION]... FILE...')),
3315 "rename|mv":
3316 "rename|mv":
3316 (rename,
3317 (rename,
3317 [('A', 'after', None, _('record a rename that has already occurred')),
3318 [('A', 'after', None, _('record a rename that has already occurred')),
3318 ('f', 'force', None,
3319 ('f', 'force', None,
3319 _('forcibly copy over an existing managed file')),
3320 _('forcibly copy over an existing managed file')),
3320 ] + walkopts + dryrunopts,
3321 ] + walkopts + dryrunopts,
3321 _('[OPTION]... SOURCE... DEST')),
3322 _('[OPTION]... SOURCE... DEST')),
3322 "resolve":
3323 "resolve":
3323 (resolve,
3324 (resolve,
3324 [('a', 'all', None, _('remerge all unresolved files')),
3325 [('a', 'all', None, _('remerge all unresolved files')),
3325 ('l', 'list', None, _('list state of files needing merge')),
3326 ('l', 'list', None, _('list state of files needing merge')),
3326 ('m', 'mark', None, _('mark files as resolved')),
3327 ('m', 'mark', None, _('mark files as resolved')),
3327 ('u', 'unmark', None, _('unmark files as resolved'))],
3328 ('u', 'unmark', None, _('unmark files as resolved'))],
3328 _('[OPTION]... [FILE]...')),
3329 _('[OPTION]... [FILE]...')),
3329 "revert":
3330 "revert":
3330 (revert,
3331 (revert,
3331 [('a', 'all', None, _('revert all changes when no arguments given')),
3332 [('a', 'all', None, _('revert all changes when no arguments given')),
3332 ('d', 'date', '', _('tipmost revision matching date')),
3333 ('d', 'date', '', _('tipmost revision matching date')),
3333 ('r', 'rev', '', _('revision to revert to')),
3334 ('r', 'rev', '', _('revision to revert to')),
3334 ('', 'no-backup', None, _('do not save backup copies of files')),
3335 ('', 'no-backup', None, _('do not save backup copies of files')),
3335 ] + walkopts + dryrunopts,
3336 ] + walkopts + dryrunopts,
3336 _('[OPTION]... [-r REV] [NAME]...')),
3337 _('[OPTION]... [-r REV] [NAME]...')),
3337 "rollback": (rollback, []),
3338 "rollback": (rollback, []),
3338 "root": (root, []),
3339 "root": (root, []),
3339 "^serve":
3340 "^serve":
3340 (serve,
3341 (serve,
3341 [('A', 'accesslog', '', _('name of access log file to write to')),
3342 [('A', 'accesslog', '', _('name of access log file to write to')),
3342 ('d', 'daemon', None, _('run server in background')),
3343 ('d', 'daemon', None, _('run server in background')),
3343 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3344 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3344 ('E', 'errorlog', '', _('name of error log file to write to')),
3345 ('E', 'errorlog', '', _('name of error log file to write to')),
3345 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3346 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3346 ('a', 'address', '', _('address to listen on (default: all interfaces)')),
3347 ('a', 'address', '', _('address to listen on (default: all interfaces)')),
3347 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3348 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3348 ('n', 'name', '',
3349 ('n', 'name', '',
3349 _('name to show in web pages (default: working dir)')),
3350 _('name to show in web pages (default: working dir)')),
3350 ('', 'webdir-conf', '', _('name of the webdir config file'
3351 ('', 'webdir-conf', '', _('name of the webdir config file'
3351 ' (serve more than one repo)')),
3352 ' (serve more than one repo)')),
3352 ('', 'pid-file', '', _('name of file to write process ID to')),
3353 ('', 'pid-file', '', _('name of file to write process ID to')),
3353 ('', 'stdio', None, _('for remote clients')),
3354 ('', 'stdio', None, _('for remote clients')),
3354 ('t', 'templates', '', _('web templates to use')),
3355 ('t', 'templates', '', _('web templates to use')),
3355 ('', 'style', '', _('template style to use')),
3356 ('', 'style', '', _('template style to use')),
3356 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3357 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3357 ('', 'certificate', '', _('SSL certificate file'))],
3358 ('', 'certificate', '', _('SSL certificate file'))],
3358 _('[OPTION]...')),
3359 _('[OPTION]...')),
3359 "showconfig|debugconfig":
3360 "showconfig|debugconfig":
3360 (showconfig,
3361 (showconfig,
3361 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3362 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3362 _('[-u] [NAME]...')),
3363 _('[-u] [NAME]...')),
3363 "^status|st":
3364 "^status|st":
3364 (status,
3365 (status,
3365 [('A', 'all', None, _('show status of all files')),
3366 [('A', 'all', None, _('show status of all files')),
3366 ('m', 'modified', None, _('show only modified files')),
3367 ('m', 'modified', None, _('show only modified files')),
3367 ('a', 'added', None, _('show only added files')),
3368 ('a', 'added', None, _('show only added files')),
3368 ('r', 'removed', None, _('show only removed files')),
3369 ('r', 'removed', None, _('show only removed files')),
3369 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3370 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3370 ('c', 'clean', None, _('show only files without changes')),
3371 ('c', 'clean', None, _('show only files without changes')),
3371 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3372 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3372 ('i', 'ignored', None, _('show only ignored files')),
3373 ('i', 'ignored', None, _('show only ignored files')),
3373 ('n', 'no-status', None, _('hide status prefix')),
3374 ('n', 'no-status', None, _('hide status prefix')),
3374 ('C', 'copies', None, _('show source of copied files')),
3375 ('C', 'copies', None, _('show source of copied files')),
3375 ('0', 'print0', None,
3376 ('0', 'print0', None,
3376 _('end filenames with NUL, for use with xargs')),
3377 _('end filenames with NUL, for use with xargs')),
3377 ('', 'rev', [], _('show difference from revision')),
3378 ('', 'rev', [], _('show difference from revision')),
3378 ] + walkopts,
3379 ] + walkopts,
3379 _('[OPTION]... [FILE]...')),
3380 _('[OPTION]... [FILE]...')),
3380 "tag":
3381 "tag":
3381 (tag,
3382 (tag,
3382 [('f', 'force', None, _('replace existing tag')),
3383 [('f', 'force', None, _('replace existing tag')),
3383 ('l', 'local', None, _('make the tag local')),
3384 ('l', 'local', None, _('make the tag local')),
3384 ('r', 'rev', '', _('revision to tag')),
3385 ('r', 'rev', '', _('revision to tag')),
3385 ('', 'remove', None, _('remove a tag')),
3386 ('', 'remove', None, _('remove a tag')),
3386 # -l/--local is already there, commitopts cannot be used
3387 # -l/--local is already there, commitopts cannot be used
3387 ('m', 'message', '', _('use <text> as commit message')),
3388 ('m', 'message', '', _('use <text> as commit message')),
3388 ] + commitopts2,
3389 ] + commitopts2,
3389 _('[-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3390 _('[-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3390 "tags": (tags, []),
3391 "tags": (tags, []),
3391 "tip":
3392 "tip":
3392 (tip,
3393 (tip,
3393 [('p', 'patch', None, _('show patch')),
3394 [('p', 'patch', None, _('show patch')),
3395 ('', 'git', None, _('use git extended diff format')),
3394 ] + templateopts,
3396 ] + templateopts,
3395 _('[-p]')),
3397 _('[-p]')),
3396 "unbundle":
3398 "unbundle":
3397 (unbundle,
3399 (unbundle,
3398 [('u', 'update', None,
3400 [('u', 'update', None,
3399 _('update to new tip if changesets were unbundled'))],
3401 _('update to new tip if changesets were unbundled'))],
3400 _('[-u] FILE...')),
3402 _('[-u] FILE...')),
3401 "^update|up|checkout|co":
3403 "^update|up|checkout|co":
3402 (update,
3404 (update,
3403 [('C', 'clean', None, _('overwrite locally modified files (no backup)')),
3405 [('C', 'clean', None, _('overwrite locally modified files (no backup)')),
3404 ('d', 'date', '', _('tipmost revision matching date')),
3406 ('d', 'date', '', _('tipmost revision matching date')),
3405 ('r', 'rev', '', _('revision'))],
3407 ('r', 'rev', '', _('revision'))],
3406 _('[-C] [-d DATE] [[-r] REV]')),
3408 _('[-C] [-d DATE] [[-r] REV]')),
3407 "verify": (verify, []),
3409 "verify": (verify, []),
3408 "version": (version_, []),
3410 "version": (version_, []),
3409 }
3411 }
3410
3412
3411 norepo = ("clone init version help debugcomplete debugdata"
3413 norepo = ("clone init version help debugcomplete debugdata"
3412 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3414 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3413 optionalrepo = ("identify paths serve showconfig debugancestor")
3415 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,52 +1,54 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 mkdir test
3 mkdir test
4 cd test
4 cd test
5 hg init
5 hg init
6 for i in 0 1 2 3 4 5 6 7 8; do
6 for i in 0 1 2 3 4 5 6 7 8; do
7 echo $i >> foo
7 echo $i >> foo
8 hg commit -A -m $i -d "1000000 0"
8 hg commit -A -m $i -d "1000000 0"
9 done
9 done
10 hg verify
10 hg verify
11 hg serve -p $HGPORT -d --pid-file=hg.pid
11 hg serve -p $HGPORT -d --pid-file=hg.pid
12 cat hg.pid >> $DAEMON_PIDS
12 cat hg.pid >> $DAEMON_PIDS
13 cd ..
13 cd ..
14
14
15 hg init new
15 hg init new
16 # http incoming
16 # http incoming
17 http_proxy= hg -R new incoming http://localhost:$HGPORT/ | sed -e 's,:[0-9][0-9]*/,/,'
17 http_proxy= hg -R new incoming http://localhost:$HGPORT/ | sed -e 's,:[0-9][0-9]*/,/,'
18 http_proxy= hg -R new incoming -r 4 http://localhost:$HGPORT/ | sed -e 's,:[0-9][0-9]*/,/,'
18 http_proxy= hg -R new incoming -r 4 http://localhost:$HGPORT/ | sed -e 's,:[0-9][0-9]*/,/,'
19 # local incoming
19 # local incoming
20 hg -R new incoming test
20 hg -R new incoming test
21 hg -R new incoming -r 4 test
21 hg -R new incoming -r 4 test
22 echo "% limit to 2 changesets"
22 echo "% limit to 2 changesets"
23 hg -R new incoming -l 2 test
23 hg -R new incoming -l 2 test
24 echo "% limit to 2 changesets, test with -p --git"
25 hg -R new incoming -l 2 -p --git test
24
26
25 # test with --bundle
27 # test with --bundle
26 http_proxy= hg -R new incoming --bundle test.hg http://localhost:$HGPORT/ | sed -e 's,:[0-9][0-9]*/,/,'
28 http_proxy= hg -R new incoming --bundle test.hg http://localhost:$HGPORT/ | sed -e 's,:[0-9][0-9]*/,/,'
27 hg -R new incoming --bundle test2.hg test
29 hg -R new incoming --bundle test2.hg test
28
30
29 # test the resulting bundles
31 # test the resulting bundles
30 hg init temp
32 hg init temp
31 hg init temp2
33 hg init temp2
32 hg -R temp unbundle test.hg
34 hg -R temp unbundle test.hg
33 hg -R temp2 unbundle test2.hg
35 hg -R temp2 unbundle test2.hg
34 hg -R temp tip
36 hg -R temp tip
35 hg -R temp2 tip
37 hg -R temp2 tip
36
38
37 rm -r temp temp2 new
39 rm -r temp temp2 new
38
40
39 # test outgoing
41 # test outgoing
40 hg clone test test-dev
42 hg clone test test-dev
41 cd test-dev
43 cd test-dev
42 for i in 9 10 11 12 13; do
44 for i in 9 10 11 12 13; do
43 echo $i >> foo
45 echo $i >> foo
44 hg commit -A -m $i -d "1000000 0"
46 hg commit -A -m $i -d "1000000 0"
45 done
47 done
46 hg verify
48 hg verify
47 cd ..
49 cd ..
48 hg -R test-dev outgoing test
50 hg -R test-dev outgoing test
49 echo "% limit to 3 changesets"
51 echo "% limit to 3 changesets"
50 hg -R test-dev outgoing -l 3 test
52 hg -R test-dev outgoing -l 3 test
51 http_proxy= hg -R test-dev outgoing http://localhost:$HGPORT/ | sed -e 's,:[0-9][0-9]*/,/,'
53 http_proxy= hg -R test-dev outgoing http://localhost:$HGPORT/ | sed -e 's,:[0-9][0-9]*/,/,'
52 http_proxy= hg -R test-dev outgoing -r 11 http://localhost:$HGPORT/ | sed -e 's,:[0-9][0-9]*/,/,'
54 http_proxy= hg -R test-dev outgoing -r 11 http://localhost:$HGPORT/ | sed -e 's,:[0-9][0-9]*/,/,'
@@ -1,379 +1,405 b''
1 adding foo
1 adding foo
2 checking changesets
2 checking changesets
3 checking manifests
3 checking manifests
4 crosschecking files in changesets and manifests
4 crosschecking files in changesets and manifests
5 checking files
5 checking files
6 1 files, 9 changesets, 9 total revisions
6 1 files, 9 changesets, 9 total revisions
7 comparing with http://localhost/
7 comparing with http://localhost/
8 changeset: 0:9cb21d99fe27
8 changeset: 0:9cb21d99fe27
9 user: test
9 user: test
10 date: Mon Jan 12 13:46:40 1970 +0000
10 date: Mon Jan 12 13:46:40 1970 +0000
11 summary: 0
11 summary: 0
12
12
13 changeset: 1:d717f5dfad6a
13 changeset: 1:d717f5dfad6a
14 user: test
14 user: test
15 date: Mon Jan 12 13:46:40 1970 +0000
15 date: Mon Jan 12 13:46:40 1970 +0000
16 summary: 1
16 summary: 1
17
17
18 changeset: 2:c0d6b86da426
18 changeset: 2:c0d6b86da426
19 user: test
19 user: test
20 date: Mon Jan 12 13:46:40 1970 +0000
20 date: Mon Jan 12 13:46:40 1970 +0000
21 summary: 2
21 summary: 2
22
22
23 changeset: 3:dfacbd43b3fe
23 changeset: 3:dfacbd43b3fe
24 user: test
24 user: test
25 date: Mon Jan 12 13:46:40 1970 +0000
25 date: Mon Jan 12 13:46:40 1970 +0000
26 summary: 3
26 summary: 3
27
27
28 changeset: 4:1f3a964b6022
28 changeset: 4:1f3a964b6022
29 user: test
29 user: test
30 date: Mon Jan 12 13:46:40 1970 +0000
30 date: Mon Jan 12 13:46:40 1970 +0000
31 summary: 4
31 summary: 4
32
32
33 changeset: 5:c028bcc7a28a
33 changeset: 5:c028bcc7a28a
34 user: test
34 user: test
35 date: Mon Jan 12 13:46:40 1970 +0000
35 date: Mon Jan 12 13:46:40 1970 +0000
36 summary: 5
36 summary: 5
37
37
38 changeset: 6:a0c0095f3389
38 changeset: 6:a0c0095f3389
39 user: test
39 user: test
40 date: Mon Jan 12 13:46:40 1970 +0000
40 date: Mon Jan 12 13:46:40 1970 +0000
41 summary: 6
41 summary: 6
42
42
43 changeset: 7:d4be65f4e891
43 changeset: 7:d4be65f4e891
44 user: test
44 user: test
45 date: Mon Jan 12 13:46:40 1970 +0000
45 date: Mon Jan 12 13:46:40 1970 +0000
46 summary: 7
46 summary: 7
47
47
48 changeset: 8:92b83e334ef8
48 changeset: 8:92b83e334ef8
49 tag: tip
49 tag: tip
50 user: test
50 user: test
51 date: Mon Jan 12 13:46:40 1970 +0000
51 date: Mon Jan 12 13:46:40 1970 +0000
52 summary: 8
52 summary: 8
53
53
54 comparing with http://localhost/
54 comparing with http://localhost/
55 changeset: 0:9cb21d99fe27
55 changeset: 0:9cb21d99fe27
56 user: test
56 user: test
57 date: Mon Jan 12 13:46:40 1970 +0000
57 date: Mon Jan 12 13:46:40 1970 +0000
58 summary: 0
58 summary: 0
59
59
60 changeset: 1:d717f5dfad6a
60 changeset: 1:d717f5dfad6a
61 user: test
61 user: test
62 date: Mon Jan 12 13:46:40 1970 +0000
62 date: Mon Jan 12 13:46:40 1970 +0000
63 summary: 1
63 summary: 1
64
64
65 changeset: 2:c0d6b86da426
65 changeset: 2:c0d6b86da426
66 user: test
66 user: test
67 date: Mon Jan 12 13:46:40 1970 +0000
67 date: Mon Jan 12 13:46:40 1970 +0000
68 summary: 2
68 summary: 2
69
69
70 changeset: 3:dfacbd43b3fe
70 changeset: 3:dfacbd43b3fe
71 user: test
71 user: test
72 date: Mon Jan 12 13:46:40 1970 +0000
72 date: Mon Jan 12 13:46:40 1970 +0000
73 summary: 3
73 summary: 3
74
74
75 changeset: 4:1f3a964b6022
75 changeset: 4:1f3a964b6022
76 tag: tip
76 tag: tip
77 user: test
77 user: test
78 date: Mon Jan 12 13:46:40 1970 +0000
78 date: Mon Jan 12 13:46:40 1970 +0000
79 summary: 4
79 summary: 4
80
80
81 comparing with test
81 comparing with test
82 changeset: 0:9cb21d99fe27
82 changeset: 0:9cb21d99fe27
83 user: test
83 user: test
84 date: Mon Jan 12 13:46:40 1970 +0000
84 date: Mon Jan 12 13:46:40 1970 +0000
85 summary: 0
85 summary: 0
86
86
87 changeset: 1:d717f5dfad6a
87 changeset: 1:d717f5dfad6a
88 user: test
88 user: test
89 date: Mon Jan 12 13:46:40 1970 +0000
89 date: Mon Jan 12 13:46:40 1970 +0000
90 summary: 1
90 summary: 1
91
91
92 changeset: 2:c0d6b86da426
92 changeset: 2:c0d6b86da426
93 user: test
93 user: test
94 date: Mon Jan 12 13:46:40 1970 +0000
94 date: Mon Jan 12 13:46:40 1970 +0000
95 summary: 2
95 summary: 2
96
96
97 changeset: 3:dfacbd43b3fe
97 changeset: 3:dfacbd43b3fe
98 user: test
98 user: test
99 date: Mon Jan 12 13:46:40 1970 +0000
99 date: Mon Jan 12 13:46:40 1970 +0000
100 summary: 3
100 summary: 3
101
101
102 changeset: 4:1f3a964b6022
102 changeset: 4:1f3a964b6022
103 user: test
103 user: test
104 date: Mon Jan 12 13:46:40 1970 +0000
104 date: Mon Jan 12 13:46:40 1970 +0000
105 summary: 4
105 summary: 4
106
106
107 changeset: 5:c028bcc7a28a
107 changeset: 5:c028bcc7a28a
108 user: test
108 user: test
109 date: Mon Jan 12 13:46:40 1970 +0000
109 date: Mon Jan 12 13:46:40 1970 +0000
110 summary: 5
110 summary: 5
111
111
112 changeset: 6:a0c0095f3389
112 changeset: 6:a0c0095f3389
113 user: test
113 user: test
114 date: Mon Jan 12 13:46:40 1970 +0000
114 date: Mon Jan 12 13:46:40 1970 +0000
115 summary: 6
115 summary: 6
116
116
117 changeset: 7:d4be65f4e891
117 changeset: 7:d4be65f4e891
118 user: test
118 user: test
119 date: Mon Jan 12 13:46:40 1970 +0000
119 date: Mon Jan 12 13:46:40 1970 +0000
120 summary: 7
120 summary: 7
121
121
122 changeset: 8:92b83e334ef8
122 changeset: 8:92b83e334ef8
123 tag: tip
123 tag: tip
124 user: test
124 user: test
125 date: Mon Jan 12 13:46:40 1970 +0000
125 date: Mon Jan 12 13:46:40 1970 +0000
126 summary: 8
126 summary: 8
127
127
128 comparing with test
128 comparing with test
129 changeset: 0:9cb21d99fe27
129 changeset: 0:9cb21d99fe27
130 user: test
130 user: test
131 date: Mon Jan 12 13:46:40 1970 +0000
131 date: Mon Jan 12 13:46:40 1970 +0000
132 summary: 0
132 summary: 0
133
133
134 changeset: 1:d717f5dfad6a
134 changeset: 1:d717f5dfad6a
135 user: test
135 user: test
136 date: Mon Jan 12 13:46:40 1970 +0000
136 date: Mon Jan 12 13:46:40 1970 +0000
137 summary: 1
137 summary: 1
138
138
139 changeset: 2:c0d6b86da426
139 changeset: 2:c0d6b86da426
140 user: test
140 user: test
141 date: Mon Jan 12 13:46:40 1970 +0000
141 date: Mon Jan 12 13:46:40 1970 +0000
142 summary: 2
142 summary: 2
143
143
144 changeset: 3:dfacbd43b3fe
144 changeset: 3:dfacbd43b3fe
145 user: test
145 user: test
146 date: Mon Jan 12 13:46:40 1970 +0000
146 date: Mon Jan 12 13:46:40 1970 +0000
147 summary: 3
147 summary: 3
148
148
149 changeset: 4:1f3a964b6022
149 changeset: 4:1f3a964b6022
150 user: test
150 user: test
151 date: Mon Jan 12 13:46:40 1970 +0000
151 date: Mon Jan 12 13:46:40 1970 +0000
152 summary: 4
152 summary: 4
153
153
154 % limit to 2 changesets
154 % limit to 2 changesets
155 comparing with test
155 comparing with test
156 changeset: 0:9cb21d99fe27
156 changeset: 0:9cb21d99fe27
157 user: test
157 user: test
158 date: Mon Jan 12 13:46:40 1970 +0000
158 date: Mon Jan 12 13:46:40 1970 +0000
159 summary: 0
159 summary: 0
160
160
161 changeset: 1:d717f5dfad6a
161 changeset: 1:d717f5dfad6a
162 user: test
162 user: test
163 date: Mon Jan 12 13:46:40 1970 +0000
163 date: Mon Jan 12 13:46:40 1970 +0000
164 summary: 1
164 summary: 1
165
165
166 % limit to 2 changesets, test with -p --git
167 comparing with test
168 changeset: 0:9cb21d99fe27
169 user: test
170 date: Mon Jan 12 13:46:40 1970 +0000
171 summary: 0
172
173 diff --git a/foo b/foo
174 new file mode 100644
175 --- /dev/null
176 +++ b/foo
177 @@ -0,0 +1,1 @@
178 +0
179
180 changeset: 1:d717f5dfad6a
181 user: test
182 date: Mon Jan 12 13:46:40 1970 +0000
183 summary: 1
184
185 diff --git a/foo b/foo
186 --- a/foo
187 +++ b/foo
188 @@ -1,1 +1,2 @@
189 0
190 +1
191
166 comparing with http://localhost/
192 comparing with http://localhost/
167 changeset: 0:9cb21d99fe27
193 changeset: 0:9cb21d99fe27
168 user: test
194 user: test
169 date: Mon Jan 12 13:46:40 1970 +0000
195 date: Mon Jan 12 13:46:40 1970 +0000
170 summary: 0
196 summary: 0
171
197
172 changeset: 1:d717f5dfad6a
198 changeset: 1:d717f5dfad6a
173 user: test
199 user: test
174 date: Mon Jan 12 13:46:40 1970 +0000
200 date: Mon Jan 12 13:46:40 1970 +0000
175 summary: 1
201 summary: 1
176
202
177 changeset: 2:c0d6b86da426
203 changeset: 2:c0d6b86da426
178 user: test
204 user: test
179 date: Mon Jan 12 13:46:40 1970 +0000
205 date: Mon Jan 12 13:46:40 1970 +0000
180 summary: 2
206 summary: 2
181
207
182 changeset: 3:dfacbd43b3fe
208 changeset: 3:dfacbd43b3fe
183 user: test
209 user: test
184 date: Mon Jan 12 13:46:40 1970 +0000
210 date: Mon Jan 12 13:46:40 1970 +0000
185 summary: 3
211 summary: 3
186
212
187 changeset: 4:1f3a964b6022
213 changeset: 4:1f3a964b6022
188 user: test
214 user: test
189 date: Mon Jan 12 13:46:40 1970 +0000
215 date: Mon Jan 12 13:46:40 1970 +0000
190 summary: 4
216 summary: 4
191
217
192 changeset: 5:c028bcc7a28a
218 changeset: 5:c028bcc7a28a
193 user: test
219 user: test
194 date: Mon Jan 12 13:46:40 1970 +0000
220 date: Mon Jan 12 13:46:40 1970 +0000
195 summary: 5
221 summary: 5
196
222
197 changeset: 6:a0c0095f3389
223 changeset: 6:a0c0095f3389
198 user: test
224 user: test
199 date: Mon Jan 12 13:46:40 1970 +0000
225 date: Mon Jan 12 13:46:40 1970 +0000
200 summary: 6
226 summary: 6
201
227
202 changeset: 7:d4be65f4e891
228 changeset: 7:d4be65f4e891
203 user: test
229 user: test
204 date: Mon Jan 12 13:46:40 1970 +0000
230 date: Mon Jan 12 13:46:40 1970 +0000
205 summary: 7
231 summary: 7
206
232
207 changeset: 8:92b83e334ef8
233 changeset: 8:92b83e334ef8
208 tag: tip
234 tag: tip
209 user: test
235 user: test
210 date: Mon Jan 12 13:46:40 1970 +0000
236 date: Mon Jan 12 13:46:40 1970 +0000
211 summary: 8
237 summary: 8
212
238
213 comparing with test
239 comparing with test
214 changeset: 0:9cb21d99fe27
240 changeset: 0:9cb21d99fe27
215 user: test
241 user: test
216 date: Mon Jan 12 13:46:40 1970 +0000
242 date: Mon Jan 12 13:46:40 1970 +0000
217 summary: 0
243 summary: 0
218
244
219 changeset: 1:d717f5dfad6a
245 changeset: 1:d717f5dfad6a
220 user: test
246 user: test
221 date: Mon Jan 12 13:46:40 1970 +0000
247 date: Mon Jan 12 13:46:40 1970 +0000
222 summary: 1
248 summary: 1
223
249
224 changeset: 2:c0d6b86da426
250 changeset: 2:c0d6b86da426
225 user: test
251 user: test
226 date: Mon Jan 12 13:46:40 1970 +0000
252 date: Mon Jan 12 13:46:40 1970 +0000
227 summary: 2
253 summary: 2
228
254
229 changeset: 3:dfacbd43b3fe
255 changeset: 3:dfacbd43b3fe
230 user: test
256 user: test
231 date: Mon Jan 12 13:46:40 1970 +0000
257 date: Mon Jan 12 13:46:40 1970 +0000
232 summary: 3
258 summary: 3
233
259
234 changeset: 4:1f3a964b6022
260 changeset: 4:1f3a964b6022
235 user: test
261 user: test
236 date: Mon Jan 12 13:46:40 1970 +0000
262 date: Mon Jan 12 13:46:40 1970 +0000
237 summary: 4
263 summary: 4
238
264
239 changeset: 5:c028bcc7a28a
265 changeset: 5:c028bcc7a28a
240 user: test
266 user: test
241 date: Mon Jan 12 13:46:40 1970 +0000
267 date: Mon Jan 12 13:46:40 1970 +0000
242 summary: 5
268 summary: 5
243
269
244 changeset: 6:a0c0095f3389
270 changeset: 6:a0c0095f3389
245 user: test
271 user: test
246 date: Mon Jan 12 13:46:40 1970 +0000
272 date: Mon Jan 12 13:46:40 1970 +0000
247 summary: 6
273 summary: 6
248
274
249 changeset: 7:d4be65f4e891
275 changeset: 7:d4be65f4e891
250 user: test
276 user: test
251 date: Mon Jan 12 13:46:40 1970 +0000
277 date: Mon Jan 12 13:46:40 1970 +0000
252 summary: 7
278 summary: 7
253
279
254 changeset: 8:92b83e334ef8
280 changeset: 8:92b83e334ef8
255 tag: tip
281 tag: tip
256 user: test
282 user: test
257 date: Mon Jan 12 13:46:40 1970 +0000
283 date: Mon Jan 12 13:46:40 1970 +0000
258 summary: 8
284 summary: 8
259
285
260 adding changesets
286 adding changesets
261 adding manifests
287 adding manifests
262 adding file changes
288 adding file changes
263 added 9 changesets with 9 changes to 1 files
289 added 9 changesets with 9 changes to 1 files
264 (run 'hg update' to get a working copy)
290 (run 'hg update' to get a working copy)
265 adding changesets
291 adding changesets
266 adding manifests
292 adding manifests
267 adding file changes
293 adding file changes
268 added 9 changesets with 9 changes to 1 files
294 added 9 changesets with 9 changes to 1 files
269 (run 'hg update' to get a working copy)
295 (run 'hg update' to get a working copy)
270 changeset: 8:92b83e334ef8
296 changeset: 8:92b83e334ef8
271 tag: tip
297 tag: tip
272 user: test
298 user: test
273 date: Mon Jan 12 13:46:40 1970 +0000
299 date: Mon Jan 12 13:46:40 1970 +0000
274 summary: 8
300 summary: 8
275
301
276 changeset: 8:92b83e334ef8
302 changeset: 8:92b83e334ef8
277 tag: tip
303 tag: tip
278 user: test
304 user: test
279 date: Mon Jan 12 13:46:40 1970 +0000
305 date: Mon Jan 12 13:46:40 1970 +0000
280 summary: 8
306 summary: 8
281
307
282 updating working directory
308 updating working directory
283 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
309 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
284 checking changesets
310 checking changesets
285 checking manifests
311 checking manifests
286 crosschecking files in changesets and manifests
312 crosschecking files in changesets and manifests
287 checking files
313 checking files
288 1 files, 14 changesets, 14 total revisions
314 1 files, 14 changesets, 14 total revisions
289 comparing with test
315 comparing with test
290 searching for changes
316 searching for changes
291 changeset: 9:3741c3ad1096
317 changeset: 9:3741c3ad1096
292 user: test
318 user: test
293 date: Mon Jan 12 13:46:40 1970 +0000
319 date: Mon Jan 12 13:46:40 1970 +0000
294 summary: 9
320 summary: 9
295
321
296 changeset: 10:de4143c8d9a5
322 changeset: 10:de4143c8d9a5
297 user: test
323 user: test
298 date: Mon Jan 12 13:46:40 1970 +0000
324 date: Mon Jan 12 13:46:40 1970 +0000
299 summary: 10
325 summary: 10
300
326
301 changeset: 11:0e1c188b9a7a
327 changeset: 11:0e1c188b9a7a
302 user: test
328 user: test
303 date: Mon Jan 12 13:46:40 1970 +0000
329 date: Mon Jan 12 13:46:40 1970 +0000
304 summary: 11
330 summary: 11
305
331
306 changeset: 12:251354d0fdd3
332 changeset: 12:251354d0fdd3
307 user: test
333 user: test
308 date: Mon Jan 12 13:46:40 1970 +0000
334 date: Mon Jan 12 13:46:40 1970 +0000
309 summary: 12
335 summary: 12
310
336
311 changeset: 13:bdaadd969642
337 changeset: 13:bdaadd969642
312 tag: tip
338 tag: tip
313 user: test
339 user: test
314 date: Mon Jan 12 13:46:40 1970 +0000
340 date: Mon Jan 12 13:46:40 1970 +0000
315 summary: 13
341 summary: 13
316
342
317 % limit to 3 changesets
343 % limit to 3 changesets
318 comparing with test
344 comparing with test
319 searching for changes
345 searching for changes
320 changeset: 9:3741c3ad1096
346 changeset: 9:3741c3ad1096
321 user: test
347 user: test
322 date: Mon Jan 12 13:46:40 1970 +0000
348 date: Mon Jan 12 13:46:40 1970 +0000
323 summary: 9
349 summary: 9
324
350
325 changeset: 10:de4143c8d9a5
351 changeset: 10:de4143c8d9a5
326 user: test
352 user: test
327 date: Mon Jan 12 13:46:40 1970 +0000
353 date: Mon Jan 12 13:46:40 1970 +0000
328 summary: 10
354 summary: 10
329
355
330 changeset: 11:0e1c188b9a7a
356 changeset: 11:0e1c188b9a7a
331 user: test
357 user: test
332 date: Mon Jan 12 13:46:40 1970 +0000
358 date: Mon Jan 12 13:46:40 1970 +0000
333 summary: 11
359 summary: 11
334
360
335 comparing with http://localhost/
361 comparing with http://localhost/
336 searching for changes
362 searching for changes
337 changeset: 9:3741c3ad1096
363 changeset: 9:3741c3ad1096
338 user: test
364 user: test
339 date: Mon Jan 12 13:46:40 1970 +0000
365 date: Mon Jan 12 13:46:40 1970 +0000
340 summary: 9
366 summary: 9
341
367
342 changeset: 10:de4143c8d9a5
368 changeset: 10:de4143c8d9a5
343 user: test
369 user: test
344 date: Mon Jan 12 13:46:40 1970 +0000
370 date: Mon Jan 12 13:46:40 1970 +0000
345 summary: 10
371 summary: 10
346
372
347 changeset: 11:0e1c188b9a7a
373 changeset: 11:0e1c188b9a7a
348 user: test
374 user: test
349 date: Mon Jan 12 13:46:40 1970 +0000
375 date: Mon Jan 12 13:46:40 1970 +0000
350 summary: 11
376 summary: 11
351
377
352 changeset: 12:251354d0fdd3
378 changeset: 12:251354d0fdd3
353 user: test
379 user: test
354 date: Mon Jan 12 13:46:40 1970 +0000
380 date: Mon Jan 12 13:46:40 1970 +0000
355 summary: 12
381 summary: 12
356
382
357 changeset: 13:bdaadd969642
383 changeset: 13:bdaadd969642
358 tag: tip
384 tag: tip
359 user: test
385 user: test
360 date: Mon Jan 12 13:46:40 1970 +0000
386 date: Mon Jan 12 13:46:40 1970 +0000
361 summary: 13
387 summary: 13
362
388
363 comparing with http://localhost/
389 comparing with http://localhost/
364 searching for changes
390 searching for changes
365 changeset: 9:3741c3ad1096
391 changeset: 9:3741c3ad1096
366 user: test
392 user: test
367 date: Mon Jan 12 13:46:40 1970 +0000
393 date: Mon Jan 12 13:46:40 1970 +0000
368 summary: 9
394 summary: 9
369
395
370 changeset: 10:de4143c8d9a5
396 changeset: 10:de4143c8d9a5
371 user: test
397 user: test
372 date: Mon Jan 12 13:46:40 1970 +0000
398 date: Mon Jan 12 13:46:40 1970 +0000
373 summary: 10
399 summary: 10
374
400
375 changeset: 11:0e1c188b9a7a
401 changeset: 11:0e1c188b9a7a
376 user: test
402 user: test
377 date: Mon Jan 12 13:46:40 1970 +0000
403 date: Mon Jan 12 13:46:40 1970 +0000
378 summary: 11
404 summary: 11
379
405
@@ -1,104 +1,107 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 hg init a
3 hg init a
4
4
5 cd a
5 cd a
6 echo a > a
6 echo a > a
7 hg ci -Ama -d '1 0'
7 hg ci -Ama -d '1 0'
8
8
9 hg cp a b
9 hg cp a b
10 hg ci -mb -d '2 0'
10 hg ci -mb -d '2 0'
11
11
12 mkdir dir
12 mkdir dir
13 hg mv b dir
13 hg mv b dir
14 hg ci -mc -d '3 0'
14 hg ci -mc -d '3 0'
15
15
16 hg mv a b
16 hg mv a b
17 echo a > d
17 echo a > d
18 hg add d
18 hg add d
19 hg ci -md -d '4 0'
19 hg ci -md -d '4 0'
20
20
21 hg mv dir/b e
21 hg mv dir/b e
22 hg ci -me -d '5 0'
22 hg ci -me -d '5 0'
23
23
24 hg log a
24 hg log a
25 echo % -f, directory
25 echo % -f, directory
26 hg log -f dir
26 hg log -f dir
27 echo % -f, but no args
27 echo % -f, but no args
28 hg log -f
28 hg log -f
29 echo % one rename
29 echo % one rename
30 hg log -vf a
30 hg log -vf a
31 echo % many renames
31 echo % many renames
32 hg log -vf e
32 hg log -vf e
33
33
34 echo % log copies
34 echo % log copies
35 hg log -vC --template '{rev} {file_copies%filecopy}\n'
35 hg log -vC --template '{rev} {file_copies%filecopy}\n'
36
36
37 echo % log copies, non-linear manifest
37 echo % log copies, non-linear manifest
38 hg up -C 3
38 hg up -C 3
39 hg mv dir/b e
39 hg mv dir/b e
40 echo foo > foo
40 echo foo > foo
41 hg ci -Ame2 -d '6 0'
41 hg ci -Ame2 -d '6 0'
42 hg log -vC --template '{rev} {file_copies%filecopy}\n' -r 5
42 hg log -vC --template '{rev} {file_copies%filecopy}\n' -r 5
43
43
44 echo % log copies, execute bit set
44 echo % log copies, execute bit set
45 chmod +x e
45 chmod +x e
46 hg ci -me3 -d '7 0'
46 hg ci -me3 -d '7 0'
47 hg log -vC --template '{rev} {file_copies%filecopy}\n' -r 6
47 hg log -vC --template '{rev} {file_copies%filecopy}\n' -r 6
48
48
49 echo '% log -p d'
49 echo '% log -p d'
50 hg log -pv d
50 hg log -pv d
51
51
52 # log --follow tests
52 # log --follow tests
53 hg init ../follow
53 hg init ../follow
54 cd ../follow
54 cd ../follow
55
55
56 echo base > base
56 echo base > base
57 hg ci -Ambase -d '1 0'
57 hg ci -Ambase -d '1 0'
58
58
59 echo r1 >> base
59 echo r1 >> base
60 hg ci -Amr1 -d '1 0'
60 hg ci -Amr1 -d '1 0'
61 echo r2 >> base
61 echo r2 >> base
62 hg ci -Amr2 -d '1 0'
62 hg ci -Amr2 -d '1 0'
63
63
64 hg up -C 1
64 hg up -C 1
65 echo b1 > b1
65 echo b1 > b1
66 hg ci -Amb1 -d '1 0'
66 hg ci -Amb1 -d '1 0'
67
67
68 echo % log -f
68 echo % log -f
69 hg log -f
69 hg log -f
70
70
71 hg up -C 0
71 hg up -C 0
72 echo b2 > b2
72 echo b2 > b2
73 hg ci -Amb2 -d '1 0'
73 hg ci -Amb2 -d '1 0'
74
74
75 echo % log -f -r 1:tip
75 echo % log -f -r 1:tip
76 hg log -f -r 1:tip
76 hg log -f -r 1:tip
77
77
78 hg up -C 3
78 hg up -C 3
79 hg merge tip
79 hg merge tip
80
80
81 echo % log -r . with two parents
81 echo % log -r . with two parents
82 hg log -r .
82 hg log -r .
83
83
84 hg ci -mm12 -d '1 0'
84 hg ci -mm12 -d '1 0'
85
85
86 echo % log -r . with one parent
86 echo % log -r . with one parent
87 hg log -r .
87 hg log -r .
88
88
89 echo postm >> b1
89 echo postm >> b1
90 hg ci -Amb1.1 -d'1 0'
90 hg ci -Amb1.1 -d'1 0'
91
91
92 echo % log --follow-first
92 echo % log --follow-first
93 hg log --follow-first
93 hg log --follow-first
94
94
95 echo % log -P 2
95 echo % log -P 2
96 hg log -P 2
96 hg log -P 2
97
97
98 echo '% log -r tip -p --git'
99 hg log -r tip -p --git
100
98 echo '% log -r ""'
101 echo '% log -r ""'
99 hg log -r ''
102 hg log -r ''
100
103
101 echo '% log -r <some unknown node id>'
104 echo '% log -r <some unknown node id>'
102 hg log -r 1000000000000000000000000000000000000000
105 hg log -r 1000000000000000000000000000000000000000
103
106
104 exit 0
107 exit 0
@@ -1,227 +1,241 b''
1 adding a
1 adding a
2 changeset: 0:8580ff50825a
2 changeset: 0:8580ff50825a
3 user: test
3 user: test
4 date: Thu Jan 01 00:00:01 1970 +0000
4 date: Thu Jan 01 00:00:01 1970 +0000
5 summary: a
5 summary: a
6
6
7 % -f, directory
7 % -f, directory
8 abort: cannot follow nonexistent file: "dir"
8 abort: cannot follow nonexistent file: "dir"
9 % -f, but no args
9 % -f, but no args
10 changeset: 4:b30c444c7c84
10 changeset: 4:b30c444c7c84
11 tag: tip
11 tag: tip
12 user: test
12 user: test
13 date: Thu Jan 01 00:00:05 1970 +0000
13 date: Thu Jan 01 00:00:05 1970 +0000
14 summary: e
14 summary: e
15
15
16 changeset: 3:16b60bf3f99a
16 changeset: 3:16b60bf3f99a
17 user: test
17 user: test
18 date: Thu Jan 01 00:00:04 1970 +0000
18 date: Thu Jan 01 00:00:04 1970 +0000
19 summary: d
19 summary: d
20
20
21 changeset: 2:21fba396af4c
21 changeset: 2:21fba396af4c
22 user: test
22 user: test
23 date: Thu Jan 01 00:00:03 1970 +0000
23 date: Thu Jan 01 00:00:03 1970 +0000
24 summary: c
24 summary: c
25
25
26 changeset: 1:c0296dabce9b
26 changeset: 1:c0296dabce9b
27 user: test
27 user: test
28 date: Thu Jan 01 00:00:02 1970 +0000
28 date: Thu Jan 01 00:00:02 1970 +0000
29 summary: b
29 summary: b
30
30
31 changeset: 0:8580ff50825a
31 changeset: 0:8580ff50825a
32 user: test
32 user: test
33 date: Thu Jan 01 00:00:01 1970 +0000
33 date: Thu Jan 01 00:00:01 1970 +0000
34 summary: a
34 summary: a
35
35
36 % one rename
36 % one rename
37 changeset: 0:8580ff50825a
37 changeset: 0:8580ff50825a
38 user: test
38 user: test
39 date: Thu Jan 01 00:00:01 1970 +0000
39 date: Thu Jan 01 00:00:01 1970 +0000
40 files: a
40 files: a
41 description:
41 description:
42 a
42 a
43
43
44
44
45 % many renames
45 % many renames
46 changeset: 4:b30c444c7c84
46 changeset: 4:b30c444c7c84
47 tag: tip
47 tag: tip
48 user: test
48 user: test
49 date: Thu Jan 01 00:00:05 1970 +0000
49 date: Thu Jan 01 00:00:05 1970 +0000
50 files: dir/b e
50 files: dir/b e
51 description:
51 description:
52 e
52 e
53
53
54
54
55 changeset: 2:21fba396af4c
55 changeset: 2:21fba396af4c
56 user: test
56 user: test
57 date: Thu Jan 01 00:00:03 1970 +0000
57 date: Thu Jan 01 00:00:03 1970 +0000
58 files: b dir/b
58 files: b dir/b
59 description:
59 description:
60 c
60 c
61
61
62
62
63 changeset: 1:c0296dabce9b
63 changeset: 1:c0296dabce9b
64 user: test
64 user: test
65 date: Thu Jan 01 00:00:02 1970 +0000
65 date: Thu Jan 01 00:00:02 1970 +0000
66 files: b
66 files: b
67 description:
67 description:
68 b
68 b
69
69
70
70
71 changeset: 0:8580ff50825a
71 changeset: 0:8580ff50825a
72 user: test
72 user: test
73 date: Thu Jan 01 00:00:01 1970 +0000
73 date: Thu Jan 01 00:00:01 1970 +0000
74 files: a
74 files: a
75 description:
75 description:
76 a
76 a
77
77
78
78
79 % log copies
79 % log copies
80 4 e (dir/b)
80 4 e (dir/b)
81 3 b (a)
81 3 b (a)
82 2 dir/b (b)
82 2 dir/b (b)
83 1 b (a)
83 1 b (a)
84 0
84 0
85 % log copies, non-linear manifest
85 % log copies, non-linear manifest
86 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
86 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
87 adding foo
87 adding foo
88 created new head
88 created new head
89 5 e (dir/b)
89 5 e (dir/b)
90 % log copies, execute bit set
90 % log copies, execute bit set
91 6
91 6
92 % log -p d
92 % log -p d
93 changeset: 3:16b60bf3f99a
93 changeset: 3:16b60bf3f99a
94 user: test
94 user: test
95 date: Thu Jan 01 00:00:04 1970 +0000
95 date: Thu Jan 01 00:00:04 1970 +0000
96 files: a b d
96 files: a b d
97 description:
97 description:
98 d
98 d
99
99
100
100
101 diff -r 21fba396af4c -r 16b60bf3f99a d
101 diff -r 21fba396af4c -r 16b60bf3f99a d
102 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
102 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
103 +++ b/d Thu Jan 01 00:00:04 1970 +0000
103 +++ b/d Thu Jan 01 00:00:04 1970 +0000
104 @@ -0,0 +1,1 @@
104 @@ -0,0 +1,1 @@
105 +a
105 +a
106
106
107 adding base
107 adding base
108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 adding b1
109 adding b1
110 created new head
110 created new head
111 % log -f
111 % log -f
112 changeset: 3:e62f78d544b4
112 changeset: 3:e62f78d544b4
113 tag: tip
113 tag: tip
114 parent: 1:3d5bf5654eda
114 parent: 1:3d5bf5654eda
115 user: test
115 user: test
116 date: Thu Jan 01 00:00:01 1970 +0000
116 date: Thu Jan 01 00:00:01 1970 +0000
117 summary: b1
117 summary: b1
118
118
119 changeset: 1:3d5bf5654eda
119 changeset: 1:3d5bf5654eda
120 user: test
120 user: test
121 date: Thu Jan 01 00:00:01 1970 +0000
121 date: Thu Jan 01 00:00:01 1970 +0000
122 summary: r1
122 summary: r1
123
123
124 changeset: 0:67e992f2c4f3
124 changeset: 0:67e992f2c4f3
125 user: test
125 user: test
126 date: Thu Jan 01 00:00:01 1970 +0000
126 date: Thu Jan 01 00:00:01 1970 +0000
127 summary: base
127 summary: base
128
128
129 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
129 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
130 adding b2
130 adding b2
131 created new head
131 created new head
132 % log -f -r 1:tip
132 % log -f -r 1:tip
133 changeset: 1:3d5bf5654eda
133 changeset: 1:3d5bf5654eda
134 user: test
134 user: test
135 date: Thu Jan 01 00:00:01 1970 +0000
135 date: Thu Jan 01 00:00:01 1970 +0000
136 summary: r1
136 summary: r1
137
137
138 changeset: 2:60c670bf5b30
138 changeset: 2:60c670bf5b30
139 user: test
139 user: test
140 date: Thu Jan 01 00:00:01 1970 +0000
140 date: Thu Jan 01 00:00:01 1970 +0000
141 summary: r2
141 summary: r2
142
142
143 changeset: 3:e62f78d544b4
143 changeset: 3:e62f78d544b4
144 parent: 1:3d5bf5654eda
144 parent: 1:3d5bf5654eda
145 user: test
145 user: test
146 date: Thu Jan 01 00:00:01 1970 +0000
146 date: Thu Jan 01 00:00:01 1970 +0000
147 summary: b1
147 summary: b1
148
148
149 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
149 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
150 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
151 (branch merge, don't forget to commit)
151 (branch merge, don't forget to commit)
152 % log -r . with two parents
152 % log -r . with two parents
153 changeset: 3:e62f78d544b4
153 changeset: 3:e62f78d544b4
154 parent: 1:3d5bf5654eda
154 parent: 1:3d5bf5654eda
155 user: test
155 user: test
156 date: Thu Jan 01 00:00:01 1970 +0000
156 date: Thu Jan 01 00:00:01 1970 +0000
157 summary: b1
157 summary: b1
158
158
159 % log -r . with one parent
159 % log -r . with one parent
160 changeset: 5:302e9dd6890d
160 changeset: 5:302e9dd6890d
161 tag: tip
161 tag: tip
162 parent: 3:e62f78d544b4
162 parent: 3:e62f78d544b4
163 parent: 4:ddb82e70d1a1
163 parent: 4:ddb82e70d1a1
164 user: test
164 user: test
165 date: Thu Jan 01 00:00:01 1970 +0000
165 date: Thu Jan 01 00:00:01 1970 +0000
166 summary: m12
166 summary: m12
167
167
168 % log --follow-first
168 % log --follow-first
169 changeset: 6:2404bbcab562
169 changeset: 6:2404bbcab562
170 tag: tip
170 tag: tip
171 user: test
171 user: test
172 date: Thu Jan 01 00:00:01 1970 +0000
172 date: Thu Jan 01 00:00:01 1970 +0000
173 summary: b1.1
173 summary: b1.1
174
174
175 changeset: 5:302e9dd6890d
175 changeset: 5:302e9dd6890d
176 parent: 3:e62f78d544b4
176 parent: 3:e62f78d544b4
177 parent: 4:ddb82e70d1a1
177 parent: 4:ddb82e70d1a1
178 user: test
178 user: test
179 date: Thu Jan 01 00:00:01 1970 +0000
179 date: Thu Jan 01 00:00:01 1970 +0000
180 summary: m12
180 summary: m12
181
181
182 changeset: 3:e62f78d544b4
182 changeset: 3:e62f78d544b4
183 parent: 1:3d5bf5654eda
183 parent: 1:3d5bf5654eda
184 user: test
184 user: test
185 date: Thu Jan 01 00:00:01 1970 +0000
185 date: Thu Jan 01 00:00:01 1970 +0000
186 summary: b1
186 summary: b1
187
187
188 changeset: 1:3d5bf5654eda
188 changeset: 1:3d5bf5654eda
189 user: test
189 user: test
190 date: Thu Jan 01 00:00:01 1970 +0000
190 date: Thu Jan 01 00:00:01 1970 +0000
191 summary: r1
191 summary: r1
192
192
193 changeset: 0:67e992f2c4f3
193 changeset: 0:67e992f2c4f3
194 user: test
194 user: test
195 date: Thu Jan 01 00:00:01 1970 +0000
195 date: Thu Jan 01 00:00:01 1970 +0000
196 summary: base
196 summary: base
197
197
198 % log -P 2
198 % log -P 2
199 changeset: 6:2404bbcab562
199 changeset: 6:2404bbcab562
200 tag: tip
200 tag: tip
201 user: test
201 user: test
202 date: Thu Jan 01 00:00:01 1970 +0000
202 date: Thu Jan 01 00:00:01 1970 +0000
203 summary: b1.1
203 summary: b1.1
204
204
205 changeset: 5:302e9dd6890d
205 changeset: 5:302e9dd6890d
206 parent: 3:e62f78d544b4
206 parent: 3:e62f78d544b4
207 parent: 4:ddb82e70d1a1
207 parent: 4:ddb82e70d1a1
208 user: test
208 user: test
209 date: Thu Jan 01 00:00:01 1970 +0000
209 date: Thu Jan 01 00:00:01 1970 +0000
210 summary: m12
210 summary: m12
211
211
212 changeset: 4:ddb82e70d1a1
212 changeset: 4:ddb82e70d1a1
213 parent: 0:67e992f2c4f3
213 parent: 0:67e992f2c4f3
214 user: test
214 user: test
215 date: Thu Jan 01 00:00:01 1970 +0000
215 date: Thu Jan 01 00:00:01 1970 +0000
216 summary: b2
216 summary: b2
217
217
218 changeset: 3:e62f78d544b4
218 changeset: 3:e62f78d544b4
219 parent: 1:3d5bf5654eda
219 parent: 1:3d5bf5654eda
220 user: test
220 user: test
221 date: Thu Jan 01 00:00:01 1970 +0000
221 date: Thu Jan 01 00:00:01 1970 +0000
222 summary: b1
222 summary: b1
223
223
224 % log -r tip -p --git
225 changeset: 6:2404bbcab562
226 tag: tip
227 user: test
228 date: Thu Jan 01 00:00:01 1970 +0000
229 summary: b1.1
230
231 diff --git a/b1 b/b1
232 --- a/b1
233 +++ b/b1
234 @@ -1,1 +1,2 @@
235 b1
236 +postm
237
224 % log -r ""
238 % log -r ""
225 abort: 00changelog.i@: ambiguous identifier!
239 abort: 00changelog.i@: ambiguous identifier!
226 % log -r <some unknown node id>
240 % log -r <some unknown node id>
227 abort: unknown revision '1000000000000000000000000000000000000000'!
241 abort: unknown revision '1000000000000000000000000000000000000000'!
General Comments 0
You need to be logged in to leave comments. Login now