##// END OF EJS Templates
templates: move changeset templating bits to cmdutils
Matt Mackall -
r3643:b4ad640a default
parent child Browse files
Show More
@@ -1,310 +1,310 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 # hook extension to update comments of bugzilla bugs when changesets
8 # hook extension to update comments of bugzilla bugs when changesets
9 # that refer to bugs by id are seen. this hook does not change bug
9 # that refer to bugs by id are seen. this hook does not change bug
10 # status, only comments.
10 # status, only comments.
11 #
11 #
12 # to configure, add items to '[bugzilla]' section of hgrc.
12 # to configure, add items to '[bugzilla]' section of hgrc.
13 #
13 #
14 # to use, configure bugzilla extension and enable like this:
14 # to use, configure bugzilla extension and enable like this:
15 #
15 #
16 # [extensions]
16 # [extensions]
17 # hgext.bugzilla =
17 # hgext.bugzilla =
18 #
18 #
19 # [hooks]
19 # [hooks]
20 # # run bugzilla hook on every change pulled or pushed in here
20 # # run bugzilla hook on every change pulled or pushed in here
21 # incoming.bugzilla = python:hgext.bugzilla.hook
21 # incoming.bugzilla = python:hgext.bugzilla.hook
22 #
22 #
23 # config items:
23 # config items:
24 #
24 #
25 # section name is 'bugzilla'.
25 # section name is 'bugzilla'.
26 # [bugzilla]
26 # [bugzilla]
27 #
27 #
28 # REQUIRED:
28 # REQUIRED:
29 # host = bugzilla # mysql server where bugzilla database lives
29 # host = bugzilla # mysql server where bugzilla database lives
30 # password = ** # user's password
30 # password = ** # user's password
31 # version = 2.16 # version of bugzilla installed
31 # version = 2.16 # version of bugzilla installed
32 #
32 #
33 # OPTIONAL:
33 # OPTIONAL:
34 # bzuser = ... # fallback bugzilla user name to record comments with
34 # bzuser = ... # fallback bugzilla user name to record comments with
35 # db = bugs # database to connect to
35 # db = bugs # database to connect to
36 # notify = ... # command to run to get bugzilla to send mail
36 # notify = ... # command to run to get bugzilla to send mail
37 # regexp = ... # regexp to match bug ids (must contain one "()" group)
37 # regexp = ... # regexp to match bug ids (must contain one "()" group)
38 # strip = 0 # number of slashes to strip for url paths
38 # strip = 0 # number of slashes to strip for url paths
39 # style = ... # style file to use when formatting comments
39 # style = ... # style file to use when formatting comments
40 # template = ... # template to use when formatting comments
40 # template = ... # template to use when formatting comments
41 # timeout = 5 # database connection timeout (seconds)
41 # timeout = 5 # database connection timeout (seconds)
42 # user = bugs # user to connect to database as
42 # user = bugs # user to connect to database as
43 # [web]
43 # [web]
44 # baseurl = http://hgserver/... # root of hg web site for browsing commits
44 # baseurl = http://hgserver/... # root of hg web site for browsing commits
45 #
45 #
46 # if hg committer names are not same as bugzilla user names, use
46 # if hg committer names are not same as bugzilla user names, use
47 # "usermap" feature to map from committer email to bugzilla user name.
47 # "usermap" feature to map from committer email to bugzilla user name.
48 # usermap can be in hgrc or separate config file.
48 # usermap can be in hgrc or separate config file.
49 #
49 #
50 # [bugzilla]
50 # [bugzilla]
51 # usermap = filename # cfg file with "committer"="bugzilla user" info
51 # usermap = filename # cfg file with "committer"="bugzilla user" info
52 # [usermap]
52 # [usermap]
53 # committer_email = bugzilla_user_name
53 # committer_email = bugzilla_user_name
54
54
55 from mercurial.demandload import *
55 from mercurial.demandload import *
56 from mercurial.i18n import gettext as _
56 from mercurial.i18n import gettext as _
57 from mercurial.node import *
57 from mercurial.node import *
58 demandload(globals(), 'mercurial:templater,util os re time')
58 demandload(globals(), 'mercurial:cmdutil,templater,util os re time')
59
59
60 MySQLdb = None
60 MySQLdb = None
61
61
62 def buglist(ids):
62 def buglist(ids):
63 return '(' + ','.join(map(str, ids)) + ')'
63 return '(' + ','.join(map(str, ids)) + ')'
64
64
65 class bugzilla_2_16(object):
65 class bugzilla_2_16(object):
66 '''support for bugzilla version 2.16.'''
66 '''support for bugzilla version 2.16.'''
67
67
68 def __init__(self, ui):
68 def __init__(self, ui):
69 self.ui = ui
69 self.ui = ui
70 host = self.ui.config('bugzilla', 'host', 'localhost')
70 host = self.ui.config('bugzilla', 'host', 'localhost')
71 user = self.ui.config('bugzilla', 'user', 'bugs')
71 user = self.ui.config('bugzilla', 'user', 'bugs')
72 passwd = self.ui.config('bugzilla', 'password')
72 passwd = self.ui.config('bugzilla', 'password')
73 db = self.ui.config('bugzilla', 'db', 'bugs')
73 db = self.ui.config('bugzilla', 'db', 'bugs')
74 timeout = int(self.ui.config('bugzilla', 'timeout', 5))
74 timeout = int(self.ui.config('bugzilla', 'timeout', 5))
75 usermap = self.ui.config('bugzilla', 'usermap')
75 usermap = self.ui.config('bugzilla', 'usermap')
76 if usermap:
76 if usermap:
77 self.ui.readsections(usermap, 'usermap')
77 self.ui.readsections(usermap, 'usermap')
78 self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
78 self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
79 (host, db, user, '*' * len(passwd)))
79 (host, db, user, '*' * len(passwd)))
80 self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
80 self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
81 db=db, connect_timeout=timeout)
81 db=db, connect_timeout=timeout)
82 self.cursor = self.conn.cursor()
82 self.cursor = self.conn.cursor()
83 self.run('select fieldid from fielddefs where name = "longdesc"')
83 self.run('select fieldid from fielddefs where name = "longdesc"')
84 ids = self.cursor.fetchall()
84 ids = self.cursor.fetchall()
85 if len(ids) != 1:
85 if len(ids) != 1:
86 raise util.Abort(_('unknown database schema'))
86 raise util.Abort(_('unknown database schema'))
87 self.longdesc_id = ids[0][0]
87 self.longdesc_id = ids[0][0]
88 self.user_ids = {}
88 self.user_ids = {}
89
89
90 def run(self, *args, **kwargs):
90 def run(self, *args, **kwargs):
91 '''run a query.'''
91 '''run a query.'''
92 self.ui.note(_('query: %s %s\n') % (args, kwargs))
92 self.ui.note(_('query: %s %s\n') % (args, kwargs))
93 try:
93 try:
94 self.cursor.execute(*args, **kwargs)
94 self.cursor.execute(*args, **kwargs)
95 except MySQLdb.MySQLError, err:
95 except MySQLdb.MySQLError, err:
96 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
96 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
97 raise
97 raise
98
98
99 def filter_real_bug_ids(self, ids):
99 def filter_real_bug_ids(self, ids):
100 '''filter not-existing bug ids from list.'''
100 '''filter not-existing bug ids from list.'''
101 self.run('select bug_id from bugs where bug_id in %s' % buglist(ids))
101 self.run('select bug_id from bugs where bug_id in %s' % buglist(ids))
102 ids = [c[0] for c in self.cursor.fetchall()]
102 ids = [c[0] for c in self.cursor.fetchall()]
103 ids.sort()
103 ids.sort()
104 return ids
104 return ids
105
105
106 def filter_unknown_bug_ids(self, node, ids):
106 def filter_unknown_bug_ids(self, node, ids):
107 '''filter bug ids from list that already refer to this changeset.'''
107 '''filter bug ids from list that already refer to this changeset.'''
108
108
109 self.run('''select bug_id from longdescs where
109 self.run('''select bug_id from longdescs where
110 bug_id in %s and thetext like "%%%s%%"''' %
110 bug_id in %s and thetext like "%%%s%%"''' %
111 (buglist(ids), short(node)))
111 (buglist(ids), short(node)))
112 unknown = dict.fromkeys(ids)
112 unknown = dict.fromkeys(ids)
113 for (id,) in self.cursor.fetchall():
113 for (id,) in self.cursor.fetchall():
114 self.ui.status(_('bug %d already knows about changeset %s\n') %
114 self.ui.status(_('bug %d already knows about changeset %s\n') %
115 (id, short(node)))
115 (id, short(node)))
116 unknown.pop(id, None)
116 unknown.pop(id, None)
117 ids = unknown.keys()
117 ids = unknown.keys()
118 ids.sort()
118 ids.sort()
119 return ids
119 return ids
120
120
121 def notify(self, ids):
121 def notify(self, ids):
122 '''tell bugzilla to send mail.'''
122 '''tell bugzilla to send mail.'''
123
123
124 self.ui.status(_('telling bugzilla to send mail:\n'))
124 self.ui.status(_('telling bugzilla to send mail:\n'))
125 for id in ids:
125 for id in ids:
126 self.ui.status(_(' bug %s\n') % id)
126 self.ui.status(_(' bug %s\n') % id)
127 cmd = self.ui.config('bugzilla', 'notify',
127 cmd = self.ui.config('bugzilla', 'notify',
128 'cd /var/www/html/bugzilla && '
128 'cd /var/www/html/bugzilla && '
129 './processmail %s nobody@nowhere.com') % id
129 './processmail %s nobody@nowhere.com') % id
130 fp = os.popen('(%s) 2>&1' % cmd)
130 fp = os.popen('(%s) 2>&1' % cmd)
131 out = fp.read()
131 out = fp.read()
132 ret = fp.close()
132 ret = fp.close()
133 if ret:
133 if ret:
134 self.ui.warn(out)
134 self.ui.warn(out)
135 raise util.Abort(_('bugzilla notify command %s') %
135 raise util.Abort(_('bugzilla notify command %s') %
136 util.explain_exit(ret)[0])
136 util.explain_exit(ret)[0])
137 self.ui.status(_('done\n'))
137 self.ui.status(_('done\n'))
138
138
139 def get_user_id(self, user):
139 def get_user_id(self, user):
140 '''look up numeric bugzilla user id.'''
140 '''look up numeric bugzilla user id.'''
141 try:
141 try:
142 return self.user_ids[user]
142 return self.user_ids[user]
143 except KeyError:
143 except KeyError:
144 try:
144 try:
145 userid = int(user)
145 userid = int(user)
146 except ValueError:
146 except ValueError:
147 self.ui.note(_('looking up user %s\n') % user)
147 self.ui.note(_('looking up user %s\n') % user)
148 self.run('''select userid from profiles
148 self.run('''select userid from profiles
149 where login_name like %s''', user)
149 where login_name like %s''', user)
150 all = self.cursor.fetchall()
150 all = self.cursor.fetchall()
151 if len(all) != 1:
151 if len(all) != 1:
152 raise KeyError(user)
152 raise KeyError(user)
153 userid = int(all[0][0])
153 userid = int(all[0][0])
154 self.user_ids[user] = userid
154 self.user_ids[user] = userid
155 return userid
155 return userid
156
156
157 def map_committer(self, user):
157 def map_committer(self, user):
158 '''map name of committer to bugzilla user name.'''
158 '''map name of committer to bugzilla user name.'''
159 for committer, bzuser in self.ui.configitems('usermap'):
159 for committer, bzuser in self.ui.configitems('usermap'):
160 if committer.lower() == user.lower():
160 if committer.lower() == user.lower():
161 return bzuser
161 return bzuser
162 return user
162 return user
163
163
164 def add_comment(self, bugid, text, committer):
164 def add_comment(self, bugid, text, committer):
165 '''add comment to bug. try adding comment as committer of
165 '''add comment to bug. try adding comment as committer of
166 changeset, otherwise as default bugzilla user.'''
166 changeset, otherwise as default bugzilla user.'''
167 user = self.map_committer(committer)
167 user = self.map_committer(committer)
168 try:
168 try:
169 userid = self.get_user_id(user)
169 userid = self.get_user_id(user)
170 except KeyError:
170 except KeyError:
171 try:
171 try:
172 defaultuser = self.ui.config('bugzilla', 'bzuser')
172 defaultuser = self.ui.config('bugzilla', 'bzuser')
173 if not defaultuser:
173 if not defaultuser:
174 raise util.Abort(_('cannot find bugzilla user id for %s') %
174 raise util.Abort(_('cannot find bugzilla user id for %s') %
175 user)
175 user)
176 userid = self.get_user_id(defaultuser)
176 userid = self.get_user_id(defaultuser)
177 except KeyError:
177 except KeyError:
178 raise util.Abort(_('cannot find bugzilla user id for %s or %s') %
178 raise util.Abort(_('cannot find bugzilla user id for %s or %s') %
179 (user, defaultuser))
179 (user, defaultuser))
180 now = time.strftime('%Y-%m-%d %H:%M:%S')
180 now = time.strftime('%Y-%m-%d %H:%M:%S')
181 self.run('''insert into longdescs
181 self.run('''insert into longdescs
182 (bug_id, who, bug_when, thetext)
182 (bug_id, who, bug_when, thetext)
183 values (%s, %s, %s, %s)''',
183 values (%s, %s, %s, %s)''',
184 (bugid, userid, now, text))
184 (bugid, userid, now, text))
185 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
185 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
186 values (%s, %s, %s, %s)''',
186 values (%s, %s, %s, %s)''',
187 (bugid, userid, now, self.longdesc_id))
187 (bugid, userid, now, self.longdesc_id))
188
188
189 class bugzilla(object):
189 class bugzilla(object):
190 # supported versions of bugzilla. different versions have
190 # supported versions of bugzilla. different versions have
191 # different schemas.
191 # different schemas.
192 _versions = {
192 _versions = {
193 '2.16': bugzilla_2_16,
193 '2.16': bugzilla_2_16,
194 }
194 }
195
195
196 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
196 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
197 r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)')
197 r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)')
198
198
199 _bz = None
199 _bz = None
200
200
201 def __init__(self, ui, repo):
201 def __init__(self, ui, repo):
202 self.ui = ui
202 self.ui = ui
203 self.repo = repo
203 self.repo = repo
204
204
205 def bz(self):
205 def bz(self):
206 '''return object that knows how to talk to bugzilla version in
206 '''return object that knows how to talk to bugzilla version in
207 use.'''
207 use.'''
208
208
209 if bugzilla._bz is None:
209 if bugzilla._bz is None:
210 bzversion = self.ui.config('bugzilla', 'version')
210 bzversion = self.ui.config('bugzilla', 'version')
211 try:
211 try:
212 bzclass = bugzilla._versions[bzversion]
212 bzclass = bugzilla._versions[bzversion]
213 except KeyError:
213 except KeyError:
214 raise util.Abort(_('bugzilla version %s not supported') %
214 raise util.Abort(_('bugzilla version %s not supported') %
215 bzversion)
215 bzversion)
216 bugzilla._bz = bzclass(self.ui)
216 bugzilla._bz = bzclass(self.ui)
217 return bugzilla._bz
217 return bugzilla._bz
218
218
219 def __getattr__(self, key):
219 def __getattr__(self, key):
220 return getattr(self.bz(), key)
220 return getattr(self.bz(), key)
221
221
222 _bug_re = None
222 _bug_re = None
223 _split_re = None
223 _split_re = None
224
224
225 def find_bug_ids(self, node, desc):
225 def find_bug_ids(self, node, desc):
226 '''find valid bug ids that are referred to in changeset
226 '''find valid bug ids that are referred to in changeset
227 comments and that do not already have references to this
227 comments and that do not already have references to this
228 changeset.'''
228 changeset.'''
229
229
230 if bugzilla._bug_re is None:
230 if bugzilla._bug_re is None:
231 bugzilla._bug_re = re.compile(
231 bugzilla._bug_re = re.compile(
232 self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re),
232 self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re),
233 re.IGNORECASE)
233 re.IGNORECASE)
234 bugzilla._split_re = re.compile(r'\D+')
234 bugzilla._split_re = re.compile(r'\D+')
235 start = 0
235 start = 0
236 ids = {}
236 ids = {}
237 while True:
237 while True:
238 m = bugzilla._bug_re.search(desc, start)
238 m = bugzilla._bug_re.search(desc, start)
239 if not m:
239 if not m:
240 break
240 break
241 start = m.end()
241 start = m.end()
242 for id in bugzilla._split_re.split(m.group(1)):
242 for id in bugzilla._split_re.split(m.group(1)):
243 if not id: continue
243 if not id: continue
244 ids[int(id)] = 1
244 ids[int(id)] = 1
245 ids = ids.keys()
245 ids = ids.keys()
246 if ids:
246 if ids:
247 ids = self.filter_real_bug_ids(ids)
247 ids = self.filter_real_bug_ids(ids)
248 if ids:
248 if ids:
249 ids = self.filter_unknown_bug_ids(node, ids)
249 ids = self.filter_unknown_bug_ids(node, ids)
250 return ids
250 return ids
251
251
252 def update(self, bugid, node, changes):
252 def update(self, bugid, node, changes):
253 '''update bugzilla bug with reference to changeset.'''
253 '''update bugzilla bug with reference to changeset.'''
254
254
255 def webroot(root):
255 def webroot(root):
256 '''strip leading prefix of repo root and turn into
256 '''strip leading prefix of repo root and turn into
257 url-safe path.'''
257 url-safe path.'''
258 count = int(self.ui.config('bugzilla', 'strip', 0))
258 count = int(self.ui.config('bugzilla', 'strip', 0))
259 root = util.pconvert(root)
259 root = util.pconvert(root)
260 while count > 0:
260 while count > 0:
261 c = root.find('/')
261 c = root.find('/')
262 if c == -1:
262 if c == -1:
263 break
263 break
264 root = root[c+1:]
264 root = root[c+1:]
265 count -= 1
265 count -= 1
266 return root
266 return root
267
267
268 mapfile = self.ui.config('bugzilla', 'style')
268 mapfile = self.ui.config('bugzilla', 'style')
269 tmpl = self.ui.config('bugzilla', 'template')
269 tmpl = self.ui.config('bugzilla', 'template')
270 sio = templater.stringio()
270 sio = cmdutil.stringio()
271 t = templater.changeset_templater(self.ui, self.repo, mapfile, sio)
271 t = cmdutil.changeset_templater(self.ui, self.repo, mapfile, sio)
272 if not mapfile and not tmpl:
272 if not mapfile and not tmpl:
273 tmpl = _('changeset {node|short} in repo {root} refers '
273 tmpl = _('changeset {node|short} in repo {root} refers '
274 'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
274 'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
275 if tmpl:
275 if tmpl:
276 tmpl = templater.parsestring(tmpl, quoted=False)
276 tmpl = templater.parsestring(tmpl, quoted=False)
277 t.use_template(tmpl)
277 t.use_template(tmpl)
278 t.show(changenode=node, changes=changes,
278 t.show(changenode=node, changes=changes,
279 bug=str(bugid),
279 bug=str(bugid),
280 hgweb=self.ui.config('web', 'baseurl'),
280 hgweb=self.ui.config('web', 'baseurl'),
281 root=self.repo.root,
281 root=self.repo.root,
282 webroot=webroot(self.repo.root))
282 webroot=webroot(self.repo.root))
283 self.add_comment(bugid, sio.getvalue(), templater.email(changes[1]))
283 self.add_comment(bugid, sio.getvalue(), templater.email(changes[1]))
284
284
285 def hook(ui, repo, hooktype, node=None, **kwargs):
285 def hook(ui, repo, hooktype, node=None, **kwargs):
286 '''add comment to bugzilla for each changeset that refers to a
286 '''add comment to bugzilla for each changeset that refers to a
287 bugzilla bug id. only add a comment once per bug, so same change
287 bugzilla bug id. only add a comment once per bug, so same change
288 seen multiple times does not fill bug with duplicate data.'''
288 seen multiple times does not fill bug with duplicate data.'''
289 try:
289 try:
290 import MySQLdb as mysql
290 import MySQLdb as mysql
291 global MySQLdb
291 global MySQLdb
292 MySQLdb = mysql
292 MySQLdb = mysql
293 except ImportError, err:
293 except ImportError, err:
294 raise util.Abort(_('python mysql support not available: %s') % err)
294 raise util.Abort(_('python mysql support not available: %s') % err)
295
295
296 if node is None:
296 if node is None:
297 raise util.Abort(_('hook type %s does not pass a changeset id') %
297 raise util.Abort(_('hook type %s does not pass a changeset id') %
298 hooktype)
298 hooktype)
299 try:
299 try:
300 bz = bugzilla(ui, repo)
300 bz = bugzilla(ui, repo)
301 bin_node = bin(node)
301 bin_node = bin(node)
302 changes = repo.changelog.read(bin_node)
302 changes = repo.changelog.read(bin_node)
303 ids = bz.find_bug_ids(bin_node, changes[4])
303 ids = bz.find_bug_ids(bin_node, changes[4])
304 if ids:
304 if ids:
305 for id in ids:
305 for id in ids:
306 bz.update(id, bin_node, changes)
306 bz.update(id, bin_node, changes)
307 bz.notify(ids)
307 bz.notify(ids)
308 except MySQLdb.MySQLError, err:
308 except MySQLdb.MySQLError, err:
309 raise util.Abort(_('database error: %s') % err[1])
309 raise util.Abort(_('database error: %s') % err[1])
310
310
@@ -1,299 +1,299 b''
1 # bisect extension for mercurial
1 # bisect extension for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
3 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
4 # Inspired by git bisect, extension skeleton taken from mq.py.
4 # Inspired by git bisect, extension skeleton taken from mq.py.
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
8
9 from mercurial.i18n import gettext as _
9 from mercurial.i18n import gettext as _
10 from mercurial.demandload import demandload
10 from mercurial.demandload import demandload
11 demandload(globals(), "os sys sets mercurial:hg,util,commands")
11 demandload(globals(), "os sys sets mercurial:hg,util,commands,cmdutil")
12
12
13 versionstr = "0.0.3"
13 versionstr = "0.0.3"
14
14
15 def lookup_rev(ui, repo, rev=None):
15 def lookup_rev(ui, repo, rev=None):
16 """returns rev or the checked-out revision if rev is None"""
16 """returns rev or the checked-out revision if rev is None"""
17 if not rev is None:
17 if not rev is None:
18 return repo.lookup(rev)
18 return repo.lookup(rev)
19 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
19 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
20 if len(parents) != 1:
20 if len(parents) != 1:
21 raise util.Abort(_("unexpected number of parents, "
21 raise util.Abort(_("unexpected number of parents, "
22 "please commit or revert"))
22 "please commit or revert"))
23 return parents.pop()
23 return parents.pop()
24
24
25 def check_clean(ui, repo):
25 def check_clean(ui, repo):
26 modified, added, removed, deleted, unknown = repo.status()[:5]
26 modified, added, removed, deleted, unknown = repo.status()[:5]
27 if modified or added or removed:
27 if modified or added or removed:
28 ui.warn("Repository is not clean, please commit or revert\n")
28 ui.warn("Repository is not clean, please commit or revert\n")
29 sys.exit(1)
29 sys.exit(1)
30
30
31 class bisect(object):
31 class bisect(object):
32 """dichotomic search in the DAG of changesets"""
32 """dichotomic search in the DAG of changesets"""
33 def __init__(self, ui, repo):
33 def __init__(self, ui, repo):
34 self.repo = repo
34 self.repo = repo
35 self.path = repo.join("bisect")
35 self.path = repo.join("bisect")
36 self.opener = util.opener(self.path)
36 self.opener = util.opener(self.path)
37 self.ui = ui
37 self.ui = ui
38 self.goodrevs = []
38 self.goodrevs = []
39 self.badrev = None
39 self.badrev = None
40 self.good_dirty = 0
40 self.good_dirty = 0
41 self.bad_dirty = 0
41 self.bad_dirty = 0
42 self.good_path = "good"
42 self.good_path = "good"
43 self.bad_path = "bad"
43 self.bad_path = "bad"
44
44
45 if os.path.exists(os.path.join(self.path, self.good_path)):
45 if os.path.exists(os.path.join(self.path, self.good_path)):
46 self.goodrevs = self.opener(self.good_path).read().splitlines()
46 self.goodrevs = self.opener(self.good_path).read().splitlines()
47 self.goodrevs = [hg.bin(x) for x in self.goodrevs]
47 self.goodrevs = [hg.bin(x) for x in self.goodrevs]
48 if os.path.exists(os.path.join(self.path, self.bad_path)):
48 if os.path.exists(os.path.join(self.path, self.bad_path)):
49 r = self.opener(self.bad_path).read().splitlines()
49 r = self.opener(self.bad_path).read().splitlines()
50 if r:
50 if r:
51 self.badrev = hg.bin(r.pop(0))
51 self.badrev = hg.bin(r.pop(0))
52
52
53 def write(self):
53 def write(self):
54 if not os.path.isdir(self.path):
54 if not os.path.isdir(self.path):
55 return
55 return
56 f = self.opener(self.good_path, "w")
56 f = self.opener(self.good_path, "w")
57 f.write("\n".join([hg.hex(r) for r in self.goodrevs]))
57 f.write("\n".join([hg.hex(r) for r in self.goodrevs]))
58 if len(self.goodrevs) > 0:
58 if len(self.goodrevs) > 0:
59 f.write("\n")
59 f.write("\n")
60 f = self.opener(self.bad_path, "w")
60 f = self.opener(self.bad_path, "w")
61 if self.badrev:
61 if self.badrev:
62 f.write(hg.hex(self.badrev) + "\n")
62 f.write(hg.hex(self.badrev) + "\n")
63
63
64 def init(self):
64 def init(self):
65 """start a new bisection"""
65 """start a new bisection"""
66 if os.path.isdir(self.path):
66 if os.path.isdir(self.path):
67 raise util.Abort(_("bisect directory already exists\n"))
67 raise util.Abort(_("bisect directory already exists\n"))
68 os.mkdir(self.path)
68 os.mkdir(self.path)
69 check_clean(self.ui, self.repo)
69 check_clean(self.ui, self.repo)
70 return 0
70 return 0
71
71
72 def reset(self):
72 def reset(self):
73 """finish a bisection"""
73 """finish a bisection"""
74 if os.path.isdir(self.path):
74 if os.path.isdir(self.path):
75 sl = [os.path.join(self.path, p)
75 sl = [os.path.join(self.path, p)
76 for p in [self.bad_path, self.good_path]]
76 for p in [self.bad_path, self.good_path]]
77 for s in sl:
77 for s in sl:
78 if os.path.exists(s):
78 if os.path.exists(s):
79 os.unlink(s)
79 os.unlink(s)
80 os.rmdir(self.path)
80 os.rmdir(self.path)
81 # Not sure about this
81 # Not sure about this
82 #self.ui.write("Going back to tip\n")
82 #self.ui.write("Going back to tip\n")
83 #self.repo.update(self.repo.changelog.tip())
83 #self.repo.update(self.repo.changelog.tip())
84 return 1
84 return 1
85
85
86 def num_ancestors(self, head=None, stop=None):
86 def num_ancestors(self, head=None, stop=None):
87 """
87 """
88 returns a dict with the mapping:
88 returns a dict with the mapping:
89 node -> number of ancestors (self included)
89 node -> number of ancestors (self included)
90 for all nodes who are ancestor of head and
90 for all nodes who are ancestor of head and
91 not in stop.
91 not in stop.
92 """
92 """
93 if head is None:
93 if head is None:
94 head = self.badrev
94 head = self.badrev
95 return self.__ancestors_and_nb_ancestors(head, stop)[1]
95 return self.__ancestors_and_nb_ancestors(head, stop)[1]
96
96
97 def ancestors(self, head=None, stop=None):
97 def ancestors(self, head=None, stop=None):
98 """
98 """
99 returns the set of the ancestors of head (self included)
99 returns the set of the ancestors of head (self included)
100 who are not in stop.
100 who are not in stop.
101 """
101 """
102 if head is None:
102 if head is None:
103 head = self.badrev
103 head = self.badrev
104 return self.__ancestors_and_nb_ancestors(head, stop)[0]
104 return self.__ancestors_and_nb_ancestors(head, stop)[0]
105
105
106 def __ancestors_and_nb_ancestors(self, head, stop=None):
106 def __ancestors_and_nb_ancestors(self, head, stop=None):
107 """
107 """
108 if stop is None then ancestors of goodrevs are used as
108 if stop is None then ancestors of goodrevs are used as
109 lower limit.
109 lower limit.
110
110
111 returns (anc, n_child) where anc is the set of the ancestors of head
111 returns (anc, n_child) where anc is the set of the ancestors of head
112 and n_child is a dictionary with the following mapping:
112 and n_child is a dictionary with the following mapping:
113 node -> number of ancestors (self included)
113 node -> number of ancestors (self included)
114 """
114 """
115 cl = self.repo.changelog
115 cl = self.repo.changelog
116 if not stop:
116 if not stop:
117 stop = sets.Set([])
117 stop = sets.Set([])
118 for i in xrange(len(self.goodrevs)-1, -1, -1):
118 for i in xrange(len(self.goodrevs)-1, -1, -1):
119 g = self.goodrevs[i]
119 g = self.goodrevs[i]
120 if g in stop:
120 if g in stop:
121 continue
121 continue
122 stop.update(cl.reachable(g))
122 stop.update(cl.reachable(g))
123 def num_children(a):
123 def num_children(a):
124 """
124 """
125 returns a dictionnary with the following mapping
125 returns a dictionnary with the following mapping
126 node -> [number of children, empty set]
126 node -> [number of children, empty set]
127 """
127 """
128 d = {a: [0, sets.Set([])]}
128 d = {a: [0, sets.Set([])]}
129 for i in xrange(cl.rev(a)+1):
129 for i in xrange(cl.rev(a)+1):
130 n = cl.node(i)
130 n = cl.node(i)
131 if not d.has_key(n):
131 if not d.has_key(n):
132 d[n] = [0, sets.Set([])]
132 d[n] = [0, sets.Set([])]
133 parents = [p for p in cl.parents(n) if p != hg.nullid]
133 parents = [p for p in cl.parents(n) if p != hg.nullid]
134 for p in parents:
134 for p in parents:
135 d[p][0] += 1
135 d[p][0] += 1
136 return d
136 return d
137
137
138 if head in stop:
138 if head in stop:
139 raise util.Abort(_("Unconsistent state, %s:%s is good and bad")
139 raise util.Abort(_("Unconsistent state, %s:%s is good and bad")
140 % (cl.rev(head), hg.short(head)))
140 % (cl.rev(head), hg.short(head)))
141 n_child = num_children(head)
141 n_child = num_children(head)
142 for i in xrange(cl.rev(head)+1):
142 for i in xrange(cl.rev(head)+1):
143 n = cl.node(i)
143 n = cl.node(i)
144 parents = [p for p in cl.parents(n) if p != hg.nullid]
144 parents = [p for p in cl.parents(n) if p != hg.nullid]
145 for p in parents:
145 for p in parents:
146 n_child[p][0] -= 1
146 n_child[p][0] -= 1
147 if not n in stop:
147 if not n in stop:
148 n_child[n][1].union_update(n_child[p][1])
148 n_child[n][1].union_update(n_child[p][1])
149 if n_child[p][0] == 0:
149 if n_child[p][0] == 0:
150 n_child[p] = len(n_child[p][1])
150 n_child[p] = len(n_child[p][1])
151 if not n in stop:
151 if not n in stop:
152 n_child[n][1].add(n)
152 n_child[n][1].add(n)
153 if n_child[n][0] == 0:
153 if n_child[n][0] == 0:
154 if n == head:
154 if n == head:
155 anc = n_child[n][1]
155 anc = n_child[n][1]
156 n_child[n] = len(n_child[n][1])
156 n_child[n] = len(n_child[n][1])
157 return anc, n_child
157 return anc, n_child
158
158
159 def next(self):
159 def next(self):
160 if not self.badrev:
160 if not self.badrev:
161 raise util.Abort(_("You should give at least one bad revision"))
161 raise util.Abort(_("You should give at least one bad revision"))
162 if not self.goodrevs:
162 if not self.goodrevs:
163 self.ui.warn(_("No good revision given\n"))
163 self.ui.warn(_("No good revision given\n"))
164 self.ui.warn(_("Marking the first revision as good\n"))
164 self.ui.warn(_("Marking the first revision as good\n"))
165 ancestors, num_ancestors = self.__ancestors_and_nb_ancestors(
165 ancestors, num_ancestors = self.__ancestors_and_nb_ancestors(
166 self.badrev)
166 self.badrev)
167 tot = len(ancestors)
167 tot = len(ancestors)
168 if tot == 1:
168 if tot == 1:
169 if ancestors.pop() != self.badrev:
169 if ancestors.pop() != self.badrev:
170 raise util.Abort(_("Could not find the first bad revision"))
170 raise util.Abort(_("Could not find the first bad revision"))
171 self.ui.write(_("The first bad revision is:\n"))
171 self.ui.write(_("The first bad revision is:\n"))
172 displayer = commands.show_changeset(self.ui, self.repo, {})
172 displayer = cmdutil.show_changeset(self.ui, self.repo, {})
173 displayer.show(changenode=self.badrev)
173 displayer.show(changenode=self.badrev)
174 return None
174 return None
175 best_rev = None
175 best_rev = None
176 best_len = -1
176 best_len = -1
177 for n in ancestors:
177 for n in ancestors:
178 l = num_ancestors[n]
178 l = num_ancestors[n]
179 l = min(l, tot - l)
179 l = min(l, tot - l)
180 if l > best_len:
180 if l > best_len:
181 best_len = l
181 best_len = l
182 best_rev = n
182 best_rev = n
183 assert best_rev is not None
183 assert best_rev is not None
184 nb_tests = 0
184 nb_tests = 0
185 q, r = divmod(tot, 2)
185 q, r = divmod(tot, 2)
186 while q:
186 while q:
187 nb_tests += 1
187 nb_tests += 1
188 q, r = divmod(q, 2)
188 q, r = divmod(q, 2)
189 msg = _("Testing changeset %s:%s (%s changesets remaining, "
189 msg = _("Testing changeset %s:%s (%s changesets remaining, "
190 "~%s tests)\n") % (self.repo.changelog.rev(best_rev),
190 "~%s tests)\n") % (self.repo.changelog.rev(best_rev),
191 hg.short(best_rev), tot, nb_tests)
191 hg.short(best_rev), tot, nb_tests)
192 self.ui.write(msg)
192 self.ui.write(msg)
193 return best_rev
193 return best_rev
194
194
195 def autonext(self):
195 def autonext(self):
196 """find and update to the next revision to test"""
196 """find and update to the next revision to test"""
197 check_clean(self.ui, self.repo)
197 check_clean(self.ui, self.repo)
198 rev = self.next()
198 rev = self.next()
199 if rev is not None:
199 if rev is not None:
200 return hg.clean(self.repo, rev)
200 return hg.clean(self.repo, rev)
201
201
202 def good(self, rev):
202 def good(self, rev):
203 self.goodrevs.append(rev)
203 self.goodrevs.append(rev)
204
204
205 def autogood(self, rev=None):
205 def autogood(self, rev=None):
206 """mark revision as good and update to the next revision to test"""
206 """mark revision as good and update to the next revision to test"""
207 check_clean(self.ui, self.repo)
207 check_clean(self.ui, self.repo)
208 rev = lookup_rev(self.ui, self.repo, rev)
208 rev = lookup_rev(self.ui, self.repo, rev)
209 self.good(rev)
209 self.good(rev)
210 if self.badrev:
210 if self.badrev:
211 return self.autonext()
211 return self.autonext()
212
212
213 def bad(self, rev):
213 def bad(self, rev):
214 self.badrev = rev
214 self.badrev = rev
215
215
216 def autobad(self, rev=None):
216 def autobad(self, rev=None):
217 """mark revision as bad and update to the next revision to test"""
217 """mark revision as bad and update to the next revision to test"""
218 check_clean(self.ui, self.repo)
218 check_clean(self.ui, self.repo)
219 rev = lookup_rev(self.ui, self.repo, rev)
219 rev = lookup_rev(self.ui, self.repo, rev)
220 self.bad(rev)
220 self.bad(rev)
221 if self.goodrevs:
221 if self.goodrevs:
222 self.autonext()
222 self.autonext()
223
223
224 # should we put it in the class ?
224 # should we put it in the class ?
225 def test(ui, repo, rev):
225 def test(ui, repo, rev):
226 """test the bisection code"""
226 """test the bisection code"""
227 b = bisect(ui, repo)
227 b = bisect(ui, repo)
228 rev = repo.lookup(rev)
228 rev = repo.lookup(rev)
229 ui.write("testing with rev %s\n" % hg.hex(rev))
229 ui.write("testing with rev %s\n" % hg.hex(rev))
230 anc = b.ancestors()
230 anc = b.ancestors()
231 while len(anc) > 1:
231 while len(anc) > 1:
232 if not rev in anc:
232 if not rev in anc:
233 ui.warn("failure while bisecting\n")
233 ui.warn("failure while bisecting\n")
234 sys.exit(1)
234 sys.exit(1)
235 ui.write("it worked :)\n")
235 ui.write("it worked :)\n")
236 new_rev = b.next()
236 new_rev = b.next()
237 ui.write("choosing if good or bad\n")
237 ui.write("choosing if good or bad\n")
238 if rev in b.ancestors(head=new_rev):
238 if rev in b.ancestors(head=new_rev):
239 b.bad(new_rev)
239 b.bad(new_rev)
240 ui.write("it is bad\n")
240 ui.write("it is bad\n")
241 else:
241 else:
242 b.good(new_rev)
242 b.good(new_rev)
243 ui.write("it is good\n")
243 ui.write("it is good\n")
244 anc = b.ancestors()
244 anc = b.ancestors()
245 #repo.update(new_rev, force=True)
245 #repo.update(new_rev, force=True)
246 for v in anc:
246 for v in anc:
247 if v != rev:
247 if v != rev:
248 ui.warn("fail to found cset! :(\n")
248 ui.warn("fail to found cset! :(\n")
249 return 1
249 return 1
250 ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
250 ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
251 ui.write("Everything is ok :)\n")
251 ui.write("Everything is ok :)\n")
252 return 0
252 return 0
253
253
254 def bisect_run(ui, repo, cmd=None, *args):
254 def bisect_run(ui, repo, cmd=None, *args):
255 """bisect extension: dichotomic search in the DAG of changesets
255 """bisect extension: dichotomic search in the DAG of changesets
256 for subcommands see "hg bisect help\"
256 for subcommands see "hg bisect help\"
257 """
257 """
258 def help_(cmd=None, *args):
258 def help_(cmd=None, *args):
259 """show help for a given bisect subcommand or all subcommands"""
259 """show help for a given bisect subcommand or all subcommands"""
260 cmdtable = bisectcmdtable
260 cmdtable = bisectcmdtable
261 if cmd:
261 if cmd:
262 doc = cmdtable[cmd][0].__doc__
262 doc = cmdtable[cmd][0].__doc__
263 synopsis = cmdtable[cmd][2]
263 synopsis = cmdtable[cmd][2]
264 ui.write(synopsis + "\n")
264 ui.write(synopsis + "\n")
265 ui.write("\n" + doc + "\n")
265 ui.write("\n" + doc + "\n")
266 return
266 return
267 ui.write(_("list of subcommands for the bisect extension\n\n"))
267 ui.write(_("list of subcommands for the bisect extension\n\n"))
268 cmds = cmdtable.keys()
268 cmds = cmdtable.keys()
269 cmds.sort()
269 cmds.sort()
270 m = max([len(c) for c in cmds])
270 m = max([len(c) for c in cmds])
271 for cmd in cmds:
271 for cmd in cmds:
272 doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip()
272 doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip()
273 ui.write(" %-*s %s\n" % (m, cmd, doc))
273 ui.write(" %-*s %s\n" % (m, cmd, doc))
274
274
275 b = bisect(ui, repo)
275 b = bisect(ui, repo)
276 bisectcmdtable = {
276 bisectcmdtable = {
277 "init": (b.init, 0, _("hg bisect init")),
277 "init": (b.init, 0, _("hg bisect init")),
278 "bad": (b.autobad, 1, _("hg bisect bad [<rev>]")),
278 "bad": (b.autobad, 1, _("hg bisect bad [<rev>]")),
279 "good": (b.autogood, 1, _("hg bisect good [<rev>]")),
279 "good": (b.autogood, 1, _("hg bisect good [<rev>]")),
280 "next": (b.autonext, 0, _("hg bisect next")),
280 "next": (b.autonext, 0, _("hg bisect next")),
281 "reset": (b.reset, 0, _("hg bisect reset")),
281 "reset": (b.reset, 0, _("hg bisect reset")),
282 "help": (help_, 1, _("hg bisect help [<subcommand>]")),
282 "help": (help_, 1, _("hg bisect help [<subcommand>]")),
283 }
283 }
284
284
285 if not bisectcmdtable.has_key(cmd):
285 if not bisectcmdtable.has_key(cmd):
286 ui.warn(_("bisect: Unknown sub-command\n"))
286 ui.warn(_("bisect: Unknown sub-command\n"))
287 return help_()
287 return help_()
288 if len(args) > bisectcmdtable[cmd][1]:
288 if len(args) > bisectcmdtable[cmd][1]:
289 ui.warn(_("bisect: Too many arguments\n"))
289 ui.warn(_("bisect: Too many arguments\n"))
290 return help_()
290 return help_()
291 try:
291 try:
292 return bisectcmdtable[cmd][0](*args)
292 return bisectcmdtable[cmd][0](*args)
293 finally:
293 finally:
294 b.write()
294 b.write()
295
295
296 cmdtable = {
296 cmdtable = {
297 "bisect": (bisect_run, [], _("hg bisect [help|init|reset|next|good|bad]")),
297 "bisect": (bisect_run, [], _("hg bisect [help|init|reset|next|good|bad]")),
298 #"bisect-test": (test, [], "hg bisect-test rev"),
298 #"bisect-test": (test, [], "hg bisect-test rev"),
299 }
299 }
@@ -1,280 +1,280 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 to people when changesets are
8 # hook extension to email notifications to people when changesets are
9 # committed to a repo they subscribe to.
9 # committed to a repo they subscribe to.
10 #
10 #
11 # default mode is to print messages to stdout, for testing and
11 # default mode is to print messages to stdout, for testing and
12 # configuring.
12 # configuring.
13 #
13 #
14 # to use, configure notify extension and enable in hgrc like this:
14 # to use, configure notify extension and enable in hgrc like this:
15 #
15 #
16 # [extensions]
16 # [extensions]
17 # hgext.notify =
17 # hgext.notify =
18 #
18 #
19 # [hooks]
19 # [hooks]
20 # # one email for each incoming changeset
20 # # one email for each incoming changeset
21 # incoming.notify = python:hgext.notify.hook
21 # incoming.notify = python:hgext.notify.hook
22 # # batch emails when many changesets incoming at one time
22 # # batch emails when many changesets incoming at one time
23 # changegroup.notify = python:hgext.notify.hook
23 # changegroup.notify = python:hgext.notify.hook
24 #
24 #
25 # [notify]
25 # [notify]
26 # # config items go in here
26 # # config items go in here
27 #
27 #
28 # config items:
28 # config items:
29 #
29 #
30 # REQUIRED:
30 # REQUIRED:
31 # config = /path/to/file # file containing subscriptions
31 # config = /path/to/file # file containing subscriptions
32 #
32 #
33 # OPTIONAL:
33 # OPTIONAL:
34 # test = True # print messages to stdout for testing
34 # test = True # print messages to stdout for testing
35 # strip = 3 # number of slashes to strip for url paths
35 # strip = 3 # number of slashes to strip for url paths
36 # domain = example.com # domain to use if committer missing domain
36 # domain = example.com # domain to use if committer missing domain
37 # style = ... # style file to use when formatting email
37 # style = ... # style file to use when formatting email
38 # template = ... # template to use when formatting email
38 # template = ... # template to use when formatting email
39 # incoming = ... # template to use when run as incoming hook
39 # incoming = ... # template to use when run as incoming hook
40 # changegroup = ... # template when run as changegroup hook
40 # changegroup = ... # template when run as changegroup hook
41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
42 # maxsubject = 67 # truncate subject line longer than this
42 # maxsubject = 67 # truncate subject line longer than this
43 # diffstat = True # add a diffstat before the diff content
43 # diffstat = True # add a diffstat before the diff content
44 # sources = serve # notify if source of incoming changes in this list
44 # sources = serve # notify if source of incoming changes in this list
45 # # (serve == ssh or http, push, pull, bundle)
45 # # (serve == ssh or http, push, pull, bundle)
46 # [email]
46 # [email]
47 # from = user@host.com # email address to send as if none given
47 # from = user@host.com # email address to send as if none given
48 # [web]
48 # [web]
49 # baseurl = http://hgserver/... # root of hg web site for browsing commits
49 # baseurl = http://hgserver/... # root of hg web site for browsing commits
50 #
50 #
51 # notify config file has same format as regular hgrc. it has two
51 # notify config file has same format as regular hgrc. it has two
52 # sections so you can express subscriptions in whatever way is handier
52 # sections so you can express subscriptions in whatever way is handier
53 # for you.
53 # for you.
54 #
54 #
55 # [usersubs]
55 # [usersubs]
56 # # key is subscriber email, value is ","-separated list of glob patterns
56 # # key is subscriber email, value is ","-separated list of glob patterns
57 # user@host = pattern
57 # user@host = pattern
58 #
58 #
59 # [reposubs]
59 # [reposubs]
60 # # key is glob pattern, value is ","-separated list of subscriber emails
60 # # key is glob pattern, value is ","-separated list of subscriber emails
61 # pattern = user@host
61 # pattern = user@host
62 #
62 #
63 # glob patterns are matched against path to repo root.
63 # glob patterns are matched against path to repo root.
64 #
64 #
65 # if you like, you can put notify config file in repo that users can
65 # if you like, you can put notify config file in repo that users can
66 # push changes to, they can manage their own subscriptions.
66 # push changes to, they can manage their own subscriptions.
67
67
68 from mercurial.demandload import *
68 from mercurial.demandload import *
69 from mercurial.i18n import gettext as _
69 from mercurial.i18n import gettext as _
70 from mercurial.node import *
70 from mercurial.node import *
71 demandload(globals(), 'mercurial:commands,patch,templater,util,mail')
71 demandload(globals(), 'mercurial:commands,patch,cmdutil,templater,util,mail')
72 demandload(globals(), 'email.Parser fnmatch socket time')
72 demandload(globals(), 'email.Parser fnmatch socket time')
73
73
74 # template for single changeset can include email headers.
74 # template for single changeset can include email headers.
75 single_template = '''
75 single_template = '''
76 Subject: changeset in {webroot}: {desc|firstline|strip}
76 Subject: changeset in {webroot}: {desc|firstline|strip}
77 From: {author}
77 From: {author}
78
78
79 changeset {node|short} in {root}
79 changeset {node|short} in {root}
80 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
80 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
81 description:
81 description:
82 \t{desc|tabindent|strip}
82 \t{desc|tabindent|strip}
83 '''.lstrip()
83 '''.lstrip()
84
84
85 # template for multiple changesets should not contain email headers,
85 # template for multiple changesets should not contain email headers,
86 # because only first set of headers will be used and result will look
86 # because only first set of headers will be used and result will look
87 # strange.
87 # strange.
88 multiple_template = '''
88 multiple_template = '''
89 changeset {node|short} in {root}
89 changeset {node|short} in {root}
90 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
90 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
91 summary: {desc|firstline}
91 summary: {desc|firstline}
92 '''
92 '''
93
93
94 deftemplates = {
94 deftemplates = {
95 'changegroup': multiple_template,
95 'changegroup': multiple_template,
96 }
96 }
97
97
98 class notifier(object):
98 class notifier(object):
99 '''email notification class.'''
99 '''email notification class.'''
100
100
101 def __init__(self, ui, repo, hooktype):
101 def __init__(self, ui, repo, hooktype):
102 self.ui = ui
102 self.ui = ui
103 cfg = self.ui.config('notify', 'config')
103 cfg = self.ui.config('notify', 'config')
104 if cfg:
104 if cfg:
105 self.ui.readsections(cfg, 'usersubs', 'reposubs')
105 self.ui.readsections(cfg, 'usersubs', 'reposubs')
106 self.repo = repo
106 self.repo = repo
107 self.stripcount = int(self.ui.config('notify', 'strip', 0))
107 self.stripcount = int(self.ui.config('notify', 'strip', 0))
108 self.root = self.strip(self.repo.root)
108 self.root = self.strip(self.repo.root)
109 self.domain = self.ui.config('notify', 'domain')
109 self.domain = self.ui.config('notify', 'domain')
110 self.sio = templater.stringio()
110 self.sio = cmdutil.stringio()
111 self.subs = self.subscribers()
111 self.subs = self.subscribers()
112
112
113 mapfile = self.ui.config('notify', 'style')
113 mapfile = self.ui.config('notify', 'style')
114 template = (self.ui.config('notify', hooktype) or
114 template = (self.ui.config('notify', hooktype) or
115 self.ui.config('notify', 'template'))
115 self.ui.config('notify', 'template'))
116 self.t = templater.changeset_templater(self.ui, self.repo, mapfile,
116 self.t = cmdutil.changeset_templater(self.ui, self.repo, mapfile,
117 self.sio)
117 self.sio)
118 if not mapfile and not template:
118 if not mapfile and not template:
119 template = deftemplates.get(hooktype) or single_template
119 template = deftemplates.get(hooktype) or single_template
120 if template:
120 if template:
121 template = templater.parsestring(template, quoted=False)
121 template = templater.parsestring(template, quoted=False)
122 self.t.use_template(template)
122 self.t.use_template(template)
123
123
124 def strip(self, path):
124 def strip(self, path):
125 '''strip leading slashes from local path, turn into web-safe path.'''
125 '''strip leading slashes from local path, turn into web-safe path.'''
126
126
127 path = util.pconvert(path)
127 path = util.pconvert(path)
128 count = self.stripcount
128 count = self.stripcount
129 while count > 0:
129 while count > 0:
130 c = path.find('/')
130 c = path.find('/')
131 if c == -1:
131 if c == -1:
132 break
132 break
133 path = path[c+1:]
133 path = path[c+1:]
134 count -= 1
134 count -= 1
135 return path
135 return path
136
136
137 def fixmail(self, addr):
137 def fixmail(self, addr):
138 '''try to clean up email addresses.'''
138 '''try to clean up email addresses.'''
139
139
140 addr = templater.email(addr.strip())
140 addr = templater.email(addr.strip())
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
150
151 subs = {}
151 subs = {}
152 for user, pats in self.ui.configitems('usersubs'):
152 for user, pats in self.ui.configitems('usersubs'):
153 for pat in pats.split(','):
153 for pat in pats.split(','):
154 if fnmatch.fnmatch(self.repo.root, pat.strip()):
154 if fnmatch.fnmatch(self.repo.root, pat.strip()):
155 subs[self.fixmail(user)] = 1
155 subs[self.fixmail(user)] = 1
156 for pat, users in self.ui.configitems('reposubs'):
156 for pat, users in self.ui.configitems('reposubs'):
157 if fnmatch.fnmatch(self.repo.root, pat):
157 if fnmatch.fnmatch(self.repo.root, pat):
158 for user in users.split(','):
158 for user in users.split(','):
159 subs[self.fixmail(user)] = 1
159 subs[self.fixmail(user)] = 1
160 subs = subs.keys()
160 subs = subs.keys()
161 subs.sort()
161 subs.sort()
162 return subs
162 return subs
163
163
164 def url(self, path=None):
164 def url(self, path=None):
165 return self.ui.config('web', 'baseurl') + (path or self.root)
165 return self.ui.config('web', 'baseurl') + (path or self.root)
166
166
167 def node(self, node):
167 def node(self, node):
168 '''format one changeset.'''
168 '''format one changeset.'''
169
169
170 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
170 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
171 baseurl=self.ui.config('web', 'baseurl'),
171 baseurl=self.ui.config('web', 'baseurl'),
172 root=self.repo.root,
172 root=self.repo.root,
173 webroot=self.root)
173 webroot=self.root)
174
174
175 def skipsource(self, source):
175 def skipsource(self, source):
176 '''true if incoming changes from this source should be skipped.'''
176 '''true if incoming changes from this source should be skipped.'''
177 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
177 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
178 return source not in ok_sources
178 return source not in ok_sources
179
179
180 def send(self, node, count):
180 def send(self, node, count):
181 '''send message.'''
181 '''send message.'''
182
182
183 p = email.Parser.Parser()
183 p = email.Parser.Parser()
184 self.sio.seek(0)
184 self.sio.seek(0)
185 msg = p.parse(self.sio)
185 msg = p.parse(self.sio)
186
186
187 def fix_subject():
187 def fix_subject():
188 '''try to make subject line exist and be useful.'''
188 '''try to make subject line exist and be useful.'''
189
189
190 subject = msg['Subject']
190 subject = msg['Subject']
191 if not subject:
191 if not subject:
192 if count > 1:
192 if count > 1:
193 subject = _('%s: %d new changesets') % (self.root, count)
193 subject = _('%s: %d new changesets') % (self.root, count)
194 else:
194 else:
195 changes = self.repo.changelog.read(node)
195 changes = self.repo.changelog.read(node)
196 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
196 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
197 subject = '%s: %s' % (self.root, s)
197 subject = '%s: %s' % (self.root, s)
198 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
198 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
199 if maxsubject and len(subject) > maxsubject:
199 if maxsubject and len(subject) > maxsubject:
200 subject = subject[:maxsubject-3] + '...'
200 subject = subject[:maxsubject-3] + '...'
201 del msg['Subject']
201 del msg['Subject']
202 msg['Subject'] = subject
202 msg['Subject'] = subject
203
203
204 def fix_sender():
204 def fix_sender():
205 '''try to make message have proper sender.'''
205 '''try to make message have proper sender.'''
206
206
207 sender = msg['From']
207 sender = msg['From']
208 if not sender:
208 if not sender:
209 sender = self.ui.config('email', 'from') or self.ui.username()
209 sender = self.ui.config('email', 'from') or self.ui.username()
210 if '@' not in sender or '@localhost' in sender:
210 if '@' not in sender or '@localhost' in sender:
211 sender = self.fixmail(sender)
211 sender = self.fixmail(sender)
212 del msg['From']
212 del msg['From']
213 msg['From'] = sender
213 msg['From'] = sender
214
214
215 fix_subject()
215 fix_subject()
216 fix_sender()
216 fix_sender()
217
217
218 msg['X-Hg-Notification'] = 'changeset ' + short(node)
218 msg['X-Hg-Notification'] = 'changeset ' + short(node)
219 if not msg['Message-Id']:
219 if not msg['Message-Id']:
220 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
220 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
221 (short(node), int(time.time()),
221 (short(node), int(time.time()),
222 hash(self.repo.root), socket.getfqdn()))
222 hash(self.repo.root), socket.getfqdn()))
223 msg['To'] = ', '.join(self.subs)
223 msg['To'] = ', '.join(self.subs)
224
224
225 msgtext = msg.as_string(0)
225 msgtext = msg.as_string(0)
226 if self.ui.configbool('notify', 'test', True):
226 if self.ui.configbool('notify', 'test', True):
227 self.ui.write(msgtext)
227 self.ui.write(msgtext)
228 if not msgtext.endswith('\n'):
228 if not msgtext.endswith('\n'):
229 self.ui.write('\n')
229 self.ui.write('\n')
230 else:
230 else:
231 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
231 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
232 (len(self.subs), count))
232 (len(self.subs), count))
233 mail.sendmail(self.ui, templater.email(msg['From']),
233 mail.sendmail(self.ui, templater.email(msg['From']),
234 self.subs, msgtext)
234 self.subs, msgtext)
235
235
236 def diff(self, node, ref):
236 def diff(self, node, ref):
237 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
237 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
238 if maxdiff == 0:
238 if maxdiff == 0:
239 return
239 return
240 fp = templater.stringio()
240 fp = cmdutil.stringio()
241 prev = self.repo.changelog.parents(node)[0]
241 prev = self.repo.changelog.parents(node)[0]
242 patch.diff(self.repo, prev, ref, fp=fp)
242 patch.diff(self.repo, prev, ref, fp=fp)
243 difflines = fp.getvalue().splitlines(1)
243 difflines = fp.getvalue().splitlines(1)
244 if self.ui.configbool('notify', 'diffstat', True):
244 if self.ui.configbool('notify', 'diffstat', True):
245 s = patch.diffstat(difflines)
245 s = patch.diffstat(difflines)
246 self.sio.write('\ndiffstat:\n\n' + s)
246 self.sio.write('\ndiffstat:\n\n' + s)
247 if maxdiff > 0 and len(difflines) > maxdiff:
247 if maxdiff > 0 and len(difflines) > maxdiff:
248 self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
248 self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
249 (len(difflines), maxdiff))
249 (len(difflines), maxdiff))
250 difflines = difflines[:maxdiff]
250 difflines = difflines[:maxdiff]
251 elif difflines:
251 elif difflines:
252 self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
252 self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
253 self.sio.write(*difflines)
253 self.sio.write(*difflines)
254
254
255 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
255 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
256 '''send email notifications to interested subscribers.
256 '''send email notifications to interested subscribers.
257
257
258 if used as changegroup hook, send one email for all changesets in
258 if used as changegroup hook, send one email for all changesets in
259 changegroup. else send one email per changeset.'''
259 changegroup. else send one email per changeset.'''
260 n = notifier(ui, repo, hooktype)
260 n = notifier(ui, repo, hooktype)
261 if not n.subs:
261 if not n.subs:
262 ui.debug(_('notify: no subscribers to repo %s\n' % n.root))
262 ui.debug(_('notify: no subscribers to repo %s\n' % n.root))
263 return
263 return
264 if n.skipsource(source):
264 if n.skipsource(source):
265 ui.debug(_('notify: changes have source "%s" - skipping\n') %
265 ui.debug(_('notify: changes have source "%s" - skipping\n') %
266 source)
266 source)
267 return
267 return
268 node = bin(node)
268 node = bin(node)
269 if hooktype == 'changegroup':
269 if hooktype == 'changegroup':
270 start = repo.changelog.rev(node)
270 start = repo.changelog.rev(node)
271 end = repo.changelog.count()
271 end = repo.changelog.count()
272 count = end - start
272 count = end - start
273 for rev in xrange(start, end):
273 for rev in xrange(start, end):
274 n.node(repo.changelog.node(rev))
274 n.node(repo.changelog.node(rev))
275 n.diff(node, repo.changelog.tip())
275 n.diff(node, repo.changelog.tip())
276 else:
276 else:
277 count = 1
277 count = 1
278 n.node(node)
278 n.node(node)
279 n.diff(node, node)
279 n.diff(node, node)
280 n.send(node, count)
280 n.send(node, count)
@@ -1,197 +1,524 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 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 demandload import demandload
8 from demandload import demandload
9 from node import *
9 from node import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 demandload(globals(), 'mdiff util')
12 demandload(globals(), 'os sys')
11 demandload(globals(), 'os sys')
12 demandload(globals(), 'mdiff util templater cStringIO')
13
13
14 revrangesep = ':'
14 revrangesep = ':'
15
15
16 def revpair(ui, repo, revs):
16 def revpair(ui, repo, revs):
17 '''return pair of nodes, given list of revisions. second item can
17 '''return pair of nodes, given list of revisions. second item can
18 be None, meaning use working dir.'''
18 be None, meaning use working dir.'''
19
19
20 def revfix(repo, val, defval):
20 def revfix(repo, val, defval):
21 if not val and val != 0:
21 if not val and val != 0:
22 val = defval
22 val = defval
23 return repo.lookup(val)
23 return repo.lookup(val)
24
24
25 if not revs:
25 if not revs:
26 return repo.dirstate.parents()[0], None
26 return repo.dirstate.parents()[0], None
27 end = None
27 end = None
28 if len(revs) == 1:
28 if len(revs) == 1:
29 if revrangesep in revs[0]:
29 if revrangesep in revs[0]:
30 start, end = revs[0].split(revrangesep, 1)
30 start, end = revs[0].split(revrangesep, 1)
31 start = revfix(repo, start, 0)
31 start = revfix(repo, start, 0)
32 end = revfix(repo, end, repo.changelog.count() - 1)
32 end = revfix(repo, end, repo.changelog.count() - 1)
33 else:
33 else:
34 start = revfix(repo, revs[0], None)
34 start = revfix(repo, revs[0], None)
35 elif len(revs) == 2:
35 elif len(revs) == 2:
36 if revrangesep in revs[0] or revrangesep in revs[1]:
36 if revrangesep in revs[0] or revrangesep in revs[1]:
37 raise util.Abort(_('too many revisions specified'))
37 raise util.Abort(_('too many revisions specified'))
38 start = revfix(repo, revs[0], None)
38 start = revfix(repo, revs[0], None)
39 end = revfix(repo, revs[1], None)
39 end = revfix(repo, revs[1], None)
40 else:
40 else:
41 raise util.Abort(_('too many revisions specified'))
41 raise util.Abort(_('too many revisions specified'))
42 return start, end
42 return start, end
43
43
44 def revrange(ui, repo, revs):
44 def revrange(ui, repo, revs):
45 """Yield revision as strings from a list of revision specifications."""
45 """Yield revision as strings from a list of revision specifications."""
46
46
47 def revfix(repo, val, defval):
47 def revfix(repo, val, defval):
48 if not val and val != 0:
48 if not val and val != 0:
49 return defval
49 return defval
50 return repo.changelog.rev(repo.lookup(val))
50 return repo.changelog.rev(repo.lookup(val))
51
51
52 seen, l = {}, []
52 seen, l = {}, []
53 for spec in revs:
53 for spec in revs:
54 if revrangesep in spec:
54 if revrangesep in spec:
55 start, end = spec.split(revrangesep, 1)
55 start, end = spec.split(revrangesep, 1)
56 start = revfix(repo, start, 0)
56 start = revfix(repo, start, 0)
57 end = revfix(repo, end, repo.changelog.count() - 1)
57 end = revfix(repo, end, repo.changelog.count() - 1)
58 step = start > end and -1 or 1
58 step = start > end and -1 or 1
59 for rev in xrange(start, end+step, step):
59 for rev in xrange(start, end+step, step):
60 if rev in seen:
60 if rev in seen:
61 continue
61 continue
62 seen[rev] = 1
62 seen[rev] = 1
63 l.append(rev)
63 l.append(rev)
64 else:
64 else:
65 rev = revfix(repo, spec, None)
65 rev = revfix(repo, spec, None)
66 if rev in seen:
66 if rev in seen:
67 continue
67 continue
68 seen[rev] = 1
68 seen[rev] = 1
69 l.append(rev)
69 l.append(rev)
70
70
71 return l
71 return l
72
72
73 def make_filename(repo, pat, node,
73 def make_filename(repo, pat, node,
74 total=None, seqno=None, revwidth=None, pathname=None):
74 total=None, seqno=None, revwidth=None, pathname=None):
75 node_expander = {
75 node_expander = {
76 'H': lambda: hex(node),
76 'H': lambda: hex(node),
77 'R': lambda: str(repo.changelog.rev(node)),
77 'R': lambda: str(repo.changelog.rev(node)),
78 'h': lambda: short(node),
78 'h': lambda: short(node),
79 }
79 }
80 expander = {
80 expander = {
81 '%': lambda: '%',
81 '%': lambda: '%',
82 'b': lambda: os.path.basename(repo.root),
82 'b': lambda: os.path.basename(repo.root),
83 }
83 }
84
84
85 try:
85 try:
86 if node:
86 if node:
87 expander.update(node_expander)
87 expander.update(node_expander)
88 if node and revwidth is not None:
88 if node and revwidth is not None:
89 expander['r'] = (lambda:
89 expander['r'] = (lambda:
90 str(repo.changelog.rev(node)).zfill(revwidth))
90 str(repo.changelog.rev(node)).zfill(revwidth))
91 if total is not None:
91 if total is not None:
92 expander['N'] = lambda: str(total)
92 expander['N'] = lambda: str(total)
93 if seqno is not None:
93 if seqno is not None:
94 expander['n'] = lambda: str(seqno)
94 expander['n'] = lambda: str(seqno)
95 if total is not None and seqno is not None:
95 if total is not None and seqno is not None:
96 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
96 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
97 if pathname is not None:
97 if pathname is not None:
98 expander['s'] = lambda: os.path.basename(pathname)
98 expander['s'] = lambda: os.path.basename(pathname)
99 expander['d'] = lambda: os.path.dirname(pathname) or '.'
99 expander['d'] = lambda: os.path.dirname(pathname) or '.'
100 expander['p'] = lambda: pathname
100 expander['p'] = lambda: pathname
101
101
102 newname = []
102 newname = []
103 patlen = len(pat)
103 patlen = len(pat)
104 i = 0
104 i = 0
105 while i < patlen:
105 while i < patlen:
106 c = pat[i]
106 c = pat[i]
107 if c == '%':
107 if c == '%':
108 i += 1
108 i += 1
109 c = pat[i]
109 c = pat[i]
110 c = expander[c]()
110 c = expander[c]()
111 newname.append(c)
111 newname.append(c)
112 i += 1
112 i += 1
113 return ''.join(newname)
113 return ''.join(newname)
114 except KeyError, inst:
114 except KeyError, inst:
115 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
115 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
116 inst.args[0])
116 inst.args[0])
117
117
118 def make_file(repo, pat, node=None,
118 def make_file(repo, pat, node=None,
119 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
119 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
120 if not pat or pat == '-':
120 if not pat or pat == '-':
121 return 'w' in mode and sys.stdout or sys.stdin
121 return 'w' in mode and sys.stdout or sys.stdin
122 if hasattr(pat, 'write') and 'w' in mode:
122 if hasattr(pat, 'write') and 'w' in mode:
123 return pat
123 return pat
124 if hasattr(pat, 'read') and 'r' in mode:
124 if hasattr(pat, 'read') and 'r' in mode:
125 return pat
125 return pat
126 return open(make_filename(repo, pat, node, total, seqno, revwidth,
126 return open(make_filename(repo, pat, node, total, seqno, revwidth,
127 pathname),
127 pathname),
128 mode)
128 mode)
129
129
130 def matchpats(repo, pats=[], opts={}, head=''):
130 def matchpats(repo, pats=[], opts={}, head=''):
131 cwd = repo.getcwd()
131 cwd = repo.getcwd()
132 if not pats and cwd:
132 if not pats and cwd:
133 opts['include'] = [os.path.join(cwd, i)
133 opts['include'] = [os.path.join(cwd, i)
134 for i in opts.get('include', [])]
134 for i in opts.get('include', [])]
135 opts['exclude'] = [os.path.join(cwd, x)
135 opts['exclude'] = [os.path.join(cwd, x)
136 for x in opts.get('exclude', [])]
136 for x in opts.get('exclude', [])]
137 cwd = ''
137 cwd = ''
138 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
138 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
139 opts.get('exclude'), head)
139 opts.get('exclude'), head)
140
140
141 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None):
141 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None):
142 files, matchfn, anypats = matchpats(repo, pats, opts, head)
142 files, matchfn, anypats = matchpats(repo, pats, opts, head)
143 exact = dict.fromkeys(files)
143 exact = dict.fromkeys(files)
144 for src, fn in repo.walk(node=node, files=files, match=matchfn,
144 for src, fn in repo.walk(node=node, files=files, match=matchfn,
145 badmatch=badmatch):
145 badmatch=badmatch):
146 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
146 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
147
147
148 def findrenames(repo, added=None, removed=None, threshold=0.5):
148 def findrenames(repo, added=None, removed=None, threshold=0.5):
149 if added is None or removed is None:
149 if added is None or removed is None:
150 added, removed = repo.status()[1:3]
150 added, removed = repo.status()[1:3]
151 changes = repo.changelog.read(repo.dirstate.parents()[0])
151 changes = repo.changelog.read(repo.dirstate.parents()[0])
152 mf = repo.manifest.read(changes[0])
152 mf = repo.manifest.read(changes[0])
153 for a in added:
153 for a in added:
154 aa = repo.wread(a)
154 aa = repo.wread(a)
155 bestscore, bestname = None, None
155 bestscore, bestname = None, None
156 for r in removed:
156 for r in removed:
157 rr = repo.file(r).read(mf[r])
157 rr = repo.file(r).read(mf[r])
158 delta = mdiff.textdiff(aa, rr)
158 delta = mdiff.textdiff(aa, rr)
159 if len(delta) < len(aa):
159 if len(delta) < len(aa):
160 myscore = 1.0 - (float(len(delta)) / len(aa))
160 myscore = 1.0 - (float(len(delta)) / len(aa))
161 if bestscore is None or myscore > bestscore:
161 if bestscore is None or myscore > bestscore:
162 bestscore, bestname = myscore, r
162 bestscore, bestname = myscore, r
163 if bestname and bestscore >= threshold:
163 if bestname and bestscore >= threshold:
164 yield bestname, a, bestscore
164 yield bestname, a, bestscore
165
165
166 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
166 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
167 similarity=None):
167 similarity=None):
168 if dry_run is None:
168 if dry_run is None:
169 dry_run = opts.get('dry_run')
169 dry_run = opts.get('dry_run')
170 if similarity is None:
170 if similarity is None:
171 similarity = float(opts.get('similarity') or 0)
171 similarity = float(opts.get('similarity') or 0)
172 add, remove = [], []
172 add, remove = [], []
173 mapping = {}
173 mapping = {}
174 for src, abs, rel, exact in walk(repo, pats, opts):
174 for src, abs, rel, exact in walk(repo, pats, opts):
175 if src == 'f' and repo.dirstate.state(abs) == '?':
175 if src == 'f' and repo.dirstate.state(abs) == '?':
176 add.append(abs)
176 add.append(abs)
177 mapping[abs] = rel, exact
177 mapping[abs] = rel, exact
178 if repo.ui.verbose or not exact:
178 if repo.ui.verbose or not exact:
179 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
179 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
180 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
180 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
181 remove.append(abs)
181 remove.append(abs)
182 mapping[abs] = rel, exact
182 mapping[abs] = rel, exact
183 if repo.ui.verbose or not exact:
183 if repo.ui.verbose or not exact:
184 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
184 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
185 if not dry_run:
185 if not dry_run:
186 repo.add(add, wlock=wlock)
186 repo.add(add, wlock=wlock)
187 repo.remove(remove, wlock=wlock)
187 repo.remove(remove, wlock=wlock)
188 if similarity > 0:
188 if similarity > 0:
189 for old, new, score in findrenames(repo, add, remove, similarity):
189 for old, new, score in findrenames(repo, add, remove, similarity):
190 oldrel, oldexact = mapping[old]
190 oldrel, oldexact = mapping[old]
191 newrel, newexact = mapping[new]
191 newrel, newexact = mapping[new]
192 if repo.ui.verbose or not oldexact or not newexact:
192 if repo.ui.verbose or not oldexact or not newexact:
193 repo.ui.status(_('recording removal of %s as rename to %s '
193 repo.ui.status(_('recording removal of %s as rename to %s '
194 '(%d%% similar)\n') %
194 '(%d%% similar)\n') %
195 (oldrel, newrel, score * 100))
195 (oldrel, newrel, score * 100))
196 if not dry_run:
196 if not dry_run:
197 repo.copy(old, new, wlock=wlock)
197 repo.copy(old, new, wlock=wlock)
198
199 class changeset_printer(object):
200 '''show changeset information when templating not requested.'''
201
202 def __init__(self, ui, repo):
203 self.ui = ui
204 self.repo = repo
205
206 def show(self, rev=0, changenode=None, brinfo=None, copies=None):
207 '''show a single changeset or file revision'''
208 log = self.repo.changelog
209 if changenode is None:
210 changenode = log.node(rev)
211 elif not rev:
212 rev = log.rev(changenode)
213
214 if self.ui.quiet:
215 self.ui.write("%d:%s\n" % (rev, short(changenode)))
216 return
217
218 changes = log.read(changenode)
219 date = util.datestr(changes[2])
220 extra = changes[5]
221 branch = extra.get("branch")
222
223 hexfunc = self.ui.debugflag and hex or short
224
225 parents = log.parentrevs(rev)
226 if not self.ui.debugflag:
227 if parents[1] == nullrev:
228 if parents[0] >= rev - 1:
229 parents = []
230 else:
231 parents = [parents[0]]
232 parents = [(p, hexfunc(log.node(p))) for p in parents]
233
234 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
235
236 if branch:
237 self.ui.write(_("branch: %s\n") % branch)
238 for tag in self.repo.nodetags(changenode):
239 self.ui.write(_("tag: %s\n") % tag)
240 for parent in parents:
241 self.ui.write(_("parent: %d:%s\n") % parent)
242
243 if brinfo and changenode in brinfo:
244 br = brinfo[changenode]
245 self.ui.write(_("branch: %s\n") % " ".join(br))
246
247 if self.ui.debugflag:
248 self.ui.write(_("manifest: %d:%s\n") %
249 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
250 self.ui.write(_("user: %s\n") % changes[1])
251 self.ui.write(_("date: %s\n") % date)
252
253 if self.ui.debugflag:
254 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
255 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
256 files):
257 if value:
258 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
259 elif changes[3] and self.ui.verbose:
260 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
261 if copies and self.ui.verbose:
262 copies = ['%s (%s)' % c for c in copies]
263 self.ui.write(_("copies: %s\n") % ' '.join(copies))
264
265 if extra and self.ui.debugflag:
266 extraitems = extra.items()
267 extraitems.sort()
268 for key, value in extraitems:
269 self.ui.write(_("extra: %s=%s\n")
270 % (key, value.encode('string_escape')))
271
272 description = changes[4].strip()
273 if description:
274 if self.ui.verbose:
275 self.ui.write(_("description:\n"))
276 self.ui.write(description)
277 self.ui.write("\n\n")
278 else:
279 self.ui.write(_("summary: %s\n") %
280 description.splitlines()[0])
281 self.ui.write("\n")
282
283 class changeset_templater(object):
284 '''format changeset information.'''
285
286 def __init__(self, ui, repo, mapfile, dest=None):
287 self.t = templater.templater(mapfile, templater.common_filters,
288 cache={'parent': '{rev}:{node|short} ',
289 'manifest': '{rev}:{node|short}',
290 'filecopy': '{name} ({source})'})
291 self.ui = ui
292 self.dest = dest
293 self.repo = repo
294
295 def use_template(self, t):
296 '''set template string to use'''
297 self.t.cache['changeset'] = t
298
299 def show(self, rev=0, changenode=None, brinfo=None, copies=[], **props):
300 '''show a single changeset or file revision'''
301 log = self.repo.changelog
302 if changenode is None:
303 changenode = log.node(rev)
304 elif not rev:
305 rev = log.rev(changenode)
306
307 changes = log.read(changenode)
308
309 def showlist(name, values, plural=None, **args):
310 '''expand set of values.
311 name is name of key in template map.
312 values is list of strings or dicts.
313 plural is plural of name, if not simply name + 's'.
314
315 expansion works like this, given name 'foo'.
316
317 if values is empty, expand 'no_foos'.
318
319 if 'foo' not in template map, return values as a string,
320 joined by space.
321
322 expand 'start_foos'.
323
324 for each value, expand 'foo'. if 'last_foo' in template
325 map, expand it instead of 'foo' for last key.
326
327 expand 'end_foos'.
328 '''
329 if plural: names = plural
330 else: names = name + 's'
331 if not values:
332 noname = 'no_' + names
333 if noname in self.t:
334 yield self.t(noname, **args)
335 return
336 if name not in self.t:
337 if isinstance(values[0], str):
338 yield ' '.join(values)
339 else:
340 for v in values:
341 yield dict(v, **args)
342 return
343 startname = 'start_' + names
344 if startname in self.t:
345 yield self.t(startname, **args)
346 vargs = args.copy()
347 def one(v, tag=name):
348 try:
349 vargs.update(v)
350 except (AttributeError, ValueError):
351 try:
352 for a, b in v:
353 vargs[a] = b
354 except ValueError:
355 vargs[name] = v
356 return self.t(tag, **vargs)
357 lastname = 'last_' + name
358 if lastname in self.t:
359 last = values.pop()
360 else:
361 last = None
362 for v in values:
363 yield one(v)
364 if last is not None:
365 yield one(last, tag=lastname)
366 endname = 'end_' + names
367 if endname in self.t:
368 yield self.t(endname, **args)
369
370 def showbranches(**args):
371 branch = changes[5].get("branch")
372 if branch:
373 yield showlist('branch', [branch], plural='branches', **args)
374 # add old style branches if requested
375 if brinfo and changenode in brinfo:
376 yield showlist('branch', brinfo[changenode],
377 plural='branches', **args)
378
379 def showparents(**args):
380 parents = [[('rev', log.rev(p)), ('node', hex(p))]
381 for p in log.parents(changenode)
382 if self.ui.debugflag or p != nullid]
383 if (not self.ui.debugflag and len(parents) == 1 and
384 parents[0][0][1] == rev - 1):
385 return
386 return showlist('parent', parents, **args)
387
388 def showtags(**args):
389 return showlist('tag', self.repo.nodetags(changenode), **args)
390
391 def showextras(**args):
392 extras = changes[5].items()
393 extras.sort()
394 for key, value in extras:
395 args = args.copy()
396 args.update(dict(key=key, value=value))
397 yield self.t('extra', **args)
398
399 def showcopies(**args):
400 c = [{'name': x[0], 'source': x[1]} for x in copies]
401 return showlist('file_copy', c, plural='file_copies', **args)
402
403 if self.ui.debugflag:
404 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
405 def showfiles(**args):
406 return showlist('file', files[0], **args)
407 def showadds(**args):
408 return showlist('file_add', files[1], **args)
409 def showdels(**args):
410 return showlist('file_del', files[2], **args)
411 def showmanifest(**args):
412 args = args.copy()
413 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
414 node=hex(changes[0])))
415 return self.t('manifest', **args)
416 else:
417 def showfiles(**args):
418 yield showlist('file', changes[3], **args)
419 showadds = ''
420 showdels = ''
421 showmanifest = ''
422
423 defprops = {
424 'author': changes[1],
425 'branches': showbranches,
426 'date': changes[2],
427 'desc': changes[4],
428 'file_adds': showadds,
429 'file_dels': showdels,
430 'files': showfiles,
431 'file_copies': showcopies,
432 'manifest': showmanifest,
433 'node': hex(changenode),
434 'parents': showparents,
435 'rev': rev,
436 'tags': showtags,
437 'extras': showextras,
438 }
439 props = props.copy()
440 props.update(defprops)
441
442 try:
443 dest = self.dest or self.ui
444 if self.ui.debugflag and 'header_debug' in self.t:
445 key = 'header_debug'
446 elif self.ui.quiet and 'header_quiet' in self.t:
447 key = 'header_quiet'
448 elif self.ui.verbose and 'header_verbose' in self.t:
449 key = 'header_verbose'
450 elif 'header' in self.t:
451 key = 'header'
452 else:
453 key = ''
454 if key:
455 dest.write_header(templater.stringify(self.t(key, **props)))
456 if self.ui.debugflag and 'changeset_debug' in self.t:
457 key = 'changeset_debug'
458 elif self.ui.quiet and 'changeset_quiet' in self.t:
459 key = 'changeset_quiet'
460 elif self.ui.verbose and 'changeset_verbose' in self.t:
461 key = 'changeset_verbose'
462 else:
463 key = 'changeset'
464 dest.write(templater.stringify(self.t(key, **props)))
465 except KeyError, inst:
466 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
467 inst.args[0]))
468 except SyntaxError, inst:
469 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
470
471 class stringio(object):
472 '''wrap cStringIO for use by changeset_templater.'''
473 def __init__(self):
474 self.fp = cStringIO.StringIO()
475
476 def write(self, *args):
477 for a in args:
478 self.fp.write(a)
479
480 write_header = write
481
482 def __getattr__(self, key):
483 return getattr(self.fp, key)
484
485 def show_changeset(ui, repo, opts):
486 """show one changeset using template or regular display.
487
488 Display format will be the first non-empty hit of:
489 1. option 'template'
490 2. option 'style'
491 3. [ui] setting 'logtemplate'
492 4. [ui] setting 'style'
493 If all of these values are either the unset or the empty string,
494 regular display via changeset_printer() is done.
495 """
496 # options
497 tmpl = opts.get('template')
498 mapfile = None
499 if tmpl:
500 tmpl = templater.parsestring(tmpl, quoted=False)
501 else:
502 mapfile = opts.get('style')
503 # ui settings
504 if not mapfile:
505 tmpl = ui.config('ui', 'logtemplate')
506 if tmpl:
507 tmpl = templater.parsestring(tmpl)
508 else:
509 mapfile = ui.config('ui', 'style')
510
511 if tmpl or mapfile:
512 if mapfile:
513 if not os.path.split(mapfile)[0]:
514 mapname = (templater.templatepath('map-cmdline.' + mapfile)
515 or templater.templatepath(mapfile))
516 if mapname: mapfile = mapname
517 try:
518 t = changeset_templater(ui, repo, mapfile)
519 except SyntaxError, inst:
520 raise util.Abort(inst.args[0])
521 if tmpl: t.use_template(tmpl)
522 return t
523 return changeset_printer(ui, repo)
524
@@ -1,3536 +1,3413 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 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 demandload import demandload
8 from demandload import demandload
9 from node import *
9 from node import *
10 from i18n import gettext as _
10 from i18n import gettext as _
11 demandload(globals(), "os re sys signal imp urllib pdb shlex")
11 demandload(globals(), "os re sys signal imp urllib pdb shlex")
12 demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo")
12 demandload(globals(), "fancyopts ui hg util lock revlog bundlerepo")
13 demandload(globals(), "difflib patch tempfile time")
13 demandload(globals(), "difflib patch tempfile time")
14 demandload(globals(), "traceback errno version atexit bz2")
14 demandload(globals(), "traceback errno version atexit bz2")
15 demandload(globals(), "archival changegroup cmdutil hgweb.server sshserver")
15 demandload(globals(), "archival changegroup cmdutil hgweb.server sshserver")
16
16
17 class UnknownCommand(Exception):
17 class UnknownCommand(Exception):
18 """Exception raised if command is not in the command table."""
18 """Exception raised if command is not in the command table."""
19 class AmbiguousCommand(Exception):
19 class AmbiguousCommand(Exception):
20 """Exception raised if command shortcut matches more than one command."""
20 """Exception raised if command shortcut matches more than one command."""
21
21
22 def bail_if_changed(repo):
22 def bail_if_changed(repo):
23 modified, added, removed, deleted = repo.status()[:4]
23 modified, added, removed, deleted = repo.status()[:4]
24 if modified or added or removed or deleted:
24 if modified or added or removed or deleted:
25 raise util.Abort(_("outstanding uncommitted changes"))
25 raise util.Abort(_("outstanding uncommitted changes"))
26
26
27 def relpath(repo, args):
27 def relpath(repo, args):
28 cwd = repo.getcwd()
28 cwd = repo.getcwd()
29 if cwd:
29 if cwd:
30 return [util.normpath(os.path.join(cwd, x)) for x in args]
30 return [util.normpath(os.path.join(cwd, x)) for x in args]
31 return args
31 return args
32
32
33 def logmessage(opts):
33 def logmessage(opts):
34 """ get the log message according to -m and -l option """
34 """ get the log message according to -m and -l option """
35 message = opts['message']
35 message = opts['message']
36 logfile = opts['logfile']
36 logfile = opts['logfile']
37
37
38 if message and logfile:
38 if message and logfile:
39 raise util.Abort(_('options --message and --logfile are mutually '
39 raise util.Abort(_('options --message and --logfile are mutually '
40 'exclusive'))
40 'exclusive'))
41 if not message and logfile:
41 if not message and logfile:
42 try:
42 try:
43 if logfile == '-':
43 if logfile == '-':
44 message = sys.stdin.read()
44 message = sys.stdin.read()
45 else:
45 else:
46 message = open(logfile).read()
46 message = open(logfile).read()
47 except IOError, inst:
47 except IOError, inst:
48 raise util.Abort(_("can't read commit message '%s': %s") %
48 raise util.Abort(_("can't read commit message '%s': %s") %
49 (logfile, inst.strerror))
49 (logfile, inst.strerror))
50 return message
50 return message
51
51
52 def walkchangerevs(ui, repo, pats, change, opts):
52 def walkchangerevs(ui, repo, pats, change, opts):
53 '''Iterate over files and the revs they changed in.
53 '''Iterate over files and the revs they changed in.
54
54
55 Callers most commonly need to iterate backwards over the history
55 Callers most commonly need to iterate backwards over the history
56 it is interested in. Doing so has awful (quadratic-looking)
56 it is interested in. Doing so has awful (quadratic-looking)
57 performance, so we use iterators in a "windowed" way.
57 performance, so we use iterators in a "windowed" way.
58
58
59 We walk a window of revisions in the desired order. Within the
59 We walk a window of revisions in the desired order. Within the
60 window, we first walk forwards to gather data, then in the desired
60 window, we first walk forwards to gather data, then in the desired
61 order (usually backwards) to display it.
61 order (usually backwards) to display it.
62
62
63 This function returns an (iterator, matchfn) tuple. The iterator
63 This function returns an (iterator, matchfn) tuple. The iterator
64 yields 3-tuples. They will be of one of the following forms:
64 yields 3-tuples. They will be of one of the following forms:
65
65
66 "window", incrementing, lastrev: stepping through a window,
66 "window", incrementing, lastrev: stepping through a window,
67 positive if walking forwards through revs, last rev in the
67 positive if walking forwards through revs, last rev in the
68 sequence iterated over - use to reset state for the current window
68 sequence iterated over - use to reset state for the current window
69
69
70 "add", rev, fns: out-of-order traversal of the given file names
70 "add", rev, fns: out-of-order traversal of the given file names
71 fns, which changed during revision rev - use to gather data for
71 fns, which changed during revision rev - use to gather data for
72 possible display
72 possible display
73
73
74 "iter", rev, None: in-order traversal of the revs earlier iterated
74 "iter", rev, None: in-order traversal of the revs earlier iterated
75 over with "add" - use to display data'''
75 over with "add" - use to display data'''
76
76
77 def increasing_windows(start, end, windowsize=8, sizelimit=512):
77 def increasing_windows(start, end, windowsize=8, sizelimit=512):
78 if start < end:
78 if start < end:
79 while start < end:
79 while start < end:
80 yield start, min(windowsize, end-start)
80 yield start, min(windowsize, end-start)
81 start += windowsize
81 start += windowsize
82 if windowsize < sizelimit:
82 if windowsize < sizelimit:
83 windowsize *= 2
83 windowsize *= 2
84 else:
84 else:
85 while start > end:
85 while start > end:
86 yield start, min(windowsize, start-end-1)
86 yield start, min(windowsize, start-end-1)
87 start -= windowsize
87 start -= windowsize
88 if windowsize < sizelimit:
88 if windowsize < sizelimit:
89 windowsize *= 2
89 windowsize *= 2
90
90
91 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
91 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
92 follow = opts.get('follow') or opts.get('follow_first')
92 follow = opts.get('follow') or opts.get('follow_first')
93
93
94 if repo.changelog.count() == 0:
94 if repo.changelog.count() == 0:
95 return [], matchfn
95 return [], matchfn
96
96
97 if follow:
97 if follow:
98 defrange = '%s:0' % repo.changectx().rev()
98 defrange = '%s:0' % repo.changectx().rev()
99 else:
99 else:
100 defrange = 'tip:0'
100 defrange = 'tip:0'
101 revs = cmdutil.revrange(ui, repo, opts['rev'] or [defrange])
101 revs = cmdutil.revrange(ui, repo, opts['rev'] or [defrange])
102 wanted = {}
102 wanted = {}
103 slowpath = anypats
103 slowpath = anypats
104 fncache = {}
104 fncache = {}
105
105
106 if not slowpath and not files:
106 if not slowpath and not files:
107 # No files, no patterns. Display all revs.
107 # No files, no patterns. Display all revs.
108 wanted = dict.fromkeys(revs)
108 wanted = dict.fromkeys(revs)
109 copies = []
109 copies = []
110 if not slowpath:
110 if not slowpath:
111 # Only files, no patterns. Check the history of each file.
111 # Only files, no patterns. Check the history of each file.
112 def filerevgen(filelog, node):
112 def filerevgen(filelog, node):
113 cl_count = repo.changelog.count()
113 cl_count = repo.changelog.count()
114 if node is None:
114 if node is None:
115 last = filelog.count() - 1
115 last = filelog.count() - 1
116 else:
116 else:
117 last = filelog.rev(node)
117 last = filelog.rev(node)
118 for i, window in increasing_windows(last, nullrev):
118 for i, window in increasing_windows(last, nullrev):
119 revs = []
119 revs = []
120 for j in xrange(i - window, i + 1):
120 for j in xrange(i - window, i + 1):
121 n = filelog.node(j)
121 n = filelog.node(j)
122 revs.append((filelog.linkrev(n),
122 revs.append((filelog.linkrev(n),
123 follow and filelog.renamed(n)))
123 follow and filelog.renamed(n)))
124 revs.reverse()
124 revs.reverse()
125 for rev in revs:
125 for rev in revs:
126 # only yield rev for which we have the changelog, it can
126 # only yield rev for which we have the changelog, it can
127 # happen while doing "hg log" during a pull or commit
127 # happen while doing "hg log" during a pull or commit
128 if rev[0] < cl_count:
128 if rev[0] < cl_count:
129 yield rev
129 yield rev
130 def iterfiles():
130 def iterfiles():
131 for filename in files:
131 for filename in files:
132 yield filename, None
132 yield filename, None
133 for filename_node in copies:
133 for filename_node in copies:
134 yield filename_node
134 yield filename_node
135 minrev, maxrev = min(revs), max(revs)
135 minrev, maxrev = min(revs), max(revs)
136 for file_, node in iterfiles():
136 for file_, node in iterfiles():
137 filelog = repo.file(file_)
137 filelog = repo.file(file_)
138 # A zero count may be a directory or deleted file, so
138 # A zero count may be a directory or deleted file, so
139 # try to find matching entries on the slow path.
139 # try to find matching entries on the slow path.
140 if filelog.count() == 0:
140 if filelog.count() == 0:
141 slowpath = True
141 slowpath = True
142 break
142 break
143 for rev, copied in filerevgen(filelog, node):
143 for rev, copied in filerevgen(filelog, node):
144 if rev <= maxrev:
144 if rev <= maxrev:
145 if rev < minrev:
145 if rev < minrev:
146 break
146 break
147 fncache.setdefault(rev, [])
147 fncache.setdefault(rev, [])
148 fncache[rev].append(file_)
148 fncache[rev].append(file_)
149 wanted[rev] = 1
149 wanted[rev] = 1
150 if follow and copied:
150 if follow and copied:
151 copies.append(copied)
151 copies.append(copied)
152 if slowpath:
152 if slowpath:
153 if follow:
153 if follow:
154 raise util.Abort(_('can only follow copies/renames for explicit '
154 raise util.Abort(_('can only follow copies/renames for explicit '
155 'file names'))
155 'file names'))
156
156
157 # The slow path checks files modified in every changeset.
157 # The slow path checks files modified in every changeset.
158 def changerevgen():
158 def changerevgen():
159 for i, window in increasing_windows(repo.changelog.count()-1,
159 for i, window in increasing_windows(repo.changelog.count()-1,
160 nullrev):
160 nullrev):
161 for j in xrange(i - window, i + 1):
161 for j in xrange(i - window, i + 1):
162 yield j, change(j)[3]
162 yield j, change(j)[3]
163
163
164 for rev, changefiles in changerevgen():
164 for rev, changefiles in changerevgen():
165 matches = filter(matchfn, changefiles)
165 matches = filter(matchfn, changefiles)
166 if matches:
166 if matches:
167 fncache[rev] = matches
167 fncache[rev] = matches
168 wanted[rev] = 1
168 wanted[rev] = 1
169
169
170 class followfilter:
170 class followfilter:
171 def __init__(self, onlyfirst=False):
171 def __init__(self, onlyfirst=False):
172 self.startrev = nullrev
172 self.startrev = nullrev
173 self.roots = []
173 self.roots = []
174 self.onlyfirst = onlyfirst
174 self.onlyfirst = onlyfirst
175
175
176 def match(self, rev):
176 def match(self, rev):
177 def realparents(rev):
177 def realparents(rev):
178 if self.onlyfirst:
178 if self.onlyfirst:
179 return repo.changelog.parentrevs(rev)[0:1]
179 return repo.changelog.parentrevs(rev)[0:1]
180 else:
180 else:
181 return filter(lambda x: x != nullrev,
181 return filter(lambda x: x != nullrev,
182 repo.changelog.parentrevs(rev))
182 repo.changelog.parentrevs(rev))
183
183
184 if self.startrev == nullrev:
184 if self.startrev == nullrev:
185 self.startrev = rev
185 self.startrev = rev
186 return True
186 return True
187
187
188 if rev > self.startrev:
188 if rev > self.startrev:
189 # forward: all descendants
189 # forward: all descendants
190 if not self.roots:
190 if not self.roots:
191 self.roots.append(self.startrev)
191 self.roots.append(self.startrev)
192 for parent in realparents(rev):
192 for parent in realparents(rev):
193 if parent in self.roots:
193 if parent in self.roots:
194 self.roots.append(rev)
194 self.roots.append(rev)
195 return True
195 return True
196 else:
196 else:
197 # backwards: all parents
197 # backwards: all parents
198 if not self.roots:
198 if not self.roots:
199 self.roots.extend(realparents(self.startrev))
199 self.roots.extend(realparents(self.startrev))
200 if rev in self.roots:
200 if rev in self.roots:
201 self.roots.remove(rev)
201 self.roots.remove(rev)
202 self.roots.extend(realparents(rev))
202 self.roots.extend(realparents(rev))
203 return True
203 return True
204
204
205 return False
205 return False
206
206
207 # it might be worthwhile to do this in the iterator if the rev range
207 # it might be worthwhile to do this in the iterator if the rev range
208 # is descending and the prune args are all within that range
208 # is descending and the prune args are all within that range
209 for rev in opts.get('prune', ()):
209 for rev in opts.get('prune', ()):
210 rev = repo.changelog.rev(repo.lookup(rev))
210 rev = repo.changelog.rev(repo.lookup(rev))
211 ff = followfilter()
211 ff = followfilter()
212 stop = min(revs[0], revs[-1])
212 stop = min(revs[0], revs[-1])
213 for x in xrange(rev, stop-1, -1):
213 for x in xrange(rev, stop-1, -1):
214 if ff.match(x) and x in wanted:
214 if ff.match(x) and x in wanted:
215 del wanted[x]
215 del wanted[x]
216
216
217 def iterate():
217 def iterate():
218 if follow and not files:
218 if follow and not files:
219 ff = followfilter(onlyfirst=opts.get('follow_first'))
219 ff = followfilter(onlyfirst=opts.get('follow_first'))
220 def want(rev):
220 def want(rev):
221 if ff.match(rev) and rev in wanted:
221 if ff.match(rev) and rev in wanted:
222 return True
222 return True
223 return False
223 return False
224 else:
224 else:
225 def want(rev):
225 def want(rev):
226 return rev in wanted
226 return rev in wanted
227
227
228 for i, window in increasing_windows(0, len(revs)):
228 for i, window in increasing_windows(0, len(revs)):
229 yield 'window', revs[0] < revs[-1], revs[-1]
229 yield 'window', revs[0] < revs[-1], revs[-1]
230 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
230 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
231 srevs = list(nrevs)
231 srevs = list(nrevs)
232 srevs.sort()
232 srevs.sort()
233 for rev in srevs:
233 for rev in srevs:
234 fns = fncache.get(rev)
234 fns = fncache.get(rev)
235 if not fns:
235 if not fns:
236 def fns_generator():
236 def fns_generator():
237 for f in change(rev)[3]:
237 for f in change(rev)[3]:
238 if matchfn(f):
238 if matchfn(f):
239 yield f
239 yield f
240 fns = fns_generator()
240 fns = fns_generator()
241 yield 'add', rev, fns
241 yield 'add', rev, fns
242 for rev in nrevs:
242 for rev in nrevs:
243 yield 'iter', rev, None
243 yield 'iter', rev, None
244 return iterate(), matchfn
244 return iterate(), matchfn
245
245
246 def write_bundle(cg, filename=None, compress=True):
246 def write_bundle(cg, filename=None, compress=True):
247 """Write a bundle file and return its filename.
247 """Write a bundle file and return its filename.
248
248
249 Existing files will not be overwritten.
249 Existing files will not be overwritten.
250 If no filename is specified, a temporary file is created.
250 If no filename is specified, a temporary file is created.
251 bz2 compression can be turned off.
251 bz2 compression can be turned off.
252 The bundle file will be deleted in case of errors.
252 The bundle file will be deleted in case of errors.
253 """
253 """
254 class nocompress(object):
254 class nocompress(object):
255 def compress(self, x):
255 def compress(self, x):
256 return x
256 return x
257 def flush(self):
257 def flush(self):
258 return ""
258 return ""
259
259
260 fh = None
260 fh = None
261 cleanup = None
261 cleanup = None
262 try:
262 try:
263 if filename:
263 if filename:
264 if os.path.exists(filename):
264 if os.path.exists(filename):
265 raise util.Abort(_("file '%s' already exists") % filename)
265 raise util.Abort(_("file '%s' already exists") % filename)
266 fh = open(filename, "wb")
266 fh = open(filename, "wb")
267 else:
267 else:
268 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
268 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
269 fh = os.fdopen(fd, "wb")
269 fh = os.fdopen(fd, "wb")
270 cleanup = filename
270 cleanup = filename
271
271
272 if compress:
272 if compress:
273 fh.write("HG10")
273 fh.write("HG10")
274 z = bz2.BZ2Compressor(9)
274 z = bz2.BZ2Compressor(9)
275 else:
275 else:
276 fh.write("HG10UN")
276 fh.write("HG10UN")
277 z = nocompress()
277 z = nocompress()
278 # parse the changegroup data, otherwise we will block
278 # parse the changegroup data, otherwise we will block
279 # in case of sshrepo because we don't know the end of the stream
279 # in case of sshrepo because we don't know the end of the stream
280
280
281 # an empty chunkiter is the end of the changegroup
281 # an empty chunkiter is the end of the changegroup
282 empty = False
282 empty = False
283 while not empty:
283 while not empty:
284 empty = True
284 empty = True
285 for chunk in changegroup.chunkiter(cg):
285 for chunk in changegroup.chunkiter(cg):
286 empty = False
286 empty = False
287 fh.write(z.compress(changegroup.genchunk(chunk)))
287 fh.write(z.compress(changegroup.genchunk(chunk)))
288 fh.write(z.compress(changegroup.closechunk()))
288 fh.write(z.compress(changegroup.closechunk()))
289 fh.write(z.flush())
289 fh.write(z.flush())
290 cleanup = None
290 cleanup = None
291 return filename
291 return filename
292 finally:
292 finally:
293 if fh is not None:
293 if fh is not None:
294 fh.close()
294 fh.close()
295 if cleanup is not None:
295 if cleanup is not None:
296 os.unlink(cleanup)
296 os.unlink(cleanup)
297
297
298 class changeset_printer(object):
299 '''show changeset information when templating not requested.'''
300
301 def __init__(self, ui, repo):
302 self.ui = ui
303 self.repo = repo
304
305 def show(self, rev=0, changenode=None, brinfo=None, copies=None):
306 '''show a single changeset or file revision'''
307 log = self.repo.changelog
308 if changenode is None:
309 changenode = log.node(rev)
310 elif not rev:
311 rev = log.rev(changenode)
312
313 if self.ui.quiet:
314 self.ui.write("%d:%s\n" % (rev, short(changenode)))
315 return
316
317 changes = log.read(changenode)
318 date = util.datestr(changes[2])
319 extra = changes[5]
320 branch = extra.get("branch")
321
322 hexfunc = self.ui.debugflag and hex or short
323
324 parents = log.parentrevs(rev)
325 if not self.ui.debugflag:
326 if parents[1] == nullrev:
327 if parents[0] >= rev - 1:
328 parents = []
329 else:
330 parents = [parents[0]]
331 parents = [(p, hexfunc(log.node(p))) for p in parents]
332
333 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
334
335 if branch:
336 self.ui.write(_("branch: %s\n") % branch)
337 for tag in self.repo.nodetags(changenode):
338 self.ui.write(_("tag: %s\n") % tag)
339 for parent in parents:
340 self.ui.write(_("parent: %d:%s\n") % parent)
341
342 if brinfo and changenode in brinfo:
343 br = brinfo[changenode]
344 self.ui.write(_("branch: %s\n") % " ".join(br))
345
346 if self.ui.debugflag:
347 self.ui.write(_("manifest: %d:%s\n") %
348 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
349 self.ui.write(_("user: %s\n") % changes[1])
350 self.ui.write(_("date: %s\n") % date)
351
352 if self.ui.debugflag:
353 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
354 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
355 files):
356 if value:
357 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
358 elif changes[3] and self.ui.verbose:
359 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
360 if copies and self.ui.verbose:
361 copies = ['%s (%s)' % c for c in copies]
362 self.ui.write(_("copies: %s\n") % ' '.join(copies))
363
364 if extra and self.ui.debugflag:
365 extraitems = extra.items()
366 extraitems.sort()
367 for key, value in extraitems:
368 self.ui.write(_("extra: %s=%s\n")
369 % (key, value.encode('string_escape')))
370
371 description = changes[4].strip()
372 if description:
373 if self.ui.verbose:
374 self.ui.write(_("description:\n"))
375 self.ui.write(description)
376 self.ui.write("\n\n")
377 else:
378 self.ui.write(_("summary: %s\n") %
379 description.splitlines()[0])
380 self.ui.write("\n")
381
382 def show_changeset(ui, repo, opts):
383 """show one changeset using template or regular display.
384
385 Display format will be the first non-empty hit of:
386 1. option 'template'
387 2. option 'style'
388 3. [ui] setting 'logtemplate'
389 4. [ui] setting 'style'
390 If all of these values are either the unset or the empty string,
391 regular display via changeset_printer() is done.
392 """
393 # options
394 tmpl = opts.get('template')
395 mapfile = None
396 if tmpl:
397 tmpl = templater.parsestring(tmpl, quoted=False)
398 else:
399 mapfile = opts.get('style')
400 # ui settings
401 if not mapfile:
402 tmpl = ui.config('ui', 'logtemplate')
403 if tmpl:
404 tmpl = templater.parsestring(tmpl)
405 else:
406 mapfile = ui.config('ui', 'style')
407
408 if tmpl or mapfile:
409 if mapfile:
410 if not os.path.split(mapfile)[0]:
411 mapname = (templater.templatepath('map-cmdline.' + mapfile)
412 or templater.templatepath(mapfile))
413 if mapname: mapfile = mapname
414 try:
415 t = templater.changeset_templater(ui, repo, mapfile)
416 except SyntaxError, inst:
417 raise util.Abort(inst.args[0])
418 if tmpl: t.use_template(tmpl)
419 return t
420 return changeset_printer(ui, repo)
421
422 def setremoteconfig(ui, opts):
298 def setremoteconfig(ui, opts):
423 "copy remote options to ui tree"
299 "copy remote options to ui tree"
424 if opts.get('ssh'):
300 if opts.get('ssh'):
425 ui.setconfig("ui", "ssh", opts['ssh'])
301 ui.setconfig("ui", "ssh", opts['ssh'])
426 if opts.get('remotecmd'):
302 if opts.get('remotecmd'):
427 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
303 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
428
304
429 def show_version(ui):
305 def show_version(ui):
430 """output version and copyright information"""
306 """output version and copyright information"""
431 ui.write(_("Mercurial Distributed SCM (version %s)\n")
307 ui.write(_("Mercurial Distributed SCM (version %s)\n")
432 % version.get_version())
308 % version.get_version())
433 ui.status(_(
309 ui.status(_(
434 "\nCopyright (C) 2005, 2006 Matt Mackall <mpm@selenic.com>\n"
310 "\nCopyright (C) 2005, 2006 Matt Mackall <mpm@selenic.com>\n"
435 "This is free software; see the source for copying conditions. "
311 "This is free software; see the source for copying conditions. "
436 "There is NO\nwarranty; "
312 "There is NO\nwarranty; "
437 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
313 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
438 ))
314 ))
439
315
440 def help_(ui, name=None, with_version=False):
316 def help_(ui, name=None, with_version=False):
441 """show help for a command, extension, or list of commands
317 """show help for a command, extension, or list of commands
442
318
443 With no arguments, print a list of commands and short help.
319 With no arguments, print a list of commands and short help.
444
320
445 Given a command name, print help for that command.
321 Given a command name, print help for that command.
446
322
447 Given an extension name, print help for that extension, and the
323 Given an extension name, print help for that extension, and the
448 commands it provides."""
324 commands it provides."""
449 option_lists = []
325 option_lists = []
450
326
451 def helpcmd(name):
327 def helpcmd(name):
452 if with_version:
328 if with_version:
453 show_version(ui)
329 show_version(ui)
454 ui.write('\n')
330 ui.write('\n')
455 aliases, i = findcmd(ui, name)
331 aliases, i = findcmd(ui, name)
456 # synopsis
332 # synopsis
457 ui.write("%s\n\n" % i[2])
333 ui.write("%s\n\n" % i[2])
458
334
459 # description
335 # description
460 doc = i[0].__doc__
336 doc = i[0].__doc__
461 if not doc:
337 if not doc:
462 doc = _("(No help text available)")
338 doc = _("(No help text available)")
463 if ui.quiet:
339 if ui.quiet:
464 doc = doc.splitlines(0)[0]
340 doc = doc.splitlines(0)[0]
465 ui.write("%s\n" % doc.rstrip())
341 ui.write("%s\n" % doc.rstrip())
466
342
467 if not ui.quiet:
343 if not ui.quiet:
468 # aliases
344 # aliases
469 if len(aliases) > 1:
345 if len(aliases) > 1:
470 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
346 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
471
347
472 # options
348 # options
473 if i[1]:
349 if i[1]:
474 option_lists.append(("options", i[1]))
350 option_lists.append(("options", i[1]))
475
351
476 def helplist(select=None):
352 def helplist(select=None):
477 h = {}
353 h = {}
478 cmds = {}
354 cmds = {}
479 for c, e in table.items():
355 for c, e in table.items():
480 f = c.split("|", 1)[0]
356 f = c.split("|", 1)[0]
481 if select and not select(f):
357 if select and not select(f):
482 continue
358 continue
483 if name == "shortlist" and not f.startswith("^"):
359 if name == "shortlist" and not f.startswith("^"):
484 continue
360 continue
485 f = f.lstrip("^")
361 f = f.lstrip("^")
486 if not ui.debugflag and f.startswith("debug"):
362 if not ui.debugflag and f.startswith("debug"):
487 continue
363 continue
488 doc = e[0].__doc__
364 doc = e[0].__doc__
489 if not doc:
365 if not doc:
490 doc = _("(No help text available)")
366 doc = _("(No help text available)")
491 h[f] = doc.splitlines(0)[0].rstrip()
367 h[f] = doc.splitlines(0)[0].rstrip()
492 cmds[f] = c.lstrip("^")
368 cmds[f] = c.lstrip("^")
493
369
494 fns = h.keys()
370 fns = h.keys()
495 fns.sort()
371 fns.sort()
496 m = max(map(len, fns))
372 m = max(map(len, fns))
497 for f in fns:
373 for f in fns:
498 if ui.verbose:
374 if ui.verbose:
499 commands = cmds[f].replace("|",", ")
375 commands = cmds[f].replace("|",", ")
500 ui.write(" %s:\n %s\n"%(commands, h[f]))
376 ui.write(" %s:\n %s\n"%(commands, h[f]))
501 else:
377 else:
502 ui.write(' %-*s %s\n' % (m, f, h[f]))
378 ui.write(' %-*s %s\n' % (m, f, h[f]))
503
379
504 def helpext(name):
380 def helpext(name):
505 try:
381 try:
506 mod = findext(name)
382 mod = findext(name)
507 except KeyError:
383 except KeyError:
508 raise UnknownCommand(name)
384 raise UnknownCommand(name)
509
385
510 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
386 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
511 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
387 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
512 for d in doc[1:]:
388 for d in doc[1:]:
513 ui.write(d, '\n')
389 ui.write(d, '\n')
514
390
515 ui.status('\n')
391 ui.status('\n')
516 if ui.verbose:
392 if ui.verbose:
517 ui.status(_('list of commands:\n\n'))
393 ui.status(_('list of commands:\n\n'))
518 else:
394 else:
519 ui.status(_('list of commands (use "hg help -v %s" '
395 ui.status(_('list of commands (use "hg help -v %s" '
520 'to show aliases and global options):\n\n') % name)
396 'to show aliases and global options):\n\n') % name)
521
397
522 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in mod.cmdtable])
398 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in mod.cmdtable])
523 helplist(modcmds.has_key)
399 helplist(modcmds.has_key)
524
400
525 if name and name != 'shortlist':
401 if name and name != 'shortlist':
526 try:
402 try:
527 helpcmd(name)
403 helpcmd(name)
528 except UnknownCommand:
404 except UnknownCommand:
529 helpext(name)
405 helpext(name)
530
406
531 else:
407 else:
532 # program name
408 # program name
533 if ui.verbose or with_version:
409 if ui.verbose or with_version:
534 show_version(ui)
410 show_version(ui)
535 else:
411 else:
536 ui.status(_("Mercurial Distributed SCM\n"))
412 ui.status(_("Mercurial Distributed SCM\n"))
537 ui.status('\n')
413 ui.status('\n')
538
414
539 # list of commands
415 # list of commands
540 if name == "shortlist":
416 if name == "shortlist":
541 ui.status(_('basic commands (use "hg help" '
417 ui.status(_('basic commands (use "hg help" '
542 'for the full list or option "-v" for details):\n\n'))
418 'for the full list or option "-v" for details):\n\n'))
543 elif ui.verbose:
419 elif ui.verbose:
544 ui.status(_('list of commands:\n\n'))
420 ui.status(_('list of commands:\n\n'))
545 else:
421 else:
546 ui.status(_('list of commands (use "hg help -v" '
422 ui.status(_('list of commands (use "hg help -v" '
547 'to show aliases and global options):\n\n'))
423 'to show aliases and global options):\n\n'))
548
424
549 helplist()
425 helplist()
550
426
551 # global options
427 # global options
552 if ui.verbose:
428 if ui.verbose:
553 option_lists.append(("global options", globalopts))
429 option_lists.append(("global options", globalopts))
554
430
555 # list all option lists
431 # list all option lists
556 opt_output = []
432 opt_output = []
557 for title, options in option_lists:
433 for title, options in option_lists:
558 opt_output.append(("\n%s:\n" % title, None))
434 opt_output.append(("\n%s:\n" % title, None))
559 for shortopt, longopt, default, desc in options:
435 for shortopt, longopt, default, desc in options:
560 if "DEPRECATED" in desc and not ui.verbose: continue
436 if "DEPRECATED" in desc and not ui.verbose: continue
561 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
437 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
562 longopt and " --%s" % longopt),
438 longopt and " --%s" % longopt),
563 "%s%s" % (desc,
439 "%s%s" % (desc,
564 default
440 default
565 and _(" (default: %s)") % default
441 and _(" (default: %s)") % default
566 or "")))
442 or "")))
567
443
568 if opt_output:
444 if opt_output:
569 opts_len = max([len(line[0]) for line in opt_output if line[1]])
445 opts_len = max([len(line[0]) for line in opt_output if line[1]])
570 for first, second in opt_output:
446 for first, second in opt_output:
571 if second:
447 if second:
572 ui.write(" %-*s %s\n" % (opts_len, first, second))
448 ui.write(" %-*s %s\n" % (opts_len, first, second))
573 else:
449 else:
574 ui.write("%s\n" % first)
450 ui.write("%s\n" % first)
575
451
576 # Commands start here, listed alphabetically
452 # Commands start here, listed alphabetically
577
453
578 def add(ui, repo, *pats, **opts):
454 def add(ui, repo, *pats, **opts):
579 """add the specified files on the next commit
455 """add the specified files on the next commit
580
456
581 Schedule files to be version controlled and added to the repository.
457 Schedule files to be version controlled and added to the repository.
582
458
583 The files will be added to the repository at the next commit.
459 The files will be added to the repository at the next commit.
584
460
585 If no names are given, add all files in the repository.
461 If no names are given, add all files in the repository.
586 """
462 """
587
463
588 names = []
464 names = []
589 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
465 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
590 if exact:
466 if exact:
591 if ui.verbose:
467 if ui.verbose:
592 ui.status(_('adding %s\n') % rel)
468 ui.status(_('adding %s\n') % rel)
593 names.append(abs)
469 names.append(abs)
594 elif repo.dirstate.state(abs) == '?':
470 elif repo.dirstate.state(abs) == '?':
595 ui.status(_('adding %s\n') % rel)
471 ui.status(_('adding %s\n') % rel)
596 names.append(abs)
472 names.append(abs)
597 if not opts.get('dry_run'):
473 if not opts.get('dry_run'):
598 repo.add(names)
474 repo.add(names)
599
475
600 def addremove(ui, repo, *pats, **opts):
476 def addremove(ui, repo, *pats, **opts):
601 """add all new files, delete all missing files
477 """add all new files, delete all missing files
602
478
603 Add all new files and remove all missing files from the repository.
479 Add all new files and remove all missing files from the repository.
604
480
605 New files are ignored if they match any of the patterns in .hgignore. As
481 New files are ignored if they match any of the patterns in .hgignore. As
606 with add, these changes take effect at the next commit.
482 with add, these changes take effect at the next commit.
607
483
608 Use the -s option to detect renamed files. With a parameter > 0,
484 Use the -s option to detect renamed files. With a parameter > 0,
609 this compares every removed file with every added file and records
485 this compares every removed file with every added file and records
610 those similar enough as renames. This option takes a percentage
486 those similar enough as renames. This option takes a percentage
611 between 0 (disabled) and 100 (files must be identical) as its
487 between 0 (disabled) and 100 (files must be identical) as its
612 parameter. Detecting renamed files this way can be expensive.
488 parameter. Detecting renamed files this way can be expensive.
613 """
489 """
614 sim = float(opts.get('similarity') or 0)
490 sim = float(opts.get('similarity') or 0)
615 if sim < 0 or sim > 100:
491 if sim < 0 or sim > 100:
616 raise util.Abort(_('similarity must be between 0 and 100'))
492 raise util.Abort(_('similarity must be between 0 and 100'))
617 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
493 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
618
494
619 def annotate(ui, repo, *pats, **opts):
495 def annotate(ui, repo, *pats, **opts):
620 """show changeset information per file line
496 """show changeset information per file line
621
497
622 List changes in files, showing the revision id responsible for each line
498 List changes in files, showing the revision id responsible for each line
623
499
624 This command is useful to discover who did a change or when a change took
500 This command is useful to discover who did a change or when a change took
625 place.
501 place.
626
502
627 Without the -a option, annotate will avoid processing files it
503 Without the -a option, annotate will avoid processing files it
628 detects as binary. With -a, annotate will generate an annotation
504 detects as binary. With -a, annotate will generate an annotation
629 anyway, probably with undesirable results.
505 anyway, probably with undesirable results.
630 """
506 """
631 getdate = util.cachefunc(lambda x: util.datestr(x.date()))
507 getdate = util.cachefunc(lambda x: util.datestr(x.date()))
632
508
633 if not pats:
509 if not pats:
634 raise util.Abort(_('at least one file name or pattern required'))
510 raise util.Abort(_('at least one file name or pattern required'))
635
511
636 opmap = [['user', lambda x: ui.shortuser(x.user())],
512 opmap = [['user', lambda x: ui.shortuser(x.user())],
637 ['number', lambda x: str(x.rev())],
513 ['number', lambda x: str(x.rev())],
638 ['changeset', lambda x: short(x.node())],
514 ['changeset', lambda x: short(x.node())],
639 ['date', getdate], ['follow', lambda x: x.path()]]
515 ['date', getdate], ['follow', lambda x: x.path()]]
640 if (not opts['user'] and not opts['changeset'] and not opts['date']
516 if (not opts['user'] and not opts['changeset'] and not opts['date']
641 and not opts['follow']):
517 and not opts['follow']):
642 opts['number'] = 1
518 opts['number'] = 1
643
519
644 ctx = repo.changectx(opts['rev'])
520 ctx = repo.changectx(opts['rev'])
645
521
646 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
522 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
647 node=ctx.node()):
523 node=ctx.node()):
648 fctx = ctx.filectx(abs)
524 fctx = ctx.filectx(abs)
649 if not opts['text'] and util.binary(fctx.data()):
525 if not opts['text'] and util.binary(fctx.data()):
650 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
526 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
651 continue
527 continue
652
528
653 lines = fctx.annotate(follow=opts.get('follow'))
529 lines = fctx.annotate(follow=opts.get('follow'))
654 pieces = []
530 pieces = []
655
531
656 for o, f in opmap:
532 for o, f in opmap:
657 if opts[o]:
533 if opts[o]:
658 l = [f(n) for n, dummy in lines]
534 l = [f(n) for n, dummy in lines]
659 if l:
535 if l:
660 m = max(map(len, l))
536 m = max(map(len, l))
661 pieces.append(["%*s" % (m, x) for x in l])
537 pieces.append(["%*s" % (m, x) for x in l])
662
538
663 if pieces:
539 if pieces:
664 for p, l in zip(zip(*pieces), lines):
540 for p, l in zip(zip(*pieces), lines):
665 ui.write("%s: %s" % (" ".join(p), l[1]))
541 ui.write("%s: %s" % (" ".join(p), l[1]))
666
542
667 def archive(ui, repo, dest, **opts):
543 def archive(ui, repo, dest, **opts):
668 '''create unversioned archive of a repository revision
544 '''create unversioned archive of a repository revision
669
545
670 By default, the revision used is the parent of the working
546 By default, the revision used is the parent of the working
671 directory; use "-r" to specify a different revision.
547 directory; use "-r" to specify a different revision.
672
548
673 To specify the type of archive to create, use "-t". Valid
549 To specify the type of archive to create, use "-t". Valid
674 types are:
550 types are:
675
551
676 "files" (default): a directory full of files
552 "files" (default): a directory full of files
677 "tar": tar archive, uncompressed
553 "tar": tar archive, uncompressed
678 "tbz2": tar archive, compressed using bzip2
554 "tbz2": tar archive, compressed using bzip2
679 "tgz": tar archive, compressed using gzip
555 "tgz": tar archive, compressed using gzip
680 "uzip": zip archive, uncompressed
556 "uzip": zip archive, uncompressed
681 "zip": zip archive, compressed using deflate
557 "zip": zip archive, compressed using deflate
682
558
683 The exact name of the destination archive or directory is given
559 The exact name of the destination archive or directory is given
684 using a format string; see "hg help export" for details.
560 using a format string; see "hg help export" for details.
685
561
686 Each member added to an archive file has a directory prefix
562 Each member added to an archive file has a directory prefix
687 prepended. Use "-p" to specify a format string for the prefix.
563 prepended. Use "-p" to specify a format string for the prefix.
688 The default is the basename of the archive, with suffixes removed.
564 The default is the basename of the archive, with suffixes removed.
689 '''
565 '''
690
566
691 node = repo.changectx(opts['rev']).node()
567 node = repo.changectx(opts['rev']).node()
692 dest = cmdutil.make_filename(repo, dest, node)
568 dest = cmdutil.make_filename(repo, dest, node)
693 if os.path.realpath(dest) == repo.root:
569 if os.path.realpath(dest) == repo.root:
694 raise util.Abort(_('repository root cannot be destination'))
570 raise util.Abort(_('repository root cannot be destination'))
695 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
571 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
696 kind = opts.get('type') or 'files'
572 kind = opts.get('type') or 'files'
697 prefix = opts['prefix']
573 prefix = opts['prefix']
698 if dest == '-':
574 if dest == '-':
699 if kind == 'files':
575 if kind == 'files':
700 raise util.Abort(_('cannot archive plain files to stdout'))
576 raise util.Abort(_('cannot archive plain files to stdout'))
701 dest = sys.stdout
577 dest = sys.stdout
702 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
578 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
703 prefix = cmdutil.make_filename(repo, prefix, node)
579 prefix = cmdutil.make_filename(repo, prefix, node)
704 archival.archive(repo, dest, node, kind, not opts['no_decode'],
580 archival.archive(repo, dest, node, kind, not opts['no_decode'],
705 matchfn, prefix)
581 matchfn, prefix)
706
582
707 def backout(ui, repo, rev, **opts):
583 def backout(ui, repo, rev, **opts):
708 '''reverse effect of earlier changeset
584 '''reverse effect of earlier changeset
709
585
710 Commit the backed out changes as a new changeset. The new
586 Commit the backed out changes as a new changeset. The new
711 changeset is a child of the backed out changeset.
587 changeset is a child of the backed out changeset.
712
588
713 If you back out a changeset other than the tip, a new head is
589 If you back out a changeset other than the tip, a new head is
714 created. This head is the parent of the working directory. If
590 created. This head is the parent of the working directory. If
715 you back out an old changeset, your working directory will appear
591 you back out an old changeset, your working directory will appear
716 old after the backout. You should merge the backout changeset
592 old after the backout. You should merge the backout changeset
717 with another head.
593 with another head.
718
594
719 The --merge option remembers the parent of the working directory
595 The --merge option remembers the parent of the working directory
720 before starting the backout, then merges the new head with that
596 before starting the backout, then merges the new head with that
721 changeset afterwards. This saves you from doing the merge by
597 changeset afterwards. This saves you from doing the merge by
722 hand. The result of this merge is not committed, as for a normal
598 hand. The result of this merge is not committed, as for a normal
723 merge.'''
599 merge.'''
724
600
725 bail_if_changed(repo)
601 bail_if_changed(repo)
726 op1, op2 = repo.dirstate.parents()
602 op1, op2 = repo.dirstate.parents()
727 if op2 != nullid:
603 if op2 != nullid:
728 raise util.Abort(_('outstanding uncommitted merge'))
604 raise util.Abort(_('outstanding uncommitted merge'))
729 node = repo.lookup(rev)
605 node = repo.lookup(rev)
730 p1, p2 = repo.changelog.parents(node)
606 p1, p2 = repo.changelog.parents(node)
731 if p1 == nullid:
607 if p1 == nullid:
732 raise util.Abort(_('cannot back out a change with no parents'))
608 raise util.Abort(_('cannot back out a change with no parents'))
733 if p2 != nullid:
609 if p2 != nullid:
734 if not opts['parent']:
610 if not opts['parent']:
735 raise util.Abort(_('cannot back out a merge changeset without '
611 raise util.Abort(_('cannot back out a merge changeset without '
736 '--parent'))
612 '--parent'))
737 p = repo.lookup(opts['parent'])
613 p = repo.lookup(opts['parent'])
738 if p not in (p1, p2):
614 if p not in (p1, p2):
739 raise util.Abort(_('%s is not a parent of %s' %
615 raise util.Abort(_('%s is not a parent of %s' %
740 (short(p), short(node))))
616 (short(p), short(node))))
741 parent = p
617 parent = p
742 else:
618 else:
743 if opts['parent']:
619 if opts['parent']:
744 raise util.Abort(_('cannot use --parent on non-merge changeset'))
620 raise util.Abort(_('cannot use --parent on non-merge changeset'))
745 parent = p1
621 parent = p1
746 hg.clean(repo, node, show_stats=False)
622 hg.clean(repo, node, show_stats=False)
747 revert_opts = opts.copy()
623 revert_opts = opts.copy()
748 revert_opts['all'] = True
624 revert_opts['all'] = True
749 revert_opts['rev'] = hex(parent)
625 revert_opts['rev'] = hex(parent)
750 revert(ui, repo, **revert_opts)
626 revert(ui, repo, **revert_opts)
751 commit_opts = opts.copy()
627 commit_opts = opts.copy()
752 commit_opts['addremove'] = False
628 commit_opts['addremove'] = False
753 if not commit_opts['message'] and not commit_opts['logfile']:
629 if not commit_opts['message'] and not commit_opts['logfile']:
754 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
630 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
755 commit_opts['force_editor'] = True
631 commit_opts['force_editor'] = True
756 commit(ui, repo, **commit_opts)
632 commit(ui, repo, **commit_opts)
757 def nice(node):
633 def nice(node):
758 return '%d:%s' % (repo.changelog.rev(node), short(node))
634 return '%d:%s' % (repo.changelog.rev(node), short(node))
759 ui.status(_('changeset %s backs out changeset %s\n') %
635 ui.status(_('changeset %s backs out changeset %s\n') %
760 (nice(repo.changelog.tip()), nice(node)))
636 (nice(repo.changelog.tip()), nice(node)))
761 if op1 != node:
637 if op1 != node:
762 if opts['merge']:
638 if opts['merge']:
763 ui.status(_('merging with changeset %s\n') % nice(op1))
639 ui.status(_('merging with changeset %s\n') % nice(op1))
764 n = _lookup(repo, hex(op1))
640 n = _lookup(repo, hex(op1))
765 hg.merge(repo, n)
641 hg.merge(repo, n)
766 else:
642 else:
767 ui.status(_('the backout changeset is a new head - '
643 ui.status(_('the backout changeset is a new head - '
768 'do not forget to merge\n'))
644 'do not forget to merge\n'))
769 ui.status(_('(use "backout --merge" '
645 ui.status(_('(use "backout --merge" '
770 'if you want to auto-merge)\n'))
646 'if you want to auto-merge)\n'))
771
647
772 def branch(ui, repo, label=None):
648 def branch(ui, repo, label=None):
773 """set or show the current branch name
649 """set or show the current branch name
774
650
775 With <name>, set the current branch name. Otherwise, show the
651 With <name>, set the current branch name. Otherwise, show the
776 current branch name.
652 current branch name.
777 """
653 """
778
654
779 if label is not None:
655 if label is not None:
780 repo.opener("branch", "w").write(label)
656 repo.opener("branch", "w").write(label)
781 else:
657 else:
782 b = repo.workingctx().branch()
658 b = repo.workingctx().branch()
783 if b:
659 if b:
784 ui.write("%s\n" % b)
660 ui.write("%s\n" % b)
785
661
786 def branches(ui, repo):
662 def branches(ui, repo):
787 """list repository named branches
663 """list repository named branches
788
664
789 List the repository's named branches.
665 List the repository's named branches.
790 """
666 """
791 b = repo.branchtags()
667 b = repo.branchtags()
792 l = [(-repo.changelog.rev(n), n, t) for t,n in b.items()]
668 l = [(-repo.changelog.rev(n), n, t) for t,n in b.items()]
793 l.sort()
669 l.sort()
794 for r, n, t in l:
670 for r, n, t in l:
795 hexfunc = ui.debugflag and hex or short
671 hexfunc = ui.debugflag and hex or short
796 if ui.quiet:
672 if ui.quiet:
797 ui.write("%s\n" % t)
673 ui.write("%s\n" % t)
798 else:
674 else:
799 ui.write("%-30s %s:%s\n" % (t, -r, hexfunc(n)))
675 ui.write("%-30s %s:%s\n" % (t, -r, hexfunc(n)))
800
676
801 def bundle(ui, repo, fname, dest=None, **opts):
677 def bundle(ui, repo, fname, dest=None, **opts):
802 """create a changegroup file
678 """create a changegroup file
803
679
804 Generate a compressed changegroup file collecting changesets not
680 Generate a compressed changegroup file collecting changesets not
805 found in the other repository.
681 found in the other repository.
806
682
807 If no destination repository is specified the destination is assumed
683 If no destination repository is specified the destination is assumed
808 to have all the nodes specified by one or more --base parameters.
684 to have all the nodes specified by one or more --base parameters.
809
685
810 The bundle file can then be transferred using conventional means and
686 The bundle file can then be transferred using conventional means and
811 applied to another repository with the unbundle or pull command.
687 applied to another repository with the unbundle or pull command.
812 This is useful when direct push and pull are not available or when
688 This is useful when direct push and pull are not available or when
813 exporting an entire repository is undesirable.
689 exporting an entire repository is undesirable.
814
690
815 Applying bundles preserves all changeset contents including
691 Applying bundles preserves all changeset contents including
816 permissions, copy/rename information, and revision history.
692 permissions, copy/rename information, and revision history.
817 """
693 """
818 revs = opts.get('rev') or None
694 revs = opts.get('rev') or None
819 if revs:
695 if revs:
820 revs = [repo.lookup(rev) for rev in revs]
696 revs = [repo.lookup(rev) for rev in revs]
821 base = opts.get('base')
697 base = opts.get('base')
822 if base:
698 if base:
823 if dest:
699 if dest:
824 raise util.Abort(_("--base is incompatible with specifiying "
700 raise util.Abort(_("--base is incompatible with specifiying "
825 "a destination"))
701 "a destination"))
826 base = [repo.lookup(rev) for rev in base]
702 base = [repo.lookup(rev) for rev in base]
827 # create the right base
703 # create the right base
828 # XXX: nodesbetween / changegroup* should be "fixed" instead
704 # XXX: nodesbetween / changegroup* should be "fixed" instead
829 o = []
705 o = []
830 has = {nullid: None}
706 has = {nullid: None}
831 for n in base:
707 for n in base:
832 has.update(repo.changelog.reachable(n))
708 has.update(repo.changelog.reachable(n))
833 if revs:
709 if revs:
834 visit = list(revs)
710 visit = list(revs)
835 else:
711 else:
836 visit = repo.changelog.heads()
712 visit = repo.changelog.heads()
837 seen = {}
713 seen = {}
838 while visit:
714 while visit:
839 n = visit.pop(0)
715 n = visit.pop(0)
840 parents = [p for p in repo.changelog.parents(n) if p not in has]
716 parents = [p for p in repo.changelog.parents(n) if p not in has]
841 if len(parents) == 0:
717 if len(parents) == 0:
842 o.insert(0, n)
718 o.insert(0, n)
843 else:
719 else:
844 for p in parents:
720 for p in parents:
845 if p not in seen:
721 if p not in seen:
846 seen[p] = 1
722 seen[p] = 1
847 visit.append(p)
723 visit.append(p)
848 else:
724 else:
849 setremoteconfig(ui, opts)
725 setremoteconfig(ui, opts)
850 dest = ui.expandpath(dest or 'default-push', dest or 'default')
726 dest = ui.expandpath(dest or 'default-push', dest or 'default')
851 other = hg.repository(ui, dest)
727 other = hg.repository(ui, dest)
852 o = repo.findoutgoing(other, force=opts['force'])
728 o = repo.findoutgoing(other, force=opts['force'])
853
729
854 if revs:
730 if revs:
855 cg = repo.changegroupsubset(o, revs, 'bundle')
731 cg = repo.changegroupsubset(o, revs, 'bundle')
856 else:
732 else:
857 cg = repo.changegroup(o, 'bundle')
733 cg = repo.changegroup(o, 'bundle')
858 write_bundle(cg, fname)
734 write_bundle(cg, fname)
859
735
860 def cat(ui, repo, file1, *pats, **opts):
736 def cat(ui, repo, file1, *pats, **opts):
861 """output the latest or given revisions of files
737 """output the latest or given revisions of files
862
738
863 Print the specified files as they were at the given revision.
739 Print the specified files as they were at the given revision.
864 If no revision is given then working dir parent is used, or tip
740 If no revision is given then working dir parent is used, or tip
865 if no revision is checked out.
741 if no revision is checked out.
866
742
867 Output may be to a file, in which case the name of the file is
743 Output may be to a file, in which case the name of the file is
868 given using a format string. The formatting rules are the same as
744 given using a format string. The formatting rules are the same as
869 for the export command, with the following additions:
745 for the export command, with the following additions:
870
746
871 %s basename of file being printed
747 %s basename of file being printed
872 %d dirname of file being printed, or '.' if in repo root
748 %d dirname of file being printed, or '.' if in repo root
873 %p root-relative path name of file being printed
749 %p root-relative path name of file being printed
874 """
750 """
875 ctx = repo.changectx(opts['rev'])
751 ctx = repo.changectx(opts['rev'])
876 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
752 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
877 ctx.node()):
753 ctx.node()):
878 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
754 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
879 fp.write(ctx.filectx(abs).data())
755 fp.write(ctx.filectx(abs).data())
880
756
881 def clone(ui, source, dest=None, **opts):
757 def clone(ui, source, dest=None, **opts):
882 """make a copy of an existing repository
758 """make a copy of an existing repository
883
759
884 Create a copy of an existing repository in a new directory.
760 Create a copy of an existing repository in a new directory.
885
761
886 If no destination directory name is specified, it defaults to the
762 If no destination directory name is specified, it defaults to the
887 basename of the source.
763 basename of the source.
888
764
889 The location of the source is added to the new repository's
765 The location of the source is added to the new repository's
890 .hg/hgrc file, as the default to be used for future pulls.
766 .hg/hgrc file, as the default to be used for future pulls.
891
767
892 For efficiency, hardlinks are used for cloning whenever the source
768 For efficiency, hardlinks are used for cloning whenever the source
893 and destination are on the same filesystem (note this applies only
769 and destination are on the same filesystem (note this applies only
894 to the repository data, not to the checked out files). Some
770 to the repository data, not to the checked out files). Some
895 filesystems, such as AFS, implement hardlinking incorrectly, but
771 filesystems, such as AFS, implement hardlinking incorrectly, but
896 do not report errors. In these cases, use the --pull option to
772 do not report errors. In these cases, use the --pull option to
897 avoid hardlinking.
773 avoid hardlinking.
898
774
899 You can safely clone repositories and checked out files using full
775 You can safely clone repositories and checked out files using full
900 hardlinks with
776 hardlinks with
901
777
902 $ cp -al REPO REPOCLONE
778 $ cp -al REPO REPOCLONE
903
779
904 which is the fastest way to clone. However, the operation is not
780 which is the fastest way to clone. However, the operation is not
905 atomic (making sure REPO is not modified during the operation is
781 atomic (making sure REPO is not modified during the operation is
906 up to you) and you have to make sure your editor breaks hardlinks
782 up to you) and you have to make sure your editor breaks hardlinks
907 (Emacs and most Linux Kernel tools do so).
783 (Emacs and most Linux Kernel tools do so).
908
784
909 If you use the -r option to clone up to a specific revision, no
785 If you use the -r option to clone up to a specific revision, no
910 subsequent revisions will be present in the cloned repository.
786 subsequent revisions will be present in the cloned repository.
911 This option implies --pull, even on local repositories.
787 This option implies --pull, even on local repositories.
912
788
913 See pull for valid source format details.
789 See pull for valid source format details.
914
790
915 It is possible to specify an ssh:// URL as the destination, but no
791 It is possible to specify an ssh:// URL as the destination, but no
916 .hg/hgrc and working directory will be created on the remote side.
792 .hg/hgrc and working directory will be created on the remote side.
917 Look at the help text for the pull command for important details
793 Look at the help text for the pull command for important details
918 about ssh:// URLs.
794 about ssh:// URLs.
919 """
795 """
920 setremoteconfig(ui, opts)
796 setremoteconfig(ui, opts)
921 hg.clone(ui, ui.expandpath(source), dest,
797 hg.clone(ui, ui.expandpath(source), dest,
922 pull=opts['pull'],
798 pull=opts['pull'],
923 stream=opts['uncompressed'],
799 stream=opts['uncompressed'],
924 rev=opts['rev'],
800 rev=opts['rev'],
925 update=not opts['noupdate'])
801 update=not opts['noupdate'])
926
802
927 def commit(ui, repo, *pats, **opts):
803 def commit(ui, repo, *pats, **opts):
928 """commit the specified files or all outstanding changes
804 """commit the specified files or all outstanding changes
929
805
930 Commit changes to the given files into the repository.
806 Commit changes to the given files into the repository.
931
807
932 If a list of files is omitted, all changes reported by "hg status"
808 If a list of files is omitted, all changes reported by "hg status"
933 will be committed.
809 will be committed.
934
810
935 If no commit message is specified, the editor configured in your hgrc
811 If no commit message is specified, the editor configured in your hgrc
936 or in the EDITOR environment variable is started to enter a message.
812 or in the EDITOR environment variable is started to enter a message.
937 """
813 """
938 message = logmessage(opts)
814 message = logmessage(opts)
939
815
940 if opts['addremove']:
816 if opts['addremove']:
941 cmdutil.addremove(repo, pats, opts)
817 cmdutil.addremove(repo, pats, opts)
942 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
818 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
943 if pats:
819 if pats:
944 modified, added, removed = repo.status(files=fns, match=match)[:3]
820 modified, added, removed = repo.status(files=fns, match=match)[:3]
945 files = modified + added + removed
821 files = modified + added + removed
946 else:
822 else:
947 files = []
823 files = []
948 try:
824 try:
949 repo.commit(files, message, opts['user'], opts['date'], match,
825 repo.commit(files, message, opts['user'], opts['date'], match,
950 force_editor=opts.get('force_editor'))
826 force_editor=opts.get('force_editor'))
951 except ValueError, inst:
827 except ValueError, inst:
952 raise util.Abort(str(inst))
828 raise util.Abort(str(inst))
953
829
954 def docopy(ui, repo, pats, opts, wlock):
830 def docopy(ui, repo, pats, opts, wlock):
955 # called with the repo lock held
831 # called with the repo lock held
956 cwd = repo.getcwd()
832 cwd = repo.getcwd()
957 errors = 0
833 errors = 0
958 copied = []
834 copied = []
959 targets = {}
835 targets = {}
960
836
961 def okaytocopy(abs, rel, exact):
837 def okaytocopy(abs, rel, exact):
962 reasons = {'?': _('is not managed'),
838 reasons = {'?': _('is not managed'),
963 'a': _('has been marked for add'),
839 'a': _('has been marked for add'),
964 'r': _('has been marked for remove')}
840 'r': _('has been marked for remove')}
965 state = repo.dirstate.state(abs)
841 state = repo.dirstate.state(abs)
966 reason = reasons.get(state)
842 reason = reasons.get(state)
967 if reason:
843 if reason:
968 if state == 'a':
844 if state == 'a':
969 origsrc = repo.dirstate.copied(abs)
845 origsrc = repo.dirstate.copied(abs)
970 if origsrc is not None:
846 if origsrc is not None:
971 return origsrc
847 return origsrc
972 if exact:
848 if exact:
973 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
849 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
974 else:
850 else:
975 return abs
851 return abs
976
852
977 def copy(origsrc, abssrc, relsrc, target, exact):
853 def copy(origsrc, abssrc, relsrc, target, exact):
978 abstarget = util.canonpath(repo.root, cwd, target)
854 abstarget = util.canonpath(repo.root, cwd, target)
979 reltarget = util.pathto(cwd, abstarget)
855 reltarget = util.pathto(cwd, abstarget)
980 prevsrc = targets.get(abstarget)
856 prevsrc = targets.get(abstarget)
981 if prevsrc is not None:
857 if prevsrc is not None:
982 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
858 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
983 (reltarget, abssrc, prevsrc))
859 (reltarget, abssrc, prevsrc))
984 return
860 return
985 if (not opts['after'] and os.path.exists(reltarget) or
861 if (not opts['after'] and os.path.exists(reltarget) or
986 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
862 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
987 if not opts['force']:
863 if not opts['force']:
988 ui.warn(_('%s: not overwriting - file exists\n') %
864 ui.warn(_('%s: not overwriting - file exists\n') %
989 reltarget)
865 reltarget)
990 return
866 return
991 if not opts['after'] and not opts.get('dry_run'):
867 if not opts['after'] and not opts.get('dry_run'):
992 os.unlink(reltarget)
868 os.unlink(reltarget)
993 if opts['after']:
869 if opts['after']:
994 if not os.path.exists(reltarget):
870 if not os.path.exists(reltarget):
995 return
871 return
996 else:
872 else:
997 targetdir = os.path.dirname(reltarget) or '.'
873 targetdir = os.path.dirname(reltarget) or '.'
998 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
874 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
999 os.makedirs(targetdir)
875 os.makedirs(targetdir)
1000 try:
876 try:
1001 restore = repo.dirstate.state(abstarget) == 'r'
877 restore = repo.dirstate.state(abstarget) == 'r'
1002 if restore and not opts.get('dry_run'):
878 if restore and not opts.get('dry_run'):
1003 repo.undelete([abstarget], wlock)
879 repo.undelete([abstarget], wlock)
1004 try:
880 try:
1005 if not opts.get('dry_run'):
881 if not opts.get('dry_run'):
1006 util.copyfile(relsrc, reltarget)
882 util.copyfile(relsrc, reltarget)
1007 restore = False
883 restore = False
1008 finally:
884 finally:
1009 if restore:
885 if restore:
1010 repo.remove([abstarget], wlock)
886 repo.remove([abstarget], wlock)
1011 except IOError, inst:
887 except IOError, inst:
1012 if inst.errno == errno.ENOENT:
888 if inst.errno == errno.ENOENT:
1013 ui.warn(_('%s: deleted in working copy\n') % relsrc)
889 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1014 else:
890 else:
1015 ui.warn(_('%s: cannot copy - %s\n') %
891 ui.warn(_('%s: cannot copy - %s\n') %
1016 (relsrc, inst.strerror))
892 (relsrc, inst.strerror))
1017 errors += 1
893 errors += 1
1018 return
894 return
1019 if ui.verbose or not exact:
895 if ui.verbose or not exact:
1020 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
896 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1021 targets[abstarget] = abssrc
897 targets[abstarget] = abssrc
1022 if abstarget != origsrc and not opts.get('dry_run'):
898 if abstarget != origsrc and not opts.get('dry_run'):
1023 repo.copy(origsrc, abstarget, wlock)
899 repo.copy(origsrc, abstarget, wlock)
1024 copied.append((abssrc, relsrc, exact))
900 copied.append((abssrc, relsrc, exact))
1025
901
1026 def targetpathfn(pat, dest, srcs):
902 def targetpathfn(pat, dest, srcs):
1027 if os.path.isdir(pat):
903 if os.path.isdir(pat):
1028 abspfx = util.canonpath(repo.root, cwd, pat)
904 abspfx = util.canonpath(repo.root, cwd, pat)
1029 if destdirexists:
905 if destdirexists:
1030 striplen = len(os.path.split(abspfx)[0])
906 striplen = len(os.path.split(abspfx)[0])
1031 else:
907 else:
1032 striplen = len(abspfx)
908 striplen = len(abspfx)
1033 if striplen:
909 if striplen:
1034 striplen += len(os.sep)
910 striplen += len(os.sep)
1035 res = lambda p: os.path.join(dest, p[striplen:])
911 res = lambda p: os.path.join(dest, p[striplen:])
1036 elif destdirexists:
912 elif destdirexists:
1037 res = lambda p: os.path.join(dest, os.path.basename(p))
913 res = lambda p: os.path.join(dest, os.path.basename(p))
1038 else:
914 else:
1039 res = lambda p: dest
915 res = lambda p: dest
1040 return res
916 return res
1041
917
1042 def targetpathafterfn(pat, dest, srcs):
918 def targetpathafterfn(pat, dest, srcs):
1043 if util.patkind(pat, None)[0]:
919 if util.patkind(pat, None)[0]:
1044 # a mercurial pattern
920 # a mercurial pattern
1045 res = lambda p: os.path.join(dest, os.path.basename(p))
921 res = lambda p: os.path.join(dest, os.path.basename(p))
1046 else:
922 else:
1047 abspfx = util.canonpath(repo.root, cwd, pat)
923 abspfx = util.canonpath(repo.root, cwd, pat)
1048 if len(abspfx) < len(srcs[0][0]):
924 if len(abspfx) < len(srcs[0][0]):
1049 # A directory. Either the target path contains the last
925 # A directory. Either the target path contains the last
1050 # component of the source path or it does not.
926 # component of the source path or it does not.
1051 def evalpath(striplen):
927 def evalpath(striplen):
1052 score = 0
928 score = 0
1053 for s in srcs:
929 for s in srcs:
1054 t = os.path.join(dest, s[0][striplen:])
930 t = os.path.join(dest, s[0][striplen:])
1055 if os.path.exists(t):
931 if os.path.exists(t):
1056 score += 1
932 score += 1
1057 return score
933 return score
1058
934
1059 striplen = len(abspfx)
935 striplen = len(abspfx)
1060 if striplen:
936 if striplen:
1061 striplen += len(os.sep)
937 striplen += len(os.sep)
1062 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
938 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1063 score = evalpath(striplen)
939 score = evalpath(striplen)
1064 striplen1 = len(os.path.split(abspfx)[0])
940 striplen1 = len(os.path.split(abspfx)[0])
1065 if striplen1:
941 if striplen1:
1066 striplen1 += len(os.sep)
942 striplen1 += len(os.sep)
1067 if evalpath(striplen1) > score:
943 if evalpath(striplen1) > score:
1068 striplen = striplen1
944 striplen = striplen1
1069 res = lambda p: os.path.join(dest, p[striplen:])
945 res = lambda p: os.path.join(dest, p[striplen:])
1070 else:
946 else:
1071 # a file
947 # a file
1072 if destdirexists:
948 if destdirexists:
1073 res = lambda p: os.path.join(dest, os.path.basename(p))
949 res = lambda p: os.path.join(dest, os.path.basename(p))
1074 else:
950 else:
1075 res = lambda p: dest
951 res = lambda p: dest
1076 return res
952 return res
1077
953
1078
954
1079 pats = list(pats)
955 pats = list(pats)
1080 if not pats:
956 if not pats:
1081 raise util.Abort(_('no source or destination specified'))
957 raise util.Abort(_('no source or destination specified'))
1082 if len(pats) == 1:
958 if len(pats) == 1:
1083 raise util.Abort(_('no destination specified'))
959 raise util.Abort(_('no destination specified'))
1084 dest = pats.pop()
960 dest = pats.pop()
1085 destdirexists = os.path.isdir(dest)
961 destdirexists = os.path.isdir(dest)
1086 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
962 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1087 raise util.Abort(_('with multiple sources, destination must be an '
963 raise util.Abort(_('with multiple sources, destination must be an '
1088 'existing directory'))
964 'existing directory'))
1089 if opts['after']:
965 if opts['after']:
1090 tfn = targetpathafterfn
966 tfn = targetpathafterfn
1091 else:
967 else:
1092 tfn = targetpathfn
968 tfn = targetpathfn
1093 copylist = []
969 copylist = []
1094 for pat in pats:
970 for pat in pats:
1095 srcs = []
971 srcs = []
1096 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts):
972 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts):
1097 origsrc = okaytocopy(abssrc, relsrc, exact)
973 origsrc = okaytocopy(abssrc, relsrc, exact)
1098 if origsrc:
974 if origsrc:
1099 srcs.append((origsrc, abssrc, relsrc, exact))
975 srcs.append((origsrc, abssrc, relsrc, exact))
1100 if not srcs:
976 if not srcs:
1101 continue
977 continue
1102 copylist.append((tfn(pat, dest, srcs), srcs))
978 copylist.append((tfn(pat, dest, srcs), srcs))
1103 if not copylist:
979 if not copylist:
1104 raise util.Abort(_('no files to copy'))
980 raise util.Abort(_('no files to copy'))
1105
981
1106 for targetpath, srcs in copylist:
982 for targetpath, srcs in copylist:
1107 for origsrc, abssrc, relsrc, exact in srcs:
983 for origsrc, abssrc, relsrc, exact in srcs:
1108 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
984 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1109
985
1110 if errors:
986 if errors:
1111 ui.warn(_('(consider using --after)\n'))
987 ui.warn(_('(consider using --after)\n'))
1112 return errors, copied
988 return errors, copied
1113
989
1114 def copy(ui, repo, *pats, **opts):
990 def copy(ui, repo, *pats, **opts):
1115 """mark files as copied for the next commit
991 """mark files as copied for the next commit
1116
992
1117 Mark dest as having copies of source files. If dest is a
993 Mark dest as having copies of source files. If dest is a
1118 directory, copies are put in that directory. If dest is a file,
994 directory, copies are put in that directory. If dest is a file,
1119 there can only be one source.
995 there can only be one source.
1120
996
1121 By default, this command copies the contents of files as they
997 By default, this command copies the contents of files as they
1122 stand in the working directory. If invoked with --after, the
998 stand in the working directory. If invoked with --after, the
1123 operation is recorded, but no copying is performed.
999 operation is recorded, but no copying is performed.
1124
1000
1125 This command takes effect in the next commit.
1001 This command takes effect in the next commit.
1126
1002
1127 NOTE: This command should be treated as experimental. While it
1003 NOTE: This command should be treated as experimental. While it
1128 should properly record copied files, this information is not yet
1004 should properly record copied files, this information is not yet
1129 fully used by merge, nor fully reported by log.
1005 fully used by merge, nor fully reported by log.
1130 """
1006 """
1131 wlock = repo.wlock(0)
1007 wlock = repo.wlock(0)
1132 errs, copied = docopy(ui, repo, pats, opts, wlock)
1008 errs, copied = docopy(ui, repo, pats, opts, wlock)
1133 return errs
1009 return errs
1134
1010
1135 def debugancestor(ui, index, rev1, rev2):
1011 def debugancestor(ui, index, rev1, rev2):
1136 """find the ancestor revision of two revisions in a given index"""
1012 """find the ancestor revision of two revisions in a given index"""
1137 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1013 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1138 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1014 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1139 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1015 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1140
1016
1141 def debugcomplete(ui, cmd='', **opts):
1017 def debugcomplete(ui, cmd='', **opts):
1142 """returns the completion list associated with the given command"""
1018 """returns the completion list associated with the given command"""
1143
1019
1144 if opts['options']:
1020 if opts['options']:
1145 options = []
1021 options = []
1146 otables = [globalopts]
1022 otables = [globalopts]
1147 if cmd:
1023 if cmd:
1148 aliases, entry = findcmd(ui, cmd)
1024 aliases, entry = findcmd(ui, cmd)
1149 otables.append(entry[1])
1025 otables.append(entry[1])
1150 for t in otables:
1026 for t in otables:
1151 for o in t:
1027 for o in t:
1152 if o[0]:
1028 if o[0]:
1153 options.append('-%s' % o[0])
1029 options.append('-%s' % o[0])
1154 options.append('--%s' % o[1])
1030 options.append('--%s' % o[1])
1155 ui.write("%s\n" % "\n".join(options))
1031 ui.write("%s\n" % "\n".join(options))
1156 return
1032 return
1157
1033
1158 clist = findpossible(ui, cmd).keys()
1034 clist = findpossible(ui, cmd).keys()
1159 clist.sort()
1035 clist.sort()
1160 ui.write("%s\n" % "\n".join(clist))
1036 ui.write("%s\n" % "\n".join(clist))
1161
1037
1162 def debugrebuildstate(ui, repo, rev=None):
1038 def debugrebuildstate(ui, repo, rev=None):
1163 """rebuild the dirstate as it would look like for the given revision"""
1039 """rebuild the dirstate as it would look like for the given revision"""
1164 if not rev:
1040 if not rev:
1165 rev = repo.changelog.tip()
1041 rev = repo.changelog.tip()
1166 else:
1042 else:
1167 rev = repo.lookup(rev)
1043 rev = repo.lookup(rev)
1168 change = repo.changelog.read(rev)
1044 change = repo.changelog.read(rev)
1169 n = change[0]
1045 n = change[0]
1170 files = repo.manifest.read(n)
1046 files = repo.manifest.read(n)
1171 wlock = repo.wlock()
1047 wlock = repo.wlock()
1172 repo.dirstate.rebuild(rev, files)
1048 repo.dirstate.rebuild(rev, files)
1173
1049
1174 def debugcheckstate(ui, repo):
1050 def debugcheckstate(ui, repo):
1175 """validate the correctness of the current dirstate"""
1051 """validate the correctness of the current dirstate"""
1176 parent1, parent2 = repo.dirstate.parents()
1052 parent1, parent2 = repo.dirstate.parents()
1177 repo.dirstate.read()
1053 repo.dirstate.read()
1178 dc = repo.dirstate.map
1054 dc = repo.dirstate.map
1179 keys = dc.keys()
1055 keys = dc.keys()
1180 keys.sort()
1056 keys.sort()
1181 m1n = repo.changelog.read(parent1)[0]
1057 m1n = repo.changelog.read(parent1)[0]
1182 m2n = repo.changelog.read(parent2)[0]
1058 m2n = repo.changelog.read(parent2)[0]
1183 m1 = repo.manifest.read(m1n)
1059 m1 = repo.manifest.read(m1n)
1184 m2 = repo.manifest.read(m2n)
1060 m2 = repo.manifest.read(m2n)
1185 errors = 0
1061 errors = 0
1186 for f in dc:
1062 for f in dc:
1187 state = repo.dirstate.state(f)
1063 state = repo.dirstate.state(f)
1188 if state in "nr" and f not in m1:
1064 if state in "nr" and f not in m1:
1189 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1065 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1190 errors += 1
1066 errors += 1
1191 if state in "a" and f in m1:
1067 if state in "a" and f in m1:
1192 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1068 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1193 errors += 1
1069 errors += 1
1194 if state in "m" and f not in m1 and f not in m2:
1070 if state in "m" and f not in m1 and f not in m2:
1195 ui.warn(_("%s in state %s, but not in either manifest\n") %
1071 ui.warn(_("%s in state %s, but not in either manifest\n") %
1196 (f, state))
1072 (f, state))
1197 errors += 1
1073 errors += 1
1198 for f in m1:
1074 for f in m1:
1199 state = repo.dirstate.state(f)
1075 state = repo.dirstate.state(f)
1200 if state not in "nrm":
1076 if state not in "nrm":
1201 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1077 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1202 errors += 1
1078 errors += 1
1203 if errors:
1079 if errors:
1204 error = _(".hg/dirstate inconsistent with current parent's manifest")
1080 error = _(".hg/dirstate inconsistent with current parent's manifest")
1205 raise util.Abort(error)
1081 raise util.Abort(error)
1206
1082
1207 def showconfig(ui, repo, *values, **opts):
1083 def showconfig(ui, repo, *values, **opts):
1208 """show combined config settings from all hgrc files
1084 """show combined config settings from all hgrc files
1209
1085
1210 With no args, print names and values of all config items.
1086 With no args, print names and values of all config items.
1211
1087
1212 With one arg of the form section.name, print just the value of
1088 With one arg of the form section.name, print just the value of
1213 that config item.
1089 that config item.
1214
1090
1215 With multiple args, print names and values of all config items
1091 With multiple args, print names and values of all config items
1216 with matching section names."""
1092 with matching section names."""
1217
1093
1218 untrusted = bool(opts.get('untrusted'))
1094 untrusted = bool(opts.get('untrusted'))
1219 if values:
1095 if values:
1220 if len([v for v in values if '.' in v]) > 1:
1096 if len([v for v in values if '.' in v]) > 1:
1221 raise util.Abort(_('only one config item permitted'))
1097 raise util.Abort(_('only one config item permitted'))
1222 for section, name, value in ui.walkconfig(untrusted=untrusted):
1098 for section, name, value in ui.walkconfig(untrusted=untrusted):
1223 sectname = section + '.' + name
1099 sectname = section + '.' + name
1224 if values:
1100 if values:
1225 for v in values:
1101 for v in values:
1226 if v == section:
1102 if v == section:
1227 ui.write('%s=%s\n' % (sectname, value))
1103 ui.write('%s=%s\n' % (sectname, value))
1228 elif v == sectname:
1104 elif v == sectname:
1229 ui.write(value, '\n')
1105 ui.write(value, '\n')
1230 else:
1106 else:
1231 ui.write('%s=%s\n' % (sectname, value))
1107 ui.write('%s=%s\n' % (sectname, value))
1232
1108
1233 def debugsetparents(ui, repo, rev1, rev2=None):
1109 def debugsetparents(ui, repo, rev1, rev2=None):
1234 """manually set the parents of the current working directory
1110 """manually set the parents of the current working directory
1235
1111
1236 This is useful for writing repository conversion tools, but should
1112 This is useful for writing repository conversion tools, but should
1237 be used with care.
1113 be used with care.
1238 """
1114 """
1239
1115
1240 if not rev2:
1116 if not rev2:
1241 rev2 = hex(nullid)
1117 rev2 = hex(nullid)
1242
1118
1243 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1119 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1244
1120
1245 def debugstate(ui, repo):
1121 def debugstate(ui, repo):
1246 """show the contents of the current dirstate"""
1122 """show the contents of the current dirstate"""
1247 repo.dirstate.read()
1123 repo.dirstate.read()
1248 dc = repo.dirstate.map
1124 dc = repo.dirstate.map
1249 keys = dc.keys()
1125 keys = dc.keys()
1250 keys.sort()
1126 keys.sort()
1251 for file_ in keys:
1127 for file_ in keys:
1252 ui.write("%c %3o %10d %s %s\n"
1128 ui.write("%c %3o %10d %s %s\n"
1253 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1129 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1254 time.strftime("%x %X",
1130 time.strftime("%x %X",
1255 time.localtime(dc[file_][3])), file_))
1131 time.localtime(dc[file_][3])), file_))
1256 for f in repo.dirstate.copies():
1132 for f in repo.dirstate.copies():
1257 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1133 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1258
1134
1259 def debugdata(ui, file_, rev):
1135 def debugdata(ui, file_, rev):
1260 """dump the contents of an data file revision"""
1136 """dump the contents of an data file revision"""
1261 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1137 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1262 file_[:-2] + ".i", file_, 0)
1138 file_[:-2] + ".i", file_, 0)
1263 try:
1139 try:
1264 ui.write(r.revision(r.lookup(rev)))
1140 ui.write(r.revision(r.lookup(rev)))
1265 except KeyError:
1141 except KeyError:
1266 raise util.Abort(_('invalid revision identifier %s') % rev)
1142 raise util.Abort(_('invalid revision identifier %s') % rev)
1267
1143
1268 def debugindex(ui, file_):
1144 def debugindex(ui, file_):
1269 """dump the contents of an index file"""
1145 """dump the contents of an index file"""
1270 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1146 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1271 ui.write(" rev offset length base linkrev" +
1147 ui.write(" rev offset length base linkrev" +
1272 " nodeid p1 p2\n")
1148 " nodeid p1 p2\n")
1273 for i in xrange(r.count()):
1149 for i in xrange(r.count()):
1274 node = r.node(i)
1150 node = r.node(i)
1275 pp = r.parents(node)
1151 pp = r.parents(node)
1276 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1152 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1277 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1153 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1278 short(node), short(pp[0]), short(pp[1])))
1154 short(node), short(pp[0]), short(pp[1])))
1279
1155
1280 def debugindexdot(ui, file_):
1156 def debugindexdot(ui, file_):
1281 """dump an index DAG as a .dot file"""
1157 """dump an index DAG as a .dot file"""
1282 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1158 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1283 ui.write("digraph G {\n")
1159 ui.write("digraph G {\n")
1284 for i in xrange(r.count()):
1160 for i in xrange(r.count()):
1285 node = r.node(i)
1161 node = r.node(i)
1286 pp = r.parents(node)
1162 pp = r.parents(node)
1287 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1163 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1288 if pp[1] != nullid:
1164 if pp[1] != nullid:
1289 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1165 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1290 ui.write("}\n")
1166 ui.write("}\n")
1291
1167
1292 def debugrename(ui, repo, file, rev=None):
1168 def debugrename(ui, repo, file, rev=None):
1293 """dump rename information"""
1169 """dump rename information"""
1294 r = repo.file(relpath(repo, [file])[0])
1170 r = repo.file(relpath(repo, [file])[0])
1295 if rev:
1171 if rev:
1296 try:
1172 try:
1297 # assume all revision numbers are for changesets
1173 # assume all revision numbers are for changesets
1298 n = repo.lookup(rev)
1174 n = repo.lookup(rev)
1299 change = repo.changelog.read(n)
1175 change = repo.changelog.read(n)
1300 m = repo.manifest.read(change[0])
1176 m = repo.manifest.read(change[0])
1301 n = m[relpath(repo, [file])[0]]
1177 n = m[relpath(repo, [file])[0]]
1302 except (hg.RepoError, KeyError):
1178 except (hg.RepoError, KeyError):
1303 n = r.lookup(rev)
1179 n = r.lookup(rev)
1304 else:
1180 else:
1305 n = r.tip()
1181 n = r.tip()
1306 m = r.renamed(n)
1182 m = r.renamed(n)
1307 if m:
1183 if m:
1308 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1184 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1309 else:
1185 else:
1310 ui.write(_("not renamed\n"))
1186 ui.write(_("not renamed\n"))
1311
1187
1312 def debugwalk(ui, repo, *pats, **opts):
1188 def debugwalk(ui, repo, *pats, **opts):
1313 """show how files match on given patterns"""
1189 """show how files match on given patterns"""
1314 items = list(cmdutil.walk(repo, pats, opts))
1190 items = list(cmdutil.walk(repo, pats, opts))
1315 if not items:
1191 if not items:
1316 return
1192 return
1317 fmt = '%%s %%-%ds %%-%ds %%s' % (
1193 fmt = '%%s %%-%ds %%-%ds %%s' % (
1318 max([len(abs) for (src, abs, rel, exact) in items]),
1194 max([len(abs) for (src, abs, rel, exact) in items]),
1319 max([len(rel) for (src, abs, rel, exact) in items]))
1195 max([len(rel) for (src, abs, rel, exact) in items]))
1320 for src, abs, rel, exact in items:
1196 for src, abs, rel, exact in items:
1321 line = fmt % (src, abs, rel, exact and 'exact' or '')
1197 line = fmt % (src, abs, rel, exact and 'exact' or '')
1322 ui.write("%s\n" % line.rstrip())
1198 ui.write("%s\n" % line.rstrip())
1323
1199
1324 def diff(ui, repo, *pats, **opts):
1200 def diff(ui, repo, *pats, **opts):
1325 """diff repository (or selected files)
1201 """diff repository (or selected files)
1326
1202
1327 Show differences between revisions for the specified files.
1203 Show differences between revisions for the specified files.
1328
1204
1329 Differences between files are shown using the unified diff format.
1205 Differences between files are shown using the unified diff format.
1330
1206
1331 When two revision arguments are given, then changes are shown
1207 When two revision arguments are given, then changes are shown
1332 between those revisions. If only one revision is specified then
1208 between those revisions. If only one revision is specified then
1333 that revision is compared to the working directory, and, when no
1209 that revision is compared to the working directory, and, when no
1334 revisions are specified, the working directory files are compared
1210 revisions are specified, the working directory files are compared
1335 to its parent.
1211 to its parent.
1336
1212
1337 Without the -a option, diff will avoid generating diffs of files
1213 Without the -a option, diff will avoid generating diffs of files
1338 it detects as binary. With -a, diff will generate a diff anyway,
1214 it detects as binary. With -a, diff will generate a diff anyway,
1339 probably with undesirable results.
1215 probably with undesirable results.
1340 """
1216 """
1341 node1, node2 = cmdutil.revpair(ui, repo, opts['rev'])
1217 node1, node2 = cmdutil.revpair(ui, repo, opts['rev'])
1342
1218
1343 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1219 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1344
1220
1345 patch.diff(repo, node1, node2, fns, match=matchfn,
1221 patch.diff(repo, node1, node2, fns, match=matchfn,
1346 opts=patch.diffopts(ui, opts))
1222 opts=patch.diffopts(ui, opts))
1347
1223
1348 def export(ui, repo, *changesets, **opts):
1224 def export(ui, repo, *changesets, **opts):
1349 """dump the header and diffs for one or more changesets
1225 """dump the header and diffs for one or more changesets
1350
1226
1351 Print the changeset header and diffs for one or more revisions.
1227 Print the changeset header and diffs for one or more revisions.
1352
1228
1353 The information shown in the changeset header is: author,
1229 The information shown in the changeset header is: author,
1354 changeset hash, parent and commit comment.
1230 changeset hash, parent and commit comment.
1355
1231
1356 Output may be to a file, in which case the name of the file is
1232 Output may be to a file, in which case the name of the file is
1357 given using a format string. The formatting rules are as follows:
1233 given using a format string. The formatting rules are as follows:
1358
1234
1359 %% literal "%" character
1235 %% literal "%" character
1360 %H changeset hash (40 bytes of hexadecimal)
1236 %H changeset hash (40 bytes of hexadecimal)
1361 %N number of patches being generated
1237 %N number of patches being generated
1362 %R changeset revision number
1238 %R changeset revision number
1363 %b basename of the exporting repository
1239 %b basename of the exporting repository
1364 %h short-form changeset hash (12 bytes of hexadecimal)
1240 %h short-form changeset hash (12 bytes of hexadecimal)
1365 %n zero-padded sequence number, starting at 1
1241 %n zero-padded sequence number, starting at 1
1366 %r zero-padded changeset revision number
1242 %r zero-padded changeset revision number
1367
1243
1368 Without the -a option, export will avoid generating diffs of files
1244 Without the -a option, export will avoid generating diffs of files
1369 it detects as binary. With -a, export will generate a diff anyway,
1245 it detects as binary. With -a, export will generate a diff anyway,
1370 probably with undesirable results.
1246 probably with undesirable results.
1371
1247
1372 With the --switch-parent option, the diff will be against the second
1248 With the --switch-parent option, the diff will be against the second
1373 parent. It can be useful to review a merge.
1249 parent. It can be useful to review a merge.
1374 """
1250 """
1375 if not changesets:
1251 if not changesets:
1376 raise util.Abort(_("export requires at least one changeset"))
1252 raise util.Abort(_("export requires at least one changeset"))
1377 revs = cmdutil.revrange(ui, repo, changesets)
1253 revs = cmdutil.revrange(ui, repo, changesets)
1378 if len(revs) > 1:
1254 if len(revs) > 1:
1379 ui.note(_('exporting patches:\n'))
1255 ui.note(_('exporting patches:\n'))
1380 else:
1256 else:
1381 ui.note(_('exporting patch:\n'))
1257 ui.note(_('exporting patch:\n'))
1382 patch.export(repo, map(repo.lookup, revs), template=opts['output'],
1258 patch.export(repo, map(repo.lookup, revs), template=opts['output'],
1383 switch_parent=opts['switch_parent'],
1259 switch_parent=opts['switch_parent'],
1384 opts=patch.diffopts(ui, opts))
1260 opts=patch.diffopts(ui, opts))
1385
1261
1386 def grep(ui, repo, pattern, *pats, **opts):
1262 def grep(ui, repo, pattern, *pats, **opts):
1387 """search for a pattern in specified files and revisions
1263 """search for a pattern in specified files and revisions
1388
1264
1389 Search revisions of files for a regular expression.
1265 Search revisions of files for a regular expression.
1390
1266
1391 This command behaves differently than Unix grep. It only accepts
1267 This command behaves differently than Unix grep. It only accepts
1392 Python/Perl regexps. It searches repository history, not the
1268 Python/Perl regexps. It searches repository history, not the
1393 working directory. It always prints the revision number in which
1269 working directory. It always prints the revision number in which
1394 a match appears.
1270 a match appears.
1395
1271
1396 By default, grep only prints output for the first revision of a
1272 By default, grep only prints output for the first revision of a
1397 file in which it finds a match. To get it to print every revision
1273 file in which it finds a match. To get it to print every revision
1398 that contains a change in match status ("-" for a match that
1274 that contains a change in match status ("-" for a match that
1399 becomes a non-match, or "+" for a non-match that becomes a match),
1275 becomes a non-match, or "+" for a non-match that becomes a match),
1400 use the --all flag.
1276 use the --all flag.
1401 """
1277 """
1402 reflags = 0
1278 reflags = 0
1403 if opts['ignore_case']:
1279 if opts['ignore_case']:
1404 reflags |= re.I
1280 reflags |= re.I
1405 regexp = re.compile(pattern, reflags)
1281 regexp = re.compile(pattern, reflags)
1406 sep, eol = ':', '\n'
1282 sep, eol = ':', '\n'
1407 if opts['print0']:
1283 if opts['print0']:
1408 sep = eol = '\0'
1284 sep = eol = '\0'
1409
1285
1410 fcache = {}
1286 fcache = {}
1411 def getfile(fn):
1287 def getfile(fn):
1412 if fn not in fcache:
1288 if fn not in fcache:
1413 fcache[fn] = repo.file(fn)
1289 fcache[fn] = repo.file(fn)
1414 return fcache[fn]
1290 return fcache[fn]
1415
1291
1416 def matchlines(body):
1292 def matchlines(body):
1417 begin = 0
1293 begin = 0
1418 linenum = 0
1294 linenum = 0
1419 while True:
1295 while True:
1420 match = regexp.search(body, begin)
1296 match = regexp.search(body, begin)
1421 if not match:
1297 if not match:
1422 break
1298 break
1423 mstart, mend = match.span()
1299 mstart, mend = match.span()
1424 linenum += body.count('\n', begin, mstart) + 1
1300 linenum += body.count('\n', begin, mstart) + 1
1425 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1301 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1426 lend = body.find('\n', mend)
1302 lend = body.find('\n', mend)
1427 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1303 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1428 begin = lend + 1
1304 begin = lend + 1
1429
1305
1430 class linestate(object):
1306 class linestate(object):
1431 def __init__(self, line, linenum, colstart, colend):
1307 def __init__(self, line, linenum, colstart, colend):
1432 self.line = line
1308 self.line = line
1433 self.linenum = linenum
1309 self.linenum = linenum
1434 self.colstart = colstart
1310 self.colstart = colstart
1435 self.colend = colend
1311 self.colend = colend
1436
1312
1437 def __eq__(self, other):
1313 def __eq__(self, other):
1438 return self.line == other.line
1314 return self.line == other.line
1439
1315
1440 matches = {}
1316 matches = {}
1441 copies = {}
1317 copies = {}
1442 def grepbody(fn, rev, body):
1318 def grepbody(fn, rev, body):
1443 matches[rev].setdefault(fn, [])
1319 matches[rev].setdefault(fn, [])
1444 m = matches[rev][fn]
1320 m = matches[rev][fn]
1445 for lnum, cstart, cend, line in matchlines(body):
1321 for lnum, cstart, cend, line in matchlines(body):
1446 s = linestate(line, lnum, cstart, cend)
1322 s = linestate(line, lnum, cstart, cend)
1447 m.append(s)
1323 m.append(s)
1448
1324
1449 def difflinestates(a, b):
1325 def difflinestates(a, b):
1450 sm = difflib.SequenceMatcher(None, a, b)
1326 sm = difflib.SequenceMatcher(None, a, b)
1451 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1327 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1452 if tag == 'insert':
1328 if tag == 'insert':
1453 for i in xrange(blo, bhi):
1329 for i in xrange(blo, bhi):
1454 yield ('+', b[i])
1330 yield ('+', b[i])
1455 elif tag == 'delete':
1331 elif tag == 'delete':
1456 for i in xrange(alo, ahi):
1332 for i in xrange(alo, ahi):
1457 yield ('-', a[i])
1333 yield ('-', a[i])
1458 elif tag == 'replace':
1334 elif tag == 'replace':
1459 for i in xrange(alo, ahi):
1335 for i in xrange(alo, ahi):
1460 yield ('-', a[i])
1336 yield ('-', a[i])
1461 for i in xrange(blo, bhi):
1337 for i in xrange(blo, bhi):
1462 yield ('+', b[i])
1338 yield ('+', b[i])
1463
1339
1464 prev = {}
1340 prev = {}
1465 def display(fn, rev, states, prevstates):
1341 def display(fn, rev, states, prevstates):
1466 counts = {'-': 0, '+': 0}
1342 counts = {'-': 0, '+': 0}
1467 filerevmatches = {}
1343 filerevmatches = {}
1468 if incrementing or not opts['all']:
1344 if incrementing or not opts['all']:
1469 a, b, r = prevstates, states, rev
1345 a, b, r = prevstates, states, rev
1470 else:
1346 else:
1471 a, b, r = states, prevstates, prev.get(fn, -1)
1347 a, b, r = states, prevstates, prev.get(fn, -1)
1472 for change, l in difflinestates(a, b):
1348 for change, l in difflinestates(a, b):
1473 cols = [fn, str(r)]
1349 cols = [fn, str(r)]
1474 if opts['line_number']:
1350 if opts['line_number']:
1475 cols.append(str(l.linenum))
1351 cols.append(str(l.linenum))
1476 if opts['all']:
1352 if opts['all']:
1477 cols.append(change)
1353 cols.append(change)
1478 if opts['user']:
1354 if opts['user']:
1479 cols.append(ui.shortuser(getchange(r)[1]))
1355 cols.append(ui.shortuser(getchange(r)[1]))
1480 if opts['files_with_matches']:
1356 if opts['files_with_matches']:
1481 c = (fn, r)
1357 c = (fn, r)
1482 if c in filerevmatches:
1358 if c in filerevmatches:
1483 continue
1359 continue
1484 filerevmatches[c] = 1
1360 filerevmatches[c] = 1
1485 else:
1361 else:
1486 cols.append(l.line)
1362 cols.append(l.line)
1487 ui.write(sep.join(cols), eol)
1363 ui.write(sep.join(cols), eol)
1488 counts[change] += 1
1364 counts[change] += 1
1489 return counts['+'], counts['-']
1365 return counts['+'], counts['-']
1490
1366
1491 fstate = {}
1367 fstate = {}
1492 skip = {}
1368 skip = {}
1493 getchange = util.cachefunc(lambda r:repo.changectx(r).changeset())
1369 getchange = util.cachefunc(lambda r:repo.changectx(r).changeset())
1494 changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts)
1370 changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts)
1495 count = 0
1371 count = 0
1496 incrementing = False
1372 incrementing = False
1497 follow = opts.get('follow')
1373 follow = opts.get('follow')
1498 for st, rev, fns in changeiter:
1374 for st, rev, fns in changeiter:
1499 if st == 'window':
1375 if st == 'window':
1500 incrementing = rev
1376 incrementing = rev
1501 matches.clear()
1377 matches.clear()
1502 elif st == 'add':
1378 elif st == 'add':
1503 mf = repo.changectx(rev).manifest()
1379 mf = repo.changectx(rev).manifest()
1504 matches[rev] = {}
1380 matches[rev] = {}
1505 for fn in fns:
1381 for fn in fns:
1506 if fn in skip:
1382 if fn in skip:
1507 continue
1383 continue
1508 fstate.setdefault(fn, {})
1384 fstate.setdefault(fn, {})
1509 try:
1385 try:
1510 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1386 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1511 if follow:
1387 if follow:
1512 copied = getfile(fn).renamed(mf[fn])
1388 copied = getfile(fn).renamed(mf[fn])
1513 if copied:
1389 if copied:
1514 copies.setdefault(rev, {})[fn] = copied[0]
1390 copies.setdefault(rev, {})[fn] = copied[0]
1515 except KeyError:
1391 except KeyError:
1516 pass
1392 pass
1517 elif st == 'iter':
1393 elif st == 'iter':
1518 states = matches[rev].items()
1394 states = matches[rev].items()
1519 states.sort()
1395 states.sort()
1520 for fn, m in states:
1396 for fn, m in states:
1521 copy = copies.get(rev, {}).get(fn)
1397 copy = copies.get(rev, {}).get(fn)
1522 if fn in skip:
1398 if fn in skip:
1523 if copy:
1399 if copy:
1524 skip[copy] = True
1400 skip[copy] = True
1525 continue
1401 continue
1526 if incrementing or not opts['all'] or fstate[fn]:
1402 if incrementing or not opts['all'] or fstate[fn]:
1527 pos, neg = display(fn, rev, m, fstate[fn])
1403 pos, neg = display(fn, rev, m, fstate[fn])
1528 count += pos + neg
1404 count += pos + neg
1529 if pos and not opts['all']:
1405 if pos and not opts['all']:
1530 skip[fn] = True
1406 skip[fn] = True
1531 if copy:
1407 if copy:
1532 skip[copy] = True
1408 skip[copy] = True
1533 fstate[fn] = m
1409 fstate[fn] = m
1534 if copy:
1410 if copy:
1535 fstate[copy] = m
1411 fstate[copy] = m
1536 prev[fn] = rev
1412 prev[fn] = rev
1537
1413
1538 if not incrementing:
1414 if not incrementing:
1539 fstate = fstate.items()
1415 fstate = fstate.items()
1540 fstate.sort()
1416 fstate.sort()
1541 for fn, state in fstate:
1417 for fn, state in fstate:
1542 if fn in skip:
1418 if fn in skip:
1543 continue
1419 continue
1544 if fn not in copies.get(prev[fn], {}):
1420 if fn not in copies.get(prev[fn], {}):
1545 display(fn, rev, {}, state)
1421 display(fn, rev, {}, state)
1546 return (count == 0 and 1) or 0
1422 return (count == 0 and 1) or 0
1547
1423
1548 def heads(ui, repo, **opts):
1424 def heads(ui, repo, **opts):
1549 """show current repository heads
1425 """show current repository heads
1550
1426
1551 Show all repository head changesets.
1427 Show all repository head changesets.
1552
1428
1553 Repository "heads" are changesets that don't have children
1429 Repository "heads" are changesets that don't have children
1554 changesets. They are where development generally takes place and
1430 changesets. They are where development generally takes place and
1555 are the usual targets for update and merge operations.
1431 are the usual targets for update and merge operations.
1556 """
1432 """
1557 if opts['rev']:
1433 if opts['rev']:
1558 heads = repo.heads(repo.lookup(opts['rev']))
1434 heads = repo.heads(repo.lookup(opts['rev']))
1559 else:
1435 else:
1560 heads = repo.heads()
1436 heads = repo.heads()
1561 br = None
1437 br = None
1562 if opts['branches']:
1438 if opts['branches']:
1563 ui.warn(_("the --branches option is deprecated, "
1439 ui.warn(_("the --branches option is deprecated, "
1564 "please use 'hg branches' instead\n"))
1440 "please use 'hg branches' instead\n"))
1565 br = repo.branchlookup(heads)
1441 br = repo.branchlookup(heads)
1566 displayer = show_changeset(ui, repo, opts)
1442 displayer = cmdutil.show_changeset(ui, repo, opts)
1567 for n in heads:
1443 for n in heads:
1568 displayer.show(changenode=n, brinfo=br)
1444 displayer.show(changenode=n, brinfo=br)
1569
1445
1570 def identify(ui, repo):
1446 def identify(ui, repo):
1571 """print information about the working copy
1447 """print information about the working copy
1572
1448
1573 Print a short summary of the current state of the repo.
1449 Print a short summary of the current state of the repo.
1574
1450
1575 This summary identifies the repository state using one or two parent
1451 This summary identifies the repository state using one or two parent
1576 hash identifiers, followed by a "+" if there are uncommitted changes
1452 hash identifiers, followed by a "+" if there are uncommitted changes
1577 in the working directory, followed by a list of tags for this revision.
1453 in the working directory, followed by a list of tags for this revision.
1578 """
1454 """
1579 parents = [p for p in repo.dirstate.parents() if p != nullid]
1455 parents = [p for p in repo.dirstate.parents() if p != nullid]
1580 if not parents:
1456 if not parents:
1581 ui.write(_("unknown\n"))
1457 ui.write(_("unknown\n"))
1582 return
1458 return
1583
1459
1584 hexfunc = ui.debugflag and hex or short
1460 hexfunc = ui.debugflag and hex or short
1585 modified, added, removed, deleted = repo.status()[:4]
1461 modified, added, removed, deleted = repo.status()[:4]
1586 output = ["%s%s" %
1462 output = ["%s%s" %
1587 ('+'.join([hexfunc(parent) for parent in parents]),
1463 ('+'.join([hexfunc(parent) for parent in parents]),
1588 (modified or added or removed or deleted) and "+" or "")]
1464 (modified or added or removed or deleted) and "+" or "")]
1589
1465
1590 if not ui.quiet:
1466 if not ui.quiet:
1591
1467
1592 branch = repo.workingctx().branch()
1468 branch = repo.workingctx().branch()
1593 if branch:
1469 if branch:
1594 output.append("(%s)" % branch)
1470 output.append("(%s)" % branch)
1595
1471
1596 # multiple tags for a single parent separated by '/'
1472 # multiple tags for a single parent separated by '/'
1597 parenttags = ['/'.join(tags)
1473 parenttags = ['/'.join(tags)
1598 for tags in map(repo.nodetags, parents) if tags]
1474 for tags in map(repo.nodetags, parents) if tags]
1599 # tags for multiple parents separated by ' + '
1475 # tags for multiple parents separated by ' + '
1600 if parenttags:
1476 if parenttags:
1601 output.append(' + '.join(parenttags))
1477 output.append(' + '.join(parenttags))
1602
1478
1603 ui.write("%s\n" % ' '.join(output))
1479 ui.write("%s\n" % ' '.join(output))
1604
1480
1605 def import_(ui, repo, patch1, *patches, **opts):
1481 def import_(ui, repo, patch1, *patches, **opts):
1606 """import an ordered set of patches
1482 """import an ordered set of patches
1607
1483
1608 Import a list of patches and commit them individually.
1484 Import a list of patches and commit them individually.
1609
1485
1610 If there are outstanding changes in the working directory, import
1486 If there are outstanding changes in the working directory, import
1611 will abort unless given the -f flag.
1487 will abort unless given the -f flag.
1612
1488
1613 You can import a patch straight from a mail message. Even patches
1489 You can import a patch straight from a mail message. Even patches
1614 as attachments work (body part must be type text/plain or
1490 as attachments work (body part must be type text/plain or
1615 text/x-patch to be used). From and Subject headers of email
1491 text/x-patch to be used). From and Subject headers of email
1616 message are used as default committer and commit message. All
1492 message are used as default committer and commit message. All
1617 text/plain body parts before first diff are added to commit
1493 text/plain body parts before first diff are added to commit
1618 message.
1494 message.
1619
1495
1620 If imported patch was generated by hg export, user and description
1496 If imported patch was generated by hg export, user and description
1621 from patch override values from message headers and body. Values
1497 from patch override values from message headers and body. Values
1622 given on command line with -m and -u override these.
1498 given on command line with -m and -u override these.
1623
1499
1624 To read a patch from standard input, use patch name "-".
1500 To read a patch from standard input, use patch name "-".
1625 """
1501 """
1626 patches = (patch1,) + patches
1502 patches = (patch1,) + patches
1627
1503
1628 if not opts['force']:
1504 if not opts['force']:
1629 bail_if_changed(repo)
1505 bail_if_changed(repo)
1630
1506
1631 d = opts["base"]
1507 d = opts["base"]
1632 strip = opts["strip"]
1508 strip = opts["strip"]
1633
1509
1634 wlock = repo.wlock()
1510 wlock = repo.wlock()
1635 lock = repo.lock()
1511 lock = repo.lock()
1636
1512
1637 for p in patches:
1513 for p in patches:
1638 pf = os.path.join(d, p)
1514 pf = os.path.join(d, p)
1639
1515
1640 if pf == '-':
1516 if pf == '-':
1641 ui.status(_("applying patch from stdin\n"))
1517 ui.status(_("applying patch from stdin\n"))
1642 tmpname, message, user, date = patch.extract(ui, sys.stdin)
1518 tmpname, message, user, date = patch.extract(ui, sys.stdin)
1643 else:
1519 else:
1644 ui.status(_("applying %s\n") % p)
1520 ui.status(_("applying %s\n") % p)
1645 tmpname, message, user, date = patch.extract(ui, file(pf))
1521 tmpname, message, user, date = patch.extract(ui, file(pf))
1646
1522
1647 if tmpname is None:
1523 if tmpname is None:
1648 raise util.Abort(_('no diffs found'))
1524 raise util.Abort(_('no diffs found'))
1649
1525
1650 try:
1526 try:
1651 if opts['message']:
1527 if opts['message']:
1652 # pickup the cmdline msg
1528 # pickup the cmdline msg
1653 message = opts['message']
1529 message = opts['message']
1654 elif message:
1530 elif message:
1655 # pickup the patch msg
1531 # pickup the patch msg
1656 message = message.strip()
1532 message = message.strip()
1657 else:
1533 else:
1658 # launch the editor
1534 # launch the editor
1659 message = None
1535 message = None
1660 ui.debug(_('message:\n%s\n') % message)
1536 ui.debug(_('message:\n%s\n') % message)
1661
1537
1662 files = {}
1538 files = {}
1663 try:
1539 try:
1664 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1540 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1665 files=files)
1541 files=files)
1666 finally:
1542 finally:
1667 files = patch.updatedir(ui, repo, files, wlock=wlock)
1543 files = patch.updatedir(ui, repo, files, wlock=wlock)
1668 repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1544 repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1669 finally:
1545 finally:
1670 os.unlink(tmpname)
1546 os.unlink(tmpname)
1671
1547
1672 def incoming(ui, repo, source="default", **opts):
1548 def incoming(ui, repo, source="default", **opts):
1673 """show new changesets found in source
1549 """show new changesets found in source
1674
1550
1675 Show new changesets found in the specified path/URL or the default
1551 Show new changesets found in the specified path/URL or the default
1676 pull location. These are the changesets that would be pulled if a pull
1552 pull location. These are the changesets that would be pulled if a pull
1677 was requested.
1553 was requested.
1678
1554
1679 For remote repository, using --bundle avoids downloading the changesets
1555 For remote repository, using --bundle avoids downloading the changesets
1680 twice if the incoming is followed by a pull.
1556 twice if the incoming is followed by a pull.
1681
1557
1682 See pull for valid source format details.
1558 See pull for valid source format details.
1683 """
1559 """
1684 source = ui.expandpath(source)
1560 source = ui.expandpath(source)
1685 setremoteconfig(ui, opts)
1561 setremoteconfig(ui, opts)
1686
1562
1687 other = hg.repository(ui, source)
1563 other = hg.repository(ui, source)
1688 incoming = repo.findincoming(other, force=opts["force"])
1564 incoming = repo.findincoming(other, force=opts["force"])
1689 if not incoming:
1565 if not incoming:
1690 ui.status(_("no changes found\n"))
1566 ui.status(_("no changes found\n"))
1691 return
1567 return
1692
1568
1693 cleanup = None
1569 cleanup = None
1694 try:
1570 try:
1695 fname = opts["bundle"]
1571 fname = opts["bundle"]
1696 if fname or not other.local():
1572 if fname or not other.local():
1697 # create a bundle (uncompressed if other repo is not local)
1573 # create a bundle (uncompressed if other repo is not local)
1698 cg = other.changegroup(incoming, "incoming")
1574 cg = other.changegroup(incoming, "incoming")
1699 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1575 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1700 # keep written bundle?
1576 # keep written bundle?
1701 if opts["bundle"]:
1577 if opts["bundle"]:
1702 cleanup = None
1578 cleanup = None
1703 if not other.local():
1579 if not other.local():
1704 # use the created uncompressed bundlerepo
1580 # use the created uncompressed bundlerepo
1705 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1581 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1706
1582
1707 revs = None
1583 revs = None
1708 if opts['rev']:
1584 if opts['rev']:
1709 revs = [other.lookup(rev) for rev in opts['rev']]
1585 revs = [other.lookup(rev) for rev in opts['rev']]
1710 o = other.changelog.nodesbetween(incoming, revs)[0]
1586 o = other.changelog.nodesbetween(incoming, revs)[0]
1711 if opts['newest_first']:
1587 if opts['newest_first']:
1712 o.reverse()
1588 o.reverse()
1713 displayer = show_changeset(ui, other, opts)
1589 displayer = cmdutil.show_changeset(ui, other, opts)
1714 for n in o:
1590 for n in o:
1715 parents = [p for p in other.changelog.parents(n) if p != nullid]
1591 parents = [p for p in other.changelog.parents(n) if p != nullid]
1716 if opts['no_merges'] and len(parents) == 2:
1592 if opts['no_merges'] and len(parents) == 2:
1717 continue
1593 continue
1718 displayer.show(changenode=n)
1594 displayer.show(changenode=n)
1719 if opts['patch']:
1595 if opts['patch']:
1720 prev = (parents and parents[0]) or nullid
1596 prev = (parents and parents[0]) or nullid
1721 patch.diff(other, prev, n, fp=repo.ui)
1597 patch.diff(other, prev, n, fp=repo.ui)
1722 ui.write("\n")
1598 ui.write("\n")
1723 finally:
1599 finally:
1724 if hasattr(other, 'close'):
1600 if hasattr(other, 'close'):
1725 other.close()
1601 other.close()
1726 if cleanup:
1602 if cleanup:
1727 os.unlink(cleanup)
1603 os.unlink(cleanup)
1728
1604
1729 def init(ui, dest=".", **opts):
1605 def init(ui, dest=".", **opts):
1730 """create a new repository in the given directory
1606 """create a new repository in the given directory
1731
1607
1732 Initialize a new repository in the given directory. If the given
1608 Initialize a new repository in the given directory. If the given
1733 directory does not exist, it is created.
1609 directory does not exist, it is created.
1734
1610
1735 If no directory is given, the current directory is used.
1611 If no directory is given, the current directory is used.
1736
1612
1737 It is possible to specify an ssh:// URL as the destination.
1613 It is possible to specify an ssh:// URL as the destination.
1738 Look at the help text for the pull command for important details
1614 Look at the help text for the pull command for important details
1739 about ssh:// URLs.
1615 about ssh:// URLs.
1740 """
1616 """
1741 setremoteconfig(ui, opts)
1617 setremoteconfig(ui, opts)
1742 hg.repository(ui, dest, create=1)
1618 hg.repository(ui, dest, create=1)
1743
1619
1744 def locate(ui, repo, *pats, **opts):
1620 def locate(ui, repo, *pats, **opts):
1745 """locate files matching specific patterns
1621 """locate files matching specific patterns
1746
1622
1747 Print all files under Mercurial control whose names match the
1623 Print all files under Mercurial control whose names match the
1748 given patterns.
1624 given patterns.
1749
1625
1750 This command searches the current directory and its
1626 This command searches the current directory and its
1751 subdirectories. To search an entire repository, move to the root
1627 subdirectories. To search an entire repository, move to the root
1752 of the repository.
1628 of the repository.
1753
1629
1754 If no patterns are given to match, this command prints all file
1630 If no patterns are given to match, this command prints all file
1755 names.
1631 names.
1756
1632
1757 If you want to feed the output of this command into the "xargs"
1633 If you want to feed the output of this command into the "xargs"
1758 command, use the "-0" option to both this command and "xargs".
1634 command, use the "-0" option to both this command and "xargs".
1759 This will avoid the problem of "xargs" treating single filenames
1635 This will avoid the problem of "xargs" treating single filenames
1760 that contain white space as multiple filenames.
1636 that contain white space as multiple filenames.
1761 """
1637 """
1762 end = opts['print0'] and '\0' or '\n'
1638 end = opts['print0'] and '\0' or '\n'
1763 rev = opts['rev']
1639 rev = opts['rev']
1764 if rev:
1640 if rev:
1765 node = repo.lookup(rev)
1641 node = repo.lookup(rev)
1766 else:
1642 else:
1767 node = None
1643 node = None
1768
1644
1769 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1645 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1770 head='(?:.*/|)'):
1646 head='(?:.*/|)'):
1771 if not node and repo.dirstate.state(abs) == '?':
1647 if not node and repo.dirstate.state(abs) == '?':
1772 continue
1648 continue
1773 if opts['fullpath']:
1649 if opts['fullpath']:
1774 ui.write(os.path.join(repo.root, abs), end)
1650 ui.write(os.path.join(repo.root, abs), end)
1775 else:
1651 else:
1776 ui.write(((pats and rel) or abs), end)
1652 ui.write(((pats and rel) or abs), end)
1777
1653
1778 def log(ui, repo, *pats, **opts):
1654 def log(ui, repo, *pats, **opts):
1779 """show revision history of entire repository or files
1655 """show revision history of entire repository or files
1780
1656
1781 Print the revision history of the specified files or the entire
1657 Print the revision history of the specified files or the entire
1782 project.
1658 project.
1783
1659
1784 File history is shown without following rename or copy history of
1660 File history is shown without following rename or copy history of
1785 files. Use -f/--follow with a file name to follow history across
1661 files. Use -f/--follow with a file name to follow history across
1786 renames and copies. --follow without a file name will only show
1662 renames and copies. --follow without a file name will only show
1787 ancestors or descendants of the starting revision. --follow-first
1663 ancestors or descendants of the starting revision. --follow-first
1788 only follows the first parent of merge revisions.
1664 only follows the first parent of merge revisions.
1789
1665
1790 If no revision range is specified, the default is tip:0 unless
1666 If no revision range is specified, the default is tip:0 unless
1791 --follow is set, in which case the working directory parent is
1667 --follow is set, in which case the working directory parent is
1792 used as the starting revision.
1668 used as the starting revision.
1793
1669
1794 By default this command outputs: changeset id and hash, tags,
1670 By default this command outputs: changeset id and hash, tags,
1795 non-trivial parents, user, date and time, and a summary for each
1671 non-trivial parents, user, date and time, and a summary for each
1796 commit. When the -v/--verbose switch is used, the list of changed
1672 commit. When the -v/--verbose switch is used, the list of changed
1797 files and full commit message is shown.
1673 files and full commit message is shown.
1798 """
1674 """
1799 class dui(object):
1675 class dui(object):
1800 # Implement and delegate some ui protocol. Save hunks of
1676 # Implement and delegate some ui protocol. Save hunks of
1801 # output for later display in the desired order.
1677 # output for later display in the desired order.
1802 def __init__(self, ui):
1678 def __init__(self, ui):
1803 self.ui = ui
1679 self.ui = ui
1804 self.hunk = {}
1680 self.hunk = {}
1805 self.header = {}
1681 self.header = {}
1806 self.quiet = ui.quiet
1682 self.quiet = ui.quiet
1807 self.verbose = ui.verbose
1683 self.verbose = ui.verbose
1808 self.debugflag = ui.debugflag
1684 self.debugflag = ui.debugflag
1809 def bump(self, rev):
1685 def bump(self, rev):
1810 self.rev = rev
1686 self.rev = rev
1811 self.hunk[rev] = []
1687 self.hunk[rev] = []
1812 self.header[rev] = []
1688 self.header[rev] = []
1813 def note(self, *args):
1689 def note(self, *args):
1814 if self.verbose:
1690 if self.verbose:
1815 self.write(*args)
1691 self.write(*args)
1816 def status(self, *args):
1692 def status(self, *args):
1817 if not self.quiet:
1693 if not self.quiet:
1818 self.write(*args)
1694 self.write(*args)
1819 def write(self, *args):
1695 def write(self, *args):
1820 self.hunk[self.rev].extend(args)
1696 self.hunk[self.rev].extend(args)
1821 def write_header(self, *args):
1697 def write_header(self, *args):
1822 self.header[self.rev].extend(args)
1698 self.header[self.rev].extend(args)
1823 def debug(self, *args):
1699 def debug(self, *args):
1824 if self.debugflag:
1700 if self.debugflag:
1825 self.write(*args)
1701 self.write(*args)
1826 def __getattr__(self, key):
1702 def __getattr__(self, key):
1827 return getattr(self.ui, key)
1703 return getattr(self.ui, key)
1828
1704
1829 getchange = util.cachefunc(lambda r:repo.changectx(r).changeset())
1705 getchange = util.cachefunc(lambda r:repo.changectx(r).changeset())
1830 changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts)
1706 changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts)
1831
1707
1832 if opts['branches']:
1708 if opts['branches']:
1833 ui.warn(_("the --branches option is deprecated, "
1709 ui.warn(_("the --branches option is deprecated, "
1834 "please use 'hg branches' instead\n"))
1710 "please use 'hg branches' instead\n"))
1835
1711
1836 if opts['limit']:
1712 if opts['limit']:
1837 try:
1713 try:
1838 limit = int(opts['limit'])
1714 limit = int(opts['limit'])
1839 except ValueError:
1715 except ValueError:
1840 raise util.Abort(_('limit must be a positive integer'))
1716 raise util.Abort(_('limit must be a positive integer'))
1841 if limit <= 0: raise util.Abort(_('limit must be positive'))
1717 if limit <= 0: raise util.Abort(_('limit must be positive'))
1842 else:
1718 else:
1843 limit = sys.maxint
1719 limit = sys.maxint
1844 count = 0
1720 count = 0
1845
1721
1846 if opts['copies'] and opts['rev']:
1722 if opts['copies'] and opts['rev']:
1847 endrev = max(cmdutil.revrange(ui, repo, opts['rev'])) + 1
1723 endrev = max(cmdutil.revrange(ui, repo, opts['rev'])) + 1
1848 else:
1724 else:
1849 endrev = repo.changelog.count()
1725 endrev = repo.changelog.count()
1850 rcache = {}
1726 rcache = {}
1851 ncache = {}
1727 ncache = {}
1852 dcache = []
1728 dcache = []
1853 def getrenamed(fn, rev, man):
1729 def getrenamed(fn, rev, man):
1854 '''looks up all renames for a file (up to endrev) the first
1730 '''looks up all renames for a file (up to endrev) the first
1855 time the file is given. It indexes on the changerev and only
1731 time the file is given. It indexes on the changerev and only
1856 parses the manifest if linkrev != changerev.
1732 parses the manifest if linkrev != changerev.
1857 Returns rename info for fn at changerev rev.'''
1733 Returns rename info for fn at changerev rev.'''
1858 if fn not in rcache:
1734 if fn not in rcache:
1859 rcache[fn] = {}
1735 rcache[fn] = {}
1860 ncache[fn] = {}
1736 ncache[fn] = {}
1861 fl = repo.file(fn)
1737 fl = repo.file(fn)
1862 for i in xrange(fl.count()):
1738 for i in xrange(fl.count()):
1863 node = fl.node(i)
1739 node = fl.node(i)
1864 lr = fl.linkrev(node)
1740 lr = fl.linkrev(node)
1865 renamed = fl.renamed(node)
1741 renamed = fl.renamed(node)
1866 rcache[fn][lr] = renamed
1742 rcache[fn][lr] = renamed
1867 if renamed:
1743 if renamed:
1868 ncache[fn][node] = renamed
1744 ncache[fn][node] = renamed
1869 if lr >= endrev:
1745 if lr >= endrev:
1870 break
1746 break
1871 if rev in rcache[fn]:
1747 if rev in rcache[fn]:
1872 return rcache[fn][rev]
1748 return rcache[fn][rev]
1873 mr = repo.manifest.rev(man)
1749 mr = repo.manifest.rev(man)
1874 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1750 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1875 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1751 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1876 if not dcache or dcache[0] != man:
1752 if not dcache or dcache[0] != man:
1877 dcache[:] = [man, repo.manifest.readdelta(man)]
1753 dcache[:] = [man, repo.manifest.readdelta(man)]
1878 if fn in dcache[1]:
1754 if fn in dcache[1]:
1879 return ncache[fn].get(dcache[1][fn])
1755 return ncache[fn].get(dcache[1][fn])
1880 return None
1756 return None
1881
1757
1882 displayer = show_changeset(ui, repo, opts)
1758 displayer = cmdutil.show_changeset(ui, repo, opts)
1883 for st, rev, fns in changeiter:
1759 for st, rev, fns in changeiter:
1884 if st == 'window':
1760 if st == 'window':
1885 du = dui(ui)
1761 du = dui(ui)
1886 displayer.ui = du
1762 displayer.ui = du
1887 elif st == 'add':
1763 elif st == 'add':
1888 du.bump(rev)
1764 du.bump(rev)
1889 changenode = repo.changelog.node(rev)
1765 changenode = repo.changelog.node(rev)
1890 parents = [p for p in repo.changelog.parentrevs(rev)
1766 parents = [p for p in repo.changelog.parentrevs(rev)
1891 if p != nullrev]
1767 if p != nullrev]
1892 if opts['no_merges'] and len(parents) == 2:
1768 if opts['no_merges'] and len(parents) == 2:
1893 continue
1769 continue
1894 if opts['only_merges'] and len(parents) != 2:
1770 if opts['only_merges'] and len(parents) != 2:
1895 continue
1771 continue
1896
1772
1897 if opts['keyword']:
1773 if opts['keyword']:
1898 changes = getchange(rev)
1774 changes = getchange(rev)
1899 miss = 0
1775 miss = 0
1900 for k in [kw.lower() for kw in opts['keyword']]:
1776 for k in [kw.lower() for kw in opts['keyword']]:
1901 if not (k in changes[1].lower() or
1777 if not (k in changes[1].lower() or
1902 k in changes[4].lower() or
1778 k in changes[4].lower() or
1903 k in " ".join(changes[3][:20]).lower()):
1779 k in " ".join(changes[3][:20]).lower()):
1904 miss = 1
1780 miss = 1
1905 break
1781 break
1906 if miss:
1782 if miss:
1907 continue
1783 continue
1908
1784
1909 br = None
1785 br = None
1910 if opts['branches']:
1786 if opts['branches']:
1911 br = repo.branchlookup([repo.changelog.node(rev)])
1787 br = repo.branchlookup([repo.changelog.node(rev)])
1912
1788
1913 copies = []
1789 copies = []
1914 if opts.get('copies') and rev:
1790 if opts.get('copies') and rev:
1915 mf = getchange(rev)[0]
1791 mf = getchange(rev)[0]
1916 for fn in getchange(rev)[3]:
1792 for fn in getchange(rev)[3]:
1917 rename = getrenamed(fn, rev, mf)
1793 rename = getrenamed(fn, rev, mf)
1918 if rename:
1794 if rename:
1919 copies.append((fn, rename[0]))
1795 copies.append((fn, rename[0]))
1920 displayer.show(rev, changenode, brinfo=br, copies=copies)
1796 displayer.show(rev, changenode, brinfo=br, copies=copies)
1921 if opts['patch']:
1797 if opts['patch']:
1922 if parents:
1798 if parents:
1923 prev = parents[0]
1799 prev = parents[0]
1924 else:
1800 else:
1925 prev = nullrev
1801 prev = nullrev
1926 prev = repo.changelog.node(prev)
1802 prev = repo.changelog.node(prev)
1927 patch.diff(repo, prev, changenode, match=matchfn, fp=du)
1803 patch.diff(repo, prev, changenode, match=matchfn, fp=du)
1928 du.write("\n\n")
1804 du.write("\n\n")
1929 elif st == 'iter':
1805 elif st == 'iter':
1930 if count == limit: break
1806 if count == limit: break
1931 if du.header[rev]:
1807 if du.header[rev]:
1932 ui.write_header(*du.header[rev])
1808 ui.write_header(*du.header[rev])
1933 if du.hunk[rev]:
1809 if du.hunk[rev]:
1934 count += 1
1810 count += 1
1935 ui.write(*du.hunk[rev])
1811 ui.write(*du.hunk[rev])
1936
1812
1937 def manifest(ui, repo, rev=None):
1813 def manifest(ui, repo, rev=None):
1938 """output the latest or given revision of the project manifest
1814 """output the latest or given revision of the project manifest
1939
1815
1940 Print a list of version controlled files for the given revision.
1816 Print a list of version controlled files for the given revision.
1941
1817
1942 The manifest is the list of files being version controlled. If no revision
1818 The manifest is the list of files being version controlled. If no revision
1943 is given then the tip is used.
1819 is given then the tip is used.
1944 """
1820 """
1945 if rev:
1821 if rev:
1946 try:
1822 try:
1947 # assume all revision numbers are for changesets
1823 # assume all revision numbers are for changesets
1948 n = repo.lookup(rev)
1824 n = repo.lookup(rev)
1949 change = repo.changelog.read(n)
1825 change = repo.changelog.read(n)
1950 n = change[0]
1826 n = change[0]
1951 except hg.RepoError:
1827 except hg.RepoError:
1952 n = repo.manifest.lookup(rev)
1828 n = repo.manifest.lookup(rev)
1953 else:
1829 else:
1954 n = repo.manifest.tip()
1830 n = repo.manifest.tip()
1955 m = repo.manifest.read(n)
1831 m = repo.manifest.read(n)
1956 files = m.keys()
1832 files = m.keys()
1957 files.sort()
1833 files.sort()
1958
1834
1959 for f in files:
1835 for f in files:
1960 ui.write("%40s %3s %s\n" % (hex(m[f]),
1836 ui.write("%40s %3s %s\n" % (hex(m[f]),
1961 m.execf(f) and "755" or "644", f))
1837 m.execf(f) and "755" or "644", f))
1962
1838
1963 def merge(ui, repo, node=None, force=None, branch=None):
1839 def merge(ui, repo, node=None, force=None, branch=None):
1964 """Merge working directory with another revision
1840 """Merge working directory with another revision
1965
1841
1966 Merge the contents of the current working directory and the
1842 Merge the contents of the current working directory and the
1967 requested revision. Files that changed between either parent are
1843 requested revision. Files that changed between either parent are
1968 marked as changed for the next commit and a commit must be
1844 marked as changed for the next commit and a commit must be
1969 performed before any further updates are allowed.
1845 performed before any further updates are allowed.
1970
1846
1971 If no revision is specified, the working directory's parent is a
1847 If no revision is specified, the working directory's parent is a
1972 head revision, and the repository contains exactly one other head,
1848 head revision, and the repository contains exactly one other head,
1973 the other head is merged with by default. Otherwise, an explicit
1849 the other head is merged with by default. Otherwise, an explicit
1974 revision to merge with must be provided.
1850 revision to merge with must be provided.
1975 """
1851 """
1976
1852
1977 if node or branch:
1853 if node or branch:
1978 node = _lookup(repo, node, branch)
1854 node = _lookup(repo, node, branch)
1979 else:
1855 else:
1980 heads = repo.heads()
1856 heads = repo.heads()
1981 if len(heads) > 2:
1857 if len(heads) > 2:
1982 raise util.Abort(_('repo has %d heads - '
1858 raise util.Abort(_('repo has %d heads - '
1983 'please merge with an explicit rev') %
1859 'please merge with an explicit rev') %
1984 len(heads))
1860 len(heads))
1985 if len(heads) == 1:
1861 if len(heads) == 1:
1986 raise util.Abort(_('there is nothing to merge - '
1862 raise util.Abort(_('there is nothing to merge - '
1987 'use "hg update" instead'))
1863 'use "hg update" instead'))
1988 parent = repo.dirstate.parents()[0]
1864 parent = repo.dirstate.parents()[0]
1989 if parent not in heads:
1865 if parent not in heads:
1990 raise util.Abort(_('working dir not at a head rev - '
1866 raise util.Abort(_('working dir not at a head rev - '
1991 'use "hg update" or merge with an explicit rev'))
1867 'use "hg update" or merge with an explicit rev'))
1992 node = parent == heads[0] and heads[-1] or heads[0]
1868 node = parent == heads[0] and heads[-1] or heads[0]
1993 return hg.merge(repo, node, force=force)
1869 return hg.merge(repo, node, force=force)
1994
1870
1995 def outgoing(ui, repo, dest=None, **opts):
1871 def outgoing(ui, repo, dest=None, **opts):
1996 """show changesets not found in destination
1872 """show changesets not found in destination
1997
1873
1998 Show changesets not found in the specified destination repository or
1874 Show changesets not found in the specified destination repository or
1999 the default push location. These are the changesets that would be pushed
1875 the default push location. These are the changesets that would be pushed
2000 if a push was requested.
1876 if a push was requested.
2001
1877
2002 See pull for valid destination format details.
1878 See pull for valid destination format details.
2003 """
1879 """
2004 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1880 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2005 setremoteconfig(ui, opts)
1881 setremoteconfig(ui, opts)
2006 revs = None
1882 revs = None
2007 if opts['rev']:
1883 if opts['rev']:
2008 revs = [repo.lookup(rev) for rev in opts['rev']]
1884 revs = [repo.lookup(rev) for rev in opts['rev']]
2009
1885
2010 other = hg.repository(ui, dest)
1886 other = hg.repository(ui, dest)
2011 o = repo.findoutgoing(other, force=opts['force'])
1887 o = repo.findoutgoing(other, force=opts['force'])
2012 if not o:
1888 if not o:
2013 ui.status(_("no changes found\n"))
1889 ui.status(_("no changes found\n"))
2014 return
1890 return
2015 o = repo.changelog.nodesbetween(o, revs)[0]
1891 o = repo.changelog.nodesbetween(o, revs)[0]
2016 if opts['newest_first']:
1892 if opts['newest_first']:
2017 o.reverse()
1893 o.reverse()
2018 displayer = show_changeset(ui, repo, opts)
1894 displayer = cmdutil.show_changeset(ui, repo, opts)
2019 for n in o:
1895 for n in o:
2020 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1896 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2021 if opts['no_merges'] and len(parents) == 2:
1897 if opts['no_merges'] and len(parents) == 2:
2022 continue
1898 continue
2023 displayer.show(changenode=n)
1899 displayer.show(changenode=n)
2024 if opts['patch']:
1900 if opts['patch']:
2025 prev = (parents and parents[0]) or nullid
1901 prev = (parents and parents[0]) or nullid
2026 patch.diff(repo, prev, n)
1902 patch.diff(repo, prev, n)
2027 ui.write("\n")
1903 ui.write("\n")
2028
1904
2029 def parents(ui, repo, file_=None, rev=None, branches=None, **opts):
1905 def parents(ui, repo, file_=None, rev=None, branches=None, **opts):
2030 """show the parents of the working dir or revision
1906 """show the parents of the working dir or revision
2031
1907
2032 Print the working directory's parent revisions.
1908 Print the working directory's parent revisions.
2033 """
1909 """
2034 # legacy
1910 # legacy
2035 if file_ and not rev:
1911 if file_ and not rev:
2036 try:
1912 try:
2037 rev = repo.lookup(file_)
1913 rev = repo.lookup(file_)
2038 file_ = None
1914 file_ = None
2039 except hg.RepoError:
1915 except hg.RepoError:
2040 pass
1916 pass
2041 else:
1917 else:
2042 ui.warn(_("'hg parent REV' is deprecated, "
1918 ui.warn(_("'hg parent REV' is deprecated, "
2043 "please use 'hg parents -r REV instead\n"))
1919 "please use 'hg parents -r REV instead\n"))
2044
1920
2045 if rev:
1921 if rev:
2046 if file_:
1922 if file_:
2047 ctx = repo.filectx(file_, changeid=rev)
1923 ctx = repo.filectx(file_, changeid=rev)
2048 else:
1924 else:
2049 ctx = repo.changectx(rev)
1925 ctx = repo.changectx(rev)
2050 p = [cp.node() for cp in ctx.parents()]
1926 p = [cp.node() for cp in ctx.parents()]
2051 else:
1927 else:
2052 p = repo.dirstate.parents()
1928 p = repo.dirstate.parents()
2053
1929
2054 br = None
1930 br = None
2055 if branches is not None:
1931 if branches is not None:
2056 ui.warn(_("the --branches option is deprecated, "
1932 ui.warn(_("the --branches option is deprecated, "
2057 "please use 'hg branches' instead\n"))
1933 "please use 'hg branches' instead\n"))
2058 br = repo.branchlookup(p)
1934 br = repo.branchlookup(p)
2059 displayer = show_changeset(ui, repo, opts)
1935 displayer = cmdutil.show_changeset(ui, repo, opts)
2060 for n in p:
1936 for n in p:
2061 if n != nullid:
1937 if n != nullid:
2062 displayer.show(changenode=n, brinfo=br)
1938 displayer.show(changenode=n, brinfo=br)
2063
1939
2064 def paths(ui, repo, search=None):
1940 def paths(ui, repo, search=None):
2065 """show definition of symbolic path names
1941 """show definition of symbolic path names
2066
1942
2067 Show definition of symbolic path name NAME. If no name is given, show
1943 Show definition of symbolic path name NAME. If no name is given, show
2068 definition of available names.
1944 definition of available names.
2069
1945
2070 Path names are defined in the [paths] section of /etc/mercurial/hgrc
1946 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2071 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
1947 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2072 """
1948 """
2073 if search:
1949 if search:
2074 for name, path in ui.configitems("paths"):
1950 for name, path in ui.configitems("paths"):
2075 if name == search:
1951 if name == search:
2076 ui.write("%s\n" % path)
1952 ui.write("%s\n" % path)
2077 return
1953 return
2078 ui.warn(_("not found!\n"))
1954 ui.warn(_("not found!\n"))
2079 return 1
1955 return 1
2080 else:
1956 else:
2081 for name, path in ui.configitems("paths"):
1957 for name, path in ui.configitems("paths"):
2082 ui.write("%s = %s\n" % (name, path))
1958 ui.write("%s = %s\n" % (name, path))
2083
1959
2084 def postincoming(ui, repo, modheads, optupdate):
1960 def postincoming(ui, repo, modheads, optupdate):
2085 if modheads == 0:
1961 if modheads == 0:
2086 return
1962 return
2087 if optupdate:
1963 if optupdate:
2088 if modheads == 1:
1964 if modheads == 1:
2089 return hg.update(repo, repo.changelog.tip()) # update
1965 return hg.update(repo, repo.changelog.tip()) # update
2090 else:
1966 else:
2091 ui.status(_("not updating, since new heads added\n"))
1967 ui.status(_("not updating, since new heads added\n"))
2092 if modheads > 1:
1968 if modheads > 1:
2093 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
1969 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2094 else:
1970 else:
2095 ui.status(_("(run 'hg update' to get a working copy)\n"))
1971 ui.status(_("(run 'hg update' to get a working copy)\n"))
2096
1972
2097 def pull(ui, repo, source="default", **opts):
1973 def pull(ui, repo, source="default", **opts):
2098 """pull changes from the specified source
1974 """pull changes from the specified source
2099
1975
2100 Pull changes from a remote repository to a local one.
1976 Pull changes from a remote repository to a local one.
2101
1977
2102 This finds all changes from the repository at the specified path
1978 This finds all changes from the repository at the specified path
2103 or URL and adds them to the local repository. By default, this
1979 or URL and adds them to the local repository. By default, this
2104 does not update the copy of the project in the working directory.
1980 does not update the copy of the project in the working directory.
2105
1981
2106 Valid URLs are of the form:
1982 Valid URLs are of the form:
2107
1983
2108 local/filesystem/path (or file://local/filesystem/path)
1984 local/filesystem/path (or file://local/filesystem/path)
2109 http://[user@]host[:port]/[path]
1985 http://[user@]host[:port]/[path]
2110 https://[user@]host[:port]/[path]
1986 https://[user@]host[:port]/[path]
2111 ssh://[user@]host[:port]/[path]
1987 ssh://[user@]host[:port]/[path]
2112 static-http://host[:port]/[path]
1988 static-http://host[:port]/[path]
2113
1989
2114 Paths in the local filesystem can either point to Mercurial
1990 Paths in the local filesystem can either point to Mercurial
2115 repositories or to bundle files (as created by 'hg bundle' or
1991 repositories or to bundle files (as created by 'hg bundle' or
2116 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
1992 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2117 allows access to a Mercurial repository where you simply use a web
1993 allows access to a Mercurial repository where you simply use a web
2118 server to publish the .hg directory as static content.
1994 server to publish the .hg directory as static content.
2119
1995
2120 Some notes about using SSH with Mercurial:
1996 Some notes about using SSH with Mercurial:
2121 - SSH requires an accessible shell account on the destination machine
1997 - SSH requires an accessible shell account on the destination machine
2122 and a copy of hg in the remote path or specified with as remotecmd.
1998 and a copy of hg in the remote path or specified with as remotecmd.
2123 - path is relative to the remote user's home directory by default.
1999 - path is relative to the remote user's home directory by default.
2124 Use an extra slash at the start of a path to specify an absolute path:
2000 Use an extra slash at the start of a path to specify an absolute path:
2125 ssh://example.com//tmp/repository
2001 ssh://example.com//tmp/repository
2126 - Mercurial doesn't use its own compression via SSH; the right thing
2002 - Mercurial doesn't use its own compression via SSH; the right thing
2127 to do is to configure it in your ~/.ssh/config, e.g.:
2003 to do is to configure it in your ~/.ssh/config, e.g.:
2128 Host *.mylocalnetwork.example.com
2004 Host *.mylocalnetwork.example.com
2129 Compression no
2005 Compression no
2130 Host *
2006 Host *
2131 Compression yes
2007 Compression yes
2132 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2008 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2133 with the --ssh command line option.
2009 with the --ssh command line option.
2134 """
2010 """
2135 source = ui.expandpath(source)
2011 source = ui.expandpath(source)
2136 setremoteconfig(ui, opts)
2012 setremoteconfig(ui, opts)
2137
2013
2138 other = hg.repository(ui, source)
2014 other = hg.repository(ui, source)
2139 ui.status(_('pulling from %s\n') % (source))
2015 ui.status(_('pulling from %s\n') % (source))
2140 revs = None
2016 revs = None
2141 if opts['rev']:
2017 if opts['rev']:
2142 if 'lookup' in other.capabilities:
2018 if 'lookup' in other.capabilities:
2143 revs = [other.lookup(rev) for rev in opts['rev']]
2019 revs = [other.lookup(rev) for rev in opts['rev']]
2144 else:
2020 else:
2145 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
2021 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
2146 raise util.Abort(error)
2022 raise util.Abort(error)
2147 modheads = repo.pull(other, heads=revs, force=opts['force'])
2023 modheads = repo.pull(other, heads=revs, force=opts['force'])
2148 return postincoming(ui, repo, modheads, opts['update'])
2024 return postincoming(ui, repo, modheads, opts['update'])
2149
2025
2150 def push(ui, repo, dest=None, **opts):
2026 def push(ui, repo, dest=None, **opts):
2151 """push changes to the specified destination
2027 """push changes to the specified destination
2152
2028
2153 Push changes from the local repository to the given destination.
2029 Push changes from the local repository to the given destination.
2154
2030
2155 This is the symmetrical operation for pull. It helps to move
2031 This is the symmetrical operation for pull. It helps to move
2156 changes from the current repository to a different one. If the
2032 changes from the current repository to a different one. If the
2157 destination is local this is identical to a pull in that directory
2033 destination is local this is identical to a pull in that directory
2158 from the current one.
2034 from the current one.
2159
2035
2160 By default, push will refuse to run if it detects the result would
2036 By default, push will refuse to run if it detects the result would
2161 increase the number of remote heads. This generally indicates the
2037 increase the number of remote heads. This generally indicates the
2162 the client has forgotten to sync and merge before pushing.
2038 the client has forgotten to sync and merge before pushing.
2163
2039
2164 Valid URLs are of the form:
2040 Valid URLs are of the form:
2165
2041
2166 local/filesystem/path (or file://local/filesystem/path)
2042 local/filesystem/path (or file://local/filesystem/path)
2167 ssh://[user@]host[:port]/[path]
2043 ssh://[user@]host[:port]/[path]
2168 http://[user@]host[:port]/[path]
2044 http://[user@]host[:port]/[path]
2169 https://[user@]host[:port]/[path]
2045 https://[user@]host[:port]/[path]
2170
2046
2171 Look at the help text for the pull command for important details
2047 Look at the help text for the pull command for important details
2172 about ssh:// URLs.
2048 about ssh:// URLs.
2173
2049
2174 Pushing to http:// and https:// URLs is only possible, if this
2050 Pushing to http:// and https:// URLs is only possible, if this
2175 feature is explicitly enabled on the remote Mercurial server.
2051 feature is explicitly enabled on the remote Mercurial server.
2176 """
2052 """
2177 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2053 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2178 setremoteconfig(ui, opts)
2054 setremoteconfig(ui, opts)
2179
2055
2180 other = hg.repository(ui, dest)
2056 other = hg.repository(ui, dest)
2181 ui.status('pushing to %s\n' % (dest))
2057 ui.status('pushing to %s\n' % (dest))
2182 revs = None
2058 revs = None
2183 if opts['rev']:
2059 if opts['rev']:
2184 revs = [repo.lookup(rev) for rev in opts['rev']]
2060 revs = [repo.lookup(rev) for rev in opts['rev']]
2185 r = repo.push(other, opts['force'], revs=revs)
2061 r = repo.push(other, opts['force'], revs=revs)
2186 return r == 0
2062 return r == 0
2187
2063
2188 def rawcommit(ui, repo, *flist, **rc):
2064 def rawcommit(ui, repo, *flist, **rc):
2189 """raw commit interface (DEPRECATED)
2065 """raw commit interface (DEPRECATED)
2190
2066
2191 (DEPRECATED)
2067 (DEPRECATED)
2192 Lowlevel commit, for use in helper scripts.
2068 Lowlevel commit, for use in helper scripts.
2193
2069
2194 This command is not intended to be used by normal users, as it is
2070 This command is not intended to be used by normal users, as it is
2195 primarily useful for importing from other SCMs.
2071 primarily useful for importing from other SCMs.
2196
2072
2197 This command is now deprecated and will be removed in a future
2073 This command is now deprecated and will be removed in a future
2198 release, please use debugsetparents and commit instead.
2074 release, please use debugsetparents and commit instead.
2199 """
2075 """
2200
2076
2201 ui.warn(_("(the rawcommit command is deprecated)\n"))
2077 ui.warn(_("(the rawcommit command is deprecated)\n"))
2202
2078
2203 message = rc['message']
2079 message = rc['message']
2204 if not message and rc['logfile']:
2080 if not message and rc['logfile']:
2205 try:
2081 try:
2206 message = open(rc['logfile']).read()
2082 message = open(rc['logfile']).read()
2207 except IOError:
2083 except IOError:
2208 pass
2084 pass
2209 if not message and not rc['logfile']:
2085 if not message and not rc['logfile']:
2210 raise util.Abort(_("missing commit message"))
2086 raise util.Abort(_("missing commit message"))
2211
2087
2212 files = relpath(repo, list(flist))
2088 files = relpath(repo, list(flist))
2213 if rc['files']:
2089 if rc['files']:
2214 files += open(rc['files']).read().splitlines()
2090 files += open(rc['files']).read().splitlines()
2215
2091
2216 rc['parent'] = map(repo.lookup, rc['parent'])
2092 rc['parent'] = map(repo.lookup, rc['parent'])
2217
2093
2218 try:
2094 try:
2219 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2095 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2220 except ValueError, inst:
2096 except ValueError, inst:
2221 raise util.Abort(str(inst))
2097 raise util.Abort(str(inst))
2222
2098
2223 def recover(ui, repo):
2099 def recover(ui, repo):
2224 """roll back an interrupted transaction
2100 """roll back an interrupted transaction
2225
2101
2226 Recover from an interrupted commit or pull.
2102 Recover from an interrupted commit or pull.
2227
2103
2228 This command tries to fix the repository status after an interrupted
2104 This command tries to fix the repository status after an interrupted
2229 operation. It should only be necessary when Mercurial suggests it.
2105 operation. It should only be necessary when Mercurial suggests it.
2230 """
2106 """
2231 if repo.recover():
2107 if repo.recover():
2232 return hg.verify(repo)
2108 return hg.verify(repo)
2233 return 1
2109 return 1
2234
2110
2235 def remove(ui, repo, *pats, **opts):
2111 def remove(ui, repo, *pats, **opts):
2236 """remove the specified files on the next commit
2112 """remove the specified files on the next commit
2237
2113
2238 Schedule the indicated files for removal from the repository.
2114 Schedule the indicated files for removal from the repository.
2239
2115
2240 This command schedules the files to be removed at the next commit.
2116 This command schedules the files to be removed at the next commit.
2241 This only removes files from the current branch, not from the
2117 This only removes files from the current branch, not from the
2242 entire project history. If the files still exist in the working
2118 entire project history. If the files still exist in the working
2243 directory, they will be deleted from it. If invoked with --after,
2119 directory, they will be deleted from it. If invoked with --after,
2244 files that have been manually deleted are marked as removed.
2120 files that have been manually deleted are marked as removed.
2245
2121
2246 Modified files and added files are not removed by default. To
2122 Modified files and added files are not removed by default. To
2247 remove them, use the -f/--force option.
2123 remove them, use the -f/--force option.
2248 """
2124 """
2249 names = []
2125 names = []
2250 if not opts['after'] and not pats:
2126 if not opts['after'] and not pats:
2251 raise util.Abort(_('no files specified'))
2127 raise util.Abort(_('no files specified'))
2252 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2128 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2253 exact = dict.fromkeys(files)
2129 exact = dict.fromkeys(files)
2254 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2130 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2255 modified, added, removed, deleted, unknown = mardu
2131 modified, added, removed, deleted, unknown = mardu
2256 remove, forget = [], []
2132 remove, forget = [], []
2257 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2133 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2258 reason = None
2134 reason = None
2259 if abs not in deleted and opts['after']:
2135 if abs not in deleted and opts['after']:
2260 reason = _('is still present')
2136 reason = _('is still present')
2261 elif abs in modified and not opts['force']:
2137 elif abs in modified and not opts['force']:
2262 reason = _('is modified (use -f to force removal)')
2138 reason = _('is modified (use -f to force removal)')
2263 elif abs in added:
2139 elif abs in added:
2264 if opts['force']:
2140 if opts['force']:
2265 forget.append(abs)
2141 forget.append(abs)
2266 continue
2142 continue
2267 reason = _('has been marked for add (use -f to force removal)')
2143 reason = _('has been marked for add (use -f to force removal)')
2268 elif abs in unknown:
2144 elif abs in unknown:
2269 reason = _('is not managed')
2145 reason = _('is not managed')
2270 elif abs in removed:
2146 elif abs in removed:
2271 continue
2147 continue
2272 if reason:
2148 if reason:
2273 if exact:
2149 if exact:
2274 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2150 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2275 else:
2151 else:
2276 if ui.verbose or not exact:
2152 if ui.verbose or not exact:
2277 ui.status(_('removing %s\n') % rel)
2153 ui.status(_('removing %s\n') % rel)
2278 remove.append(abs)
2154 remove.append(abs)
2279 repo.forget(forget)
2155 repo.forget(forget)
2280 repo.remove(remove, unlink=not opts['after'])
2156 repo.remove(remove, unlink=not opts['after'])
2281
2157
2282 def rename(ui, repo, *pats, **opts):
2158 def rename(ui, repo, *pats, **opts):
2283 """rename files; equivalent of copy + remove
2159 """rename files; equivalent of copy + remove
2284
2160
2285 Mark dest as copies of sources; mark sources for deletion. If
2161 Mark dest as copies of sources; mark sources for deletion. If
2286 dest is a directory, copies are put in that directory. If dest is
2162 dest is a directory, copies are put in that directory. If dest is
2287 a file, there can only be one source.
2163 a file, there can only be one source.
2288
2164
2289 By default, this command copies the contents of files as they
2165 By default, this command copies the contents of files as they
2290 stand in the working directory. If invoked with --after, the
2166 stand in the working directory. If invoked with --after, the
2291 operation is recorded, but no copying is performed.
2167 operation is recorded, but no copying is performed.
2292
2168
2293 This command takes effect in the next commit.
2169 This command takes effect in the next commit.
2294
2170
2295 NOTE: This command should be treated as experimental. While it
2171 NOTE: This command should be treated as experimental. While it
2296 should properly record rename files, this information is not yet
2172 should properly record rename files, this information is not yet
2297 fully used by merge, nor fully reported by log.
2173 fully used by merge, nor fully reported by log.
2298 """
2174 """
2299 wlock = repo.wlock(0)
2175 wlock = repo.wlock(0)
2300 errs, copied = docopy(ui, repo, pats, opts, wlock)
2176 errs, copied = docopy(ui, repo, pats, opts, wlock)
2301 names = []
2177 names = []
2302 for abs, rel, exact in copied:
2178 for abs, rel, exact in copied:
2303 if ui.verbose or not exact:
2179 if ui.verbose or not exact:
2304 ui.status(_('removing %s\n') % rel)
2180 ui.status(_('removing %s\n') % rel)
2305 names.append(abs)
2181 names.append(abs)
2306 if not opts.get('dry_run'):
2182 if not opts.get('dry_run'):
2307 repo.remove(names, True, wlock)
2183 repo.remove(names, True, wlock)
2308 return errs
2184 return errs
2309
2185
2310 def revert(ui, repo, *pats, **opts):
2186 def revert(ui, repo, *pats, **opts):
2311 """revert files or dirs to their states as of some revision
2187 """revert files or dirs to their states as of some revision
2312
2188
2313 With no revision specified, revert the named files or directories
2189 With no revision specified, revert the named files or directories
2314 to the contents they had in the parent of the working directory.
2190 to the contents they had in the parent of the working directory.
2315 This restores the contents of the affected files to an unmodified
2191 This restores the contents of the affected files to an unmodified
2316 state. If the working directory has two parents, you must
2192 state. If the working directory has two parents, you must
2317 explicitly specify the revision to revert to.
2193 explicitly specify the revision to revert to.
2318
2194
2319 Modified files are saved with a .orig suffix before reverting.
2195 Modified files are saved with a .orig suffix before reverting.
2320 To disable these backups, use --no-backup.
2196 To disable these backups, use --no-backup.
2321
2197
2322 Using the -r option, revert the given files or directories to their
2198 Using the -r option, revert the given files or directories to their
2323 contents as of a specific revision. This can be helpful to "roll
2199 contents as of a specific revision. This can be helpful to "roll
2324 back" some or all of a change that should not have been committed.
2200 back" some or all of a change that should not have been committed.
2325
2201
2326 Revert modifies the working directory. It does not commit any
2202 Revert modifies the working directory. It does not commit any
2327 changes, or change the parent of the working directory. If you
2203 changes, or change the parent of the working directory. If you
2328 revert to a revision other than the parent of the working
2204 revert to a revision other than the parent of the working
2329 directory, the reverted files will thus appear modified
2205 directory, the reverted files will thus appear modified
2330 afterwards.
2206 afterwards.
2331
2207
2332 If a file has been deleted, it is recreated. If the executable
2208 If a file has been deleted, it is recreated. If the executable
2333 mode of a file was changed, it is reset.
2209 mode of a file was changed, it is reset.
2334
2210
2335 If names are given, all files matching the names are reverted.
2211 If names are given, all files matching the names are reverted.
2336
2212
2337 If no arguments are given, no files are reverted.
2213 If no arguments are given, no files are reverted.
2338 """
2214 """
2339
2215
2340 if not pats and not opts['all']:
2216 if not pats and not opts['all']:
2341 raise util.Abort(_('no files or directories specified; '
2217 raise util.Abort(_('no files or directories specified; '
2342 'use --all to revert the whole repo'))
2218 'use --all to revert the whole repo'))
2343
2219
2344 parent, p2 = repo.dirstate.parents()
2220 parent, p2 = repo.dirstate.parents()
2345 if not opts['rev'] and p2 != nullid:
2221 if not opts['rev'] and p2 != nullid:
2346 raise util.Abort(_('uncommitted merge - please provide a '
2222 raise util.Abort(_('uncommitted merge - please provide a '
2347 'specific revision'))
2223 'specific revision'))
2348 node = repo.changectx(opts['rev']).node()
2224 node = repo.changectx(opts['rev']).node()
2349 mf = repo.manifest.read(repo.changelog.read(node)[0])
2225 mf = repo.manifest.read(repo.changelog.read(node)[0])
2350 if node == parent:
2226 if node == parent:
2351 pmf = mf
2227 pmf = mf
2352 else:
2228 else:
2353 pmf = None
2229 pmf = None
2354
2230
2355 wlock = repo.wlock()
2231 wlock = repo.wlock()
2356
2232
2357 # need all matching names in dirstate and manifest of target rev,
2233 # need all matching names in dirstate and manifest of target rev,
2358 # so have to walk both. do not print errors if files exist in one
2234 # so have to walk both. do not print errors if files exist in one
2359 # but not other.
2235 # but not other.
2360
2236
2361 names = {}
2237 names = {}
2362 target_only = {}
2238 target_only = {}
2363
2239
2364 # walk dirstate.
2240 # walk dirstate.
2365
2241
2366 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2242 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2367 badmatch=mf.has_key):
2243 badmatch=mf.has_key):
2368 names[abs] = (rel, exact)
2244 names[abs] = (rel, exact)
2369 if src == 'b':
2245 if src == 'b':
2370 target_only[abs] = True
2246 target_only[abs] = True
2371
2247
2372 # walk target manifest.
2248 # walk target manifest.
2373
2249
2374 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2250 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2375 badmatch=names.has_key):
2251 badmatch=names.has_key):
2376 if abs in names: continue
2252 if abs in names: continue
2377 names[abs] = (rel, exact)
2253 names[abs] = (rel, exact)
2378 target_only[abs] = True
2254 target_only[abs] = True
2379
2255
2380 changes = repo.status(match=names.has_key, wlock=wlock)[:5]
2256 changes = repo.status(match=names.has_key, wlock=wlock)[:5]
2381 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2257 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2382
2258
2383 revert = ([], _('reverting %s\n'))
2259 revert = ([], _('reverting %s\n'))
2384 add = ([], _('adding %s\n'))
2260 add = ([], _('adding %s\n'))
2385 remove = ([], _('removing %s\n'))
2261 remove = ([], _('removing %s\n'))
2386 forget = ([], _('forgetting %s\n'))
2262 forget = ([], _('forgetting %s\n'))
2387 undelete = ([], _('undeleting %s\n'))
2263 undelete = ([], _('undeleting %s\n'))
2388 update = {}
2264 update = {}
2389
2265
2390 disptable = (
2266 disptable = (
2391 # dispatch table:
2267 # dispatch table:
2392 # file state
2268 # file state
2393 # action if in target manifest
2269 # action if in target manifest
2394 # action if not in target manifest
2270 # action if not in target manifest
2395 # make backup if in target manifest
2271 # make backup if in target manifest
2396 # make backup if not in target manifest
2272 # make backup if not in target manifest
2397 (modified, revert, remove, True, True),
2273 (modified, revert, remove, True, True),
2398 (added, revert, forget, True, False),
2274 (added, revert, forget, True, False),
2399 (removed, undelete, None, False, False),
2275 (removed, undelete, None, False, False),
2400 (deleted, revert, remove, False, False),
2276 (deleted, revert, remove, False, False),
2401 (unknown, add, None, True, False),
2277 (unknown, add, None, True, False),
2402 (target_only, add, None, False, False),
2278 (target_only, add, None, False, False),
2403 )
2279 )
2404
2280
2405 entries = names.items()
2281 entries = names.items()
2406 entries.sort()
2282 entries.sort()
2407
2283
2408 for abs, (rel, exact) in entries:
2284 for abs, (rel, exact) in entries:
2409 mfentry = mf.get(abs)
2285 mfentry = mf.get(abs)
2410 def handle(xlist, dobackup):
2286 def handle(xlist, dobackup):
2411 xlist[0].append(abs)
2287 xlist[0].append(abs)
2412 update[abs] = 1
2288 update[abs] = 1
2413 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2289 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2414 bakname = "%s.orig" % rel
2290 bakname = "%s.orig" % rel
2415 ui.note(_('saving current version of %s as %s\n') %
2291 ui.note(_('saving current version of %s as %s\n') %
2416 (rel, bakname))
2292 (rel, bakname))
2417 if not opts.get('dry_run'):
2293 if not opts.get('dry_run'):
2418 util.copyfile(rel, bakname)
2294 util.copyfile(rel, bakname)
2419 if ui.verbose or not exact:
2295 if ui.verbose or not exact:
2420 ui.status(xlist[1] % rel)
2296 ui.status(xlist[1] % rel)
2421 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2297 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2422 if abs not in table: continue
2298 if abs not in table: continue
2423 # file has changed in dirstate
2299 # file has changed in dirstate
2424 if mfentry:
2300 if mfentry:
2425 handle(hitlist, backuphit)
2301 handle(hitlist, backuphit)
2426 elif misslist is not None:
2302 elif misslist is not None:
2427 handle(misslist, backupmiss)
2303 handle(misslist, backupmiss)
2428 else:
2304 else:
2429 if exact: ui.warn(_('file not managed: %s\n' % rel))
2305 if exact: ui.warn(_('file not managed: %s\n' % rel))
2430 break
2306 break
2431 else:
2307 else:
2432 # file has not changed in dirstate
2308 # file has not changed in dirstate
2433 if node == parent:
2309 if node == parent:
2434 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2310 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2435 continue
2311 continue
2436 if pmf is None:
2312 if pmf is None:
2437 # only need parent manifest in this unlikely case,
2313 # only need parent manifest in this unlikely case,
2438 # so do not read by default
2314 # so do not read by default
2439 pmf = repo.manifest.read(repo.changelog.read(parent)[0])
2315 pmf = repo.manifest.read(repo.changelog.read(parent)[0])
2440 if abs in pmf:
2316 if abs in pmf:
2441 if mfentry:
2317 if mfentry:
2442 # if version of file is same in parent and target
2318 # if version of file is same in parent and target
2443 # manifests, do nothing
2319 # manifests, do nothing
2444 if pmf[abs] != mfentry:
2320 if pmf[abs] != mfentry:
2445 handle(revert, False)
2321 handle(revert, False)
2446 else:
2322 else:
2447 handle(remove, False)
2323 handle(remove, False)
2448
2324
2449 if not opts.get('dry_run'):
2325 if not opts.get('dry_run'):
2450 repo.dirstate.forget(forget[0])
2326 repo.dirstate.forget(forget[0])
2451 r = hg.revert(repo, node, update.has_key, wlock)
2327 r = hg.revert(repo, node, update.has_key, wlock)
2452 repo.dirstate.update(add[0], 'a')
2328 repo.dirstate.update(add[0], 'a')
2453 repo.dirstate.update(undelete[0], 'n')
2329 repo.dirstate.update(undelete[0], 'n')
2454 repo.dirstate.update(remove[0], 'r')
2330 repo.dirstate.update(remove[0], 'r')
2455 return r
2331 return r
2456
2332
2457 def rollback(ui, repo):
2333 def rollback(ui, repo):
2458 """roll back the last transaction in this repository
2334 """roll back the last transaction in this repository
2459
2335
2460 Roll back the last transaction in this repository, restoring the
2336 Roll back the last transaction in this repository, restoring the
2461 project to its state prior to the transaction.
2337 project to its state prior to the transaction.
2462
2338
2463 Transactions are used to encapsulate the effects of all commands
2339 Transactions are used to encapsulate the effects of all commands
2464 that create new changesets or propagate existing changesets into a
2340 that create new changesets or propagate existing changesets into a
2465 repository. For example, the following commands are transactional,
2341 repository. For example, the following commands are transactional,
2466 and their effects can be rolled back:
2342 and their effects can be rolled back:
2467
2343
2468 commit
2344 commit
2469 import
2345 import
2470 pull
2346 pull
2471 push (with this repository as destination)
2347 push (with this repository as destination)
2472 unbundle
2348 unbundle
2473
2349
2474 This command should be used with care. There is only one level of
2350 This command should be used with care. There is only one level of
2475 rollback, and there is no way to undo a rollback.
2351 rollback, and there is no way to undo a rollback.
2476
2352
2477 This command is not intended for use on public repositories. Once
2353 This command is not intended for use on public repositories. Once
2478 changes are visible for pull by other users, rolling a transaction
2354 changes are visible for pull by other users, rolling a transaction
2479 back locally is ineffective (someone else may already have pulled
2355 back locally is ineffective (someone else may already have pulled
2480 the changes). Furthermore, a race is possible with readers of the
2356 the changes). Furthermore, a race is possible with readers of the
2481 repository; for example an in-progress pull from the repository
2357 repository; for example an in-progress pull from the repository
2482 may fail if a rollback is performed.
2358 may fail if a rollback is performed.
2483 """
2359 """
2484 repo.rollback()
2360 repo.rollback()
2485
2361
2486 def root(ui, repo):
2362 def root(ui, repo):
2487 """print the root (top) of the current working dir
2363 """print the root (top) of the current working dir
2488
2364
2489 Print the root directory of the current repository.
2365 Print the root directory of the current repository.
2490 """
2366 """
2491 ui.write(repo.root + "\n")
2367 ui.write(repo.root + "\n")
2492
2368
2493 def serve(ui, repo, **opts):
2369 def serve(ui, repo, **opts):
2494 """export the repository via HTTP
2370 """export the repository via HTTP
2495
2371
2496 Start a local HTTP repository browser and pull server.
2372 Start a local HTTP repository browser and pull server.
2497
2373
2498 By default, the server logs accesses to stdout and errors to
2374 By default, the server logs accesses to stdout and errors to
2499 stderr. Use the "-A" and "-E" options to log to files.
2375 stderr. Use the "-A" and "-E" options to log to files.
2500 """
2376 """
2501
2377
2502 if opts["stdio"]:
2378 if opts["stdio"]:
2503 if repo is None:
2379 if repo is None:
2504 raise hg.RepoError(_("There is no Mercurial repository here"
2380 raise hg.RepoError(_("There is no Mercurial repository here"
2505 " (.hg not found)"))
2381 " (.hg not found)"))
2506 s = sshserver.sshserver(ui, repo)
2382 s = sshserver.sshserver(ui, repo)
2507 s.serve_forever()
2383 s.serve_forever()
2508
2384
2509 optlist = ("name templates style address port ipv6"
2385 optlist = ("name templates style address port ipv6"
2510 " accesslog errorlog webdir_conf")
2386 " accesslog errorlog webdir_conf")
2511 for o in optlist.split():
2387 for o in optlist.split():
2512 if opts[o]:
2388 if opts[o]:
2513 ui.setconfig("web", o, str(opts[o]))
2389 ui.setconfig("web", o, str(opts[o]))
2514
2390
2515 if repo is None and not ui.config("web", "webdir_conf"):
2391 if repo is None and not ui.config("web", "webdir_conf"):
2516 raise hg.RepoError(_("There is no Mercurial repository here"
2392 raise hg.RepoError(_("There is no Mercurial repository here"
2517 " (.hg not found)"))
2393 " (.hg not found)"))
2518
2394
2519 if opts['daemon'] and not opts['daemon_pipefds']:
2395 if opts['daemon'] and not opts['daemon_pipefds']:
2520 rfd, wfd = os.pipe()
2396 rfd, wfd = os.pipe()
2521 args = sys.argv[:]
2397 args = sys.argv[:]
2522 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2398 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2523 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2399 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2524 args[0], args)
2400 args[0], args)
2525 os.close(wfd)
2401 os.close(wfd)
2526 os.read(rfd, 1)
2402 os.read(rfd, 1)
2527 os._exit(0)
2403 os._exit(0)
2528
2404
2529 httpd = hgweb.server.create_server(ui, repo)
2405 httpd = hgweb.server.create_server(ui, repo)
2530
2406
2531 if ui.verbose:
2407 if ui.verbose:
2532 if httpd.port != 80:
2408 if httpd.port != 80:
2533 ui.status(_('listening at http://%s:%d/\n') %
2409 ui.status(_('listening at http://%s:%d/\n') %
2534 (httpd.addr, httpd.port))
2410 (httpd.addr, httpd.port))
2535 else:
2411 else:
2536 ui.status(_('listening at http://%s/\n') % httpd.addr)
2412 ui.status(_('listening at http://%s/\n') % httpd.addr)
2537
2413
2538 if opts['pid_file']:
2414 if opts['pid_file']:
2539 fp = open(opts['pid_file'], 'w')
2415 fp = open(opts['pid_file'], 'w')
2540 fp.write(str(os.getpid()) + '\n')
2416 fp.write(str(os.getpid()) + '\n')
2541 fp.close()
2417 fp.close()
2542
2418
2543 if opts['daemon_pipefds']:
2419 if opts['daemon_pipefds']:
2544 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2420 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2545 os.close(rfd)
2421 os.close(rfd)
2546 os.write(wfd, 'y')
2422 os.write(wfd, 'y')
2547 os.close(wfd)
2423 os.close(wfd)
2548 sys.stdout.flush()
2424 sys.stdout.flush()
2549 sys.stderr.flush()
2425 sys.stderr.flush()
2550 fd = os.open(util.nulldev, os.O_RDWR)
2426 fd = os.open(util.nulldev, os.O_RDWR)
2551 if fd != 0: os.dup2(fd, 0)
2427 if fd != 0: os.dup2(fd, 0)
2552 if fd != 1: os.dup2(fd, 1)
2428 if fd != 1: os.dup2(fd, 1)
2553 if fd != 2: os.dup2(fd, 2)
2429 if fd != 2: os.dup2(fd, 2)
2554 if fd not in (0, 1, 2): os.close(fd)
2430 if fd not in (0, 1, 2): os.close(fd)
2555
2431
2556 httpd.serve_forever()
2432 httpd.serve_forever()
2557
2433
2558 def status(ui, repo, *pats, **opts):
2434 def status(ui, repo, *pats, **opts):
2559 """show changed files in the working directory
2435 """show changed files in the working directory
2560
2436
2561 Show status of files in the repository. If names are given, only
2437 Show status of files in the repository. If names are given, only
2562 files that match are shown. Files that are clean or ignored, are
2438 files that match are shown. Files that are clean or ignored, are
2563 not listed unless -c (clean), -i (ignored) or -A is given.
2439 not listed unless -c (clean), -i (ignored) or -A is given.
2564
2440
2565 If one revision is given, it is used as the base revision.
2441 If one revision is given, it is used as the base revision.
2566 If two revisions are given, the difference between them is shown.
2442 If two revisions are given, the difference between them is shown.
2567
2443
2568 The codes used to show the status of files are:
2444 The codes used to show the status of files are:
2569 M = modified
2445 M = modified
2570 A = added
2446 A = added
2571 R = removed
2447 R = removed
2572 C = clean
2448 C = clean
2573 ! = deleted, but still tracked
2449 ! = deleted, but still tracked
2574 ? = not tracked
2450 ? = not tracked
2575 I = ignored (not shown by default)
2451 I = ignored (not shown by default)
2576 = the previous added file was copied from here
2452 = the previous added file was copied from here
2577 """
2453 """
2578
2454
2579 all = opts['all']
2455 all = opts['all']
2580 node1, node2 = cmdutil.revpair(ui, repo, opts.get('rev'))
2456 node1, node2 = cmdutil.revpair(ui, repo, opts.get('rev'))
2581
2457
2582 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2458 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2583 cwd = (pats and repo.getcwd()) or ''
2459 cwd = (pats and repo.getcwd()) or ''
2584 modified, added, removed, deleted, unknown, ignored, clean = [
2460 modified, added, removed, deleted, unknown, ignored, clean = [
2585 [util.pathto(cwd, x) for x in n]
2461 [util.pathto(cwd, x) for x in n]
2586 for n in repo.status(node1=node1, node2=node2, files=files,
2462 for n in repo.status(node1=node1, node2=node2, files=files,
2587 match=matchfn,
2463 match=matchfn,
2588 list_ignored=all or opts['ignored'],
2464 list_ignored=all or opts['ignored'],
2589 list_clean=all or opts['clean'])]
2465 list_clean=all or opts['clean'])]
2590
2466
2591 changetypes = (('modified', 'M', modified),
2467 changetypes = (('modified', 'M', modified),
2592 ('added', 'A', added),
2468 ('added', 'A', added),
2593 ('removed', 'R', removed),
2469 ('removed', 'R', removed),
2594 ('deleted', '!', deleted),
2470 ('deleted', '!', deleted),
2595 ('unknown', '?', unknown),
2471 ('unknown', '?', unknown),
2596 ('ignored', 'I', ignored))
2472 ('ignored', 'I', ignored))
2597
2473
2598 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2474 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2599
2475
2600 end = opts['print0'] and '\0' or '\n'
2476 end = opts['print0'] and '\0' or '\n'
2601
2477
2602 for opt, char, changes in ([ct for ct in explicit_changetypes
2478 for opt, char, changes in ([ct for ct in explicit_changetypes
2603 if all or opts[ct[0]]]
2479 if all or opts[ct[0]]]
2604 or changetypes):
2480 or changetypes):
2605 if opts['no_status']:
2481 if opts['no_status']:
2606 format = "%%s%s" % end
2482 format = "%%s%s" % end
2607 else:
2483 else:
2608 format = "%s %%s%s" % (char, end)
2484 format = "%s %%s%s" % (char, end)
2609
2485
2610 for f in changes:
2486 for f in changes:
2611 ui.write(format % f)
2487 ui.write(format % f)
2612 if ((all or opts.get('copies')) and not opts.get('no_status')):
2488 if ((all or opts.get('copies')) and not opts.get('no_status')):
2613 copied = repo.dirstate.copied(f)
2489 copied = repo.dirstate.copied(f)
2614 if copied:
2490 if copied:
2615 ui.write(' %s%s' % (copied, end))
2491 ui.write(' %s%s' % (copied, end))
2616
2492
2617 def tag(ui, repo, name, rev_=None, **opts):
2493 def tag(ui, repo, name, rev_=None, **opts):
2618 """add a tag for the current tip or a given revision
2494 """add a tag for the current tip or a given revision
2619
2495
2620 Name a particular revision using <name>.
2496 Name a particular revision using <name>.
2621
2497
2622 Tags are used to name particular revisions of the repository and are
2498 Tags are used to name particular revisions of the repository and are
2623 very useful to compare different revision, to go back to significant
2499 very useful to compare different revision, to go back to significant
2624 earlier versions or to mark branch points as releases, etc.
2500 earlier versions or to mark branch points as releases, etc.
2625
2501
2626 If no revision is given, the parent of the working directory is used.
2502 If no revision is given, the parent of the working directory is used.
2627
2503
2628 To facilitate version control, distribution, and merging of tags,
2504 To facilitate version control, distribution, and merging of tags,
2629 they are stored as a file named ".hgtags" which is managed
2505 they are stored as a file named ".hgtags" which is managed
2630 similarly to other project files and can be hand-edited if
2506 similarly to other project files and can be hand-edited if
2631 necessary. The file '.hg/localtags' is used for local tags (not
2507 necessary. The file '.hg/localtags' is used for local tags (not
2632 shared among repositories).
2508 shared among repositories).
2633 """
2509 """
2634 if name in ['tip', '.']:
2510 if name in ['tip', '.']:
2635 raise util.Abort(_("the name '%s' is reserved") % name)
2511 raise util.Abort(_("the name '%s' is reserved") % name)
2636 if rev_ is not None:
2512 if rev_ is not None:
2637 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2513 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2638 "please use 'hg tag [-r REV] NAME' instead\n"))
2514 "please use 'hg tag [-r REV] NAME' instead\n"))
2639 if opts['rev']:
2515 if opts['rev']:
2640 raise util.Abort(_("use only one form to specify the revision"))
2516 raise util.Abort(_("use only one form to specify the revision"))
2641 if opts['rev']:
2517 if opts['rev']:
2642 rev_ = opts['rev']
2518 rev_ = opts['rev']
2643 if not rev_ and repo.dirstate.parents()[1] != nullid:
2519 if not rev_ and repo.dirstate.parents()[1] != nullid:
2644 raise util.Abort(_('uncommitted merge - please provide a '
2520 raise util.Abort(_('uncommitted merge - please provide a '
2645 'specific revision'))
2521 'specific revision'))
2646 r = repo.changectx(rev_).node()
2522 r = repo.changectx(rev_).node()
2647
2523
2648 message = opts['message']
2524 message = opts['message']
2649 if not message:
2525 if not message:
2650 message = _('Added tag %s for changeset %s') % (name, short(r))
2526 message = _('Added tag %s for changeset %s') % (name, short(r))
2651
2527
2652 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2528 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2653
2529
2654 def tags(ui, repo):
2530 def tags(ui, repo):
2655 """list repository tags
2531 """list repository tags
2656
2532
2657 List the repository tags.
2533 List the repository tags.
2658
2534
2659 This lists both regular and local tags.
2535 This lists both regular and local tags.
2660 """
2536 """
2661
2537
2662 l = repo.tagslist()
2538 l = repo.tagslist()
2663 l.reverse()
2539 l.reverse()
2664 hexfunc = ui.debugflag and hex or short
2540 hexfunc = ui.debugflag and hex or short
2665 for t, n in l:
2541 for t, n in l:
2666 try:
2542 try:
2667 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2543 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2668 except KeyError:
2544 except KeyError:
2669 r = " ?:?"
2545 r = " ?:?"
2670 if ui.quiet:
2546 if ui.quiet:
2671 ui.write("%s\n" % t)
2547 ui.write("%s\n" % t)
2672 else:
2548 else:
2673 ui.write("%-30s %s\n" % (t, r))
2549 ui.write("%-30s %s\n" % (t, r))
2674
2550
2675 def tip(ui, repo, **opts):
2551 def tip(ui, repo, **opts):
2676 """show the tip revision
2552 """show the tip revision
2677
2553
2678 Show the tip revision.
2554 Show the tip revision.
2679 """
2555 """
2680 n = repo.changelog.tip()
2556 n = repo.changelog.tip()
2681 br = None
2557 br = None
2682 if opts['branches']:
2558 if opts['branches']:
2683 ui.warn(_("the --branches option is deprecated, "
2559 ui.warn(_("the --branches option is deprecated, "
2684 "please use 'hg branches' instead\n"))
2560 "please use 'hg branches' instead\n"))
2685 br = repo.branchlookup([n])
2561 br = repo.branchlookup([n])
2686 show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2562 cmdutil.show_changeset(ui, repo, opts).show(changenode=n, brinfo=br)
2687 if opts['patch']:
2563 if opts['patch']:
2688 patch.diff(repo, repo.changelog.parents(n)[0], n)
2564 patch.diff(repo, repo.changelog.parents(n)[0], n)
2689
2565
2690 def unbundle(ui, repo, fname, **opts):
2566 def unbundle(ui, repo, fname, **opts):
2691 """apply a changegroup file
2567 """apply a changegroup file
2692
2568
2693 Apply a compressed changegroup file generated by the bundle
2569 Apply a compressed changegroup file generated by the bundle
2694 command.
2570 command.
2695 """
2571 """
2696 f = urllib.urlopen(fname)
2572 f = urllib.urlopen(fname)
2697
2573
2698 header = f.read(6)
2574 header = f.read(6)
2699 if not header.startswith("HG"):
2575 if not header.startswith("HG"):
2700 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2576 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2701 elif not header.startswith("HG10"):
2577 elif not header.startswith("HG10"):
2702 raise util.Abort(_("%s: unknown bundle version") % fname)
2578 raise util.Abort(_("%s: unknown bundle version") % fname)
2703 elif header == "HG10BZ":
2579 elif header == "HG10BZ":
2704 def generator(f):
2580 def generator(f):
2705 zd = bz2.BZ2Decompressor()
2581 zd = bz2.BZ2Decompressor()
2706 zd.decompress("BZ")
2582 zd.decompress("BZ")
2707 for chunk in f:
2583 for chunk in f:
2708 yield zd.decompress(chunk)
2584 yield zd.decompress(chunk)
2709 elif header == "HG10UN":
2585 elif header == "HG10UN":
2710 def generator(f):
2586 def generator(f):
2711 for chunk in f:
2587 for chunk in f:
2712 yield chunk
2588 yield chunk
2713 else:
2589 else:
2714 raise util.Abort(_("%s: unknown bundle compression type")
2590 raise util.Abort(_("%s: unknown bundle compression type")
2715 % fname)
2591 % fname)
2716 gen = generator(util.filechunkiter(f, 4096))
2592 gen = generator(util.filechunkiter(f, 4096))
2717 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle',
2593 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle',
2718 'bundle:' + fname)
2594 'bundle:' + fname)
2719 return postincoming(ui, repo, modheads, opts['update'])
2595 return postincoming(ui, repo, modheads, opts['update'])
2720
2596
2721 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2597 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2722 branch=None):
2598 branch=None):
2723 """update or merge working directory
2599 """update or merge working directory
2724
2600
2725 Update the working directory to the specified revision.
2601 Update the working directory to the specified revision.
2726
2602
2727 If there are no outstanding changes in the working directory and
2603 If there are no outstanding changes in the working directory and
2728 there is a linear relationship between the current version and the
2604 there is a linear relationship between the current version and the
2729 requested version, the result is the requested version.
2605 requested version, the result is the requested version.
2730
2606
2731 To merge the working directory with another revision, use the
2607 To merge the working directory with another revision, use the
2732 merge command.
2608 merge command.
2733
2609
2734 By default, update will refuse to run if doing so would require
2610 By default, update will refuse to run if doing so would require
2735 merging or discarding local changes.
2611 merging or discarding local changes.
2736 """
2612 """
2737 node = _lookup(repo, node, branch)
2613 node = _lookup(repo, node, branch)
2738 if clean:
2614 if clean:
2739 return hg.clean(repo, node)
2615 return hg.clean(repo, node)
2740 else:
2616 else:
2741 return hg.update(repo, node)
2617 return hg.update(repo, node)
2742
2618
2743 def _lookup(repo, node, branch=None):
2619 def _lookup(repo, node, branch=None):
2744 if branch:
2620 if branch:
2745 repo.ui.warn(_("the --branch option is deprecated, "
2621 repo.ui.warn(_("the --branch option is deprecated, "
2746 "please use 'hg branch' instead\n"))
2622 "please use 'hg branch' instead\n"))
2747 br = repo.branchlookup(branch=branch)
2623 br = repo.branchlookup(branch=branch)
2748 found = []
2624 found = []
2749 for x in br:
2625 for x in br:
2750 if branch in br[x]:
2626 if branch in br[x]:
2751 found.append(x)
2627 found.append(x)
2752 if len(found) > 1:
2628 if len(found) > 1:
2753 repo.ui.warn(_("Found multiple heads for %s\n") % branch)
2629 repo.ui.warn(_("Found multiple heads for %s\n") % branch)
2754 for x in found:
2630 for x in found:
2755 show_changeset(ui, repo, {}).show(changenode=x, brinfo=br)
2631 cmdutil.show_changeset(ui, repo, {}).show(
2632 changenode=x, brinfo=br)
2756 raise util.Abort("")
2633 raise util.Abort("")
2757 if len(found) == 1:
2634 if len(found) == 1:
2758 node = found[0]
2635 node = found[0]
2759 repo.ui.warn(_("Using head %s for branch %s\n")
2636 repo.ui.warn(_("Using head %s for branch %s\n")
2760 % (short(node), branch))
2637 % (short(node), branch))
2761 else:
2638 else:
2762 raise util.Abort(_("branch %s not found") % branch)
2639 raise util.Abort(_("branch %s not found") % branch)
2763 else:
2640 else:
2764 node = node and repo.lookup(node) or repo.changelog.tip()
2641 node = node and repo.lookup(node) or repo.changelog.tip()
2765 return node
2642 return node
2766
2643
2767 def verify(ui, repo):
2644 def verify(ui, repo):
2768 """verify the integrity of the repository
2645 """verify the integrity of the repository
2769
2646
2770 Verify the integrity of the current repository.
2647 Verify the integrity of the current repository.
2771
2648
2772 This will perform an extensive check of the repository's
2649 This will perform an extensive check of the repository's
2773 integrity, validating the hashes and checksums of each entry in
2650 integrity, validating the hashes and checksums of each entry in
2774 the changelog, manifest, and tracked files, as well as the
2651 the changelog, manifest, and tracked files, as well as the
2775 integrity of their crosslinks and indices.
2652 integrity of their crosslinks and indices.
2776 """
2653 """
2777 return hg.verify(repo)
2654 return hg.verify(repo)
2778
2655
2779 # Command options and aliases are listed here, alphabetically
2656 # Command options and aliases are listed here, alphabetically
2780
2657
2781 globalopts = [
2658 globalopts = [
2782 ('R', 'repository', '',
2659 ('R', 'repository', '',
2783 _('repository root directory or symbolic path name')),
2660 _('repository root directory or symbolic path name')),
2784 ('', 'cwd', '', _('change working directory')),
2661 ('', 'cwd', '', _('change working directory')),
2785 ('y', 'noninteractive', None,
2662 ('y', 'noninteractive', None,
2786 _('do not prompt, assume \'yes\' for any required answers')),
2663 _('do not prompt, assume \'yes\' for any required answers')),
2787 ('q', 'quiet', None, _('suppress output')),
2664 ('q', 'quiet', None, _('suppress output')),
2788 ('v', 'verbose', None, _('enable additional output')),
2665 ('v', 'verbose', None, _('enable additional output')),
2789 ('', 'config', [], _('set/override config option')),
2666 ('', 'config', [], _('set/override config option')),
2790 ('', 'debug', None, _('enable debugging output')),
2667 ('', 'debug', None, _('enable debugging output')),
2791 ('', 'debugger', None, _('start debugger')),
2668 ('', 'debugger', None, _('start debugger')),
2792 ('', 'lsprof', None, _('print improved command execution profile')),
2669 ('', 'lsprof', None, _('print improved command execution profile')),
2793 ('', 'traceback', None, _('print traceback on exception')),
2670 ('', 'traceback', None, _('print traceback on exception')),
2794 ('', 'time', None, _('time how long the command takes')),
2671 ('', 'time', None, _('time how long the command takes')),
2795 ('', 'profile', None, _('print command execution profile')),
2672 ('', 'profile', None, _('print command execution profile')),
2796 ('', 'version', None, _('output version information and exit')),
2673 ('', 'version', None, _('output version information and exit')),
2797 ('h', 'help', None, _('display help and exit')),
2674 ('h', 'help', None, _('display help and exit')),
2798 ]
2675 ]
2799
2676
2800 dryrunopts = [('n', 'dry-run', None,
2677 dryrunopts = [('n', 'dry-run', None,
2801 _('do not perform actions, just print output'))]
2678 _('do not perform actions, just print output'))]
2802
2679
2803 remoteopts = [
2680 remoteopts = [
2804 ('e', 'ssh', '', _('specify ssh command to use')),
2681 ('e', 'ssh', '', _('specify ssh command to use')),
2805 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2682 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2806 ]
2683 ]
2807
2684
2808 walkopts = [
2685 walkopts = [
2809 ('I', 'include', [], _('include names matching the given patterns')),
2686 ('I', 'include', [], _('include names matching the given patterns')),
2810 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2687 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2811 ]
2688 ]
2812
2689
2813 table = {
2690 table = {
2814 "^add":
2691 "^add":
2815 (add,
2692 (add,
2816 walkopts + dryrunopts,
2693 walkopts + dryrunopts,
2817 _('hg add [OPTION]... [FILE]...')),
2694 _('hg add [OPTION]... [FILE]...')),
2818 "addremove":
2695 "addremove":
2819 (addremove,
2696 (addremove,
2820 [('s', 'similarity', '',
2697 [('s', 'similarity', '',
2821 _('guess renamed files by similarity (0<=s<=100)')),
2698 _('guess renamed files by similarity (0<=s<=100)')),
2822 ] + walkopts + dryrunopts,
2699 ] + walkopts + dryrunopts,
2823 _('hg addremove [OPTION]... [FILE]...')),
2700 _('hg addremove [OPTION]... [FILE]...')),
2824 "^annotate":
2701 "^annotate":
2825 (annotate,
2702 (annotate,
2826 [('r', 'rev', '', _('annotate the specified revision')),
2703 [('r', 'rev', '', _('annotate the specified revision')),
2827 ('f', 'follow', None, _('follow file copies and renames')),
2704 ('f', 'follow', None, _('follow file copies and renames')),
2828 ('a', 'text', None, _('treat all files as text')),
2705 ('a', 'text', None, _('treat all files as text')),
2829 ('u', 'user', None, _('list the author')),
2706 ('u', 'user', None, _('list the author')),
2830 ('d', 'date', None, _('list the date')),
2707 ('d', 'date', None, _('list the date')),
2831 ('n', 'number', None, _('list the revision number (default)')),
2708 ('n', 'number', None, _('list the revision number (default)')),
2832 ('c', 'changeset', None, _('list the changeset')),
2709 ('c', 'changeset', None, _('list the changeset')),
2833 ] + walkopts,
2710 ] + walkopts,
2834 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2711 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2835 "archive":
2712 "archive":
2836 (archive,
2713 (archive,
2837 [('', 'no-decode', None, _('do not pass files through decoders')),
2714 [('', 'no-decode', None, _('do not pass files through decoders')),
2838 ('p', 'prefix', '', _('directory prefix for files in archive')),
2715 ('p', 'prefix', '', _('directory prefix for files in archive')),
2839 ('r', 'rev', '', _('revision to distribute')),
2716 ('r', 'rev', '', _('revision to distribute')),
2840 ('t', 'type', '', _('type of distribution to create')),
2717 ('t', 'type', '', _('type of distribution to create')),
2841 ] + walkopts,
2718 ] + walkopts,
2842 _('hg archive [OPTION]... DEST')),
2719 _('hg archive [OPTION]... DEST')),
2843 "backout":
2720 "backout":
2844 (backout,
2721 (backout,
2845 [('', 'merge', None,
2722 [('', 'merge', None,
2846 _('merge with old dirstate parent after backout')),
2723 _('merge with old dirstate parent after backout')),
2847 ('m', 'message', '', _('use <text> as commit message')),
2724 ('m', 'message', '', _('use <text> as commit message')),
2848 ('l', 'logfile', '', _('read commit message from <file>')),
2725 ('l', 'logfile', '', _('read commit message from <file>')),
2849 ('d', 'date', '', _('record datecode as commit date')),
2726 ('d', 'date', '', _('record datecode as commit date')),
2850 ('', 'parent', '', _('parent to choose when backing out merge')),
2727 ('', 'parent', '', _('parent to choose when backing out merge')),
2851 ('u', 'user', '', _('record user as committer')),
2728 ('u', 'user', '', _('record user as committer')),
2852 ] + walkopts,
2729 ] + walkopts,
2853 _('hg backout [OPTION]... REV')),
2730 _('hg backout [OPTION]... REV')),
2854 "branch": (branch, [], _('hg branch [NAME]')),
2731 "branch": (branch, [], _('hg branch [NAME]')),
2855 "branches": (branches, [], _('hg branches')),
2732 "branches": (branches, [], _('hg branches')),
2856 "bundle":
2733 "bundle":
2857 (bundle,
2734 (bundle,
2858 [('f', 'force', None,
2735 [('f', 'force', None,
2859 _('run even when remote repository is unrelated')),
2736 _('run even when remote repository is unrelated')),
2860 ('r', 'rev', [],
2737 ('r', 'rev', [],
2861 _('a changeset you would like to bundle')),
2738 _('a changeset you would like to bundle')),
2862 ('', 'base', [],
2739 ('', 'base', [],
2863 _('a base changeset to specify instead of a destination')),
2740 _('a base changeset to specify instead of a destination')),
2864 ] + remoteopts,
2741 ] + remoteopts,
2865 _('hg bundle [--base REV]... [--rev REV]... FILE [DEST]')),
2742 _('hg bundle [--base REV]... [--rev REV]... FILE [DEST]')),
2866 "cat":
2743 "cat":
2867 (cat,
2744 (cat,
2868 [('o', 'output', '', _('print output to file with formatted name')),
2745 [('o', 'output', '', _('print output to file with formatted name')),
2869 ('r', 'rev', '', _('print the given revision')),
2746 ('r', 'rev', '', _('print the given revision')),
2870 ] + walkopts,
2747 ] + walkopts,
2871 _('hg cat [OPTION]... FILE...')),
2748 _('hg cat [OPTION]... FILE...')),
2872 "^clone":
2749 "^clone":
2873 (clone,
2750 (clone,
2874 [('U', 'noupdate', None, _('do not update the new working directory')),
2751 [('U', 'noupdate', None, _('do not update the new working directory')),
2875 ('r', 'rev', [],
2752 ('r', 'rev', [],
2876 _('a changeset you would like to have after cloning')),
2753 _('a changeset you would like to have after cloning')),
2877 ('', 'pull', None, _('use pull protocol to copy metadata')),
2754 ('', 'pull', None, _('use pull protocol to copy metadata')),
2878 ('', 'uncompressed', None,
2755 ('', 'uncompressed', None,
2879 _('use uncompressed transfer (fast over LAN)')),
2756 _('use uncompressed transfer (fast over LAN)')),
2880 ] + remoteopts,
2757 ] + remoteopts,
2881 _('hg clone [OPTION]... SOURCE [DEST]')),
2758 _('hg clone [OPTION]... SOURCE [DEST]')),
2882 "^commit|ci":
2759 "^commit|ci":
2883 (commit,
2760 (commit,
2884 [('A', 'addremove', None,
2761 [('A', 'addremove', None,
2885 _('mark new/missing files as added/removed before committing')),
2762 _('mark new/missing files as added/removed before committing')),
2886 ('m', 'message', '', _('use <text> as commit message')),
2763 ('m', 'message', '', _('use <text> as commit message')),
2887 ('l', 'logfile', '', _('read the commit message from <file>')),
2764 ('l', 'logfile', '', _('read the commit message from <file>')),
2888 ('d', 'date', '', _('record datecode as commit date')),
2765 ('d', 'date', '', _('record datecode as commit date')),
2889 ('u', 'user', '', _('record user as commiter')),
2766 ('u', 'user', '', _('record user as commiter')),
2890 ] + walkopts,
2767 ] + walkopts,
2891 _('hg commit [OPTION]... [FILE]...')),
2768 _('hg commit [OPTION]... [FILE]...')),
2892 "copy|cp":
2769 "copy|cp":
2893 (copy,
2770 (copy,
2894 [('A', 'after', None, _('record a copy that has already occurred')),
2771 [('A', 'after', None, _('record a copy that has already occurred')),
2895 ('f', 'force', None,
2772 ('f', 'force', None,
2896 _('forcibly copy over an existing managed file')),
2773 _('forcibly copy over an existing managed file')),
2897 ] + walkopts + dryrunopts,
2774 ] + walkopts + dryrunopts,
2898 _('hg copy [OPTION]... [SOURCE]... DEST')),
2775 _('hg copy [OPTION]... [SOURCE]... DEST')),
2899 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2776 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2900 "debugcomplete":
2777 "debugcomplete":
2901 (debugcomplete,
2778 (debugcomplete,
2902 [('o', 'options', None, _('show the command options'))],
2779 [('o', 'options', None, _('show the command options'))],
2903 _('debugcomplete [-o] CMD')),
2780 _('debugcomplete [-o] CMD')),
2904 "debugrebuildstate":
2781 "debugrebuildstate":
2905 (debugrebuildstate,
2782 (debugrebuildstate,
2906 [('r', 'rev', '', _('revision to rebuild to'))],
2783 [('r', 'rev', '', _('revision to rebuild to'))],
2907 _('debugrebuildstate [-r REV] [REV]')),
2784 _('debugrebuildstate [-r REV] [REV]')),
2908 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2785 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2909 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2786 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2910 "debugstate": (debugstate, [], _('debugstate')),
2787 "debugstate": (debugstate, [], _('debugstate')),
2911 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2788 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2912 "debugindex": (debugindex, [], _('debugindex FILE')),
2789 "debugindex": (debugindex, [], _('debugindex FILE')),
2913 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2790 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2914 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2791 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2915 "debugwalk":
2792 "debugwalk":
2916 (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2793 (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2917 "^diff":
2794 "^diff":
2918 (diff,
2795 (diff,
2919 [('r', 'rev', [], _('revision')),
2796 [('r', 'rev', [], _('revision')),
2920 ('a', 'text', None, _('treat all files as text')),
2797 ('a', 'text', None, _('treat all files as text')),
2921 ('p', 'show-function', None,
2798 ('p', 'show-function', None,
2922 _('show which function each change is in')),
2799 _('show which function each change is in')),
2923 ('g', 'git', None, _('use git extended diff format')),
2800 ('g', 'git', None, _('use git extended diff format')),
2924 ('', 'nodates', None, _("don't include dates in diff headers")),
2801 ('', 'nodates', None, _("don't include dates in diff headers")),
2925 ('w', 'ignore-all-space', None,
2802 ('w', 'ignore-all-space', None,
2926 _('ignore white space when comparing lines')),
2803 _('ignore white space when comparing lines')),
2927 ('b', 'ignore-space-change', None,
2804 ('b', 'ignore-space-change', None,
2928 _('ignore changes in the amount of white space')),
2805 _('ignore changes in the amount of white space')),
2929 ('B', 'ignore-blank-lines', None,
2806 ('B', 'ignore-blank-lines', None,
2930 _('ignore changes whose lines are all blank')),
2807 _('ignore changes whose lines are all blank')),
2931 ] + walkopts,
2808 ] + walkopts,
2932 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2809 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2933 "^export":
2810 "^export":
2934 (export,
2811 (export,
2935 [('o', 'output', '', _('print output to file with formatted name')),
2812 [('o', 'output', '', _('print output to file with formatted name')),
2936 ('a', 'text', None, _('treat all files as text')),
2813 ('a', 'text', None, _('treat all files as text')),
2937 ('g', 'git', None, _('use git extended diff format')),
2814 ('g', 'git', None, _('use git extended diff format')),
2938 ('', 'nodates', None, _("don't include dates in diff headers")),
2815 ('', 'nodates', None, _("don't include dates in diff headers")),
2939 ('', 'switch-parent', None, _('diff against the second parent'))],
2816 ('', 'switch-parent', None, _('diff against the second parent'))],
2940 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2817 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2941 "grep":
2818 "grep":
2942 (grep,
2819 (grep,
2943 [('0', 'print0', None, _('end fields with NUL')),
2820 [('0', 'print0', None, _('end fields with NUL')),
2944 ('', 'all', None, _('print all revisions that match')),
2821 ('', 'all', None, _('print all revisions that match')),
2945 ('f', 'follow', None,
2822 ('f', 'follow', None,
2946 _('follow changeset history, or file history across copies and renames')),
2823 _('follow changeset history, or file history across copies and renames')),
2947 ('i', 'ignore-case', None, _('ignore case when matching')),
2824 ('i', 'ignore-case', None, _('ignore case when matching')),
2948 ('l', 'files-with-matches', None,
2825 ('l', 'files-with-matches', None,
2949 _('print only filenames and revs that match')),
2826 _('print only filenames and revs that match')),
2950 ('n', 'line-number', None, _('print matching line numbers')),
2827 ('n', 'line-number', None, _('print matching line numbers')),
2951 ('r', 'rev', [], _('search in given revision range')),
2828 ('r', 'rev', [], _('search in given revision range')),
2952 ('u', 'user', None, _('print user who committed change')),
2829 ('u', 'user', None, _('print user who committed change')),
2953 ] + walkopts,
2830 ] + walkopts,
2954 _('hg grep [OPTION]... PATTERN [FILE]...')),
2831 _('hg grep [OPTION]... PATTERN [FILE]...')),
2955 "heads":
2832 "heads":
2956 (heads,
2833 (heads,
2957 [('b', 'branches', None, _('show branches (DEPRECATED)')),
2834 [('b', 'branches', None, _('show branches (DEPRECATED)')),
2958 ('', 'style', '', _('display using template map file')),
2835 ('', 'style', '', _('display using template map file')),
2959 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2836 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2960 ('', 'template', '', _('display with template'))],
2837 ('', 'template', '', _('display with template'))],
2961 _('hg heads [-r REV]')),
2838 _('hg heads [-r REV]')),
2962 "help": (help_, [], _('hg help [COMMAND]')),
2839 "help": (help_, [], _('hg help [COMMAND]')),
2963 "identify|id": (identify, [], _('hg identify')),
2840 "identify|id": (identify, [], _('hg identify')),
2964 "import|patch":
2841 "import|patch":
2965 (import_,
2842 (import_,
2966 [('p', 'strip', 1,
2843 [('p', 'strip', 1,
2967 _('directory strip option for patch. This has the same\n'
2844 _('directory strip option for patch. This has the same\n'
2968 'meaning as the corresponding patch option')),
2845 'meaning as the corresponding patch option')),
2969 ('m', 'message', '', _('use <text> as commit message')),
2846 ('m', 'message', '', _('use <text> as commit message')),
2970 ('b', 'base', '', _('base path (DEPRECATED)')),
2847 ('b', 'base', '', _('base path (DEPRECATED)')),
2971 ('f', 'force', None,
2848 ('f', 'force', None,
2972 _('skip check for outstanding uncommitted changes'))],
2849 _('skip check for outstanding uncommitted changes'))],
2973 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2850 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2974 "incoming|in": (incoming,
2851 "incoming|in": (incoming,
2975 [('M', 'no-merges', None, _('do not show merges')),
2852 [('M', 'no-merges', None, _('do not show merges')),
2976 ('f', 'force', None,
2853 ('f', 'force', None,
2977 _('run even when remote repository is unrelated')),
2854 _('run even when remote repository is unrelated')),
2978 ('', 'style', '', _('display using template map file')),
2855 ('', 'style', '', _('display using template map file')),
2979 ('n', 'newest-first', None, _('show newest record first')),
2856 ('n', 'newest-first', None, _('show newest record first')),
2980 ('', 'bundle', '', _('file to store the bundles into')),
2857 ('', 'bundle', '', _('file to store the bundles into')),
2981 ('p', 'patch', None, _('show patch')),
2858 ('p', 'patch', None, _('show patch')),
2982 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2859 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2983 ('', 'template', '', _('display with template')),
2860 ('', 'template', '', _('display with template')),
2984 ] + remoteopts,
2861 ] + remoteopts,
2985 _('hg incoming [-p] [-n] [-M] [-r REV]...'
2862 _('hg incoming [-p] [-n] [-M] [-r REV]...'
2986 ' [--bundle FILENAME] [SOURCE]')),
2863 ' [--bundle FILENAME] [SOURCE]')),
2987 "^init":
2864 "^init":
2988 (init, remoteopts, _('hg init [-e FILE] [--remotecmd FILE] [DEST]')),
2865 (init, remoteopts, _('hg init [-e FILE] [--remotecmd FILE] [DEST]')),
2989 "locate":
2866 "locate":
2990 (locate,
2867 (locate,
2991 [('r', 'rev', '', _('search the repository as it stood at rev')),
2868 [('r', 'rev', '', _('search the repository as it stood at rev')),
2992 ('0', 'print0', None,
2869 ('0', 'print0', None,
2993 _('end filenames with NUL, for use with xargs')),
2870 _('end filenames with NUL, for use with xargs')),
2994 ('f', 'fullpath', None,
2871 ('f', 'fullpath', None,
2995 _('print complete paths from the filesystem root')),
2872 _('print complete paths from the filesystem root')),
2996 ] + walkopts,
2873 ] + walkopts,
2997 _('hg locate [OPTION]... [PATTERN]...')),
2874 _('hg locate [OPTION]... [PATTERN]...')),
2998 "^log|history":
2875 "^log|history":
2999 (log,
2876 (log,
3000 [('b', 'branches', None, _('show branches (DEPRECATED)')),
2877 [('b', 'branches', None, _('show branches (DEPRECATED)')),
3001 ('f', 'follow', None,
2878 ('f', 'follow', None,
3002 _('follow changeset history, or file history across copies and renames')),
2879 _('follow changeset history, or file history across copies and renames')),
3003 ('', 'follow-first', None,
2880 ('', 'follow-first', None,
3004 _('only follow the first parent of merge changesets')),
2881 _('only follow the first parent of merge changesets')),
3005 ('C', 'copies', None, _('show copied files')),
2882 ('C', 'copies', None, _('show copied files')),
3006 ('k', 'keyword', [], _('search for a keyword')),
2883 ('k', 'keyword', [], _('search for a keyword')),
3007 ('l', 'limit', '', _('limit number of changes displayed')),
2884 ('l', 'limit', '', _('limit number of changes displayed')),
3008 ('r', 'rev', [], _('show the specified revision or range')),
2885 ('r', 'rev', [], _('show the specified revision or range')),
3009 ('M', 'no-merges', None, _('do not show merges')),
2886 ('M', 'no-merges', None, _('do not show merges')),
3010 ('', 'style', '', _('display using template map file')),
2887 ('', 'style', '', _('display using template map file')),
3011 ('m', 'only-merges', None, _('show only merges')),
2888 ('m', 'only-merges', None, _('show only merges')),
3012 ('p', 'patch', None, _('show patch')),
2889 ('p', 'patch', None, _('show patch')),
3013 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
2890 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3014 ('', 'template', '', _('display with template')),
2891 ('', 'template', '', _('display with template')),
3015 ] + walkopts,
2892 ] + walkopts,
3016 _('hg log [OPTION]... [FILE]')),
2893 _('hg log [OPTION]... [FILE]')),
3017 "manifest": (manifest, [], _('hg manifest [REV]')),
2894 "manifest": (manifest, [], _('hg manifest [REV]')),
3018 "merge":
2895 "merge":
3019 (merge,
2896 (merge,
3020 [('b', 'branch', '', _('merge with head of a specific branch (DEPRECATED)')),
2897 [('b', 'branch', '', _('merge with head of a specific branch (DEPRECATED)')),
3021 ('f', 'force', None, _('force a merge with outstanding changes'))],
2898 ('f', 'force', None, _('force a merge with outstanding changes'))],
3022 _('hg merge [-f] [REV]')),
2899 _('hg merge [-f] [REV]')),
3023 "outgoing|out": (outgoing,
2900 "outgoing|out": (outgoing,
3024 [('M', 'no-merges', None, _('do not show merges')),
2901 [('M', 'no-merges', None, _('do not show merges')),
3025 ('f', 'force', None,
2902 ('f', 'force', None,
3026 _('run even when remote repository is unrelated')),
2903 _('run even when remote repository is unrelated')),
3027 ('p', 'patch', None, _('show patch')),
2904 ('p', 'patch', None, _('show patch')),
3028 ('', 'style', '', _('display using template map file')),
2905 ('', 'style', '', _('display using template map file')),
3029 ('r', 'rev', [], _('a specific revision you would like to push')),
2906 ('r', 'rev', [], _('a specific revision you would like to push')),
3030 ('n', 'newest-first', None, _('show newest record first')),
2907 ('n', 'newest-first', None, _('show newest record first')),
3031 ('', 'template', '', _('display with template')),
2908 ('', 'template', '', _('display with template')),
3032 ] + remoteopts,
2909 ] + remoteopts,
3033 _('hg outgoing [-M] [-p] [-n] [-r REV]... [DEST]')),
2910 _('hg outgoing [-M] [-p] [-n] [-r REV]... [DEST]')),
3034 "^parents":
2911 "^parents":
3035 (parents,
2912 (parents,
3036 [('b', 'branches', None, _('show branches (DEPRECATED)')),
2913 [('b', 'branches', None, _('show branches (DEPRECATED)')),
3037 ('r', 'rev', '', _('show parents from the specified rev')),
2914 ('r', 'rev', '', _('show parents from the specified rev')),
3038 ('', 'style', '', _('display using template map file')),
2915 ('', 'style', '', _('display using template map file')),
3039 ('', 'template', '', _('display with template'))],
2916 ('', 'template', '', _('display with template'))],
3040 _('hg parents [-r REV] [FILE]')),
2917 _('hg parents [-r REV] [FILE]')),
3041 "paths": (paths, [], _('hg paths [NAME]')),
2918 "paths": (paths, [], _('hg paths [NAME]')),
3042 "^pull":
2919 "^pull":
3043 (pull,
2920 (pull,
3044 [('u', 'update', None,
2921 [('u', 'update', None,
3045 _('update to new tip if changesets were pulled')),
2922 _('update to new tip if changesets were pulled')),
3046 ('f', 'force', None,
2923 ('f', 'force', None,
3047 _('run even when remote repository is unrelated')),
2924 _('run even when remote repository is unrelated')),
3048 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2925 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
3049 ] + remoteopts,
2926 ] + remoteopts,
3050 _('hg pull [-u] [-r REV]... [-e FILE] [--remotecmd FILE] [SOURCE]')),
2927 _('hg pull [-u] [-r REV]... [-e FILE] [--remotecmd FILE] [SOURCE]')),
3051 "^push":
2928 "^push":
3052 (push,
2929 (push,
3053 [('f', 'force', None, _('force push')),
2930 [('f', 'force', None, _('force push')),
3054 ('r', 'rev', [], _('a specific revision you would like to push')),
2931 ('r', 'rev', [], _('a specific revision you would like to push')),
3055 ] + remoteopts,
2932 ] + remoteopts,
3056 _('hg push [-f] [-r REV]... [-e FILE] [--remotecmd FILE] [DEST]')),
2933 _('hg push [-f] [-r REV]... [-e FILE] [--remotecmd FILE] [DEST]')),
3057 "debugrawcommit|rawcommit":
2934 "debugrawcommit|rawcommit":
3058 (rawcommit,
2935 (rawcommit,
3059 [('p', 'parent', [], _('parent')),
2936 [('p', 'parent', [], _('parent')),
3060 ('d', 'date', '', _('date code')),
2937 ('d', 'date', '', _('date code')),
3061 ('u', 'user', '', _('user')),
2938 ('u', 'user', '', _('user')),
3062 ('F', 'files', '', _('file list')),
2939 ('F', 'files', '', _('file list')),
3063 ('m', 'message', '', _('commit message')),
2940 ('m', 'message', '', _('commit message')),
3064 ('l', 'logfile', '', _('commit message file'))],
2941 ('l', 'logfile', '', _('commit message file'))],
3065 _('hg debugrawcommit [OPTION]... [FILE]...')),
2942 _('hg debugrawcommit [OPTION]... [FILE]...')),
3066 "recover": (recover, [], _('hg recover')),
2943 "recover": (recover, [], _('hg recover')),
3067 "^remove|rm":
2944 "^remove|rm":
3068 (remove,
2945 (remove,
3069 [('A', 'after', None, _('record remove that has already occurred')),
2946 [('A', 'after', None, _('record remove that has already occurred')),
3070 ('f', 'force', None, _('remove file even if modified')),
2947 ('f', 'force', None, _('remove file even if modified')),
3071 ] + walkopts,
2948 ] + walkopts,
3072 _('hg remove [OPTION]... FILE...')),
2949 _('hg remove [OPTION]... FILE...')),
3073 "rename|mv":
2950 "rename|mv":
3074 (rename,
2951 (rename,
3075 [('A', 'after', None, _('record a rename that has already occurred')),
2952 [('A', 'after', None, _('record a rename that has already occurred')),
3076 ('f', 'force', None,
2953 ('f', 'force', None,
3077 _('forcibly copy over an existing managed file')),
2954 _('forcibly copy over an existing managed file')),
3078 ] + walkopts + dryrunopts,
2955 ] + walkopts + dryrunopts,
3079 _('hg rename [OPTION]... SOURCE... DEST')),
2956 _('hg rename [OPTION]... SOURCE... DEST')),
3080 "^revert":
2957 "^revert":
3081 (revert,
2958 (revert,
3082 [('a', 'all', None, _('revert all changes when no arguments given')),
2959 [('a', 'all', None, _('revert all changes when no arguments given')),
3083 ('r', 'rev', '', _('revision to revert to')),
2960 ('r', 'rev', '', _('revision to revert to')),
3084 ('', 'no-backup', None, _('do not save backup copies of files')),
2961 ('', 'no-backup', None, _('do not save backup copies of files')),
3085 ] + walkopts + dryrunopts,
2962 ] + walkopts + dryrunopts,
3086 _('hg revert [-r REV] [NAME]...')),
2963 _('hg revert [-r REV] [NAME]...')),
3087 "rollback": (rollback, [], _('hg rollback')),
2964 "rollback": (rollback, [], _('hg rollback')),
3088 "root": (root, [], _('hg root')),
2965 "root": (root, [], _('hg root')),
3089 "showconfig|debugconfig":
2966 "showconfig|debugconfig":
3090 (showconfig,
2967 (showconfig,
3091 [('u', 'untrusted', None, _('show untrusted configuration options'))],
2968 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3092 _('showconfig [-u] [NAME]...')),
2969 _('showconfig [-u] [NAME]...')),
3093 "^serve":
2970 "^serve":
3094 (serve,
2971 (serve,
3095 [('A', 'accesslog', '', _('name of access log file to write to')),
2972 [('A', 'accesslog', '', _('name of access log file to write to')),
3096 ('d', 'daemon', None, _('run server in background')),
2973 ('d', 'daemon', None, _('run server in background')),
3097 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
2974 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3098 ('E', 'errorlog', '', _('name of error log file to write to')),
2975 ('E', 'errorlog', '', _('name of error log file to write to')),
3099 ('p', 'port', 0, _('port to use (default: 8000)')),
2976 ('p', 'port', 0, _('port to use (default: 8000)')),
3100 ('a', 'address', '', _('address to use')),
2977 ('a', 'address', '', _('address to use')),
3101 ('n', 'name', '',
2978 ('n', 'name', '',
3102 _('name to show in web pages (default: working dir)')),
2979 _('name to show in web pages (default: working dir)')),
3103 ('', 'webdir-conf', '', _('name of the webdir config file'
2980 ('', 'webdir-conf', '', _('name of the webdir config file'
3104 ' (serve more than one repo)')),
2981 ' (serve more than one repo)')),
3105 ('', 'pid-file', '', _('name of file to write process ID to')),
2982 ('', 'pid-file', '', _('name of file to write process ID to')),
3106 ('', 'stdio', None, _('for remote clients')),
2983 ('', 'stdio', None, _('for remote clients')),
3107 ('t', 'templates', '', _('web templates to use')),
2984 ('t', 'templates', '', _('web templates to use')),
3108 ('', 'style', '', _('template style to use')),
2985 ('', 'style', '', _('template style to use')),
3109 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
2986 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3110 _('hg serve [OPTION]...')),
2987 _('hg serve [OPTION]...')),
3111 "^status|st":
2988 "^status|st":
3112 (status,
2989 (status,
3113 [('A', 'all', None, _('show status of all files')),
2990 [('A', 'all', None, _('show status of all files')),
3114 ('m', 'modified', None, _('show only modified files')),
2991 ('m', 'modified', None, _('show only modified files')),
3115 ('a', 'added', None, _('show only added files')),
2992 ('a', 'added', None, _('show only added files')),
3116 ('r', 'removed', None, _('show only removed files')),
2993 ('r', 'removed', None, _('show only removed files')),
3117 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
2994 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3118 ('c', 'clean', None, _('show only files without changes')),
2995 ('c', 'clean', None, _('show only files without changes')),
3119 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
2996 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3120 ('i', 'ignored', None, _('show ignored files')),
2997 ('i', 'ignored', None, _('show ignored files')),
3121 ('n', 'no-status', None, _('hide status prefix')),
2998 ('n', 'no-status', None, _('hide status prefix')),
3122 ('C', 'copies', None, _('show source of copied files')),
2999 ('C', 'copies', None, _('show source of copied files')),
3123 ('0', 'print0', None,
3000 ('0', 'print0', None,
3124 _('end filenames with NUL, for use with xargs')),
3001 _('end filenames with NUL, for use with xargs')),
3125 ('', 'rev', [], _('show difference from revision')),
3002 ('', 'rev', [], _('show difference from revision')),
3126 ] + walkopts,
3003 ] + walkopts,
3127 _('hg status [OPTION]... [FILE]...')),
3004 _('hg status [OPTION]... [FILE]...')),
3128 "tag":
3005 "tag":
3129 (tag,
3006 (tag,
3130 [('l', 'local', None, _('make the tag local')),
3007 [('l', 'local', None, _('make the tag local')),
3131 ('m', 'message', '', _('message for tag commit log entry')),
3008 ('m', 'message', '', _('message for tag commit log entry')),
3132 ('d', 'date', '', _('record datecode as commit date')),
3009 ('d', 'date', '', _('record datecode as commit date')),
3133 ('u', 'user', '', _('record user as commiter')),
3010 ('u', 'user', '', _('record user as commiter')),
3134 ('r', 'rev', '', _('revision to tag'))],
3011 ('r', 'rev', '', _('revision to tag'))],
3135 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3012 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3136 "tags": (tags, [], _('hg tags')),
3013 "tags": (tags, [], _('hg tags')),
3137 "tip":
3014 "tip":
3138 (tip,
3015 (tip,
3139 [('b', 'branches', None, _('show branches (DEPRECATED)')),
3016 [('b', 'branches', None, _('show branches (DEPRECATED)')),
3140 ('', 'style', '', _('display using template map file')),
3017 ('', 'style', '', _('display using template map file')),
3141 ('p', 'patch', None, _('show patch')),
3018 ('p', 'patch', None, _('show patch')),
3142 ('', 'template', '', _('display with template'))],
3019 ('', 'template', '', _('display with template'))],
3143 _('hg tip [-p]')),
3020 _('hg tip [-p]')),
3144 "unbundle":
3021 "unbundle":
3145 (unbundle,
3022 (unbundle,
3146 [('u', 'update', None,
3023 [('u', 'update', None,
3147 _('update to new tip if changesets were unbundled'))],
3024 _('update to new tip if changesets were unbundled'))],
3148 _('hg unbundle [-u] FILE')),
3025 _('hg unbundle [-u] FILE')),
3149 "^update|up|checkout|co":
3026 "^update|up|checkout|co":
3150 (update,
3027 (update,
3151 [('b', 'branch', '',
3028 [('b', 'branch', '',
3152 _('checkout the head of a specific branch (DEPRECATED)')),
3029 _('checkout the head of a specific branch (DEPRECATED)')),
3153 ('m', 'merge', None, _('allow merging of branches (DEPRECATED)')),
3030 ('m', 'merge', None, _('allow merging of branches (DEPRECATED)')),
3154 ('C', 'clean', None, _('overwrite locally modified files')),
3031 ('C', 'clean', None, _('overwrite locally modified files')),
3155 ('f', 'force', None, _('force a merge with outstanding changes'))],
3032 ('f', 'force', None, _('force a merge with outstanding changes'))],
3156 _('hg update [-C] [-f] [REV]')),
3033 _('hg update [-C] [-f] [REV]')),
3157 "verify": (verify, [], _('hg verify')),
3034 "verify": (verify, [], _('hg verify')),
3158 "version": (show_version, [], _('hg version')),
3035 "version": (show_version, [], _('hg version')),
3159 }
3036 }
3160
3037
3161 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3038 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3162 " debugindex debugindexdot")
3039 " debugindex debugindexdot")
3163 optionalrepo = ("paths serve showconfig")
3040 optionalrepo = ("paths serve showconfig")
3164
3041
3165 def findpossible(ui, cmd):
3042 def findpossible(ui, cmd):
3166 """
3043 """
3167 Return cmd -> (aliases, command table entry)
3044 Return cmd -> (aliases, command table entry)
3168 for each matching command.
3045 for each matching command.
3169 Return debug commands (or their aliases) only if no normal command matches.
3046 Return debug commands (or their aliases) only if no normal command matches.
3170 """
3047 """
3171 choice = {}
3048 choice = {}
3172 debugchoice = {}
3049 debugchoice = {}
3173 for e in table.keys():
3050 for e in table.keys():
3174 aliases = e.lstrip("^").split("|")
3051 aliases = e.lstrip("^").split("|")
3175 found = None
3052 found = None
3176 if cmd in aliases:
3053 if cmd in aliases:
3177 found = cmd
3054 found = cmd
3178 elif not ui.config("ui", "strict"):
3055 elif not ui.config("ui", "strict"):
3179 for a in aliases:
3056 for a in aliases:
3180 if a.startswith(cmd):
3057 if a.startswith(cmd):
3181 found = a
3058 found = a
3182 break
3059 break
3183 if found is not None:
3060 if found is not None:
3184 if aliases[0].startswith("debug") or found.startswith("debug"):
3061 if aliases[0].startswith("debug") or found.startswith("debug"):
3185 debugchoice[found] = (aliases, table[e])
3062 debugchoice[found] = (aliases, table[e])
3186 else:
3063 else:
3187 choice[found] = (aliases, table[e])
3064 choice[found] = (aliases, table[e])
3188
3065
3189 if not choice and debugchoice:
3066 if not choice and debugchoice:
3190 choice = debugchoice
3067 choice = debugchoice
3191
3068
3192 return choice
3069 return choice
3193
3070
3194 def findcmd(ui, cmd):
3071 def findcmd(ui, cmd):
3195 """Return (aliases, command table entry) for command string."""
3072 """Return (aliases, command table entry) for command string."""
3196 choice = findpossible(ui, cmd)
3073 choice = findpossible(ui, cmd)
3197
3074
3198 if choice.has_key(cmd):
3075 if choice.has_key(cmd):
3199 return choice[cmd]
3076 return choice[cmd]
3200
3077
3201 if len(choice) > 1:
3078 if len(choice) > 1:
3202 clist = choice.keys()
3079 clist = choice.keys()
3203 clist.sort()
3080 clist.sort()
3204 raise AmbiguousCommand(cmd, clist)
3081 raise AmbiguousCommand(cmd, clist)
3205
3082
3206 if choice:
3083 if choice:
3207 return choice.values()[0]
3084 return choice.values()[0]
3208
3085
3209 raise UnknownCommand(cmd)
3086 raise UnknownCommand(cmd)
3210
3087
3211 def catchterm(*args):
3088 def catchterm(*args):
3212 raise util.SignalInterrupt
3089 raise util.SignalInterrupt
3213
3090
3214 def run():
3091 def run():
3215 sys.exit(dispatch(sys.argv[1:]))
3092 sys.exit(dispatch(sys.argv[1:]))
3216
3093
3217 class ParseError(Exception):
3094 class ParseError(Exception):
3218 """Exception raised on errors in parsing the command line."""
3095 """Exception raised on errors in parsing the command line."""
3219
3096
3220 def parse(ui, args):
3097 def parse(ui, args):
3221 options = {}
3098 options = {}
3222 cmdoptions = {}
3099 cmdoptions = {}
3223
3100
3224 try:
3101 try:
3225 args = fancyopts.fancyopts(args, globalopts, options)
3102 args = fancyopts.fancyopts(args, globalopts, options)
3226 except fancyopts.getopt.GetoptError, inst:
3103 except fancyopts.getopt.GetoptError, inst:
3227 raise ParseError(None, inst)
3104 raise ParseError(None, inst)
3228
3105
3229 if args:
3106 if args:
3230 cmd, args = args[0], args[1:]
3107 cmd, args = args[0], args[1:]
3231 aliases, i = findcmd(ui, cmd)
3108 aliases, i = findcmd(ui, cmd)
3232 cmd = aliases[0]
3109 cmd = aliases[0]
3233 defaults = ui.config("defaults", cmd)
3110 defaults = ui.config("defaults", cmd)
3234 if defaults:
3111 if defaults:
3235 args = shlex.split(defaults) + args
3112 args = shlex.split(defaults) + args
3236 c = list(i[1])
3113 c = list(i[1])
3237 else:
3114 else:
3238 cmd = None
3115 cmd = None
3239 c = []
3116 c = []
3240
3117
3241 # combine global options into local
3118 # combine global options into local
3242 for o in globalopts:
3119 for o in globalopts:
3243 c.append((o[0], o[1], options[o[1]], o[3]))
3120 c.append((o[0], o[1], options[o[1]], o[3]))
3244
3121
3245 try:
3122 try:
3246 args = fancyopts.fancyopts(args, c, cmdoptions)
3123 args = fancyopts.fancyopts(args, c, cmdoptions)
3247 except fancyopts.getopt.GetoptError, inst:
3124 except fancyopts.getopt.GetoptError, inst:
3248 raise ParseError(cmd, inst)
3125 raise ParseError(cmd, inst)
3249
3126
3250 # separate global options back out
3127 # separate global options back out
3251 for o in globalopts:
3128 for o in globalopts:
3252 n = o[1]
3129 n = o[1]
3253 options[n] = cmdoptions[n]
3130 options[n] = cmdoptions[n]
3254 del cmdoptions[n]
3131 del cmdoptions[n]
3255
3132
3256 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3133 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3257
3134
3258 external = {}
3135 external = {}
3259
3136
3260 def findext(name):
3137 def findext(name):
3261 '''return module with given extension name'''
3138 '''return module with given extension name'''
3262 try:
3139 try:
3263 return sys.modules[external[name]]
3140 return sys.modules[external[name]]
3264 except KeyError:
3141 except KeyError:
3265 for k, v in external.iteritems():
3142 for k, v in external.iteritems():
3266 if k.endswith('.' + name) or k.endswith('/' + name) or v == name:
3143 if k.endswith('.' + name) or k.endswith('/' + name) or v == name:
3267 return sys.modules[v]
3144 return sys.modules[v]
3268 raise KeyError(name)
3145 raise KeyError(name)
3269
3146
3270 def load_extensions(ui):
3147 def load_extensions(ui):
3271 added = []
3148 added = []
3272 for ext_name, load_from_name in ui.extensions():
3149 for ext_name, load_from_name in ui.extensions():
3273 if ext_name in external:
3150 if ext_name in external:
3274 continue
3151 continue
3275 try:
3152 try:
3276 if load_from_name:
3153 if load_from_name:
3277 # the module will be loaded in sys.modules
3154 # the module will be loaded in sys.modules
3278 # choose an unique name so that it doesn't
3155 # choose an unique name so that it doesn't
3279 # conflicts with other modules
3156 # conflicts with other modules
3280 module_name = "hgext_%s" % ext_name.replace('.', '_')
3157 module_name = "hgext_%s" % ext_name.replace('.', '_')
3281 mod = imp.load_source(module_name, load_from_name)
3158 mod = imp.load_source(module_name, load_from_name)
3282 else:
3159 else:
3283 def importh(name):
3160 def importh(name):
3284 mod = __import__(name)
3161 mod = __import__(name)
3285 components = name.split('.')
3162 components = name.split('.')
3286 for comp in components[1:]:
3163 for comp in components[1:]:
3287 mod = getattr(mod, comp)
3164 mod = getattr(mod, comp)
3288 return mod
3165 return mod
3289 try:
3166 try:
3290 mod = importh("hgext.%s" % ext_name)
3167 mod = importh("hgext.%s" % ext_name)
3291 except ImportError:
3168 except ImportError:
3292 mod = importh(ext_name)
3169 mod = importh(ext_name)
3293 external[ext_name] = mod.__name__
3170 external[ext_name] = mod.__name__
3294 added.append((mod, ext_name))
3171 added.append((mod, ext_name))
3295 except (util.SignalInterrupt, KeyboardInterrupt):
3172 except (util.SignalInterrupt, KeyboardInterrupt):
3296 raise
3173 raise
3297 except Exception, inst:
3174 except Exception, inst:
3298 ui.warn(_("*** failed to import extension %s: %s\n") %
3175 ui.warn(_("*** failed to import extension %s: %s\n") %
3299 (ext_name, inst))
3176 (ext_name, inst))
3300 if ui.print_exc():
3177 if ui.print_exc():
3301 return 1
3178 return 1
3302
3179
3303 for mod, name in added:
3180 for mod, name in added:
3304 uisetup = getattr(mod, 'uisetup', None)
3181 uisetup = getattr(mod, 'uisetup', None)
3305 if uisetup:
3182 if uisetup:
3306 uisetup(ui)
3183 uisetup(ui)
3307 cmdtable = getattr(mod, 'cmdtable', {})
3184 cmdtable = getattr(mod, 'cmdtable', {})
3308 for t in cmdtable:
3185 for t in cmdtable:
3309 if t in table:
3186 if t in table:
3310 ui.warn(_("module %s overrides %s\n") % (name, t))
3187 ui.warn(_("module %s overrides %s\n") % (name, t))
3311 table.update(cmdtable)
3188 table.update(cmdtable)
3312
3189
3313 def parseconfig(config):
3190 def parseconfig(config):
3314 """parse the --config options from the command line"""
3191 """parse the --config options from the command line"""
3315 parsed = []
3192 parsed = []
3316 for cfg in config:
3193 for cfg in config:
3317 try:
3194 try:
3318 name, value = cfg.split('=', 1)
3195 name, value = cfg.split('=', 1)
3319 section, name = name.split('.', 1)
3196 section, name = name.split('.', 1)
3320 if not section or not name:
3197 if not section or not name:
3321 raise IndexError
3198 raise IndexError
3322 parsed.append((section, name, value))
3199 parsed.append((section, name, value))
3323 except (IndexError, ValueError):
3200 except (IndexError, ValueError):
3324 raise util.Abort(_('malformed --config option: %s') % cfg)
3201 raise util.Abort(_('malformed --config option: %s') % cfg)
3325 return parsed
3202 return parsed
3326
3203
3327 def dispatch(args):
3204 def dispatch(args):
3328 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3205 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3329 num = getattr(signal, name, None)
3206 num = getattr(signal, name, None)
3330 if num: signal.signal(num, catchterm)
3207 if num: signal.signal(num, catchterm)
3331
3208
3332 try:
3209 try:
3333 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3210 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3334 except util.Abort, inst:
3211 except util.Abort, inst:
3335 sys.stderr.write(_("abort: %s\n") % inst)
3212 sys.stderr.write(_("abort: %s\n") % inst)
3336 return -1
3213 return -1
3337
3214
3338 load_extensions(u)
3215 load_extensions(u)
3339 u.addreadhook(load_extensions)
3216 u.addreadhook(load_extensions)
3340
3217
3341 try:
3218 try:
3342 cmd, func, args, options, cmdoptions = parse(u, args)
3219 cmd, func, args, options, cmdoptions = parse(u, args)
3343 if options["time"]:
3220 if options["time"]:
3344 def get_times():
3221 def get_times():
3345 t = os.times()
3222 t = os.times()
3346 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3223 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3347 t = (t[0], t[1], t[2], t[3], time.clock())
3224 t = (t[0], t[1], t[2], t[3], time.clock())
3348 return t
3225 return t
3349 s = get_times()
3226 s = get_times()
3350 def print_time():
3227 def print_time():
3351 t = get_times()
3228 t = get_times()
3352 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3229 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3353 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3230 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3354 atexit.register(print_time)
3231 atexit.register(print_time)
3355
3232
3356 # enter the debugger before command execution
3233 # enter the debugger before command execution
3357 if options['debugger']:
3234 if options['debugger']:
3358 pdb.set_trace()
3235 pdb.set_trace()
3359
3236
3360 try:
3237 try:
3361 if options['cwd']:
3238 if options['cwd']:
3362 try:
3239 try:
3363 os.chdir(options['cwd'])
3240 os.chdir(options['cwd'])
3364 except OSError, inst:
3241 except OSError, inst:
3365 raise util.Abort('%s: %s' %
3242 raise util.Abort('%s: %s' %
3366 (options['cwd'], inst.strerror))
3243 (options['cwd'], inst.strerror))
3367
3244
3368 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3245 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3369 not options["noninteractive"], options["traceback"],
3246 not options["noninteractive"], options["traceback"],
3370 parseconfig(options["config"]))
3247 parseconfig(options["config"]))
3371
3248
3372 path = u.expandpath(options["repository"]) or ""
3249 path = u.expandpath(options["repository"]) or ""
3373 repo = path and hg.repository(u, path=path) or None
3250 repo = path and hg.repository(u, path=path) or None
3374 if repo and not repo.local():
3251 if repo and not repo.local():
3375 raise util.Abort(_("repository '%s' is not local") % path)
3252 raise util.Abort(_("repository '%s' is not local") % path)
3376
3253
3377 if options['help']:
3254 if options['help']:
3378 return help_(u, cmd, options['version'])
3255 return help_(u, cmd, options['version'])
3379 elif options['version']:
3256 elif options['version']:
3380 return show_version(u)
3257 return show_version(u)
3381 elif not cmd:
3258 elif not cmd:
3382 return help_(u, 'shortlist')
3259 return help_(u, 'shortlist')
3383
3260
3384 if cmd not in norepo.split():
3261 if cmd not in norepo.split():
3385 try:
3262 try:
3386 if not repo:
3263 if not repo:
3387 repo = hg.repository(u, path=path)
3264 repo = hg.repository(u, path=path)
3388 u = repo.ui
3265 u = repo.ui
3389 for name in external.itervalues():
3266 for name in external.itervalues():
3390 mod = sys.modules[name]
3267 mod = sys.modules[name]
3391 if hasattr(mod, 'reposetup'):
3268 if hasattr(mod, 'reposetup'):
3392 mod.reposetup(u, repo)
3269 mod.reposetup(u, repo)
3393 hg.repo_setup_hooks.append(mod.reposetup)
3270 hg.repo_setup_hooks.append(mod.reposetup)
3394 except hg.RepoError:
3271 except hg.RepoError:
3395 if cmd not in optionalrepo.split():
3272 if cmd not in optionalrepo.split():
3396 raise
3273 raise
3397 d = lambda: func(u, repo, *args, **cmdoptions)
3274 d = lambda: func(u, repo, *args, **cmdoptions)
3398 else:
3275 else:
3399 d = lambda: func(u, *args, **cmdoptions)
3276 d = lambda: func(u, *args, **cmdoptions)
3400
3277
3401 try:
3278 try:
3402 if options['profile']:
3279 if options['profile']:
3403 import hotshot, hotshot.stats
3280 import hotshot, hotshot.stats
3404 prof = hotshot.Profile("hg.prof")
3281 prof = hotshot.Profile("hg.prof")
3405 try:
3282 try:
3406 try:
3283 try:
3407 return prof.runcall(d)
3284 return prof.runcall(d)
3408 except:
3285 except:
3409 try:
3286 try:
3410 u.warn(_('exception raised - generating '
3287 u.warn(_('exception raised - generating '
3411 'profile anyway\n'))
3288 'profile anyway\n'))
3412 except:
3289 except:
3413 pass
3290 pass
3414 raise
3291 raise
3415 finally:
3292 finally:
3416 prof.close()
3293 prof.close()
3417 stats = hotshot.stats.load("hg.prof")
3294 stats = hotshot.stats.load("hg.prof")
3418 stats.strip_dirs()
3295 stats.strip_dirs()
3419 stats.sort_stats('time', 'calls')
3296 stats.sort_stats('time', 'calls')
3420 stats.print_stats(40)
3297 stats.print_stats(40)
3421 elif options['lsprof']:
3298 elif options['lsprof']:
3422 try:
3299 try:
3423 from mercurial import lsprof
3300 from mercurial import lsprof
3424 except ImportError:
3301 except ImportError:
3425 raise util.Abort(_(
3302 raise util.Abort(_(
3426 'lsprof not available - install from '
3303 'lsprof not available - install from '
3427 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
3304 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
3428 p = lsprof.Profiler()
3305 p = lsprof.Profiler()
3429 p.enable(subcalls=True)
3306 p.enable(subcalls=True)
3430 try:
3307 try:
3431 return d()
3308 return d()
3432 finally:
3309 finally:
3433 p.disable()
3310 p.disable()
3434 stats = lsprof.Stats(p.getstats())
3311 stats = lsprof.Stats(p.getstats())
3435 stats.sort()
3312 stats.sort()
3436 stats.pprint(top=10, file=sys.stderr, climit=5)
3313 stats.pprint(top=10, file=sys.stderr, climit=5)
3437 else:
3314 else:
3438 return d()
3315 return d()
3439 finally:
3316 finally:
3440 u.flush()
3317 u.flush()
3441 except:
3318 except:
3442 # enter the debugger when we hit an exception
3319 # enter the debugger when we hit an exception
3443 if options['debugger']:
3320 if options['debugger']:
3444 pdb.post_mortem(sys.exc_info()[2])
3321 pdb.post_mortem(sys.exc_info()[2])
3445 u.print_exc()
3322 u.print_exc()
3446 raise
3323 raise
3447 except ParseError, inst:
3324 except ParseError, inst:
3448 if inst.args[0]:
3325 if inst.args[0]:
3449 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3326 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3450 help_(u, inst.args[0])
3327 help_(u, inst.args[0])
3451 else:
3328 else:
3452 u.warn(_("hg: %s\n") % inst.args[1])
3329 u.warn(_("hg: %s\n") % inst.args[1])
3453 help_(u, 'shortlist')
3330 help_(u, 'shortlist')
3454 except AmbiguousCommand, inst:
3331 except AmbiguousCommand, inst:
3455 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3332 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3456 (inst.args[0], " ".join(inst.args[1])))
3333 (inst.args[0], " ".join(inst.args[1])))
3457 except UnknownCommand, inst:
3334 except UnknownCommand, inst:
3458 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3335 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3459 help_(u, 'shortlist')
3336 help_(u, 'shortlist')
3460 except hg.RepoError, inst:
3337 except hg.RepoError, inst:
3461 u.warn(_("abort: %s!\n") % inst)
3338 u.warn(_("abort: %s!\n") % inst)
3462 except lock.LockHeld, inst:
3339 except lock.LockHeld, inst:
3463 if inst.errno == errno.ETIMEDOUT:
3340 if inst.errno == errno.ETIMEDOUT:
3464 reason = _('timed out waiting for lock held by %s') % inst.locker
3341 reason = _('timed out waiting for lock held by %s') % inst.locker
3465 else:
3342 else:
3466 reason = _('lock held by %s') % inst.locker
3343 reason = _('lock held by %s') % inst.locker
3467 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3344 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3468 except lock.LockUnavailable, inst:
3345 except lock.LockUnavailable, inst:
3469 u.warn(_("abort: could not lock %s: %s\n") %
3346 u.warn(_("abort: could not lock %s: %s\n") %
3470 (inst.desc or inst.filename, inst.strerror))
3347 (inst.desc or inst.filename, inst.strerror))
3471 except revlog.RevlogError, inst:
3348 except revlog.RevlogError, inst:
3472 u.warn(_("abort: %s!\n") % inst)
3349 u.warn(_("abort: %s!\n") % inst)
3473 except util.SignalInterrupt:
3350 except util.SignalInterrupt:
3474 u.warn(_("killed!\n"))
3351 u.warn(_("killed!\n"))
3475 except KeyboardInterrupt:
3352 except KeyboardInterrupt:
3476 try:
3353 try:
3477 u.warn(_("interrupted!\n"))
3354 u.warn(_("interrupted!\n"))
3478 except IOError, inst:
3355 except IOError, inst:
3479 if inst.errno == errno.EPIPE:
3356 if inst.errno == errno.EPIPE:
3480 if u.debugflag:
3357 if u.debugflag:
3481 u.warn(_("\nbroken pipe\n"))
3358 u.warn(_("\nbroken pipe\n"))
3482 else:
3359 else:
3483 raise
3360 raise
3484 except IOError, inst:
3361 except IOError, inst:
3485 if hasattr(inst, "code"):
3362 if hasattr(inst, "code"):
3486 u.warn(_("abort: %s\n") % inst)
3363 u.warn(_("abort: %s\n") % inst)
3487 elif hasattr(inst, "reason"):
3364 elif hasattr(inst, "reason"):
3488 u.warn(_("abort: error: %s\n") % inst.reason[1])
3365 u.warn(_("abort: error: %s\n") % inst.reason[1])
3489 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3366 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3490 if u.debugflag:
3367 if u.debugflag:
3491 u.warn(_("broken pipe\n"))
3368 u.warn(_("broken pipe\n"))
3492 elif getattr(inst, "strerror", None):
3369 elif getattr(inst, "strerror", None):
3493 if getattr(inst, "filename", None):
3370 if getattr(inst, "filename", None):
3494 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3371 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3495 else:
3372 else:
3496 u.warn(_("abort: %s\n") % inst.strerror)
3373 u.warn(_("abort: %s\n") % inst.strerror)
3497 else:
3374 else:
3498 raise
3375 raise
3499 except OSError, inst:
3376 except OSError, inst:
3500 if getattr(inst, "filename", None):
3377 if getattr(inst, "filename", None):
3501 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3378 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3502 else:
3379 else:
3503 u.warn(_("abort: %s\n") % inst.strerror)
3380 u.warn(_("abort: %s\n") % inst.strerror)
3504 except util.UnexpectedOutput, inst:
3381 except util.UnexpectedOutput, inst:
3505 u.warn(_("abort: %s") % inst[0])
3382 u.warn(_("abort: %s") % inst[0])
3506 if not isinstance(inst[1], basestring):
3383 if not isinstance(inst[1], basestring):
3507 u.warn(" %r\n" % (inst[1],))
3384 u.warn(" %r\n" % (inst[1],))
3508 elif not inst[1]:
3385 elif not inst[1]:
3509 u.warn(_(" empty string\n"))
3386 u.warn(_(" empty string\n"))
3510 else:
3387 else:
3511 u.warn("\n%r%s\n" %
3388 u.warn("\n%r%s\n" %
3512 (inst[1][:400], len(inst[1]) > 400 and '...' or ''))
3389 (inst[1][:400], len(inst[1]) > 400 and '...' or ''))
3513 except util.Abort, inst:
3390 except util.Abort, inst:
3514 u.warn(_("abort: %s\n") % inst)
3391 u.warn(_("abort: %s\n") % inst)
3515 except TypeError, inst:
3392 except TypeError, inst:
3516 # was this an argument error?
3393 # was this an argument error?
3517 tb = traceback.extract_tb(sys.exc_info()[2])
3394 tb = traceback.extract_tb(sys.exc_info()[2])
3518 if len(tb) > 2: # no
3395 if len(tb) > 2: # no
3519 raise
3396 raise
3520 u.debug(inst, "\n")
3397 u.debug(inst, "\n")
3521 u.warn(_("%s: invalid arguments\n") % cmd)
3398 u.warn(_("%s: invalid arguments\n") % cmd)
3522 help_(u, cmd)
3399 help_(u, cmd)
3523 except SystemExit, inst:
3400 except SystemExit, inst:
3524 # Commands shouldn't sys.exit directly, but give a return code.
3401 # Commands shouldn't sys.exit directly, but give a return code.
3525 # Just in case catch this and and pass exit code to caller.
3402 # Just in case catch this and and pass exit code to caller.
3526 return inst.code
3403 return inst.code
3527 except:
3404 except:
3528 u.warn(_("** unknown exception encountered, details follow\n"))
3405 u.warn(_("** unknown exception encountered, details follow\n"))
3529 u.warn(_("** report bug details to "
3406 u.warn(_("** report bug details to "
3530 "http://www.selenic.com/mercurial/bts\n"))
3407 "http://www.selenic.com/mercurial/bts\n"))
3531 u.warn(_("** or mercurial@selenic.com\n"))
3408 u.warn(_("** or mercurial@selenic.com\n"))
3532 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3409 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3533 % version.get_version())
3410 % version.get_version())
3534 raise
3411 raise
3535
3412
3536 return -1
3413 return -1
@@ -1,493 +1,292 b''
1 # templater.py - template expansion for output
1 # templater.py - template expansion for output
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 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 demandload import demandload
8 from demandload import demandload
9 from i18n import gettext as _
9 from i18n import gettext as _
10 from node import *
10 from node import *
11 demandload(globals(), "cStringIO cgi re sys os time urllib util textwrap")
11 demandload(globals(), "cgi re sys os time urllib util textwrap")
12
12
13 def parsestring(s, quoted=True):
13 def parsestring(s, quoted=True):
14 '''parse a string using simple c-like syntax.
14 '''parse a string using simple c-like syntax.
15 string must be in quotes if quoted is True.'''
15 string must be in quotes if quoted is True.'''
16 if quoted:
16 if quoted:
17 if len(s) < 2 or s[0] != s[-1]:
17 if len(s) < 2 or s[0] != s[-1]:
18 raise SyntaxError(_('unmatched quotes'))
18 raise SyntaxError(_('unmatched quotes'))
19 return s[1:-1].decode('string_escape')
19 return s[1:-1].decode('string_escape')
20
20
21 return s.decode('string_escape')
21 return s.decode('string_escape')
22
22
23 class templater(object):
23 class templater(object):
24 '''template expansion engine.
24 '''template expansion engine.
25
25
26 template expansion works like this. a map file contains key=value
26 template expansion works like this. a map file contains key=value
27 pairs. if value is quoted, it is treated as string. otherwise, it
27 pairs. if value is quoted, it is treated as string. otherwise, it
28 is treated as name of template file.
28 is treated as name of template file.
29
29
30 templater is asked to expand a key in map. it looks up key, and
30 templater is asked to expand a key in map. it looks up key, and
31 looks for atrings like this: {foo}. it expands {foo} by looking up
31 looks for atrings like this: {foo}. it expands {foo} by looking up
32 foo in map, and substituting it. expansion is recursive: it stops
32 foo in map, and substituting it. expansion is recursive: it stops
33 when there is no more {foo} to replace.
33 when there is no more {foo} to replace.
34
34
35 expansion also allows formatting and filtering.
35 expansion also allows formatting and filtering.
36
36
37 format uses key to expand each item in list. syntax is
37 format uses key to expand each item in list. syntax is
38 {key%format}.
38 {key%format}.
39
39
40 filter uses function to transform value. syntax is
40 filter uses function to transform value. syntax is
41 {key|filter1|filter2|...}.'''
41 {key|filter1|filter2|...}.'''
42
42
43 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
43 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
44 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
44 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
45
45
46 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
46 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
47 '''set up template engine.
47 '''set up template engine.
48 mapfile is name of file to read map definitions from.
48 mapfile is name of file to read map definitions from.
49 filters is dict of functions. each transforms a value into another.
49 filters is dict of functions. each transforms a value into another.
50 defaults is dict of default map definitions.'''
50 defaults is dict of default map definitions.'''
51 self.mapfile = mapfile or 'template'
51 self.mapfile = mapfile or 'template'
52 self.cache = cache.copy()
52 self.cache = cache.copy()
53 self.map = {}
53 self.map = {}
54 self.base = (mapfile and os.path.dirname(mapfile)) or ''
54 self.base = (mapfile and os.path.dirname(mapfile)) or ''
55 self.filters = filters
55 self.filters = filters
56 self.defaults = defaults
56 self.defaults = defaults
57
57
58 if not mapfile:
58 if not mapfile:
59 return
59 return
60 i = 0
60 i = 0
61 for l in file(mapfile):
61 for l in file(mapfile):
62 l = l.strip()
62 l = l.strip()
63 i += 1
63 i += 1
64 if not l or l[0] in '#;': continue
64 if not l or l[0] in '#;': continue
65 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
65 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
66 if m:
66 if m:
67 key, val = m.groups()
67 key, val = m.groups()
68 if val[0] in "'\"":
68 if val[0] in "'\"":
69 try:
69 try:
70 self.cache[key] = parsestring(val)
70 self.cache[key] = parsestring(val)
71 except SyntaxError, inst:
71 except SyntaxError, inst:
72 raise SyntaxError('%s:%s: %s' %
72 raise SyntaxError('%s:%s: %s' %
73 (mapfile, i, inst.args[0]))
73 (mapfile, i, inst.args[0]))
74 else:
74 else:
75 self.map[key] = os.path.join(self.base, val)
75 self.map[key] = os.path.join(self.base, val)
76 else:
76 else:
77 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
77 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
78
78
79 def __contains__(self, key):
79 def __contains__(self, key):
80 return key in self.cache or key in self.map
80 return key in self.cache or key in self.map
81
81
82 def __call__(self, t, **map):
82 def __call__(self, t, **map):
83 '''perform expansion.
83 '''perform expansion.
84 t is name of map element to expand.
84 t is name of map element to expand.
85 map is added elements to use during expansion.'''
85 map is added elements to use during expansion.'''
86 if not self.cache.has_key(t):
86 if not self.cache.has_key(t):
87 try:
87 try:
88 self.cache[t] = file(self.map[t]).read()
88 self.cache[t] = file(self.map[t]).read()
89 except IOError, inst:
89 except IOError, inst:
90 raise IOError(inst.args[0], _('template file %s: %s') %
90 raise IOError(inst.args[0], _('template file %s: %s') %
91 (self.map[t], inst.args[1]))
91 (self.map[t], inst.args[1]))
92 tmpl = self.cache[t]
92 tmpl = self.cache[t]
93
93
94 while tmpl:
94 while tmpl:
95 m = self.template_re.search(tmpl)
95 m = self.template_re.search(tmpl)
96 if not m:
96 if not m:
97 yield tmpl
97 yield tmpl
98 break
98 break
99
99
100 start, end = m.span(0)
100 start, end = m.span(0)
101 key, format, fl = m.groups()
101 key, format, fl = m.groups()
102
102
103 if start:
103 if start:
104 yield tmpl[:start]
104 yield tmpl[:start]
105 tmpl = tmpl[end:]
105 tmpl = tmpl[end:]
106
106
107 if key in map:
107 if key in map:
108 v = map[key]
108 v = map[key]
109 else:
109 else:
110 v = self.defaults.get(key, "")
110 v = self.defaults.get(key, "")
111 if callable(v):
111 if callable(v):
112 v = v(**map)
112 v = v(**map)
113 if format:
113 if format:
114 if not hasattr(v, '__iter__'):
114 if not hasattr(v, '__iter__'):
115 raise SyntaxError(_("Error expanding '%s%s'")
115 raise SyntaxError(_("Error expanding '%s%s'")
116 % (key, format))
116 % (key, format))
117 lm = map.copy()
117 lm = map.copy()
118 for i in v:
118 for i in v:
119 lm.update(i)
119 lm.update(i)
120 yield self(format, **lm)
120 yield self(format, **lm)
121 else:
121 else:
122 if fl:
122 if fl:
123 for f in fl.split("|")[1:]:
123 for f in fl.split("|")[1:]:
124 v = self.filters[f](v)
124 v = self.filters[f](v)
125 yield v
125 yield v
126
126
127 agescales = [("second", 1),
127 agescales = [("second", 1),
128 ("minute", 60),
128 ("minute", 60),
129 ("hour", 3600),
129 ("hour", 3600),
130 ("day", 3600 * 24),
130 ("day", 3600 * 24),
131 ("week", 3600 * 24 * 7),
131 ("week", 3600 * 24 * 7),
132 ("month", 3600 * 24 * 30),
132 ("month", 3600 * 24 * 30),
133 ("year", 3600 * 24 * 365)]
133 ("year", 3600 * 24 * 365)]
134
134
135 agescales.reverse()
135 agescales.reverse()
136
136
137 def age(date):
137 def age(date):
138 '''turn a (timestamp, tzoff) tuple into an age string.'''
138 '''turn a (timestamp, tzoff) tuple into an age string.'''
139
139
140 def plural(t, c):
140 def plural(t, c):
141 if c == 1:
141 if c == 1:
142 return t
142 return t
143 return t + "s"
143 return t + "s"
144 def fmt(t, c):
144 def fmt(t, c):
145 return "%d %s" % (c, plural(t, c))
145 return "%d %s" % (c, plural(t, c))
146
146
147 now = time.time()
147 now = time.time()
148 then = date[0]
148 then = date[0]
149 delta = max(1, int(now - then))
149 delta = max(1, int(now - then))
150
150
151 for t, s in agescales:
151 for t, s in agescales:
152 n = delta / s
152 n = delta / s
153 if n >= 2 or s == 1:
153 if n >= 2 or s == 1:
154 return fmt(t, n)
154 return fmt(t, n)
155
155
156 def stringify(thing):
156 def stringify(thing):
157 '''turn nested template iterator into string.'''
157 '''turn nested template iterator into string.'''
158 if hasattr(thing, '__iter__'):
158 if hasattr(thing, '__iter__'):
159 return "".join([stringify(t) for t in thing])
159 return "".join([stringify(t) for t in thing])
160 if thing is None: return ""
160 if thing is None: return ""
161 return str(thing)
161 return str(thing)
162
162
163 para_re = None
163 para_re = None
164 space_re = None
164 space_re = None
165
165
166 def fill(text, width):
166 def fill(text, width):
167 '''fill many paragraphs.'''
167 '''fill many paragraphs.'''
168 global para_re, space_re
168 global para_re, space_re
169 if para_re is None:
169 if para_re is None:
170 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
170 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
171 space_re = re.compile(r' +')
171 space_re = re.compile(r' +')
172
172
173 def findparas():
173 def findparas():
174 start = 0
174 start = 0
175 while True:
175 while True:
176 m = para_re.search(text, start)
176 m = para_re.search(text, start)
177 if not m:
177 if not m:
178 w = len(text)
178 w = len(text)
179 while w > start and text[w-1].isspace(): w -= 1
179 while w > start and text[w-1].isspace(): w -= 1
180 yield text[start:w], text[w:]
180 yield text[start:w], text[w:]
181 break
181 break
182 yield text[start:m.start(0)], m.group(1)
182 yield text[start:m.start(0)], m.group(1)
183 start = m.end(1)
183 start = m.end(1)
184
184
185 return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest
185 return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest
186 for para, rest in findparas()])
186 for para, rest in findparas()])
187
187
188 def firstline(text):
188 def firstline(text):
189 '''return the first line of text'''
189 '''return the first line of text'''
190 try:
190 try:
191 return text.splitlines(1)[0].rstrip('\r\n')
191 return text.splitlines(1)[0].rstrip('\r\n')
192 except IndexError:
192 except IndexError:
193 return ''
193 return ''
194
194
195 def isodate(date):
195 def isodate(date):
196 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
196 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
197 return util.datestr(date, format='%Y-%m-%d %H:%M')
197 return util.datestr(date, format='%Y-%m-%d %H:%M')
198
198
199 def hgdate(date):
199 def hgdate(date):
200 '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
200 '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
201 return "%d %d" % date
201 return "%d %d" % date
202
202
203 def nl2br(text):
203 def nl2br(text):
204 '''replace raw newlines with xhtml line breaks.'''
204 '''replace raw newlines with xhtml line breaks.'''
205 return text.replace('\n', '<br/>\n')
205 return text.replace('\n', '<br/>\n')
206
206
207 def obfuscate(text):
207 def obfuscate(text):
208 text = unicode(text, 'utf-8', 'replace')
208 text = unicode(text, 'utf-8', 'replace')
209 return ''.join(['&#%d;' % ord(c) for c in text])
209 return ''.join(['&#%d;' % ord(c) for c in text])
210
210
211 def domain(author):
211 def domain(author):
212 '''get domain of author, or empty string if none.'''
212 '''get domain of author, or empty string if none.'''
213 f = author.find('@')
213 f = author.find('@')
214 if f == -1: return ''
214 if f == -1: return ''
215 author = author[f+1:]
215 author = author[f+1:]
216 f = author.find('>')
216 f = author.find('>')
217 if f >= 0: author = author[:f]
217 if f >= 0: author = author[:f]
218 return author
218 return author
219
219
220 def email(author):
220 def email(author):
221 '''get email of author.'''
221 '''get email of author.'''
222 r = author.find('>')
222 r = author.find('>')
223 if r == -1: r = None
223 if r == -1: r = None
224 return author[author.find('<')+1:r]
224 return author[author.find('<')+1:r]
225
225
226 def person(author):
226 def person(author):
227 '''get name of author, or else username.'''
227 '''get name of author, or else username.'''
228 f = author.find('<')
228 f = author.find('<')
229 if f == -1: return util.shortuser(author)
229 if f == -1: return util.shortuser(author)
230 return author[:f].rstrip()
230 return author[:f].rstrip()
231
231
232 def shortdate(date):
232 def shortdate(date):
233 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
233 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
234 return util.datestr(date, format='%Y-%m-%d', timezone=False)
234 return util.datestr(date, format='%Y-%m-%d', timezone=False)
235
235
236 def indent(text, prefix):
236 def indent(text, prefix):
237 '''indent each non-empty line of text after first with prefix.'''
237 '''indent each non-empty line of text after first with prefix.'''
238 lines = text.splitlines()
238 lines = text.splitlines()
239 num_lines = len(lines)
239 num_lines = len(lines)
240 def indenter():
240 def indenter():
241 for i in xrange(num_lines):
241 for i in xrange(num_lines):
242 l = lines[i]
242 l = lines[i]
243 if i and l.strip():
243 if i and l.strip():
244 yield prefix
244 yield prefix
245 yield l
245 yield l
246 if i < num_lines - 1 or text.endswith('\n'):
246 if i < num_lines - 1 or text.endswith('\n'):
247 yield '\n'
247 yield '\n'
248 return "".join(indenter())
248 return "".join(indenter())
249
249
250 common_filters = {
250 common_filters = {
251 "addbreaks": nl2br,
251 "addbreaks": nl2br,
252 "basename": os.path.basename,
252 "basename": os.path.basename,
253 "age": age,
253 "age": age,
254 "date": lambda x: util.datestr(x),
254 "date": lambda x: util.datestr(x),
255 "domain": domain,
255 "domain": domain,
256 "email": email,
256 "email": email,
257 "escape": lambda x: cgi.escape(x, True),
257 "escape": lambda x: cgi.escape(x, True),
258 "fill68": lambda x: fill(x, width=68),
258 "fill68": lambda x: fill(x, width=68),
259 "fill76": lambda x: fill(x, width=76),
259 "fill76": lambda x: fill(x, width=76),
260 "firstline": firstline,
260 "firstline": firstline,
261 "tabindent": lambda x: indent(x, '\t'),
261 "tabindent": lambda x: indent(x, '\t'),
262 "hgdate": hgdate,
262 "hgdate": hgdate,
263 "isodate": isodate,
263 "isodate": isodate,
264 "obfuscate": obfuscate,
264 "obfuscate": obfuscate,
265 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
265 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
266 "person": person,
266 "person": person,
267 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
267 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
268 "short": lambda x: x[:12],
268 "short": lambda x: x[:12],
269 "shortdate": shortdate,
269 "shortdate": shortdate,
270 "stringify": stringify,
270 "stringify": stringify,
271 "strip": lambda x: x.strip(),
271 "strip": lambda x: x.strip(),
272 "urlescape": lambda x: urllib.quote(x),
272 "urlescape": lambda x: urllib.quote(x),
273 "user": lambda x: util.shortuser(x),
273 "user": lambda x: util.shortuser(x),
274 "stringescape": lambda x: x.encode('string_escape'),
274 "stringescape": lambda x: x.encode('string_escape'),
275 }
275 }
276
276
277 def templatepath(name=None):
277 def templatepath(name=None):
278 '''return location of template file or directory (if no name).
278 '''return location of template file or directory (if no name).
279 returns None if not found.'''
279 returns None if not found.'''
280
280
281 # executable version (py2exe) doesn't support __file__
281 # executable version (py2exe) doesn't support __file__
282 if hasattr(sys, 'frozen'):
282 if hasattr(sys, 'frozen'):
283 module = sys.executable
283 module = sys.executable
284 else:
284 else:
285 module = __file__
285 module = __file__
286 for f in 'templates', '../templates':
286 for f in 'templates', '../templates':
287 fl = f.split('/')
287 fl = f.split('/')
288 if name: fl.append(name)
288 if name: fl.append(name)
289 p = os.path.join(os.path.dirname(module), *fl)
289 p = os.path.join(os.path.dirname(module), *fl)
290 if (name and os.path.exists(p)) or os.path.isdir(p):
290 if (name and os.path.exists(p)) or os.path.isdir(p):
291 return os.path.normpath(p)
291 return os.path.normpath(p)
292
292
293 class changeset_templater(object):
294 '''format changeset information.'''
295
296 def __init__(self, ui, repo, mapfile, dest=None):
297 self.t = templater(mapfile, common_filters,
298 cache={'parent': '{rev}:{node|short} ',
299 'manifest': '{rev}:{node|short}',
300 'filecopy': '{name} ({source})'})
301 self.ui = ui
302 self.dest = dest
303 self.repo = repo
304
305 def use_template(self, t):
306 '''set template string to use'''
307 self.t.cache['changeset'] = t
308
309 def show(self, rev=0, changenode=None, brinfo=None, copies=[], **props):
310 '''show a single changeset or file revision'''
311 log = self.repo.changelog
312 if changenode is None:
313 changenode = log.node(rev)
314 elif not rev:
315 rev = log.rev(changenode)
316
317 changes = log.read(changenode)
318
319 def showlist(name, values, plural=None, **args):
320 '''expand set of values.
321 name is name of key in template map.
322 values is list of strings or dicts.
323 plural is plural of name, if not simply name + 's'.
324
325 expansion works like this, given name 'foo'.
326
327 if values is empty, expand 'no_foos'.
328
329 if 'foo' not in template map, return values as a string,
330 joined by space.
331
332 expand 'start_foos'.
333
334 for each value, expand 'foo'. if 'last_foo' in template
335 map, expand it instead of 'foo' for last key.
336
337 expand 'end_foos'.
338 '''
339 if plural: names = plural
340 else: names = name + 's'
341 if not values:
342 noname = 'no_' + names
343 if noname in self.t:
344 yield self.t(noname, **args)
345 return
346 if name not in self.t:
347 if isinstance(values[0], str):
348 yield ' '.join(values)
349 else:
350 for v in values:
351 yield dict(v, **args)
352 return
353 startname = 'start_' + names
354 if startname in self.t:
355 yield self.t(startname, **args)
356 vargs = args.copy()
357 def one(v, tag=name):
358 try:
359 vargs.update(v)
360 except (AttributeError, ValueError):
361 try:
362 for a, b in v:
363 vargs[a] = b
364 except ValueError:
365 vargs[name] = v
366 return self.t(tag, **vargs)
367 lastname = 'last_' + name
368 if lastname in self.t:
369 last = values.pop()
370 else:
371 last = None
372 for v in values:
373 yield one(v)
374 if last is not None:
375 yield one(last, tag=lastname)
376 endname = 'end_' + names
377 if endname in self.t:
378 yield self.t(endname, **args)
379
380 def showbranches(**args):
381 branch = changes[5].get("branch")
382 if branch:
383 yield showlist('branch', [branch], plural='branches', **args)
384 # add old style branches if requested
385 if brinfo and changenode in brinfo:
386 yield showlist('branch', brinfo[changenode],
387 plural='branches', **args)
388
389 def showparents(**args):
390 parents = [[('rev', log.rev(p)), ('node', hex(p))]
391 for p in log.parents(changenode)
392 if self.ui.debugflag or p != nullid]
393 if (not self.ui.debugflag and len(parents) == 1 and
394 parents[0][0][1] == rev - 1):
395 return
396 return showlist('parent', parents, **args)
397
398 def showtags(**args):
399 return showlist('tag', self.repo.nodetags(changenode), **args)
400
401 def showextras(**args):
402 extras = changes[5].items()
403 extras.sort()
404 for key, value in extras:
405 args = args.copy()
406 args.update(dict(key=key, value=value))
407 yield self.t('extra', **args)
408
409 def showcopies(**args):
410 c = [{'name': x[0], 'source': x[1]} for x in copies]
411 return showlist('file_copy', c, plural='file_copies', **args)
412
413 if self.ui.debugflag:
414 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
415 def showfiles(**args):
416 return showlist('file', files[0], **args)
417 def showadds(**args):
418 return showlist('file_add', files[1], **args)
419 def showdels(**args):
420 return showlist('file_del', files[2], **args)
421 def showmanifest(**args):
422 args = args.copy()
423 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
424 node=hex(changes[0])))
425 return self.t('manifest', **args)
426 else:
427 def showfiles(**args):
428 yield showlist('file', changes[3], **args)
429 showadds = ''
430 showdels = ''
431 showmanifest = ''
432
433 defprops = {
434 'author': changes[1],
435 'branches': showbranches,
436 'date': changes[2],
437 'desc': changes[4],
438 'file_adds': showadds,
439 'file_dels': showdels,
440 'files': showfiles,
441 'file_copies': showcopies,
442 'manifest': showmanifest,
443 'node': hex(changenode),
444 'parents': showparents,
445 'rev': rev,
446 'tags': showtags,
447 'extras': showextras,
448 }
449 props = props.copy()
450 props.update(defprops)
451
452 try:
453 dest = self.dest or self.ui
454 if self.ui.debugflag and 'header_debug' in self.t:
455 key = 'header_debug'
456 elif self.ui.quiet and 'header_quiet' in self.t:
457 key = 'header_quiet'
458 elif self.ui.verbose and 'header_verbose' in self.t:
459 key = 'header_verbose'
460 elif 'header' in self.t:
461 key = 'header'
462 else:
463 key = ''
464 if key:
465 dest.write_header(stringify(self.t(key, **props)))
466 if self.ui.debugflag and 'changeset_debug' in self.t:
467 key = 'changeset_debug'
468 elif self.ui.quiet and 'changeset_quiet' in self.t:
469 key = 'changeset_quiet'
470 elif self.ui.verbose and 'changeset_verbose' in self.t:
471 key = 'changeset_verbose'
472 else:
473 key = 'changeset'
474 dest.write(stringify(self.t(key, **props)))
475 except KeyError, inst:
476 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
477 inst.args[0]))
478 except SyntaxError, inst:
479 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
480
481 class stringio(object):
482 '''wrap cStringIO for use by changeset_templater.'''
483 def __init__(self):
484 self.fp = cStringIO.StringIO()
485
486 def write(self, *args):
487 for a in args:
488 self.fp.write(a)
489
490 write_header = write
491
492 def __getattr__(self, key):
493 return getattr(self.fp, key)
General Comments 0
You need to be logged in to leave comments. Login now