##// 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 1 # bugzilla.py - bugzilla integration for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7 #
8 8 # hook extension to update comments of bugzilla bugs when changesets
9 9 # that refer to bugs by id are seen. this hook does not change bug
10 10 # status, only comments.
11 11 #
12 12 # to configure, add items to '[bugzilla]' section of hgrc.
13 13 #
14 14 # to use, configure bugzilla extension and enable like this:
15 15 #
16 16 # [extensions]
17 17 # hgext.bugzilla =
18 18 #
19 19 # [hooks]
20 20 # # run bugzilla hook on every change pulled or pushed in here
21 21 # incoming.bugzilla = python:hgext.bugzilla.hook
22 22 #
23 23 # config items:
24 24 #
25 25 # section name is 'bugzilla'.
26 26 # [bugzilla]
27 27 #
28 28 # REQUIRED:
29 29 # host = bugzilla # mysql server where bugzilla database lives
30 30 # password = ** # user's password
31 31 # version = 2.16 # version of bugzilla installed
32 32 #
33 33 # OPTIONAL:
34 34 # bzuser = ... # fallback bugzilla user name to record comments with
35 35 # db = bugs # database to connect to
36 36 # notify = ... # command to run to get bugzilla to send mail
37 37 # regexp = ... # regexp to match bug ids (must contain one "()" group)
38 38 # strip = 0 # number of slashes to strip for url paths
39 39 # style = ... # style file to use when formatting comments
40 40 # template = ... # template to use when formatting comments
41 41 # timeout = 5 # database connection timeout (seconds)
42 42 # user = bugs # user to connect to database as
43 43 # [web]
44 44 # baseurl = http://hgserver/... # root of hg web site for browsing commits
45 45 #
46 46 # if hg committer names are not same as bugzilla user names, use
47 47 # "usermap" feature to map from committer email to bugzilla user name.
48 48 # usermap can be in hgrc or separate config file.
49 49 #
50 50 # [bugzilla]
51 51 # usermap = filename # cfg file with "committer"="bugzilla user" info
52 52 # [usermap]
53 53 # committer_email = bugzilla_user_name
54 54
55 55 from mercurial.demandload import *
56 56 from mercurial.i18n import gettext as _
57 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 60 MySQLdb = None
61 61
62 62 def buglist(ids):
63 63 return '(' + ','.join(map(str, ids)) + ')'
64 64
65 65 class bugzilla_2_16(object):
66 66 '''support for bugzilla version 2.16.'''
67 67
68 68 def __init__(self, ui):
69 69 self.ui = ui
70 70 host = self.ui.config('bugzilla', 'host', 'localhost')
71 71 user = self.ui.config('bugzilla', 'user', 'bugs')
72 72 passwd = self.ui.config('bugzilla', 'password')
73 73 db = self.ui.config('bugzilla', 'db', 'bugs')
74 74 timeout = int(self.ui.config('bugzilla', 'timeout', 5))
75 75 usermap = self.ui.config('bugzilla', 'usermap')
76 76 if usermap:
77 77 self.ui.readsections(usermap, 'usermap')
78 78 self.ui.note(_('connecting to %s:%s as %s, password %s\n') %
79 79 (host, db, user, '*' * len(passwd)))
80 80 self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd,
81 81 db=db, connect_timeout=timeout)
82 82 self.cursor = self.conn.cursor()
83 83 self.run('select fieldid from fielddefs where name = "longdesc"')
84 84 ids = self.cursor.fetchall()
85 85 if len(ids) != 1:
86 86 raise util.Abort(_('unknown database schema'))
87 87 self.longdesc_id = ids[0][0]
88 88 self.user_ids = {}
89 89
90 90 def run(self, *args, **kwargs):
91 91 '''run a query.'''
92 92 self.ui.note(_('query: %s %s\n') % (args, kwargs))
93 93 try:
94 94 self.cursor.execute(*args, **kwargs)
95 95 except MySQLdb.MySQLError, err:
96 96 self.ui.note(_('failed query: %s %s\n') % (args, kwargs))
97 97 raise
98 98
99 99 def filter_real_bug_ids(self, ids):
100 100 '''filter not-existing bug ids from list.'''
101 101 self.run('select bug_id from bugs where bug_id in %s' % buglist(ids))
102 102 ids = [c[0] for c in self.cursor.fetchall()]
103 103 ids.sort()
104 104 return ids
105 105
106 106 def filter_unknown_bug_ids(self, node, ids):
107 107 '''filter bug ids from list that already refer to this changeset.'''
108 108
109 109 self.run('''select bug_id from longdescs where
110 110 bug_id in %s and thetext like "%%%s%%"''' %
111 111 (buglist(ids), short(node)))
112 112 unknown = dict.fromkeys(ids)
113 113 for (id,) in self.cursor.fetchall():
114 114 self.ui.status(_('bug %d already knows about changeset %s\n') %
115 115 (id, short(node)))
116 116 unknown.pop(id, None)
117 117 ids = unknown.keys()
118 118 ids.sort()
119 119 return ids
120 120
121 121 def notify(self, ids):
122 122 '''tell bugzilla to send mail.'''
123 123
124 124 self.ui.status(_('telling bugzilla to send mail:\n'))
125 125 for id in ids:
126 126 self.ui.status(_(' bug %s\n') % id)
127 127 cmd = self.ui.config('bugzilla', 'notify',
128 128 'cd /var/www/html/bugzilla && '
129 129 './processmail %s nobody@nowhere.com') % id
130 130 fp = os.popen('(%s) 2>&1' % cmd)
131 131 out = fp.read()
132 132 ret = fp.close()
133 133 if ret:
134 134 self.ui.warn(out)
135 135 raise util.Abort(_('bugzilla notify command %s') %
136 136 util.explain_exit(ret)[0])
137 137 self.ui.status(_('done\n'))
138 138
139 139 def get_user_id(self, user):
140 140 '''look up numeric bugzilla user id.'''
141 141 try:
142 142 return self.user_ids[user]
143 143 except KeyError:
144 144 try:
145 145 userid = int(user)
146 146 except ValueError:
147 147 self.ui.note(_('looking up user %s\n') % user)
148 148 self.run('''select userid from profiles
149 149 where login_name like %s''', user)
150 150 all = self.cursor.fetchall()
151 151 if len(all) != 1:
152 152 raise KeyError(user)
153 153 userid = int(all[0][0])
154 154 self.user_ids[user] = userid
155 155 return userid
156 156
157 157 def map_committer(self, user):
158 158 '''map name of committer to bugzilla user name.'''
159 159 for committer, bzuser in self.ui.configitems('usermap'):
160 160 if committer.lower() == user.lower():
161 161 return bzuser
162 162 return user
163 163
164 164 def add_comment(self, bugid, text, committer):
165 165 '''add comment to bug. try adding comment as committer of
166 166 changeset, otherwise as default bugzilla user.'''
167 167 user = self.map_committer(committer)
168 168 try:
169 169 userid = self.get_user_id(user)
170 170 except KeyError:
171 171 try:
172 172 defaultuser = self.ui.config('bugzilla', 'bzuser')
173 173 if not defaultuser:
174 174 raise util.Abort(_('cannot find bugzilla user id for %s') %
175 175 user)
176 176 userid = self.get_user_id(defaultuser)
177 177 except KeyError:
178 178 raise util.Abort(_('cannot find bugzilla user id for %s or %s') %
179 179 (user, defaultuser))
180 180 now = time.strftime('%Y-%m-%d %H:%M:%S')
181 181 self.run('''insert into longdescs
182 182 (bug_id, who, bug_when, thetext)
183 183 values (%s, %s, %s, %s)''',
184 184 (bugid, userid, now, text))
185 185 self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid)
186 186 values (%s, %s, %s, %s)''',
187 187 (bugid, userid, now, self.longdesc_id))
188 188
189 189 class bugzilla(object):
190 190 # supported versions of bugzilla. different versions have
191 191 # different schemas.
192 192 _versions = {
193 193 '2.16': bugzilla_2_16,
194 194 }
195 195
196 196 _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*'
197 197 r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)')
198 198
199 199 _bz = None
200 200
201 201 def __init__(self, ui, repo):
202 202 self.ui = ui
203 203 self.repo = repo
204 204
205 205 def bz(self):
206 206 '''return object that knows how to talk to bugzilla version in
207 207 use.'''
208 208
209 209 if bugzilla._bz is None:
210 210 bzversion = self.ui.config('bugzilla', 'version')
211 211 try:
212 212 bzclass = bugzilla._versions[bzversion]
213 213 except KeyError:
214 214 raise util.Abort(_('bugzilla version %s not supported') %
215 215 bzversion)
216 216 bugzilla._bz = bzclass(self.ui)
217 217 return bugzilla._bz
218 218
219 219 def __getattr__(self, key):
220 220 return getattr(self.bz(), key)
221 221
222 222 _bug_re = None
223 223 _split_re = None
224 224
225 225 def find_bug_ids(self, node, desc):
226 226 '''find valid bug ids that are referred to in changeset
227 227 comments and that do not already have references to this
228 228 changeset.'''
229 229
230 230 if bugzilla._bug_re is None:
231 231 bugzilla._bug_re = re.compile(
232 232 self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re),
233 233 re.IGNORECASE)
234 234 bugzilla._split_re = re.compile(r'\D+')
235 235 start = 0
236 236 ids = {}
237 237 while True:
238 238 m = bugzilla._bug_re.search(desc, start)
239 239 if not m:
240 240 break
241 241 start = m.end()
242 242 for id in bugzilla._split_re.split(m.group(1)):
243 243 if not id: continue
244 244 ids[int(id)] = 1
245 245 ids = ids.keys()
246 246 if ids:
247 247 ids = self.filter_real_bug_ids(ids)
248 248 if ids:
249 249 ids = self.filter_unknown_bug_ids(node, ids)
250 250 return ids
251 251
252 252 def update(self, bugid, node, changes):
253 253 '''update bugzilla bug with reference to changeset.'''
254 254
255 255 def webroot(root):
256 256 '''strip leading prefix of repo root and turn into
257 257 url-safe path.'''
258 258 count = int(self.ui.config('bugzilla', 'strip', 0))
259 259 root = util.pconvert(root)
260 260 while count > 0:
261 261 c = root.find('/')
262 262 if c == -1:
263 263 break
264 264 root = root[c+1:]
265 265 count -= 1
266 266 return root
267 267
268 268 mapfile = self.ui.config('bugzilla', 'style')
269 269 tmpl = self.ui.config('bugzilla', 'template')
270 sio = templater.stringio()
271 t = templater.changeset_templater(self.ui, self.repo, mapfile, sio)
270 sio = cmdutil.stringio()
271 t = cmdutil.changeset_templater(self.ui, self.repo, mapfile, sio)
272 272 if not mapfile and not tmpl:
273 273 tmpl = _('changeset {node|short} in repo {root} refers '
274 274 'to bug {bug}.\ndetails:\n\t{desc|tabindent}')
275 275 if tmpl:
276 276 tmpl = templater.parsestring(tmpl, quoted=False)
277 277 t.use_template(tmpl)
278 278 t.show(changenode=node, changes=changes,
279 279 bug=str(bugid),
280 280 hgweb=self.ui.config('web', 'baseurl'),
281 281 root=self.repo.root,
282 282 webroot=webroot(self.repo.root))
283 283 self.add_comment(bugid, sio.getvalue(), templater.email(changes[1]))
284 284
285 285 def hook(ui, repo, hooktype, node=None, **kwargs):
286 286 '''add comment to bugzilla for each changeset that refers to a
287 287 bugzilla bug id. only add a comment once per bug, so same change
288 288 seen multiple times does not fill bug with duplicate data.'''
289 289 try:
290 290 import MySQLdb as mysql
291 291 global MySQLdb
292 292 MySQLdb = mysql
293 293 except ImportError, err:
294 294 raise util.Abort(_('python mysql support not available: %s') % err)
295 295
296 296 if node is None:
297 297 raise util.Abort(_('hook type %s does not pass a changeset id') %
298 298 hooktype)
299 299 try:
300 300 bz = bugzilla(ui, repo)
301 301 bin_node = bin(node)
302 302 changes = repo.changelog.read(bin_node)
303 303 ids = bz.find_bug_ids(bin_node, changes[4])
304 304 if ids:
305 305 for id in ids:
306 306 bz.update(id, bin_node, changes)
307 307 bz.notify(ids)
308 308 except MySQLdb.MySQLError, err:
309 309 raise util.Abort(_('database error: %s') % err[1])
310 310
@@ -1,299 +1,299 b''
1 1 # bisect extension for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
4 4 # Inspired by git bisect, extension skeleton taken from mq.py.
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 from mercurial.i18n import gettext as _
10 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 13 versionstr = "0.0.3"
14 14
15 15 def lookup_rev(ui, repo, rev=None):
16 16 """returns rev or the checked-out revision if rev is None"""
17 17 if not rev is None:
18 18 return repo.lookup(rev)
19 19 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
20 20 if len(parents) != 1:
21 21 raise util.Abort(_("unexpected number of parents, "
22 22 "please commit or revert"))
23 23 return parents.pop()
24 24
25 25 def check_clean(ui, repo):
26 26 modified, added, removed, deleted, unknown = repo.status()[:5]
27 27 if modified or added or removed:
28 28 ui.warn("Repository is not clean, please commit or revert\n")
29 29 sys.exit(1)
30 30
31 31 class bisect(object):
32 32 """dichotomic search in the DAG of changesets"""
33 33 def __init__(self, ui, repo):
34 34 self.repo = repo
35 35 self.path = repo.join("bisect")
36 36 self.opener = util.opener(self.path)
37 37 self.ui = ui
38 38 self.goodrevs = []
39 39 self.badrev = None
40 40 self.good_dirty = 0
41 41 self.bad_dirty = 0
42 42 self.good_path = "good"
43 43 self.bad_path = "bad"
44 44
45 45 if os.path.exists(os.path.join(self.path, self.good_path)):
46 46 self.goodrevs = self.opener(self.good_path).read().splitlines()
47 47 self.goodrevs = [hg.bin(x) for x in self.goodrevs]
48 48 if os.path.exists(os.path.join(self.path, self.bad_path)):
49 49 r = self.opener(self.bad_path).read().splitlines()
50 50 if r:
51 51 self.badrev = hg.bin(r.pop(0))
52 52
53 53 def write(self):
54 54 if not os.path.isdir(self.path):
55 55 return
56 56 f = self.opener(self.good_path, "w")
57 57 f.write("\n".join([hg.hex(r) for r in self.goodrevs]))
58 58 if len(self.goodrevs) > 0:
59 59 f.write("\n")
60 60 f = self.opener(self.bad_path, "w")
61 61 if self.badrev:
62 62 f.write(hg.hex(self.badrev) + "\n")
63 63
64 64 def init(self):
65 65 """start a new bisection"""
66 66 if os.path.isdir(self.path):
67 67 raise util.Abort(_("bisect directory already exists\n"))
68 68 os.mkdir(self.path)
69 69 check_clean(self.ui, self.repo)
70 70 return 0
71 71
72 72 def reset(self):
73 73 """finish a bisection"""
74 74 if os.path.isdir(self.path):
75 75 sl = [os.path.join(self.path, p)
76 76 for p in [self.bad_path, self.good_path]]
77 77 for s in sl:
78 78 if os.path.exists(s):
79 79 os.unlink(s)
80 80 os.rmdir(self.path)
81 81 # Not sure about this
82 82 #self.ui.write("Going back to tip\n")
83 83 #self.repo.update(self.repo.changelog.tip())
84 84 return 1
85 85
86 86 def num_ancestors(self, head=None, stop=None):
87 87 """
88 88 returns a dict with the mapping:
89 89 node -> number of ancestors (self included)
90 90 for all nodes who are ancestor of head and
91 91 not in stop.
92 92 """
93 93 if head is None:
94 94 head = self.badrev
95 95 return self.__ancestors_and_nb_ancestors(head, stop)[1]
96 96
97 97 def ancestors(self, head=None, stop=None):
98 98 """
99 99 returns the set of the ancestors of head (self included)
100 100 who are not in stop.
101 101 """
102 102 if head is None:
103 103 head = self.badrev
104 104 return self.__ancestors_and_nb_ancestors(head, stop)[0]
105 105
106 106 def __ancestors_and_nb_ancestors(self, head, stop=None):
107 107 """
108 108 if stop is None then ancestors of goodrevs are used as
109 109 lower limit.
110 110
111 111 returns (anc, n_child) where anc is the set of the ancestors of head
112 112 and n_child is a dictionary with the following mapping:
113 113 node -> number of ancestors (self included)
114 114 """
115 115 cl = self.repo.changelog
116 116 if not stop:
117 117 stop = sets.Set([])
118 118 for i in xrange(len(self.goodrevs)-1, -1, -1):
119 119 g = self.goodrevs[i]
120 120 if g in stop:
121 121 continue
122 122 stop.update(cl.reachable(g))
123 123 def num_children(a):
124 124 """
125 125 returns a dictionnary with the following mapping
126 126 node -> [number of children, empty set]
127 127 """
128 128 d = {a: [0, sets.Set([])]}
129 129 for i in xrange(cl.rev(a)+1):
130 130 n = cl.node(i)
131 131 if not d.has_key(n):
132 132 d[n] = [0, sets.Set([])]
133 133 parents = [p for p in cl.parents(n) if p != hg.nullid]
134 134 for p in parents:
135 135 d[p][0] += 1
136 136 return d
137 137
138 138 if head in stop:
139 139 raise util.Abort(_("Unconsistent state, %s:%s is good and bad")
140 140 % (cl.rev(head), hg.short(head)))
141 141 n_child = num_children(head)
142 142 for i in xrange(cl.rev(head)+1):
143 143 n = cl.node(i)
144 144 parents = [p for p in cl.parents(n) if p != hg.nullid]
145 145 for p in parents:
146 146 n_child[p][0] -= 1
147 147 if not n in stop:
148 148 n_child[n][1].union_update(n_child[p][1])
149 149 if n_child[p][0] == 0:
150 150 n_child[p] = len(n_child[p][1])
151 151 if not n in stop:
152 152 n_child[n][1].add(n)
153 153 if n_child[n][0] == 0:
154 154 if n == head:
155 155 anc = n_child[n][1]
156 156 n_child[n] = len(n_child[n][1])
157 157 return anc, n_child
158 158
159 159 def next(self):
160 160 if not self.badrev:
161 161 raise util.Abort(_("You should give at least one bad revision"))
162 162 if not self.goodrevs:
163 163 self.ui.warn(_("No good revision given\n"))
164 164 self.ui.warn(_("Marking the first revision as good\n"))
165 165 ancestors, num_ancestors = self.__ancestors_and_nb_ancestors(
166 166 self.badrev)
167 167 tot = len(ancestors)
168 168 if tot == 1:
169 169 if ancestors.pop() != self.badrev:
170 170 raise util.Abort(_("Could not find the first bad revision"))
171 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 173 displayer.show(changenode=self.badrev)
174 174 return None
175 175 best_rev = None
176 176 best_len = -1
177 177 for n in ancestors:
178 178 l = num_ancestors[n]
179 179 l = min(l, tot - l)
180 180 if l > best_len:
181 181 best_len = l
182 182 best_rev = n
183 183 assert best_rev is not None
184 184 nb_tests = 0
185 185 q, r = divmod(tot, 2)
186 186 while q:
187 187 nb_tests += 1
188 188 q, r = divmod(q, 2)
189 189 msg = _("Testing changeset %s:%s (%s changesets remaining, "
190 190 "~%s tests)\n") % (self.repo.changelog.rev(best_rev),
191 191 hg.short(best_rev), tot, nb_tests)
192 192 self.ui.write(msg)
193 193 return best_rev
194 194
195 195 def autonext(self):
196 196 """find and update to the next revision to test"""
197 197 check_clean(self.ui, self.repo)
198 198 rev = self.next()
199 199 if rev is not None:
200 200 return hg.clean(self.repo, rev)
201 201
202 202 def good(self, rev):
203 203 self.goodrevs.append(rev)
204 204
205 205 def autogood(self, rev=None):
206 206 """mark revision as good and update to the next revision to test"""
207 207 check_clean(self.ui, self.repo)
208 208 rev = lookup_rev(self.ui, self.repo, rev)
209 209 self.good(rev)
210 210 if self.badrev:
211 211 return self.autonext()
212 212
213 213 def bad(self, rev):
214 214 self.badrev = rev
215 215
216 216 def autobad(self, rev=None):
217 217 """mark revision as bad and update to the next revision to test"""
218 218 check_clean(self.ui, self.repo)
219 219 rev = lookup_rev(self.ui, self.repo, rev)
220 220 self.bad(rev)
221 221 if self.goodrevs:
222 222 self.autonext()
223 223
224 224 # should we put it in the class ?
225 225 def test(ui, repo, rev):
226 226 """test the bisection code"""
227 227 b = bisect(ui, repo)
228 228 rev = repo.lookup(rev)
229 229 ui.write("testing with rev %s\n" % hg.hex(rev))
230 230 anc = b.ancestors()
231 231 while len(anc) > 1:
232 232 if not rev in anc:
233 233 ui.warn("failure while bisecting\n")
234 234 sys.exit(1)
235 235 ui.write("it worked :)\n")
236 236 new_rev = b.next()
237 237 ui.write("choosing if good or bad\n")
238 238 if rev in b.ancestors(head=new_rev):
239 239 b.bad(new_rev)
240 240 ui.write("it is bad\n")
241 241 else:
242 242 b.good(new_rev)
243 243 ui.write("it is good\n")
244 244 anc = b.ancestors()
245 245 #repo.update(new_rev, force=True)
246 246 for v in anc:
247 247 if v != rev:
248 248 ui.warn("fail to found cset! :(\n")
249 249 return 1
250 250 ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
251 251 ui.write("Everything is ok :)\n")
252 252 return 0
253 253
254 254 def bisect_run(ui, repo, cmd=None, *args):
255 255 """bisect extension: dichotomic search in the DAG of changesets
256 256 for subcommands see "hg bisect help\"
257 257 """
258 258 def help_(cmd=None, *args):
259 259 """show help for a given bisect subcommand or all subcommands"""
260 260 cmdtable = bisectcmdtable
261 261 if cmd:
262 262 doc = cmdtable[cmd][0].__doc__
263 263 synopsis = cmdtable[cmd][2]
264 264 ui.write(synopsis + "\n")
265 265 ui.write("\n" + doc + "\n")
266 266 return
267 267 ui.write(_("list of subcommands for the bisect extension\n\n"))
268 268 cmds = cmdtable.keys()
269 269 cmds.sort()
270 270 m = max([len(c) for c in cmds])
271 271 for cmd in cmds:
272 272 doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip()
273 273 ui.write(" %-*s %s\n" % (m, cmd, doc))
274 274
275 275 b = bisect(ui, repo)
276 276 bisectcmdtable = {
277 277 "init": (b.init, 0, _("hg bisect init")),
278 278 "bad": (b.autobad, 1, _("hg bisect bad [<rev>]")),
279 279 "good": (b.autogood, 1, _("hg bisect good [<rev>]")),
280 280 "next": (b.autonext, 0, _("hg bisect next")),
281 281 "reset": (b.reset, 0, _("hg bisect reset")),
282 282 "help": (help_, 1, _("hg bisect help [<subcommand>]")),
283 283 }
284 284
285 285 if not bisectcmdtable.has_key(cmd):
286 286 ui.warn(_("bisect: Unknown sub-command\n"))
287 287 return help_()
288 288 if len(args) > bisectcmdtable[cmd][1]:
289 289 ui.warn(_("bisect: Too many arguments\n"))
290 290 return help_()
291 291 try:
292 292 return bisectcmdtable[cmd][0](*args)
293 293 finally:
294 294 b.write()
295 295
296 296 cmdtable = {
297 297 "bisect": (bisect_run, [], _("hg bisect [help|init|reset|next|good|bad]")),
298 298 #"bisect-test": (test, [], "hg bisect-test rev"),
299 299 }
@@ -1,280 +1,280 b''
1 1 # notify.py - email notifications for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7 #
8 8 # hook extension to email notifications to people when changesets are
9 9 # committed to a repo they subscribe to.
10 10 #
11 11 # default mode is to print messages to stdout, for testing and
12 12 # configuring.
13 13 #
14 14 # to use, configure notify extension and enable in hgrc like this:
15 15 #
16 16 # [extensions]
17 17 # hgext.notify =
18 18 #
19 19 # [hooks]
20 20 # # one email for each incoming changeset
21 21 # incoming.notify = python:hgext.notify.hook
22 22 # # batch emails when many changesets incoming at one time
23 23 # changegroup.notify = python:hgext.notify.hook
24 24 #
25 25 # [notify]
26 26 # # config items go in here
27 27 #
28 28 # config items:
29 29 #
30 30 # REQUIRED:
31 31 # config = /path/to/file # file containing subscriptions
32 32 #
33 33 # OPTIONAL:
34 34 # test = True # print messages to stdout for testing
35 35 # strip = 3 # number of slashes to strip for url paths
36 36 # domain = example.com # domain to use if committer missing domain
37 37 # style = ... # style file to use when formatting email
38 38 # template = ... # template to use when formatting email
39 39 # incoming = ... # template to use when run as incoming hook
40 40 # changegroup = ... # template when run as changegroup hook
41 41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
42 42 # maxsubject = 67 # truncate subject line longer than this
43 43 # diffstat = True # add a diffstat before the diff content
44 44 # sources = serve # notify if source of incoming changes in this list
45 45 # # (serve == ssh or http, push, pull, bundle)
46 46 # [email]
47 47 # from = user@host.com # email address to send as if none given
48 48 # [web]
49 49 # baseurl = http://hgserver/... # root of hg web site for browsing commits
50 50 #
51 51 # notify config file has same format as regular hgrc. it has two
52 52 # sections so you can express subscriptions in whatever way is handier
53 53 # for you.
54 54 #
55 55 # [usersubs]
56 56 # # key is subscriber email, value is ","-separated list of glob patterns
57 57 # user@host = pattern
58 58 #
59 59 # [reposubs]
60 60 # # key is glob pattern, value is ","-separated list of subscriber emails
61 61 # pattern = user@host
62 62 #
63 63 # glob patterns are matched against path to repo root.
64 64 #
65 65 # if you like, you can put notify config file in repo that users can
66 66 # push changes to, they can manage their own subscriptions.
67 67
68 68 from mercurial.demandload import *
69 69 from mercurial.i18n import gettext as _
70 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 72 demandload(globals(), 'email.Parser fnmatch socket time')
73 73
74 74 # template for single changeset can include email headers.
75 75 single_template = '''
76 76 Subject: changeset in {webroot}: {desc|firstline|strip}
77 77 From: {author}
78 78
79 79 changeset {node|short} in {root}
80 80 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
81 81 description:
82 82 \t{desc|tabindent|strip}
83 83 '''.lstrip()
84 84
85 85 # template for multiple changesets should not contain email headers,
86 86 # because only first set of headers will be used and result will look
87 87 # strange.
88 88 multiple_template = '''
89 89 changeset {node|short} in {root}
90 90 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
91 91 summary: {desc|firstline}
92 92 '''
93 93
94 94 deftemplates = {
95 95 'changegroup': multiple_template,
96 96 }
97 97
98 98 class notifier(object):
99 99 '''email notification class.'''
100 100
101 101 def __init__(self, ui, repo, hooktype):
102 102 self.ui = ui
103 103 cfg = self.ui.config('notify', 'config')
104 104 if cfg:
105 105 self.ui.readsections(cfg, 'usersubs', 'reposubs')
106 106 self.repo = repo
107 107 self.stripcount = int(self.ui.config('notify', 'strip', 0))
108 108 self.root = self.strip(self.repo.root)
109 109 self.domain = self.ui.config('notify', 'domain')
110 self.sio = templater.stringio()
110 self.sio = cmdutil.stringio()
111 111 self.subs = self.subscribers()
112 112
113 113 mapfile = self.ui.config('notify', 'style')
114 114 template = (self.ui.config('notify', hooktype) or
115 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 117 self.sio)
118 118 if not mapfile and not template:
119 119 template = deftemplates.get(hooktype) or single_template
120 120 if template:
121 121 template = templater.parsestring(template, quoted=False)
122 122 self.t.use_template(template)
123 123
124 124 def strip(self, path):
125 125 '''strip leading slashes from local path, turn into web-safe path.'''
126 126
127 127 path = util.pconvert(path)
128 128 count = self.stripcount
129 129 while count > 0:
130 130 c = path.find('/')
131 131 if c == -1:
132 132 break
133 133 path = path[c+1:]
134 134 count -= 1
135 135 return path
136 136
137 137 def fixmail(self, addr):
138 138 '''try to clean up email addresses.'''
139 139
140 140 addr = templater.email(addr.strip())
141 141 a = addr.find('@localhost')
142 142 if a != -1:
143 143 addr = addr[:a]
144 144 if '@' not in addr:
145 145 return addr + '@' + self.domain
146 146 return addr
147 147
148 148 def subscribers(self):
149 149 '''return list of email addresses of subscribers to this repo.'''
150 150
151 151 subs = {}
152 152 for user, pats in self.ui.configitems('usersubs'):
153 153 for pat in pats.split(','):
154 154 if fnmatch.fnmatch(self.repo.root, pat.strip()):
155 155 subs[self.fixmail(user)] = 1
156 156 for pat, users in self.ui.configitems('reposubs'):
157 157 if fnmatch.fnmatch(self.repo.root, pat):
158 158 for user in users.split(','):
159 159 subs[self.fixmail(user)] = 1
160 160 subs = subs.keys()
161 161 subs.sort()
162 162 return subs
163 163
164 164 def url(self, path=None):
165 165 return self.ui.config('web', 'baseurl') + (path or self.root)
166 166
167 167 def node(self, node):
168 168 '''format one changeset.'''
169 169
170 170 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
171 171 baseurl=self.ui.config('web', 'baseurl'),
172 172 root=self.repo.root,
173 173 webroot=self.root)
174 174
175 175 def skipsource(self, source):
176 176 '''true if incoming changes from this source should be skipped.'''
177 177 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
178 178 return source not in ok_sources
179 179
180 180 def send(self, node, count):
181 181 '''send message.'''
182 182
183 183 p = email.Parser.Parser()
184 184 self.sio.seek(0)
185 185 msg = p.parse(self.sio)
186 186
187 187 def fix_subject():
188 188 '''try to make subject line exist and be useful.'''
189 189
190 190 subject = msg['Subject']
191 191 if not subject:
192 192 if count > 1:
193 193 subject = _('%s: %d new changesets') % (self.root, count)
194 194 else:
195 195 changes = self.repo.changelog.read(node)
196 196 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
197 197 subject = '%s: %s' % (self.root, s)
198 198 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
199 199 if maxsubject and len(subject) > maxsubject:
200 200 subject = subject[:maxsubject-3] + '...'
201 201 del msg['Subject']
202 202 msg['Subject'] = subject
203 203
204 204 def fix_sender():
205 205 '''try to make message have proper sender.'''
206 206
207 207 sender = msg['From']
208 208 if not sender:
209 209 sender = self.ui.config('email', 'from') or self.ui.username()
210 210 if '@' not in sender or '@localhost' in sender:
211 211 sender = self.fixmail(sender)
212 212 del msg['From']
213 213 msg['From'] = sender
214 214
215 215 fix_subject()
216 216 fix_sender()
217 217
218 218 msg['X-Hg-Notification'] = 'changeset ' + short(node)
219 219 if not msg['Message-Id']:
220 220 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
221 221 (short(node), int(time.time()),
222 222 hash(self.repo.root), socket.getfqdn()))
223 223 msg['To'] = ', '.join(self.subs)
224 224
225 225 msgtext = msg.as_string(0)
226 226 if self.ui.configbool('notify', 'test', True):
227 227 self.ui.write(msgtext)
228 228 if not msgtext.endswith('\n'):
229 229 self.ui.write('\n')
230 230 else:
231 231 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
232 232 (len(self.subs), count))
233 233 mail.sendmail(self.ui, templater.email(msg['From']),
234 234 self.subs, msgtext)
235 235
236 236 def diff(self, node, ref):
237 237 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
238 238 if maxdiff == 0:
239 239 return
240 fp = templater.stringio()
240 fp = cmdutil.stringio()
241 241 prev = self.repo.changelog.parents(node)[0]
242 242 patch.diff(self.repo, prev, ref, fp=fp)
243 243 difflines = fp.getvalue().splitlines(1)
244 244 if self.ui.configbool('notify', 'diffstat', True):
245 245 s = patch.diffstat(difflines)
246 246 self.sio.write('\ndiffstat:\n\n' + s)
247 247 if maxdiff > 0 and len(difflines) > maxdiff:
248 248 self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
249 249 (len(difflines), maxdiff))
250 250 difflines = difflines[:maxdiff]
251 251 elif difflines:
252 252 self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
253 253 self.sio.write(*difflines)
254 254
255 255 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
256 256 '''send email notifications to interested subscribers.
257 257
258 258 if used as changegroup hook, send one email for all changesets in
259 259 changegroup. else send one email per changeset.'''
260 260 n = notifier(ui, repo, hooktype)
261 261 if not n.subs:
262 262 ui.debug(_('notify: no subscribers to repo %s\n' % n.root))
263 263 return
264 264 if n.skipsource(source):
265 265 ui.debug(_('notify: changes have source "%s" - skipping\n') %
266 266 source)
267 267 return
268 268 node = bin(node)
269 269 if hooktype == 'changegroup':
270 270 start = repo.changelog.rev(node)
271 271 end = repo.changelog.count()
272 272 count = end - start
273 273 for rev in xrange(start, end):
274 274 n.node(repo.changelog.node(rev))
275 275 n.diff(node, repo.changelog.tip())
276 276 else:
277 277 count = 1
278 278 n.node(node)
279 279 n.diff(node, node)
280 280 n.send(node, count)
@@ -1,197 +1,524 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from node import *
10 10 from i18n import gettext as _
11 demandload(globals(), 'mdiff util')
12 11 demandload(globals(), 'os sys')
12 demandload(globals(), 'mdiff util templater cStringIO')
13 13
14 14 revrangesep = ':'
15 15
16 16 def revpair(ui, repo, revs):
17 17 '''return pair of nodes, given list of revisions. second item can
18 18 be None, meaning use working dir.'''
19 19
20 20 def revfix(repo, val, defval):
21 21 if not val and val != 0:
22 22 val = defval
23 23 return repo.lookup(val)
24 24
25 25 if not revs:
26 26 return repo.dirstate.parents()[0], None
27 27 end = None
28 28 if len(revs) == 1:
29 29 if revrangesep in revs[0]:
30 30 start, end = revs[0].split(revrangesep, 1)
31 31 start = revfix(repo, start, 0)
32 32 end = revfix(repo, end, repo.changelog.count() - 1)
33 33 else:
34 34 start = revfix(repo, revs[0], None)
35 35 elif len(revs) == 2:
36 36 if revrangesep in revs[0] or revrangesep in revs[1]:
37 37 raise util.Abort(_('too many revisions specified'))
38 38 start = revfix(repo, revs[0], None)
39 39 end = revfix(repo, revs[1], None)
40 40 else:
41 41 raise util.Abort(_('too many revisions specified'))
42 42 return start, end
43 43
44 44 def revrange(ui, repo, revs):
45 45 """Yield revision as strings from a list of revision specifications."""
46 46
47 47 def revfix(repo, val, defval):
48 48 if not val and val != 0:
49 49 return defval
50 50 return repo.changelog.rev(repo.lookup(val))
51 51
52 52 seen, l = {}, []
53 53 for spec in revs:
54 54 if revrangesep in spec:
55 55 start, end = spec.split(revrangesep, 1)
56 56 start = revfix(repo, start, 0)
57 57 end = revfix(repo, end, repo.changelog.count() - 1)
58 58 step = start > end and -1 or 1
59 59 for rev in xrange(start, end+step, step):
60 60 if rev in seen:
61 61 continue
62 62 seen[rev] = 1
63 63 l.append(rev)
64 64 else:
65 65 rev = revfix(repo, spec, None)
66 66 if rev in seen:
67 67 continue
68 68 seen[rev] = 1
69 69 l.append(rev)
70 70
71 71 return l
72 72
73 73 def make_filename(repo, pat, node,
74 74 total=None, seqno=None, revwidth=None, pathname=None):
75 75 node_expander = {
76 76 'H': lambda: hex(node),
77 77 'R': lambda: str(repo.changelog.rev(node)),
78 78 'h': lambda: short(node),
79 79 }
80 80 expander = {
81 81 '%': lambda: '%',
82 82 'b': lambda: os.path.basename(repo.root),
83 83 }
84 84
85 85 try:
86 86 if node:
87 87 expander.update(node_expander)
88 88 if node and revwidth is not None:
89 89 expander['r'] = (lambda:
90 90 str(repo.changelog.rev(node)).zfill(revwidth))
91 91 if total is not None:
92 92 expander['N'] = lambda: str(total)
93 93 if seqno is not None:
94 94 expander['n'] = lambda: str(seqno)
95 95 if total is not None and seqno is not None:
96 96 expander['n'] = lambda:str(seqno).zfill(len(str(total)))
97 97 if pathname is not None:
98 98 expander['s'] = lambda: os.path.basename(pathname)
99 99 expander['d'] = lambda: os.path.dirname(pathname) or '.'
100 100 expander['p'] = lambda: pathname
101 101
102 102 newname = []
103 103 patlen = len(pat)
104 104 i = 0
105 105 while i < patlen:
106 106 c = pat[i]
107 107 if c == '%':
108 108 i += 1
109 109 c = pat[i]
110 110 c = expander[c]()
111 111 newname.append(c)
112 112 i += 1
113 113 return ''.join(newname)
114 114 except KeyError, inst:
115 115 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
116 116 inst.args[0])
117 117
118 118 def make_file(repo, pat, node=None,
119 119 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
120 120 if not pat or pat == '-':
121 121 return 'w' in mode and sys.stdout or sys.stdin
122 122 if hasattr(pat, 'write') and 'w' in mode:
123 123 return pat
124 124 if hasattr(pat, 'read') and 'r' in mode:
125 125 return pat
126 126 return open(make_filename(repo, pat, node, total, seqno, revwidth,
127 127 pathname),
128 128 mode)
129 129
130 130 def matchpats(repo, pats=[], opts={}, head=''):
131 131 cwd = repo.getcwd()
132 132 if not pats and cwd:
133 133 opts['include'] = [os.path.join(cwd, i)
134 134 for i in opts.get('include', [])]
135 135 opts['exclude'] = [os.path.join(cwd, x)
136 136 for x in opts.get('exclude', [])]
137 137 cwd = ''
138 138 return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'),
139 139 opts.get('exclude'), head)
140 140
141 141 def walk(repo, pats=[], opts={}, node=None, head='', badmatch=None):
142 142 files, matchfn, anypats = matchpats(repo, pats, opts, head)
143 143 exact = dict.fromkeys(files)
144 144 for src, fn in repo.walk(node=node, files=files, match=matchfn,
145 145 badmatch=badmatch):
146 146 yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact
147 147
148 148 def findrenames(repo, added=None, removed=None, threshold=0.5):
149 149 if added is None or removed is None:
150 150 added, removed = repo.status()[1:3]
151 151 changes = repo.changelog.read(repo.dirstate.parents()[0])
152 152 mf = repo.manifest.read(changes[0])
153 153 for a in added:
154 154 aa = repo.wread(a)
155 155 bestscore, bestname = None, None
156 156 for r in removed:
157 157 rr = repo.file(r).read(mf[r])
158 158 delta = mdiff.textdiff(aa, rr)
159 159 if len(delta) < len(aa):
160 160 myscore = 1.0 - (float(len(delta)) / len(aa))
161 161 if bestscore is None or myscore > bestscore:
162 162 bestscore, bestname = myscore, r
163 163 if bestname and bestscore >= threshold:
164 164 yield bestname, a, bestscore
165 165
166 166 def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None,
167 167 similarity=None):
168 168 if dry_run is None:
169 169 dry_run = opts.get('dry_run')
170 170 if similarity is None:
171 171 similarity = float(opts.get('similarity') or 0)
172 172 add, remove = [], []
173 173 mapping = {}
174 174 for src, abs, rel, exact in walk(repo, pats, opts):
175 175 if src == 'f' and repo.dirstate.state(abs) == '?':
176 176 add.append(abs)
177 177 mapping[abs] = rel, exact
178 178 if repo.ui.verbose or not exact:
179 179 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
180 180 if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel):
181 181 remove.append(abs)
182 182 mapping[abs] = rel, exact
183 183 if repo.ui.verbose or not exact:
184 184 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
185 185 if not dry_run:
186 186 repo.add(add, wlock=wlock)
187 187 repo.remove(remove, wlock=wlock)
188 188 if similarity > 0:
189 189 for old, new, score in findrenames(repo, add, remove, similarity):
190 190 oldrel, oldexact = mapping[old]
191 191 newrel, newexact = mapping[new]
192 192 if repo.ui.verbose or not oldexact or not newexact:
193 193 repo.ui.status(_('recording removal of %s as rename to %s '
194 194 '(%d%% similar)\n') %
195 195 (oldrel, newrel, score * 100))
196 196 if not dry_run:
197 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 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from node import *
10 10 from i18n import gettext as _
11 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 13 demandload(globals(), "difflib patch tempfile time")
14 14 demandload(globals(), "traceback errno version atexit bz2")
15 15 demandload(globals(), "archival changegroup cmdutil hgweb.server sshserver")
16 16
17 17 class UnknownCommand(Exception):
18 18 """Exception raised if command is not in the command table."""
19 19 class AmbiguousCommand(Exception):
20 20 """Exception raised if command shortcut matches more than one command."""
21 21
22 22 def bail_if_changed(repo):
23 23 modified, added, removed, deleted = repo.status()[:4]
24 24 if modified or added or removed or deleted:
25 25 raise util.Abort(_("outstanding uncommitted changes"))
26 26
27 27 def relpath(repo, args):
28 28 cwd = repo.getcwd()
29 29 if cwd:
30 30 return [util.normpath(os.path.join(cwd, x)) for x in args]
31 31 return args
32 32
33 33 def logmessage(opts):
34 34 """ get the log message according to -m and -l option """
35 35 message = opts['message']
36 36 logfile = opts['logfile']
37 37
38 38 if message and logfile:
39 39 raise util.Abort(_('options --message and --logfile are mutually '
40 40 'exclusive'))
41 41 if not message and logfile:
42 42 try:
43 43 if logfile == '-':
44 44 message = sys.stdin.read()
45 45 else:
46 46 message = open(logfile).read()
47 47 except IOError, inst:
48 48 raise util.Abort(_("can't read commit message '%s': %s") %
49 49 (logfile, inst.strerror))
50 50 return message
51 51
52 52 def walkchangerevs(ui, repo, pats, change, opts):
53 53 '''Iterate over files and the revs they changed in.
54 54
55 55 Callers most commonly need to iterate backwards over the history
56 56 it is interested in. Doing so has awful (quadratic-looking)
57 57 performance, so we use iterators in a "windowed" way.
58 58
59 59 We walk a window of revisions in the desired order. Within the
60 60 window, we first walk forwards to gather data, then in the desired
61 61 order (usually backwards) to display it.
62 62
63 63 This function returns an (iterator, matchfn) tuple. The iterator
64 64 yields 3-tuples. They will be of one of the following forms:
65 65
66 66 "window", incrementing, lastrev: stepping through a window,
67 67 positive if walking forwards through revs, last rev in the
68 68 sequence iterated over - use to reset state for the current window
69 69
70 70 "add", rev, fns: out-of-order traversal of the given file names
71 71 fns, which changed during revision rev - use to gather data for
72 72 possible display
73 73
74 74 "iter", rev, None: in-order traversal of the revs earlier iterated
75 75 over with "add" - use to display data'''
76 76
77 77 def increasing_windows(start, end, windowsize=8, sizelimit=512):
78 78 if start < end:
79 79 while start < end:
80 80 yield start, min(windowsize, end-start)
81 81 start += windowsize
82 82 if windowsize < sizelimit:
83 83 windowsize *= 2
84 84 else:
85 85 while start > end:
86 86 yield start, min(windowsize, start-end-1)
87 87 start -= windowsize
88 88 if windowsize < sizelimit:
89 89 windowsize *= 2
90 90
91 91 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
92 92 follow = opts.get('follow') or opts.get('follow_first')
93 93
94 94 if repo.changelog.count() == 0:
95 95 return [], matchfn
96 96
97 97 if follow:
98 98 defrange = '%s:0' % repo.changectx().rev()
99 99 else:
100 100 defrange = 'tip:0'
101 101 revs = cmdutil.revrange(ui, repo, opts['rev'] or [defrange])
102 102 wanted = {}
103 103 slowpath = anypats
104 104 fncache = {}
105 105
106 106 if not slowpath and not files:
107 107 # No files, no patterns. Display all revs.
108 108 wanted = dict.fromkeys(revs)
109 109 copies = []
110 110 if not slowpath:
111 111 # Only files, no patterns. Check the history of each file.
112 112 def filerevgen(filelog, node):
113 113 cl_count = repo.changelog.count()
114 114 if node is None:
115 115 last = filelog.count() - 1
116 116 else:
117 117 last = filelog.rev(node)
118 118 for i, window in increasing_windows(last, nullrev):
119 119 revs = []
120 120 for j in xrange(i - window, i + 1):
121 121 n = filelog.node(j)
122 122 revs.append((filelog.linkrev(n),
123 123 follow and filelog.renamed(n)))
124 124 revs.reverse()
125 125 for rev in revs:
126 126 # only yield rev for which we have the changelog, it can
127 127 # happen while doing "hg log" during a pull or commit
128 128 if rev[0] < cl_count:
129 129 yield rev
130 130 def iterfiles():
131 131 for filename in files:
132 132 yield filename, None
133 133 for filename_node in copies:
134 134 yield filename_node
135 135 minrev, maxrev = min(revs), max(revs)
136 136 for file_, node in iterfiles():
137 137 filelog = repo.file(file_)
138 138 # A zero count may be a directory or deleted file, so
139 139 # try to find matching entries on the slow path.
140 140 if filelog.count() == 0:
141 141 slowpath = True
142 142 break
143 143 for rev, copied in filerevgen(filelog, node):
144 144 if rev <= maxrev:
145 145 if rev < minrev:
146 146 break
147 147 fncache.setdefault(rev, [])
148 148 fncache[rev].append(file_)
149 149 wanted[rev] = 1
150 150 if follow and copied:
151 151 copies.append(copied)
152 152 if slowpath:
153 153 if follow:
154 154 raise util.Abort(_('can only follow copies/renames for explicit '
155 155 'file names'))
156 156
157 157 # The slow path checks files modified in every changeset.
158 158 def changerevgen():
159 159 for i, window in increasing_windows(repo.changelog.count()-1,
160 160 nullrev):
161 161 for j in xrange(i - window, i + 1):
162 162 yield j, change(j)[3]
163 163
164 164 for rev, changefiles in changerevgen():
165 165 matches = filter(matchfn, changefiles)
166 166 if matches:
167 167 fncache[rev] = matches
168 168 wanted[rev] = 1
169 169
170 170 class followfilter:
171 171 def __init__(self, onlyfirst=False):
172 172 self.startrev = nullrev
173 173 self.roots = []
174 174 self.onlyfirst = onlyfirst
175 175
176 176 def match(self, rev):
177 177 def realparents(rev):
178 178 if self.onlyfirst:
179 179 return repo.changelog.parentrevs(rev)[0:1]
180 180 else:
181 181 return filter(lambda x: x != nullrev,
182 182 repo.changelog.parentrevs(rev))
183 183
184 184 if self.startrev == nullrev:
185 185 self.startrev = rev
186 186 return True
187 187
188 188 if rev > self.startrev:
189 189 # forward: all descendants
190 190 if not self.roots:
191 191 self.roots.append(self.startrev)
192 192 for parent in realparents(rev):
193 193 if parent in self.roots:
194 194 self.roots.append(rev)
195 195 return True
196 196 else:
197 197 # backwards: all parents
198 198 if not self.roots:
199 199 self.roots.extend(realparents(self.startrev))
200 200 if rev in self.roots:
201 201 self.roots.remove(rev)
202 202 self.roots.extend(realparents(rev))
203 203 return True
204 204
205 205 return False
206 206
207 207 # it might be worthwhile to do this in the iterator if the rev range
208 208 # is descending and the prune args are all within that range
209 209 for rev in opts.get('prune', ()):
210 210 rev = repo.changelog.rev(repo.lookup(rev))
211 211 ff = followfilter()
212 212 stop = min(revs[0], revs[-1])
213 213 for x in xrange(rev, stop-1, -1):
214 214 if ff.match(x) and x in wanted:
215 215 del wanted[x]
216 216
217 217 def iterate():
218 218 if follow and not files:
219 219 ff = followfilter(onlyfirst=opts.get('follow_first'))
220 220 def want(rev):
221 221 if ff.match(rev) and rev in wanted:
222 222 return True
223 223 return False
224 224 else:
225 225 def want(rev):
226 226 return rev in wanted
227 227
228 228 for i, window in increasing_windows(0, len(revs)):
229 229 yield 'window', revs[0] < revs[-1], revs[-1]
230 230 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
231 231 srevs = list(nrevs)
232 232 srevs.sort()
233 233 for rev in srevs:
234 234 fns = fncache.get(rev)
235 235 if not fns:
236 236 def fns_generator():
237 237 for f in change(rev)[3]:
238 238 if matchfn(f):
239 239 yield f
240 240 fns = fns_generator()
241 241 yield 'add', rev, fns
242 242 for rev in nrevs:
243 243 yield 'iter', rev, None
244 244 return iterate(), matchfn
245 245
246 246 def write_bundle(cg, filename=None, compress=True):
247 247 """Write a bundle file and return its filename.
248 248
249 249 Existing files will not be overwritten.
250 250 If no filename is specified, a temporary file is created.
251 251 bz2 compression can be turned off.
252 252 The bundle file will be deleted in case of errors.
253 253 """
254 254 class nocompress(object):
255 255 def compress(self, x):
256 256 return x
257 257 def flush(self):
258 258 return ""
259 259
260 260 fh = None
261 261 cleanup = None
262 262 try:
263 263 if filename:
264 264 if os.path.exists(filename):
265 265 raise util.Abort(_("file '%s' already exists") % filename)
266 266 fh = open(filename, "wb")
267 267 else:
268 268 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
269 269 fh = os.fdopen(fd, "wb")
270 270 cleanup = filename
271 271
272 272 if compress:
273 273 fh.write("HG10")
274 274 z = bz2.BZ2Compressor(9)
275 275 else:
276 276 fh.write("HG10UN")
277 277 z = nocompress()
278 278 # parse the changegroup data, otherwise we will block
279 279 # in case of sshrepo because we don't know the end of the stream
280 280
281 281 # an empty chunkiter is the end of the changegroup
282 282 empty = False
283 283 while not empty:
284 284 empty = True
285 285 for chunk in changegroup.chunkiter(cg):
286 286 empty = False
287 287 fh.write(z.compress(changegroup.genchunk(chunk)))
288 288 fh.write(z.compress(changegroup.closechunk()))
289 289 fh.write(z.flush())
290 290 cleanup = None
291 291 return filename
292 292 finally:
293 293 if fh is not None:
294 294 fh.close()
295 295 if cleanup is not None:
296 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 298 def setremoteconfig(ui, opts):
423 299 "copy remote options to ui tree"
424 300 if opts.get('ssh'):
425 301 ui.setconfig("ui", "ssh", opts['ssh'])
426 302 if opts.get('remotecmd'):
427 303 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
428 304
429 305 def show_version(ui):
430 306 """output version and copyright information"""
431 307 ui.write(_("Mercurial Distributed SCM (version %s)\n")
432 308 % version.get_version())
433 309 ui.status(_(
434 310 "\nCopyright (C) 2005, 2006 Matt Mackall <mpm@selenic.com>\n"
435 311 "This is free software; see the source for copying conditions. "
436 312 "There is NO\nwarranty; "
437 313 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
438 314 ))
439 315
440 316 def help_(ui, name=None, with_version=False):
441 317 """show help for a command, extension, or list of commands
442 318
443 319 With no arguments, print a list of commands and short help.
444 320
445 321 Given a command name, print help for that command.
446 322
447 323 Given an extension name, print help for that extension, and the
448 324 commands it provides."""
449 325 option_lists = []
450 326
451 327 def helpcmd(name):
452 328 if with_version:
453 329 show_version(ui)
454 330 ui.write('\n')
455 331 aliases, i = findcmd(ui, name)
456 332 # synopsis
457 333 ui.write("%s\n\n" % i[2])
458 334
459 335 # description
460 336 doc = i[0].__doc__
461 337 if not doc:
462 338 doc = _("(No help text available)")
463 339 if ui.quiet:
464 340 doc = doc.splitlines(0)[0]
465 341 ui.write("%s\n" % doc.rstrip())
466 342
467 343 if not ui.quiet:
468 344 # aliases
469 345 if len(aliases) > 1:
470 346 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
471 347
472 348 # options
473 349 if i[1]:
474 350 option_lists.append(("options", i[1]))
475 351
476 352 def helplist(select=None):
477 353 h = {}
478 354 cmds = {}
479 355 for c, e in table.items():
480 356 f = c.split("|", 1)[0]
481 357 if select and not select(f):
482 358 continue
483 359 if name == "shortlist" and not f.startswith("^"):
484 360 continue
485 361 f = f.lstrip("^")
486 362 if not ui.debugflag and f.startswith("debug"):
487 363 continue
488 364 doc = e[0].__doc__
489 365 if not doc:
490 366 doc = _("(No help text available)")
491 367 h[f] = doc.splitlines(0)[0].rstrip()
492 368 cmds[f] = c.lstrip("^")
493 369
494 370 fns = h.keys()
495 371 fns.sort()
496 372 m = max(map(len, fns))
497 373 for f in fns:
498 374 if ui.verbose:
499 375 commands = cmds[f].replace("|",", ")
500 376 ui.write(" %s:\n %s\n"%(commands, h[f]))
501 377 else:
502 378 ui.write(' %-*s %s\n' % (m, f, h[f]))
503 379
504 380 def helpext(name):
505 381 try:
506 382 mod = findext(name)
507 383 except KeyError:
508 384 raise UnknownCommand(name)
509 385
510 386 doc = (mod.__doc__ or _('No help text available')).splitlines(0)
511 387 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
512 388 for d in doc[1:]:
513 389 ui.write(d, '\n')
514 390
515 391 ui.status('\n')
516 392 if ui.verbose:
517 393 ui.status(_('list of commands:\n\n'))
518 394 else:
519 395 ui.status(_('list of commands (use "hg help -v %s" '
520 396 'to show aliases and global options):\n\n') % name)
521 397
522 398 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in mod.cmdtable])
523 399 helplist(modcmds.has_key)
524 400
525 401 if name and name != 'shortlist':
526 402 try:
527 403 helpcmd(name)
528 404 except UnknownCommand:
529 405 helpext(name)
530 406
531 407 else:
532 408 # program name
533 409 if ui.verbose or with_version:
534 410 show_version(ui)
535 411 else:
536 412 ui.status(_("Mercurial Distributed SCM\n"))
537 413 ui.status('\n')
538 414
539 415 # list of commands
540 416 if name == "shortlist":
541 417 ui.status(_('basic commands (use "hg help" '
542 418 'for the full list or option "-v" for details):\n\n'))
543 419 elif ui.verbose:
544 420 ui.status(_('list of commands:\n\n'))
545 421 else:
546 422 ui.status(_('list of commands (use "hg help -v" '
547 423 'to show aliases and global options):\n\n'))
548 424
549 425 helplist()
550 426
551 427 # global options
552 428 if ui.verbose:
553 429 option_lists.append(("global options", globalopts))
554 430
555 431 # list all option lists
556 432 opt_output = []
557 433 for title, options in option_lists:
558 434 opt_output.append(("\n%s:\n" % title, None))
559 435 for shortopt, longopt, default, desc in options:
560 436 if "DEPRECATED" in desc and not ui.verbose: continue
561 437 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
562 438 longopt and " --%s" % longopt),
563 439 "%s%s" % (desc,
564 440 default
565 441 and _(" (default: %s)") % default
566 442 or "")))
567 443
568 444 if opt_output:
569 445 opts_len = max([len(line[0]) for line in opt_output if line[1]])
570 446 for first, second in opt_output:
571 447 if second:
572 448 ui.write(" %-*s %s\n" % (opts_len, first, second))
573 449 else:
574 450 ui.write("%s\n" % first)
575 451
576 452 # Commands start here, listed alphabetically
577 453
578 454 def add(ui, repo, *pats, **opts):
579 455 """add the specified files on the next commit
580 456
581 457 Schedule files to be version controlled and added to the repository.
582 458
583 459 The files will be added to the repository at the next commit.
584 460
585 461 If no names are given, add all files in the repository.
586 462 """
587 463
588 464 names = []
589 465 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
590 466 if exact:
591 467 if ui.verbose:
592 468 ui.status(_('adding %s\n') % rel)
593 469 names.append(abs)
594 470 elif repo.dirstate.state(abs) == '?':
595 471 ui.status(_('adding %s\n') % rel)
596 472 names.append(abs)
597 473 if not opts.get('dry_run'):
598 474 repo.add(names)
599 475
600 476 def addremove(ui, repo, *pats, **opts):
601 477 """add all new files, delete all missing files
602 478
603 479 Add all new files and remove all missing files from the repository.
604 480
605 481 New files are ignored if they match any of the patterns in .hgignore. As
606 482 with add, these changes take effect at the next commit.
607 483
608 484 Use the -s option to detect renamed files. With a parameter > 0,
609 485 this compares every removed file with every added file and records
610 486 those similar enough as renames. This option takes a percentage
611 487 between 0 (disabled) and 100 (files must be identical) as its
612 488 parameter. Detecting renamed files this way can be expensive.
613 489 """
614 490 sim = float(opts.get('similarity') or 0)
615 491 if sim < 0 or sim > 100:
616 492 raise util.Abort(_('similarity must be between 0 and 100'))
617 493 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
618 494
619 495 def annotate(ui, repo, *pats, **opts):
620 496 """show changeset information per file line
621 497
622 498 List changes in files, showing the revision id responsible for each line
623 499
624 500 This command is useful to discover who did a change or when a change took
625 501 place.
626 502
627 503 Without the -a option, annotate will avoid processing files it
628 504 detects as binary. With -a, annotate will generate an annotation
629 505 anyway, probably with undesirable results.
630 506 """
631 507 getdate = util.cachefunc(lambda x: util.datestr(x.date()))
632 508
633 509 if not pats:
634 510 raise util.Abort(_('at least one file name or pattern required'))
635 511
636 512 opmap = [['user', lambda x: ui.shortuser(x.user())],
637 513 ['number', lambda x: str(x.rev())],
638 514 ['changeset', lambda x: short(x.node())],
639 515 ['date', getdate], ['follow', lambda x: x.path()]]
640 516 if (not opts['user'] and not opts['changeset'] and not opts['date']
641 517 and not opts['follow']):
642 518 opts['number'] = 1
643 519
644 520 ctx = repo.changectx(opts['rev'])
645 521
646 522 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
647 523 node=ctx.node()):
648 524 fctx = ctx.filectx(abs)
649 525 if not opts['text'] and util.binary(fctx.data()):
650 526 ui.write(_("%s: binary file\n") % ((pats and rel) or abs))
651 527 continue
652 528
653 529 lines = fctx.annotate(follow=opts.get('follow'))
654 530 pieces = []
655 531
656 532 for o, f in opmap:
657 533 if opts[o]:
658 534 l = [f(n) for n, dummy in lines]
659 535 if l:
660 536 m = max(map(len, l))
661 537 pieces.append(["%*s" % (m, x) for x in l])
662 538
663 539 if pieces:
664 540 for p, l in zip(zip(*pieces), lines):
665 541 ui.write("%s: %s" % (" ".join(p), l[1]))
666 542
667 543 def archive(ui, repo, dest, **opts):
668 544 '''create unversioned archive of a repository revision
669 545
670 546 By default, the revision used is the parent of the working
671 547 directory; use "-r" to specify a different revision.
672 548
673 549 To specify the type of archive to create, use "-t". Valid
674 550 types are:
675 551
676 552 "files" (default): a directory full of files
677 553 "tar": tar archive, uncompressed
678 554 "tbz2": tar archive, compressed using bzip2
679 555 "tgz": tar archive, compressed using gzip
680 556 "uzip": zip archive, uncompressed
681 557 "zip": zip archive, compressed using deflate
682 558
683 559 The exact name of the destination archive or directory is given
684 560 using a format string; see "hg help export" for details.
685 561
686 562 Each member added to an archive file has a directory prefix
687 563 prepended. Use "-p" to specify a format string for the prefix.
688 564 The default is the basename of the archive, with suffixes removed.
689 565 '''
690 566
691 567 node = repo.changectx(opts['rev']).node()
692 568 dest = cmdutil.make_filename(repo, dest, node)
693 569 if os.path.realpath(dest) == repo.root:
694 570 raise util.Abort(_('repository root cannot be destination'))
695 571 dummy, matchfn, dummy = cmdutil.matchpats(repo, [], opts)
696 572 kind = opts.get('type') or 'files'
697 573 prefix = opts['prefix']
698 574 if dest == '-':
699 575 if kind == 'files':
700 576 raise util.Abort(_('cannot archive plain files to stdout'))
701 577 dest = sys.stdout
702 578 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
703 579 prefix = cmdutil.make_filename(repo, prefix, node)
704 580 archival.archive(repo, dest, node, kind, not opts['no_decode'],
705 581 matchfn, prefix)
706 582
707 583 def backout(ui, repo, rev, **opts):
708 584 '''reverse effect of earlier changeset
709 585
710 586 Commit the backed out changes as a new changeset. The new
711 587 changeset is a child of the backed out changeset.
712 588
713 589 If you back out a changeset other than the tip, a new head is
714 590 created. This head is the parent of the working directory. If
715 591 you back out an old changeset, your working directory will appear
716 592 old after the backout. You should merge the backout changeset
717 593 with another head.
718 594
719 595 The --merge option remembers the parent of the working directory
720 596 before starting the backout, then merges the new head with that
721 597 changeset afterwards. This saves you from doing the merge by
722 598 hand. The result of this merge is not committed, as for a normal
723 599 merge.'''
724 600
725 601 bail_if_changed(repo)
726 602 op1, op2 = repo.dirstate.parents()
727 603 if op2 != nullid:
728 604 raise util.Abort(_('outstanding uncommitted merge'))
729 605 node = repo.lookup(rev)
730 606 p1, p2 = repo.changelog.parents(node)
731 607 if p1 == nullid:
732 608 raise util.Abort(_('cannot back out a change with no parents'))
733 609 if p2 != nullid:
734 610 if not opts['parent']:
735 611 raise util.Abort(_('cannot back out a merge changeset without '
736 612 '--parent'))
737 613 p = repo.lookup(opts['parent'])
738 614 if p not in (p1, p2):
739 615 raise util.Abort(_('%s is not a parent of %s' %
740 616 (short(p), short(node))))
741 617 parent = p
742 618 else:
743 619 if opts['parent']:
744 620 raise util.Abort(_('cannot use --parent on non-merge changeset'))
745 621 parent = p1
746 622 hg.clean(repo, node, show_stats=False)
747 623 revert_opts = opts.copy()
748 624 revert_opts['all'] = True
749 625 revert_opts['rev'] = hex(parent)
750 626 revert(ui, repo, **revert_opts)
751 627 commit_opts = opts.copy()
752 628 commit_opts['addremove'] = False
753 629 if not commit_opts['message'] and not commit_opts['logfile']:
754 630 commit_opts['message'] = _("Backed out changeset %s") % (hex(node))
755 631 commit_opts['force_editor'] = True
756 632 commit(ui, repo, **commit_opts)
757 633 def nice(node):
758 634 return '%d:%s' % (repo.changelog.rev(node), short(node))
759 635 ui.status(_('changeset %s backs out changeset %s\n') %
760 636 (nice(repo.changelog.tip()), nice(node)))
761 637 if op1 != node:
762 638 if opts['merge']:
763 639 ui.status(_('merging with changeset %s\n') % nice(op1))
764 640 n = _lookup(repo, hex(op1))
765 641 hg.merge(repo, n)
766 642 else:
767 643 ui.status(_('the backout changeset is a new head - '
768 644 'do not forget to merge\n'))
769 645 ui.status(_('(use "backout --merge" '
770 646 'if you want to auto-merge)\n'))
771 647
772 648 def branch(ui, repo, label=None):
773 649 """set or show the current branch name
774 650
775 651 With <name>, set the current branch name. Otherwise, show the
776 652 current branch name.
777 653 """
778 654
779 655 if label is not None:
780 656 repo.opener("branch", "w").write(label)
781 657 else:
782 658 b = repo.workingctx().branch()
783 659 if b:
784 660 ui.write("%s\n" % b)
785 661
786 662 def branches(ui, repo):
787 663 """list repository named branches
788 664
789 665 List the repository's named branches.
790 666 """
791 667 b = repo.branchtags()
792 668 l = [(-repo.changelog.rev(n), n, t) for t,n in b.items()]
793 669 l.sort()
794 670 for r, n, t in l:
795 671 hexfunc = ui.debugflag and hex or short
796 672 if ui.quiet:
797 673 ui.write("%s\n" % t)
798 674 else:
799 675 ui.write("%-30s %s:%s\n" % (t, -r, hexfunc(n)))
800 676
801 677 def bundle(ui, repo, fname, dest=None, **opts):
802 678 """create a changegroup file
803 679
804 680 Generate a compressed changegroup file collecting changesets not
805 681 found in the other repository.
806 682
807 683 If no destination repository is specified the destination is assumed
808 684 to have all the nodes specified by one or more --base parameters.
809 685
810 686 The bundle file can then be transferred using conventional means and
811 687 applied to another repository with the unbundle or pull command.
812 688 This is useful when direct push and pull are not available or when
813 689 exporting an entire repository is undesirable.
814 690
815 691 Applying bundles preserves all changeset contents including
816 692 permissions, copy/rename information, and revision history.
817 693 """
818 694 revs = opts.get('rev') or None
819 695 if revs:
820 696 revs = [repo.lookup(rev) for rev in revs]
821 697 base = opts.get('base')
822 698 if base:
823 699 if dest:
824 700 raise util.Abort(_("--base is incompatible with specifiying "
825 701 "a destination"))
826 702 base = [repo.lookup(rev) for rev in base]
827 703 # create the right base
828 704 # XXX: nodesbetween / changegroup* should be "fixed" instead
829 705 o = []
830 706 has = {nullid: None}
831 707 for n in base:
832 708 has.update(repo.changelog.reachable(n))
833 709 if revs:
834 710 visit = list(revs)
835 711 else:
836 712 visit = repo.changelog.heads()
837 713 seen = {}
838 714 while visit:
839 715 n = visit.pop(0)
840 716 parents = [p for p in repo.changelog.parents(n) if p not in has]
841 717 if len(parents) == 0:
842 718 o.insert(0, n)
843 719 else:
844 720 for p in parents:
845 721 if p not in seen:
846 722 seen[p] = 1
847 723 visit.append(p)
848 724 else:
849 725 setremoteconfig(ui, opts)
850 726 dest = ui.expandpath(dest or 'default-push', dest or 'default')
851 727 other = hg.repository(ui, dest)
852 728 o = repo.findoutgoing(other, force=opts['force'])
853 729
854 730 if revs:
855 731 cg = repo.changegroupsubset(o, revs, 'bundle')
856 732 else:
857 733 cg = repo.changegroup(o, 'bundle')
858 734 write_bundle(cg, fname)
859 735
860 736 def cat(ui, repo, file1, *pats, **opts):
861 737 """output the latest or given revisions of files
862 738
863 739 Print the specified files as they were at the given revision.
864 740 If no revision is given then working dir parent is used, or tip
865 741 if no revision is checked out.
866 742
867 743 Output may be to a file, in which case the name of the file is
868 744 given using a format string. The formatting rules are the same as
869 745 for the export command, with the following additions:
870 746
871 747 %s basename of file being printed
872 748 %d dirname of file being printed, or '.' if in repo root
873 749 %p root-relative path name of file being printed
874 750 """
875 751 ctx = repo.changectx(opts['rev'])
876 752 for src, abs, rel, exact in cmdutil.walk(repo, (file1,) + pats, opts,
877 753 ctx.node()):
878 754 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
879 755 fp.write(ctx.filectx(abs).data())
880 756
881 757 def clone(ui, source, dest=None, **opts):
882 758 """make a copy of an existing repository
883 759
884 760 Create a copy of an existing repository in a new directory.
885 761
886 762 If no destination directory name is specified, it defaults to the
887 763 basename of the source.
888 764
889 765 The location of the source is added to the new repository's
890 766 .hg/hgrc file, as the default to be used for future pulls.
891 767
892 768 For efficiency, hardlinks are used for cloning whenever the source
893 769 and destination are on the same filesystem (note this applies only
894 770 to the repository data, not to the checked out files). Some
895 771 filesystems, such as AFS, implement hardlinking incorrectly, but
896 772 do not report errors. In these cases, use the --pull option to
897 773 avoid hardlinking.
898 774
899 775 You can safely clone repositories and checked out files using full
900 776 hardlinks with
901 777
902 778 $ cp -al REPO REPOCLONE
903 779
904 780 which is the fastest way to clone. However, the operation is not
905 781 atomic (making sure REPO is not modified during the operation is
906 782 up to you) and you have to make sure your editor breaks hardlinks
907 783 (Emacs and most Linux Kernel tools do so).
908 784
909 785 If you use the -r option to clone up to a specific revision, no
910 786 subsequent revisions will be present in the cloned repository.
911 787 This option implies --pull, even on local repositories.
912 788
913 789 See pull for valid source format details.
914 790
915 791 It is possible to specify an ssh:// URL as the destination, but no
916 792 .hg/hgrc and working directory will be created on the remote side.
917 793 Look at the help text for the pull command for important details
918 794 about ssh:// URLs.
919 795 """
920 796 setremoteconfig(ui, opts)
921 797 hg.clone(ui, ui.expandpath(source), dest,
922 798 pull=opts['pull'],
923 799 stream=opts['uncompressed'],
924 800 rev=opts['rev'],
925 801 update=not opts['noupdate'])
926 802
927 803 def commit(ui, repo, *pats, **opts):
928 804 """commit the specified files or all outstanding changes
929 805
930 806 Commit changes to the given files into the repository.
931 807
932 808 If a list of files is omitted, all changes reported by "hg status"
933 809 will be committed.
934 810
935 811 If no commit message is specified, the editor configured in your hgrc
936 812 or in the EDITOR environment variable is started to enter a message.
937 813 """
938 814 message = logmessage(opts)
939 815
940 816 if opts['addremove']:
941 817 cmdutil.addremove(repo, pats, opts)
942 818 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
943 819 if pats:
944 820 modified, added, removed = repo.status(files=fns, match=match)[:3]
945 821 files = modified + added + removed
946 822 else:
947 823 files = []
948 824 try:
949 825 repo.commit(files, message, opts['user'], opts['date'], match,
950 826 force_editor=opts.get('force_editor'))
951 827 except ValueError, inst:
952 828 raise util.Abort(str(inst))
953 829
954 830 def docopy(ui, repo, pats, opts, wlock):
955 831 # called with the repo lock held
956 832 cwd = repo.getcwd()
957 833 errors = 0
958 834 copied = []
959 835 targets = {}
960 836
961 837 def okaytocopy(abs, rel, exact):
962 838 reasons = {'?': _('is not managed'),
963 839 'a': _('has been marked for add'),
964 840 'r': _('has been marked for remove')}
965 841 state = repo.dirstate.state(abs)
966 842 reason = reasons.get(state)
967 843 if reason:
968 844 if state == 'a':
969 845 origsrc = repo.dirstate.copied(abs)
970 846 if origsrc is not None:
971 847 return origsrc
972 848 if exact:
973 849 ui.warn(_('%s: not copying - file %s\n') % (rel, reason))
974 850 else:
975 851 return abs
976 852
977 853 def copy(origsrc, abssrc, relsrc, target, exact):
978 854 abstarget = util.canonpath(repo.root, cwd, target)
979 855 reltarget = util.pathto(cwd, abstarget)
980 856 prevsrc = targets.get(abstarget)
981 857 if prevsrc is not None:
982 858 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
983 859 (reltarget, abssrc, prevsrc))
984 860 return
985 861 if (not opts['after'] and os.path.exists(reltarget) or
986 862 opts['after'] and repo.dirstate.state(abstarget) not in '?r'):
987 863 if not opts['force']:
988 864 ui.warn(_('%s: not overwriting - file exists\n') %
989 865 reltarget)
990 866 return
991 867 if not opts['after'] and not opts.get('dry_run'):
992 868 os.unlink(reltarget)
993 869 if opts['after']:
994 870 if not os.path.exists(reltarget):
995 871 return
996 872 else:
997 873 targetdir = os.path.dirname(reltarget) or '.'
998 874 if not os.path.isdir(targetdir) and not opts.get('dry_run'):
999 875 os.makedirs(targetdir)
1000 876 try:
1001 877 restore = repo.dirstate.state(abstarget) == 'r'
1002 878 if restore and not opts.get('dry_run'):
1003 879 repo.undelete([abstarget], wlock)
1004 880 try:
1005 881 if not opts.get('dry_run'):
1006 882 util.copyfile(relsrc, reltarget)
1007 883 restore = False
1008 884 finally:
1009 885 if restore:
1010 886 repo.remove([abstarget], wlock)
1011 887 except IOError, inst:
1012 888 if inst.errno == errno.ENOENT:
1013 889 ui.warn(_('%s: deleted in working copy\n') % relsrc)
1014 890 else:
1015 891 ui.warn(_('%s: cannot copy - %s\n') %
1016 892 (relsrc, inst.strerror))
1017 893 errors += 1
1018 894 return
1019 895 if ui.verbose or not exact:
1020 896 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1021 897 targets[abstarget] = abssrc
1022 898 if abstarget != origsrc and not opts.get('dry_run'):
1023 899 repo.copy(origsrc, abstarget, wlock)
1024 900 copied.append((abssrc, relsrc, exact))
1025 901
1026 902 def targetpathfn(pat, dest, srcs):
1027 903 if os.path.isdir(pat):
1028 904 abspfx = util.canonpath(repo.root, cwd, pat)
1029 905 if destdirexists:
1030 906 striplen = len(os.path.split(abspfx)[0])
1031 907 else:
1032 908 striplen = len(abspfx)
1033 909 if striplen:
1034 910 striplen += len(os.sep)
1035 911 res = lambda p: os.path.join(dest, p[striplen:])
1036 912 elif destdirexists:
1037 913 res = lambda p: os.path.join(dest, os.path.basename(p))
1038 914 else:
1039 915 res = lambda p: dest
1040 916 return res
1041 917
1042 918 def targetpathafterfn(pat, dest, srcs):
1043 919 if util.patkind(pat, None)[0]:
1044 920 # a mercurial pattern
1045 921 res = lambda p: os.path.join(dest, os.path.basename(p))
1046 922 else:
1047 923 abspfx = util.canonpath(repo.root, cwd, pat)
1048 924 if len(abspfx) < len(srcs[0][0]):
1049 925 # A directory. Either the target path contains the last
1050 926 # component of the source path or it does not.
1051 927 def evalpath(striplen):
1052 928 score = 0
1053 929 for s in srcs:
1054 930 t = os.path.join(dest, s[0][striplen:])
1055 931 if os.path.exists(t):
1056 932 score += 1
1057 933 return score
1058 934
1059 935 striplen = len(abspfx)
1060 936 if striplen:
1061 937 striplen += len(os.sep)
1062 938 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1063 939 score = evalpath(striplen)
1064 940 striplen1 = len(os.path.split(abspfx)[0])
1065 941 if striplen1:
1066 942 striplen1 += len(os.sep)
1067 943 if evalpath(striplen1) > score:
1068 944 striplen = striplen1
1069 945 res = lambda p: os.path.join(dest, p[striplen:])
1070 946 else:
1071 947 # a file
1072 948 if destdirexists:
1073 949 res = lambda p: os.path.join(dest, os.path.basename(p))
1074 950 else:
1075 951 res = lambda p: dest
1076 952 return res
1077 953
1078 954
1079 955 pats = list(pats)
1080 956 if not pats:
1081 957 raise util.Abort(_('no source or destination specified'))
1082 958 if len(pats) == 1:
1083 959 raise util.Abort(_('no destination specified'))
1084 960 dest = pats.pop()
1085 961 destdirexists = os.path.isdir(dest)
1086 962 if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists:
1087 963 raise util.Abort(_('with multiple sources, destination must be an '
1088 964 'existing directory'))
1089 965 if opts['after']:
1090 966 tfn = targetpathafterfn
1091 967 else:
1092 968 tfn = targetpathfn
1093 969 copylist = []
1094 970 for pat in pats:
1095 971 srcs = []
1096 972 for tag, abssrc, relsrc, exact in cmdutil.walk(repo, [pat], opts):
1097 973 origsrc = okaytocopy(abssrc, relsrc, exact)
1098 974 if origsrc:
1099 975 srcs.append((origsrc, abssrc, relsrc, exact))
1100 976 if not srcs:
1101 977 continue
1102 978 copylist.append((tfn(pat, dest, srcs), srcs))
1103 979 if not copylist:
1104 980 raise util.Abort(_('no files to copy'))
1105 981
1106 982 for targetpath, srcs in copylist:
1107 983 for origsrc, abssrc, relsrc, exact in srcs:
1108 984 copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact)
1109 985
1110 986 if errors:
1111 987 ui.warn(_('(consider using --after)\n'))
1112 988 return errors, copied
1113 989
1114 990 def copy(ui, repo, *pats, **opts):
1115 991 """mark files as copied for the next commit
1116 992
1117 993 Mark dest as having copies of source files. If dest is a
1118 994 directory, copies are put in that directory. If dest is a file,
1119 995 there can only be one source.
1120 996
1121 997 By default, this command copies the contents of files as they
1122 998 stand in the working directory. If invoked with --after, the
1123 999 operation is recorded, but no copying is performed.
1124 1000
1125 1001 This command takes effect in the next commit.
1126 1002
1127 1003 NOTE: This command should be treated as experimental. While it
1128 1004 should properly record copied files, this information is not yet
1129 1005 fully used by merge, nor fully reported by log.
1130 1006 """
1131 1007 wlock = repo.wlock(0)
1132 1008 errs, copied = docopy(ui, repo, pats, opts, wlock)
1133 1009 return errs
1134 1010
1135 1011 def debugancestor(ui, index, rev1, rev2):
1136 1012 """find the ancestor revision of two revisions in a given index"""
1137 1013 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0)
1138 1014 a = r.ancestor(r.lookup(rev1), r.lookup(rev2))
1139 1015 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
1140 1016
1141 1017 def debugcomplete(ui, cmd='', **opts):
1142 1018 """returns the completion list associated with the given command"""
1143 1019
1144 1020 if opts['options']:
1145 1021 options = []
1146 1022 otables = [globalopts]
1147 1023 if cmd:
1148 1024 aliases, entry = findcmd(ui, cmd)
1149 1025 otables.append(entry[1])
1150 1026 for t in otables:
1151 1027 for o in t:
1152 1028 if o[0]:
1153 1029 options.append('-%s' % o[0])
1154 1030 options.append('--%s' % o[1])
1155 1031 ui.write("%s\n" % "\n".join(options))
1156 1032 return
1157 1033
1158 1034 clist = findpossible(ui, cmd).keys()
1159 1035 clist.sort()
1160 1036 ui.write("%s\n" % "\n".join(clist))
1161 1037
1162 1038 def debugrebuildstate(ui, repo, rev=None):
1163 1039 """rebuild the dirstate as it would look like for the given revision"""
1164 1040 if not rev:
1165 1041 rev = repo.changelog.tip()
1166 1042 else:
1167 1043 rev = repo.lookup(rev)
1168 1044 change = repo.changelog.read(rev)
1169 1045 n = change[0]
1170 1046 files = repo.manifest.read(n)
1171 1047 wlock = repo.wlock()
1172 1048 repo.dirstate.rebuild(rev, files)
1173 1049
1174 1050 def debugcheckstate(ui, repo):
1175 1051 """validate the correctness of the current dirstate"""
1176 1052 parent1, parent2 = repo.dirstate.parents()
1177 1053 repo.dirstate.read()
1178 1054 dc = repo.dirstate.map
1179 1055 keys = dc.keys()
1180 1056 keys.sort()
1181 1057 m1n = repo.changelog.read(parent1)[0]
1182 1058 m2n = repo.changelog.read(parent2)[0]
1183 1059 m1 = repo.manifest.read(m1n)
1184 1060 m2 = repo.manifest.read(m2n)
1185 1061 errors = 0
1186 1062 for f in dc:
1187 1063 state = repo.dirstate.state(f)
1188 1064 if state in "nr" and f not in m1:
1189 1065 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
1190 1066 errors += 1
1191 1067 if state in "a" and f in m1:
1192 1068 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
1193 1069 errors += 1
1194 1070 if state in "m" and f not in m1 and f not in m2:
1195 1071 ui.warn(_("%s in state %s, but not in either manifest\n") %
1196 1072 (f, state))
1197 1073 errors += 1
1198 1074 for f in m1:
1199 1075 state = repo.dirstate.state(f)
1200 1076 if state not in "nrm":
1201 1077 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
1202 1078 errors += 1
1203 1079 if errors:
1204 1080 error = _(".hg/dirstate inconsistent with current parent's manifest")
1205 1081 raise util.Abort(error)
1206 1082
1207 1083 def showconfig(ui, repo, *values, **opts):
1208 1084 """show combined config settings from all hgrc files
1209 1085
1210 1086 With no args, print names and values of all config items.
1211 1087
1212 1088 With one arg of the form section.name, print just the value of
1213 1089 that config item.
1214 1090
1215 1091 With multiple args, print names and values of all config items
1216 1092 with matching section names."""
1217 1093
1218 1094 untrusted = bool(opts.get('untrusted'))
1219 1095 if values:
1220 1096 if len([v for v in values if '.' in v]) > 1:
1221 1097 raise util.Abort(_('only one config item permitted'))
1222 1098 for section, name, value in ui.walkconfig(untrusted=untrusted):
1223 1099 sectname = section + '.' + name
1224 1100 if values:
1225 1101 for v in values:
1226 1102 if v == section:
1227 1103 ui.write('%s=%s\n' % (sectname, value))
1228 1104 elif v == sectname:
1229 1105 ui.write(value, '\n')
1230 1106 else:
1231 1107 ui.write('%s=%s\n' % (sectname, value))
1232 1108
1233 1109 def debugsetparents(ui, repo, rev1, rev2=None):
1234 1110 """manually set the parents of the current working directory
1235 1111
1236 1112 This is useful for writing repository conversion tools, but should
1237 1113 be used with care.
1238 1114 """
1239 1115
1240 1116 if not rev2:
1241 1117 rev2 = hex(nullid)
1242 1118
1243 1119 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
1244 1120
1245 1121 def debugstate(ui, repo):
1246 1122 """show the contents of the current dirstate"""
1247 1123 repo.dirstate.read()
1248 1124 dc = repo.dirstate.map
1249 1125 keys = dc.keys()
1250 1126 keys.sort()
1251 1127 for file_ in keys:
1252 1128 ui.write("%c %3o %10d %s %s\n"
1253 1129 % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2],
1254 1130 time.strftime("%x %X",
1255 1131 time.localtime(dc[file_][3])), file_))
1256 1132 for f in repo.dirstate.copies():
1257 1133 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1258 1134
1259 1135 def debugdata(ui, file_, rev):
1260 1136 """dump the contents of an data file revision"""
1261 1137 r = revlog.revlog(util.opener(os.getcwd(), audit=False),
1262 1138 file_[:-2] + ".i", file_, 0)
1263 1139 try:
1264 1140 ui.write(r.revision(r.lookup(rev)))
1265 1141 except KeyError:
1266 1142 raise util.Abort(_('invalid revision identifier %s') % rev)
1267 1143
1268 1144 def debugindex(ui, file_):
1269 1145 """dump the contents of an index file"""
1270 1146 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1271 1147 ui.write(" rev offset length base linkrev" +
1272 1148 " nodeid p1 p2\n")
1273 1149 for i in xrange(r.count()):
1274 1150 node = r.node(i)
1275 1151 pp = r.parents(node)
1276 1152 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1277 1153 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
1278 1154 short(node), short(pp[0]), short(pp[1])))
1279 1155
1280 1156 def debugindexdot(ui, file_):
1281 1157 """dump an index DAG as a .dot file"""
1282 1158 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0)
1283 1159 ui.write("digraph G {\n")
1284 1160 for i in xrange(r.count()):
1285 1161 node = r.node(i)
1286 1162 pp = r.parents(node)
1287 1163 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1288 1164 if pp[1] != nullid:
1289 1165 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1290 1166 ui.write("}\n")
1291 1167
1292 1168 def debugrename(ui, repo, file, rev=None):
1293 1169 """dump rename information"""
1294 1170 r = repo.file(relpath(repo, [file])[0])
1295 1171 if rev:
1296 1172 try:
1297 1173 # assume all revision numbers are for changesets
1298 1174 n = repo.lookup(rev)
1299 1175 change = repo.changelog.read(n)
1300 1176 m = repo.manifest.read(change[0])
1301 1177 n = m[relpath(repo, [file])[0]]
1302 1178 except (hg.RepoError, KeyError):
1303 1179 n = r.lookup(rev)
1304 1180 else:
1305 1181 n = r.tip()
1306 1182 m = r.renamed(n)
1307 1183 if m:
1308 1184 ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1])))
1309 1185 else:
1310 1186 ui.write(_("not renamed\n"))
1311 1187
1312 1188 def debugwalk(ui, repo, *pats, **opts):
1313 1189 """show how files match on given patterns"""
1314 1190 items = list(cmdutil.walk(repo, pats, opts))
1315 1191 if not items:
1316 1192 return
1317 1193 fmt = '%%s %%-%ds %%-%ds %%s' % (
1318 1194 max([len(abs) for (src, abs, rel, exact) in items]),
1319 1195 max([len(rel) for (src, abs, rel, exact) in items]))
1320 1196 for src, abs, rel, exact in items:
1321 1197 line = fmt % (src, abs, rel, exact and 'exact' or '')
1322 1198 ui.write("%s\n" % line.rstrip())
1323 1199
1324 1200 def diff(ui, repo, *pats, **opts):
1325 1201 """diff repository (or selected files)
1326 1202
1327 1203 Show differences between revisions for the specified files.
1328 1204
1329 1205 Differences between files are shown using the unified diff format.
1330 1206
1331 1207 When two revision arguments are given, then changes are shown
1332 1208 between those revisions. If only one revision is specified then
1333 1209 that revision is compared to the working directory, and, when no
1334 1210 revisions are specified, the working directory files are compared
1335 1211 to its parent.
1336 1212
1337 1213 Without the -a option, diff will avoid generating diffs of files
1338 1214 it detects as binary. With -a, diff will generate a diff anyway,
1339 1215 probably with undesirable results.
1340 1216 """
1341 1217 node1, node2 = cmdutil.revpair(ui, repo, opts['rev'])
1342 1218
1343 1219 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1344 1220
1345 1221 patch.diff(repo, node1, node2, fns, match=matchfn,
1346 1222 opts=patch.diffopts(ui, opts))
1347 1223
1348 1224 def export(ui, repo, *changesets, **opts):
1349 1225 """dump the header and diffs for one or more changesets
1350 1226
1351 1227 Print the changeset header and diffs for one or more revisions.
1352 1228
1353 1229 The information shown in the changeset header is: author,
1354 1230 changeset hash, parent and commit comment.
1355 1231
1356 1232 Output may be to a file, in which case the name of the file is
1357 1233 given using a format string. The formatting rules are as follows:
1358 1234
1359 1235 %% literal "%" character
1360 1236 %H changeset hash (40 bytes of hexadecimal)
1361 1237 %N number of patches being generated
1362 1238 %R changeset revision number
1363 1239 %b basename of the exporting repository
1364 1240 %h short-form changeset hash (12 bytes of hexadecimal)
1365 1241 %n zero-padded sequence number, starting at 1
1366 1242 %r zero-padded changeset revision number
1367 1243
1368 1244 Without the -a option, export will avoid generating diffs of files
1369 1245 it detects as binary. With -a, export will generate a diff anyway,
1370 1246 probably with undesirable results.
1371 1247
1372 1248 With the --switch-parent option, the diff will be against the second
1373 1249 parent. It can be useful to review a merge.
1374 1250 """
1375 1251 if not changesets:
1376 1252 raise util.Abort(_("export requires at least one changeset"))
1377 1253 revs = cmdutil.revrange(ui, repo, changesets)
1378 1254 if len(revs) > 1:
1379 1255 ui.note(_('exporting patches:\n'))
1380 1256 else:
1381 1257 ui.note(_('exporting patch:\n'))
1382 1258 patch.export(repo, map(repo.lookup, revs), template=opts['output'],
1383 1259 switch_parent=opts['switch_parent'],
1384 1260 opts=patch.diffopts(ui, opts))
1385 1261
1386 1262 def grep(ui, repo, pattern, *pats, **opts):
1387 1263 """search for a pattern in specified files and revisions
1388 1264
1389 1265 Search revisions of files for a regular expression.
1390 1266
1391 1267 This command behaves differently than Unix grep. It only accepts
1392 1268 Python/Perl regexps. It searches repository history, not the
1393 1269 working directory. It always prints the revision number in which
1394 1270 a match appears.
1395 1271
1396 1272 By default, grep only prints output for the first revision of a
1397 1273 file in which it finds a match. To get it to print every revision
1398 1274 that contains a change in match status ("-" for a match that
1399 1275 becomes a non-match, or "+" for a non-match that becomes a match),
1400 1276 use the --all flag.
1401 1277 """
1402 1278 reflags = 0
1403 1279 if opts['ignore_case']:
1404 1280 reflags |= re.I
1405 1281 regexp = re.compile(pattern, reflags)
1406 1282 sep, eol = ':', '\n'
1407 1283 if opts['print0']:
1408 1284 sep = eol = '\0'
1409 1285
1410 1286 fcache = {}
1411 1287 def getfile(fn):
1412 1288 if fn not in fcache:
1413 1289 fcache[fn] = repo.file(fn)
1414 1290 return fcache[fn]
1415 1291
1416 1292 def matchlines(body):
1417 1293 begin = 0
1418 1294 linenum = 0
1419 1295 while True:
1420 1296 match = regexp.search(body, begin)
1421 1297 if not match:
1422 1298 break
1423 1299 mstart, mend = match.span()
1424 1300 linenum += body.count('\n', begin, mstart) + 1
1425 1301 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1426 1302 lend = body.find('\n', mend)
1427 1303 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1428 1304 begin = lend + 1
1429 1305
1430 1306 class linestate(object):
1431 1307 def __init__(self, line, linenum, colstart, colend):
1432 1308 self.line = line
1433 1309 self.linenum = linenum
1434 1310 self.colstart = colstart
1435 1311 self.colend = colend
1436 1312
1437 1313 def __eq__(self, other):
1438 1314 return self.line == other.line
1439 1315
1440 1316 matches = {}
1441 1317 copies = {}
1442 1318 def grepbody(fn, rev, body):
1443 1319 matches[rev].setdefault(fn, [])
1444 1320 m = matches[rev][fn]
1445 1321 for lnum, cstart, cend, line in matchlines(body):
1446 1322 s = linestate(line, lnum, cstart, cend)
1447 1323 m.append(s)
1448 1324
1449 1325 def difflinestates(a, b):
1450 1326 sm = difflib.SequenceMatcher(None, a, b)
1451 1327 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1452 1328 if tag == 'insert':
1453 1329 for i in xrange(blo, bhi):
1454 1330 yield ('+', b[i])
1455 1331 elif tag == 'delete':
1456 1332 for i in xrange(alo, ahi):
1457 1333 yield ('-', a[i])
1458 1334 elif tag == 'replace':
1459 1335 for i in xrange(alo, ahi):
1460 1336 yield ('-', a[i])
1461 1337 for i in xrange(blo, bhi):
1462 1338 yield ('+', b[i])
1463 1339
1464 1340 prev = {}
1465 1341 def display(fn, rev, states, prevstates):
1466 1342 counts = {'-': 0, '+': 0}
1467 1343 filerevmatches = {}
1468 1344 if incrementing or not opts['all']:
1469 1345 a, b, r = prevstates, states, rev
1470 1346 else:
1471 1347 a, b, r = states, prevstates, prev.get(fn, -1)
1472 1348 for change, l in difflinestates(a, b):
1473 1349 cols = [fn, str(r)]
1474 1350 if opts['line_number']:
1475 1351 cols.append(str(l.linenum))
1476 1352 if opts['all']:
1477 1353 cols.append(change)
1478 1354 if opts['user']:
1479 1355 cols.append(ui.shortuser(getchange(r)[1]))
1480 1356 if opts['files_with_matches']:
1481 1357 c = (fn, r)
1482 1358 if c in filerevmatches:
1483 1359 continue
1484 1360 filerevmatches[c] = 1
1485 1361 else:
1486 1362 cols.append(l.line)
1487 1363 ui.write(sep.join(cols), eol)
1488 1364 counts[change] += 1
1489 1365 return counts['+'], counts['-']
1490 1366
1491 1367 fstate = {}
1492 1368 skip = {}
1493 1369 getchange = util.cachefunc(lambda r:repo.changectx(r).changeset())
1494 1370 changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts)
1495 1371 count = 0
1496 1372 incrementing = False
1497 1373 follow = opts.get('follow')
1498 1374 for st, rev, fns in changeiter:
1499 1375 if st == 'window':
1500 1376 incrementing = rev
1501 1377 matches.clear()
1502 1378 elif st == 'add':
1503 1379 mf = repo.changectx(rev).manifest()
1504 1380 matches[rev] = {}
1505 1381 for fn in fns:
1506 1382 if fn in skip:
1507 1383 continue
1508 1384 fstate.setdefault(fn, {})
1509 1385 try:
1510 1386 grepbody(fn, rev, getfile(fn).read(mf[fn]))
1511 1387 if follow:
1512 1388 copied = getfile(fn).renamed(mf[fn])
1513 1389 if copied:
1514 1390 copies.setdefault(rev, {})[fn] = copied[0]
1515 1391 except KeyError:
1516 1392 pass
1517 1393 elif st == 'iter':
1518 1394 states = matches[rev].items()
1519 1395 states.sort()
1520 1396 for fn, m in states:
1521 1397 copy = copies.get(rev, {}).get(fn)
1522 1398 if fn in skip:
1523 1399 if copy:
1524 1400 skip[copy] = True
1525 1401 continue
1526 1402 if incrementing or not opts['all'] or fstate[fn]:
1527 1403 pos, neg = display(fn, rev, m, fstate[fn])
1528 1404 count += pos + neg
1529 1405 if pos and not opts['all']:
1530 1406 skip[fn] = True
1531 1407 if copy:
1532 1408 skip[copy] = True
1533 1409 fstate[fn] = m
1534 1410 if copy:
1535 1411 fstate[copy] = m
1536 1412 prev[fn] = rev
1537 1413
1538 1414 if not incrementing:
1539 1415 fstate = fstate.items()
1540 1416 fstate.sort()
1541 1417 for fn, state in fstate:
1542 1418 if fn in skip:
1543 1419 continue
1544 1420 if fn not in copies.get(prev[fn], {}):
1545 1421 display(fn, rev, {}, state)
1546 1422 return (count == 0 and 1) or 0
1547 1423
1548 1424 def heads(ui, repo, **opts):
1549 1425 """show current repository heads
1550 1426
1551 1427 Show all repository head changesets.
1552 1428
1553 1429 Repository "heads" are changesets that don't have children
1554 1430 changesets. They are where development generally takes place and
1555 1431 are the usual targets for update and merge operations.
1556 1432 """
1557 1433 if opts['rev']:
1558 1434 heads = repo.heads(repo.lookup(opts['rev']))
1559 1435 else:
1560 1436 heads = repo.heads()
1561 1437 br = None
1562 1438 if opts['branches']:
1563 1439 ui.warn(_("the --branches option is deprecated, "
1564 1440 "please use 'hg branches' instead\n"))
1565 1441 br = repo.branchlookup(heads)
1566 displayer = show_changeset(ui, repo, opts)
1442 displayer = cmdutil.show_changeset(ui, repo, opts)
1567 1443 for n in heads:
1568 1444 displayer.show(changenode=n, brinfo=br)
1569 1445
1570 1446 def identify(ui, repo):
1571 1447 """print information about the working copy
1572 1448
1573 1449 Print a short summary of the current state of the repo.
1574 1450
1575 1451 This summary identifies the repository state using one or two parent
1576 1452 hash identifiers, followed by a "+" if there are uncommitted changes
1577 1453 in the working directory, followed by a list of tags for this revision.
1578 1454 """
1579 1455 parents = [p for p in repo.dirstate.parents() if p != nullid]
1580 1456 if not parents:
1581 1457 ui.write(_("unknown\n"))
1582 1458 return
1583 1459
1584 1460 hexfunc = ui.debugflag and hex or short
1585 1461 modified, added, removed, deleted = repo.status()[:4]
1586 1462 output = ["%s%s" %
1587 1463 ('+'.join([hexfunc(parent) for parent in parents]),
1588 1464 (modified or added or removed or deleted) and "+" or "")]
1589 1465
1590 1466 if not ui.quiet:
1591 1467
1592 1468 branch = repo.workingctx().branch()
1593 1469 if branch:
1594 1470 output.append("(%s)" % branch)
1595 1471
1596 1472 # multiple tags for a single parent separated by '/'
1597 1473 parenttags = ['/'.join(tags)
1598 1474 for tags in map(repo.nodetags, parents) if tags]
1599 1475 # tags for multiple parents separated by ' + '
1600 1476 if parenttags:
1601 1477 output.append(' + '.join(parenttags))
1602 1478
1603 1479 ui.write("%s\n" % ' '.join(output))
1604 1480
1605 1481 def import_(ui, repo, patch1, *patches, **opts):
1606 1482 """import an ordered set of patches
1607 1483
1608 1484 Import a list of patches and commit them individually.
1609 1485
1610 1486 If there are outstanding changes in the working directory, import
1611 1487 will abort unless given the -f flag.
1612 1488
1613 1489 You can import a patch straight from a mail message. Even patches
1614 1490 as attachments work (body part must be type text/plain or
1615 1491 text/x-patch to be used). From and Subject headers of email
1616 1492 message are used as default committer and commit message. All
1617 1493 text/plain body parts before first diff are added to commit
1618 1494 message.
1619 1495
1620 1496 If imported patch was generated by hg export, user and description
1621 1497 from patch override values from message headers and body. Values
1622 1498 given on command line with -m and -u override these.
1623 1499
1624 1500 To read a patch from standard input, use patch name "-".
1625 1501 """
1626 1502 patches = (patch1,) + patches
1627 1503
1628 1504 if not opts['force']:
1629 1505 bail_if_changed(repo)
1630 1506
1631 1507 d = opts["base"]
1632 1508 strip = opts["strip"]
1633 1509
1634 1510 wlock = repo.wlock()
1635 1511 lock = repo.lock()
1636 1512
1637 1513 for p in patches:
1638 1514 pf = os.path.join(d, p)
1639 1515
1640 1516 if pf == '-':
1641 1517 ui.status(_("applying patch from stdin\n"))
1642 1518 tmpname, message, user, date = patch.extract(ui, sys.stdin)
1643 1519 else:
1644 1520 ui.status(_("applying %s\n") % p)
1645 1521 tmpname, message, user, date = patch.extract(ui, file(pf))
1646 1522
1647 1523 if tmpname is None:
1648 1524 raise util.Abort(_('no diffs found'))
1649 1525
1650 1526 try:
1651 1527 if opts['message']:
1652 1528 # pickup the cmdline msg
1653 1529 message = opts['message']
1654 1530 elif message:
1655 1531 # pickup the patch msg
1656 1532 message = message.strip()
1657 1533 else:
1658 1534 # launch the editor
1659 1535 message = None
1660 1536 ui.debug(_('message:\n%s\n') % message)
1661 1537
1662 1538 files = {}
1663 1539 try:
1664 1540 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1665 1541 files=files)
1666 1542 finally:
1667 1543 files = patch.updatedir(ui, repo, files, wlock=wlock)
1668 1544 repo.commit(files, message, user, date, wlock=wlock, lock=lock)
1669 1545 finally:
1670 1546 os.unlink(tmpname)
1671 1547
1672 1548 def incoming(ui, repo, source="default", **opts):
1673 1549 """show new changesets found in source
1674 1550
1675 1551 Show new changesets found in the specified path/URL or the default
1676 1552 pull location. These are the changesets that would be pulled if a pull
1677 1553 was requested.
1678 1554
1679 1555 For remote repository, using --bundle avoids downloading the changesets
1680 1556 twice if the incoming is followed by a pull.
1681 1557
1682 1558 See pull for valid source format details.
1683 1559 """
1684 1560 source = ui.expandpath(source)
1685 1561 setremoteconfig(ui, opts)
1686 1562
1687 1563 other = hg.repository(ui, source)
1688 1564 incoming = repo.findincoming(other, force=opts["force"])
1689 1565 if not incoming:
1690 1566 ui.status(_("no changes found\n"))
1691 1567 return
1692 1568
1693 1569 cleanup = None
1694 1570 try:
1695 1571 fname = opts["bundle"]
1696 1572 if fname or not other.local():
1697 1573 # create a bundle (uncompressed if other repo is not local)
1698 1574 cg = other.changegroup(incoming, "incoming")
1699 1575 fname = cleanup = write_bundle(cg, fname, compress=other.local())
1700 1576 # keep written bundle?
1701 1577 if opts["bundle"]:
1702 1578 cleanup = None
1703 1579 if not other.local():
1704 1580 # use the created uncompressed bundlerepo
1705 1581 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1706 1582
1707 1583 revs = None
1708 1584 if opts['rev']:
1709 1585 revs = [other.lookup(rev) for rev in opts['rev']]
1710 1586 o = other.changelog.nodesbetween(incoming, revs)[0]
1711 1587 if opts['newest_first']:
1712 1588 o.reverse()
1713 displayer = show_changeset(ui, other, opts)
1589 displayer = cmdutil.show_changeset(ui, other, opts)
1714 1590 for n in o:
1715 1591 parents = [p for p in other.changelog.parents(n) if p != nullid]
1716 1592 if opts['no_merges'] and len(parents) == 2:
1717 1593 continue
1718 1594 displayer.show(changenode=n)
1719 1595 if opts['patch']:
1720 1596 prev = (parents and parents[0]) or nullid
1721 1597 patch.diff(other, prev, n, fp=repo.ui)
1722 1598 ui.write("\n")
1723 1599 finally:
1724 1600 if hasattr(other, 'close'):
1725 1601 other.close()
1726 1602 if cleanup:
1727 1603 os.unlink(cleanup)
1728 1604
1729 1605 def init(ui, dest=".", **opts):
1730 1606 """create a new repository in the given directory
1731 1607
1732 1608 Initialize a new repository in the given directory. If the given
1733 1609 directory does not exist, it is created.
1734 1610
1735 1611 If no directory is given, the current directory is used.
1736 1612
1737 1613 It is possible to specify an ssh:// URL as the destination.
1738 1614 Look at the help text for the pull command for important details
1739 1615 about ssh:// URLs.
1740 1616 """
1741 1617 setremoteconfig(ui, opts)
1742 1618 hg.repository(ui, dest, create=1)
1743 1619
1744 1620 def locate(ui, repo, *pats, **opts):
1745 1621 """locate files matching specific patterns
1746 1622
1747 1623 Print all files under Mercurial control whose names match the
1748 1624 given patterns.
1749 1625
1750 1626 This command searches the current directory and its
1751 1627 subdirectories. To search an entire repository, move to the root
1752 1628 of the repository.
1753 1629
1754 1630 If no patterns are given to match, this command prints all file
1755 1631 names.
1756 1632
1757 1633 If you want to feed the output of this command into the "xargs"
1758 1634 command, use the "-0" option to both this command and "xargs".
1759 1635 This will avoid the problem of "xargs" treating single filenames
1760 1636 that contain white space as multiple filenames.
1761 1637 """
1762 1638 end = opts['print0'] and '\0' or '\n'
1763 1639 rev = opts['rev']
1764 1640 if rev:
1765 1641 node = repo.lookup(rev)
1766 1642 else:
1767 1643 node = None
1768 1644
1769 1645 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
1770 1646 head='(?:.*/|)'):
1771 1647 if not node and repo.dirstate.state(abs) == '?':
1772 1648 continue
1773 1649 if opts['fullpath']:
1774 1650 ui.write(os.path.join(repo.root, abs), end)
1775 1651 else:
1776 1652 ui.write(((pats and rel) or abs), end)
1777 1653
1778 1654 def log(ui, repo, *pats, **opts):
1779 1655 """show revision history of entire repository or files
1780 1656
1781 1657 Print the revision history of the specified files or the entire
1782 1658 project.
1783 1659
1784 1660 File history is shown without following rename or copy history of
1785 1661 files. Use -f/--follow with a file name to follow history across
1786 1662 renames and copies. --follow without a file name will only show
1787 1663 ancestors or descendants of the starting revision. --follow-first
1788 1664 only follows the first parent of merge revisions.
1789 1665
1790 1666 If no revision range is specified, the default is tip:0 unless
1791 1667 --follow is set, in which case the working directory parent is
1792 1668 used as the starting revision.
1793 1669
1794 1670 By default this command outputs: changeset id and hash, tags,
1795 1671 non-trivial parents, user, date and time, and a summary for each
1796 1672 commit. When the -v/--verbose switch is used, the list of changed
1797 1673 files and full commit message is shown.
1798 1674 """
1799 1675 class dui(object):
1800 1676 # Implement and delegate some ui protocol. Save hunks of
1801 1677 # output for later display in the desired order.
1802 1678 def __init__(self, ui):
1803 1679 self.ui = ui
1804 1680 self.hunk = {}
1805 1681 self.header = {}
1806 1682 self.quiet = ui.quiet
1807 1683 self.verbose = ui.verbose
1808 1684 self.debugflag = ui.debugflag
1809 1685 def bump(self, rev):
1810 1686 self.rev = rev
1811 1687 self.hunk[rev] = []
1812 1688 self.header[rev] = []
1813 1689 def note(self, *args):
1814 1690 if self.verbose:
1815 1691 self.write(*args)
1816 1692 def status(self, *args):
1817 1693 if not self.quiet:
1818 1694 self.write(*args)
1819 1695 def write(self, *args):
1820 1696 self.hunk[self.rev].extend(args)
1821 1697 def write_header(self, *args):
1822 1698 self.header[self.rev].extend(args)
1823 1699 def debug(self, *args):
1824 1700 if self.debugflag:
1825 1701 self.write(*args)
1826 1702 def __getattr__(self, key):
1827 1703 return getattr(self.ui, key)
1828 1704
1829 1705 getchange = util.cachefunc(lambda r:repo.changectx(r).changeset())
1830 1706 changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts)
1831 1707
1832 1708 if opts['branches']:
1833 1709 ui.warn(_("the --branches option is deprecated, "
1834 1710 "please use 'hg branches' instead\n"))
1835 1711
1836 1712 if opts['limit']:
1837 1713 try:
1838 1714 limit = int(opts['limit'])
1839 1715 except ValueError:
1840 1716 raise util.Abort(_('limit must be a positive integer'))
1841 1717 if limit <= 0: raise util.Abort(_('limit must be positive'))
1842 1718 else:
1843 1719 limit = sys.maxint
1844 1720 count = 0
1845 1721
1846 1722 if opts['copies'] and opts['rev']:
1847 1723 endrev = max(cmdutil.revrange(ui, repo, opts['rev'])) + 1
1848 1724 else:
1849 1725 endrev = repo.changelog.count()
1850 1726 rcache = {}
1851 1727 ncache = {}
1852 1728 dcache = []
1853 1729 def getrenamed(fn, rev, man):
1854 1730 '''looks up all renames for a file (up to endrev) the first
1855 1731 time the file is given. It indexes on the changerev and only
1856 1732 parses the manifest if linkrev != changerev.
1857 1733 Returns rename info for fn at changerev rev.'''
1858 1734 if fn not in rcache:
1859 1735 rcache[fn] = {}
1860 1736 ncache[fn] = {}
1861 1737 fl = repo.file(fn)
1862 1738 for i in xrange(fl.count()):
1863 1739 node = fl.node(i)
1864 1740 lr = fl.linkrev(node)
1865 1741 renamed = fl.renamed(node)
1866 1742 rcache[fn][lr] = renamed
1867 1743 if renamed:
1868 1744 ncache[fn][node] = renamed
1869 1745 if lr >= endrev:
1870 1746 break
1871 1747 if rev in rcache[fn]:
1872 1748 return rcache[fn][rev]
1873 1749 mr = repo.manifest.rev(man)
1874 1750 if repo.manifest.parentrevs(mr) != (mr - 1, nullrev):
1875 1751 return ncache[fn].get(repo.manifest.find(man, fn)[0])
1876 1752 if not dcache or dcache[0] != man:
1877 1753 dcache[:] = [man, repo.manifest.readdelta(man)]
1878 1754 if fn in dcache[1]:
1879 1755 return ncache[fn].get(dcache[1][fn])
1880 1756 return None
1881 1757
1882 displayer = show_changeset(ui, repo, opts)
1758 displayer = cmdutil.show_changeset(ui, repo, opts)
1883 1759 for st, rev, fns in changeiter:
1884 1760 if st == 'window':
1885 1761 du = dui(ui)
1886 1762 displayer.ui = du
1887 1763 elif st == 'add':
1888 1764 du.bump(rev)
1889 1765 changenode = repo.changelog.node(rev)
1890 1766 parents = [p for p in repo.changelog.parentrevs(rev)
1891 1767 if p != nullrev]
1892 1768 if opts['no_merges'] and len(parents) == 2:
1893 1769 continue
1894 1770 if opts['only_merges'] and len(parents) != 2:
1895 1771 continue
1896 1772
1897 1773 if opts['keyword']:
1898 1774 changes = getchange(rev)
1899 1775 miss = 0
1900 1776 for k in [kw.lower() for kw in opts['keyword']]:
1901 1777 if not (k in changes[1].lower() or
1902 1778 k in changes[4].lower() or
1903 1779 k in " ".join(changes[3][:20]).lower()):
1904 1780 miss = 1
1905 1781 break
1906 1782 if miss:
1907 1783 continue
1908 1784
1909 1785 br = None
1910 1786 if opts['branches']:
1911 1787 br = repo.branchlookup([repo.changelog.node(rev)])
1912 1788
1913 1789 copies = []
1914 1790 if opts.get('copies') and rev:
1915 1791 mf = getchange(rev)[0]
1916 1792 for fn in getchange(rev)[3]:
1917 1793 rename = getrenamed(fn, rev, mf)
1918 1794 if rename:
1919 1795 copies.append((fn, rename[0]))
1920 1796 displayer.show(rev, changenode, brinfo=br, copies=copies)
1921 1797 if opts['patch']:
1922 1798 if parents:
1923 1799 prev = parents[0]
1924 1800 else:
1925 1801 prev = nullrev
1926 1802 prev = repo.changelog.node(prev)
1927 1803 patch.diff(repo, prev, changenode, match=matchfn, fp=du)
1928 1804 du.write("\n\n")
1929 1805 elif st == 'iter':
1930 1806 if count == limit: break
1931 1807 if du.header[rev]:
1932 1808 ui.write_header(*du.header[rev])
1933 1809 if du.hunk[rev]:
1934 1810 count += 1
1935 1811 ui.write(*du.hunk[rev])
1936 1812
1937 1813 def manifest(ui, repo, rev=None):
1938 1814 """output the latest or given revision of the project manifest
1939 1815
1940 1816 Print a list of version controlled files for the given revision.
1941 1817
1942 1818 The manifest is the list of files being version controlled. If no revision
1943 1819 is given then the tip is used.
1944 1820 """
1945 1821 if rev:
1946 1822 try:
1947 1823 # assume all revision numbers are for changesets
1948 1824 n = repo.lookup(rev)
1949 1825 change = repo.changelog.read(n)
1950 1826 n = change[0]
1951 1827 except hg.RepoError:
1952 1828 n = repo.manifest.lookup(rev)
1953 1829 else:
1954 1830 n = repo.manifest.tip()
1955 1831 m = repo.manifest.read(n)
1956 1832 files = m.keys()
1957 1833 files.sort()
1958 1834
1959 1835 for f in files:
1960 1836 ui.write("%40s %3s %s\n" % (hex(m[f]),
1961 1837 m.execf(f) and "755" or "644", f))
1962 1838
1963 1839 def merge(ui, repo, node=None, force=None, branch=None):
1964 1840 """Merge working directory with another revision
1965 1841
1966 1842 Merge the contents of the current working directory and the
1967 1843 requested revision. Files that changed between either parent are
1968 1844 marked as changed for the next commit and a commit must be
1969 1845 performed before any further updates are allowed.
1970 1846
1971 1847 If no revision is specified, the working directory's parent is a
1972 1848 head revision, and the repository contains exactly one other head,
1973 1849 the other head is merged with by default. Otherwise, an explicit
1974 1850 revision to merge with must be provided.
1975 1851 """
1976 1852
1977 1853 if node or branch:
1978 1854 node = _lookup(repo, node, branch)
1979 1855 else:
1980 1856 heads = repo.heads()
1981 1857 if len(heads) > 2:
1982 1858 raise util.Abort(_('repo has %d heads - '
1983 1859 'please merge with an explicit rev') %
1984 1860 len(heads))
1985 1861 if len(heads) == 1:
1986 1862 raise util.Abort(_('there is nothing to merge - '
1987 1863 'use "hg update" instead'))
1988 1864 parent = repo.dirstate.parents()[0]
1989 1865 if parent not in heads:
1990 1866 raise util.Abort(_('working dir not at a head rev - '
1991 1867 'use "hg update" or merge with an explicit rev'))
1992 1868 node = parent == heads[0] and heads[-1] or heads[0]
1993 1869 return hg.merge(repo, node, force=force)
1994 1870
1995 1871 def outgoing(ui, repo, dest=None, **opts):
1996 1872 """show changesets not found in destination
1997 1873
1998 1874 Show changesets not found in the specified destination repository or
1999 1875 the default push location. These are the changesets that would be pushed
2000 1876 if a push was requested.
2001 1877
2002 1878 See pull for valid destination format details.
2003 1879 """
2004 1880 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2005 1881 setremoteconfig(ui, opts)
2006 1882 revs = None
2007 1883 if opts['rev']:
2008 1884 revs = [repo.lookup(rev) for rev in opts['rev']]
2009 1885
2010 1886 other = hg.repository(ui, dest)
2011 1887 o = repo.findoutgoing(other, force=opts['force'])
2012 1888 if not o:
2013 1889 ui.status(_("no changes found\n"))
2014 1890 return
2015 1891 o = repo.changelog.nodesbetween(o, revs)[0]
2016 1892 if opts['newest_first']:
2017 1893 o.reverse()
2018 displayer = show_changeset(ui, repo, opts)
1894 displayer = cmdutil.show_changeset(ui, repo, opts)
2019 1895 for n in o:
2020 1896 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2021 1897 if opts['no_merges'] and len(parents) == 2:
2022 1898 continue
2023 1899 displayer.show(changenode=n)
2024 1900 if opts['patch']:
2025 1901 prev = (parents and parents[0]) or nullid
2026 1902 patch.diff(repo, prev, n)
2027 1903 ui.write("\n")
2028 1904
2029 1905 def parents(ui, repo, file_=None, rev=None, branches=None, **opts):
2030 1906 """show the parents of the working dir or revision
2031 1907
2032 1908 Print the working directory's parent revisions.
2033 1909 """
2034 1910 # legacy
2035 1911 if file_ and not rev:
2036 1912 try:
2037 1913 rev = repo.lookup(file_)
2038 1914 file_ = None
2039 1915 except hg.RepoError:
2040 1916 pass
2041 1917 else:
2042 1918 ui.warn(_("'hg parent REV' is deprecated, "
2043 1919 "please use 'hg parents -r REV instead\n"))
2044 1920
2045 1921 if rev:
2046 1922 if file_:
2047 1923 ctx = repo.filectx(file_, changeid=rev)
2048 1924 else:
2049 1925 ctx = repo.changectx(rev)
2050 1926 p = [cp.node() for cp in ctx.parents()]
2051 1927 else:
2052 1928 p = repo.dirstate.parents()
2053 1929
2054 1930 br = None
2055 1931 if branches is not None:
2056 1932 ui.warn(_("the --branches option is deprecated, "
2057 1933 "please use 'hg branches' instead\n"))
2058 1934 br = repo.branchlookup(p)
2059 displayer = show_changeset(ui, repo, opts)
1935 displayer = cmdutil.show_changeset(ui, repo, opts)
2060 1936 for n in p:
2061 1937 if n != nullid:
2062 1938 displayer.show(changenode=n, brinfo=br)
2063 1939
2064 1940 def paths(ui, repo, search=None):
2065 1941 """show definition of symbolic path names
2066 1942
2067 1943 Show definition of symbolic path name NAME. If no name is given, show
2068 1944 definition of available names.
2069 1945
2070 1946 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2071 1947 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2072 1948 """
2073 1949 if search:
2074 1950 for name, path in ui.configitems("paths"):
2075 1951 if name == search:
2076 1952 ui.write("%s\n" % path)
2077 1953 return
2078 1954 ui.warn(_("not found!\n"))
2079 1955 return 1
2080 1956 else:
2081 1957 for name, path in ui.configitems("paths"):
2082 1958 ui.write("%s = %s\n" % (name, path))
2083 1959
2084 1960 def postincoming(ui, repo, modheads, optupdate):
2085 1961 if modheads == 0:
2086 1962 return
2087 1963 if optupdate:
2088 1964 if modheads == 1:
2089 1965 return hg.update(repo, repo.changelog.tip()) # update
2090 1966 else:
2091 1967 ui.status(_("not updating, since new heads added\n"))
2092 1968 if modheads > 1:
2093 1969 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2094 1970 else:
2095 1971 ui.status(_("(run 'hg update' to get a working copy)\n"))
2096 1972
2097 1973 def pull(ui, repo, source="default", **opts):
2098 1974 """pull changes from the specified source
2099 1975
2100 1976 Pull changes from a remote repository to a local one.
2101 1977
2102 1978 This finds all changes from the repository at the specified path
2103 1979 or URL and adds them to the local repository. By default, this
2104 1980 does not update the copy of the project in the working directory.
2105 1981
2106 1982 Valid URLs are of the form:
2107 1983
2108 1984 local/filesystem/path (or file://local/filesystem/path)
2109 1985 http://[user@]host[:port]/[path]
2110 1986 https://[user@]host[:port]/[path]
2111 1987 ssh://[user@]host[:port]/[path]
2112 1988 static-http://host[:port]/[path]
2113 1989
2114 1990 Paths in the local filesystem can either point to Mercurial
2115 1991 repositories or to bundle files (as created by 'hg bundle' or
2116 1992 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2117 1993 allows access to a Mercurial repository where you simply use a web
2118 1994 server to publish the .hg directory as static content.
2119 1995
2120 1996 Some notes about using SSH with Mercurial:
2121 1997 - SSH requires an accessible shell account on the destination machine
2122 1998 and a copy of hg in the remote path or specified with as remotecmd.
2123 1999 - path is relative to the remote user's home directory by default.
2124 2000 Use an extra slash at the start of a path to specify an absolute path:
2125 2001 ssh://example.com//tmp/repository
2126 2002 - Mercurial doesn't use its own compression via SSH; the right thing
2127 2003 to do is to configure it in your ~/.ssh/config, e.g.:
2128 2004 Host *.mylocalnetwork.example.com
2129 2005 Compression no
2130 2006 Host *
2131 2007 Compression yes
2132 2008 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2133 2009 with the --ssh command line option.
2134 2010 """
2135 2011 source = ui.expandpath(source)
2136 2012 setremoteconfig(ui, opts)
2137 2013
2138 2014 other = hg.repository(ui, source)
2139 2015 ui.status(_('pulling from %s\n') % (source))
2140 2016 revs = None
2141 2017 if opts['rev']:
2142 2018 if 'lookup' in other.capabilities:
2143 2019 revs = [other.lookup(rev) for rev in opts['rev']]
2144 2020 else:
2145 2021 error = _("Other repository doesn't support revision lookup, so a rev cannot be specified.")
2146 2022 raise util.Abort(error)
2147 2023 modheads = repo.pull(other, heads=revs, force=opts['force'])
2148 2024 return postincoming(ui, repo, modheads, opts['update'])
2149 2025
2150 2026 def push(ui, repo, dest=None, **opts):
2151 2027 """push changes to the specified destination
2152 2028
2153 2029 Push changes from the local repository to the given destination.
2154 2030
2155 2031 This is the symmetrical operation for pull. It helps to move
2156 2032 changes from the current repository to a different one. If the
2157 2033 destination is local this is identical to a pull in that directory
2158 2034 from the current one.
2159 2035
2160 2036 By default, push will refuse to run if it detects the result would
2161 2037 increase the number of remote heads. This generally indicates the
2162 2038 the client has forgotten to sync and merge before pushing.
2163 2039
2164 2040 Valid URLs are of the form:
2165 2041
2166 2042 local/filesystem/path (or file://local/filesystem/path)
2167 2043 ssh://[user@]host[:port]/[path]
2168 2044 http://[user@]host[:port]/[path]
2169 2045 https://[user@]host[:port]/[path]
2170 2046
2171 2047 Look at the help text for the pull command for important details
2172 2048 about ssh:// URLs.
2173 2049
2174 2050 Pushing to http:// and https:// URLs is only possible, if this
2175 2051 feature is explicitly enabled on the remote Mercurial server.
2176 2052 """
2177 2053 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2178 2054 setremoteconfig(ui, opts)
2179 2055
2180 2056 other = hg.repository(ui, dest)
2181 2057 ui.status('pushing to %s\n' % (dest))
2182 2058 revs = None
2183 2059 if opts['rev']:
2184 2060 revs = [repo.lookup(rev) for rev in opts['rev']]
2185 2061 r = repo.push(other, opts['force'], revs=revs)
2186 2062 return r == 0
2187 2063
2188 2064 def rawcommit(ui, repo, *flist, **rc):
2189 2065 """raw commit interface (DEPRECATED)
2190 2066
2191 2067 (DEPRECATED)
2192 2068 Lowlevel commit, for use in helper scripts.
2193 2069
2194 2070 This command is not intended to be used by normal users, as it is
2195 2071 primarily useful for importing from other SCMs.
2196 2072
2197 2073 This command is now deprecated and will be removed in a future
2198 2074 release, please use debugsetparents and commit instead.
2199 2075 """
2200 2076
2201 2077 ui.warn(_("(the rawcommit command is deprecated)\n"))
2202 2078
2203 2079 message = rc['message']
2204 2080 if not message and rc['logfile']:
2205 2081 try:
2206 2082 message = open(rc['logfile']).read()
2207 2083 except IOError:
2208 2084 pass
2209 2085 if not message and not rc['logfile']:
2210 2086 raise util.Abort(_("missing commit message"))
2211 2087
2212 2088 files = relpath(repo, list(flist))
2213 2089 if rc['files']:
2214 2090 files += open(rc['files']).read().splitlines()
2215 2091
2216 2092 rc['parent'] = map(repo.lookup, rc['parent'])
2217 2093
2218 2094 try:
2219 2095 repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent'])
2220 2096 except ValueError, inst:
2221 2097 raise util.Abort(str(inst))
2222 2098
2223 2099 def recover(ui, repo):
2224 2100 """roll back an interrupted transaction
2225 2101
2226 2102 Recover from an interrupted commit or pull.
2227 2103
2228 2104 This command tries to fix the repository status after an interrupted
2229 2105 operation. It should only be necessary when Mercurial suggests it.
2230 2106 """
2231 2107 if repo.recover():
2232 2108 return hg.verify(repo)
2233 2109 return 1
2234 2110
2235 2111 def remove(ui, repo, *pats, **opts):
2236 2112 """remove the specified files on the next commit
2237 2113
2238 2114 Schedule the indicated files for removal from the repository.
2239 2115
2240 2116 This command schedules the files to be removed at the next commit.
2241 2117 This only removes files from the current branch, not from the
2242 2118 entire project history. If the files still exist in the working
2243 2119 directory, they will be deleted from it. If invoked with --after,
2244 2120 files that have been manually deleted are marked as removed.
2245 2121
2246 2122 Modified files and added files are not removed by default. To
2247 2123 remove them, use the -f/--force option.
2248 2124 """
2249 2125 names = []
2250 2126 if not opts['after'] and not pats:
2251 2127 raise util.Abort(_('no files specified'))
2252 2128 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2253 2129 exact = dict.fromkeys(files)
2254 2130 mardu = map(dict.fromkeys, repo.status(files=files, match=matchfn))[:5]
2255 2131 modified, added, removed, deleted, unknown = mardu
2256 2132 remove, forget = [], []
2257 2133 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts):
2258 2134 reason = None
2259 2135 if abs not in deleted and opts['after']:
2260 2136 reason = _('is still present')
2261 2137 elif abs in modified and not opts['force']:
2262 2138 reason = _('is modified (use -f to force removal)')
2263 2139 elif abs in added:
2264 2140 if opts['force']:
2265 2141 forget.append(abs)
2266 2142 continue
2267 2143 reason = _('has been marked for add (use -f to force removal)')
2268 2144 elif abs in unknown:
2269 2145 reason = _('is not managed')
2270 2146 elif abs in removed:
2271 2147 continue
2272 2148 if reason:
2273 2149 if exact:
2274 2150 ui.warn(_('not removing %s: file %s\n') % (rel, reason))
2275 2151 else:
2276 2152 if ui.verbose or not exact:
2277 2153 ui.status(_('removing %s\n') % rel)
2278 2154 remove.append(abs)
2279 2155 repo.forget(forget)
2280 2156 repo.remove(remove, unlink=not opts['after'])
2281 2157
2282 2158 def rename(ui, repo, *pats, **opts):
2283 2159 """rename files; equivalent of copy + remove
2284 2160
2285 2161 Mark dest as copies of sources; mark sources for deletion. If
2286 2162 dest is a directory, copies are put in that directory. If dest is
2287 2163 a file, there can only be one source.
2288 2164
2289 2165 By default, this command copies the contents of files as they
2290 2166 stand in the working directory. If invoked with --after, the
2291 2167 operation is recorded, but no copying is performed.
2292 2168
2293 2169 This command takes effect in the next commit.
2294 2170
2295 2171 NOTE: This command should be treated as experimental. While it
2296 2172 should properly record rename files, this information is not yet
2297 2173 fully used by merge, nor fully reported by log.
2298 2174 """
2299 2175 wlock = repo.wlock(0)
2300 2176 errs, copied = docopy(ui, repo, pats, opts, wlock)
2301 2177 names = []
2302 2178 for abs, rel, exact in copied:
2303 2179 if ui.verbose or not exact:
2304 2180 ui.status(_('removing %s\n') % rel)
2305 2181 names.append(abs)
2306 2182 if not opts.get('dry_run'):
2307 2183 repo.remove(names, True, wlock)
2308 2184 return errs
2309 2185
2310 2186 def revert(ui, repo, *pats, **opts):
2311 2187 """revert files or dirs to their states as of some revision
2312 2188
2313 2189 With no revision specified, revert the named files or directories
2314 2190 to the contents they had in the parent of the working directory.
2315 2191 This restores the contents of the affected files to an unmodified
2316 2192 state. If the working directory has two parents, you must
2317 2193 explicitly specify the revision to revert to.
2318 2194
2319 2195 Modified files are saved with a .orig suffix before reverting.
2320 2196 To disable these backups, use --no-backup.
2321 2197
2322 2198 Using the -r option, revert the given files or directories to their
2323 2199 contents as of a specific revision. This can be helpful to "roll
2324 2200 back" some or all of a change that should not have been committed.
2325 2201
2326 2202 Revert modifies the working directory. It does not commit any
2327 2203 changes, or change the parent of the working directory. If you
2328 2204 revert to a revision other than the parent of the working
2329 2205 directory, the reverted files will thus appear modified
2330 2206 afterwards.
2331 2207
2332 2208 If a file has been deleted, it is recreated. If the executable
2333 2209 mode of a file was changed, it is reset.
2334 2210
2335 2211 If names are given, all files matching the names are reverted.
2336 2212
2337 2213 If no arguments are given, no files are reverted.
2338 2214 """
2339 2215
2340 2216 if not pats and not opts['all']:
2341 2217 raise util.Abort(_('no files or directories specified; '
2342 2218 'use --all to revert the whole repo'))
2343 2219
2344 2220 parent, p2 = repo.dirstate.parents()
2345 2221 if not opts['rev'] and p2 != nullid:
2346 2222 raise util.Abort(_('uncommitted merge - please provide a '
2347 2223 'specific revision'))
2348 2224 node = repo.changectx(opts['rev']).node()
2349 2225 mf = repo.manifest.read(repo.changelog.read(node)[0])
2350 2226 if node == parent:
2351 2227 pmf = mf
2352 2228 else:
2353 2229 pmf = None
2354 2230
2355 2231 wlock = repo.wlock()
2356 2232
2357 2233 # need all matching names in dirstate and manifest of target rev,
2358 2234 # so have to walk both. do not print errors if files exist in one
2359 2235 # but not other.
2360 2236
2361 2237 names = {}
2362 2238 target_only = {}
2363 2239
2364 2240 # walk dirstate.
2365 2241
2366 2242 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts,
2367 2243 badmatch=mf.has_key):
2368 2244 names[abs] = (rel, exact)
2369 2245 if src == 'b':
2370 2246 target_only[abs] = True
2371 2247
2372 2248 # walk target manifest.
2373 2249
2374 2250 for src, abs, rel, exact in cmdutil.walk(repo, pats, opts, node=node,
2375 2251 badmatch=names.has_key):
2376 2252 if abs in names: continue
2377 2253 names[abs] = (rel, exact)
2378 2254 target_only[abs] = True
2379 2255
2380 2256 changes = repo.status(match=names.has_key, wlock=wlock)[:5]
2381 2257 modified, added, removed, deleted, unknown = map(dict.fromkeys, changes)
2382 2258
2383 2259 revert = ([], _('reverting %s\n'))
2384 2260 add = ([], _('adding %s\n'))
2385 2261 remove = ([], _('removing %s\n'))
2386 2262 forget = ([], _('forgetting %s\n'))
2387 2263 undelete = ([], _('undeleting %s\n'))
2388 2264 update = {}
2389 2265
2390 2266 disptable = (
2391 2267 # dispatch table:
2392 2268 # file state
2393 2269 # action if in target manifest
2394 2270 # action if not in target manifest
2395 2271 # make backup if in target manifest
2396 2272 # make backup if not in target manifest
2397 2273 (modified, revert, remove, True, True),
2398 2274 (added, revert, forget, True, False),
2399 2275 (removed, undelete, None, False, False),
2400 2276 (deleted, revert, remove, False, False),
2401 2277 (unknown, add, None, True, False),
2402 2278 (target_only, add, None, False, False),
2403 2279 )
2404 2280
2405 2281 entries = names.items()
2406 2282 entries.sort()
2407 2283
2408 2284 for abs, (rel, exact) in entries:
2409 2285 mfentry = mf.get(abs)
2410 2286 def handle(xlist, dobackup):
2411 2287 xlist[0].append(abs)
2412 2288 update[abs] = 1
2413 2289 if dobackup and not opts['no_backup'] and os.path.exists(rel):
2414 2290 bakname = "%s.orig" % rel
2415 2291 ui.note(_('saving current version of %s as %s\n') %
2416 2292 (rel, bakname))
2417 2293 if not opts.get('dry_run'):
2418 2294 util.copyfile(rel, bakname)
2419 2295 if ui.verbose or not exact:
2420 2296 ui.status(xlist[1] % rel)
2421 2297 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2422 2298 if abs not in table: continue
2423 2299 # file has changed in dirstate
2424 2300 if mfentry:
2425 2301 handle(hitlist, backuphit)
2426 2302 elif misslist is not None:
2427 2303 handle(misslist, backupmiss)
2428 2304 else:
2429 2305 if exact: ui.warn(_('file not managed: %s\n' % rel))
2430 2306 break
2431 2307 else:
2432 2308 # file has not changed in dirstate
2433 2309 if node == parent:
2434 2310 if exact: ui.warn(_('no changes needed to %s\n' % rel))
2435 2311 continue
2436 2312 if pmf is None:
2437 2313 # only need parent manifest in this unlikely case,
2438 2314 # so do not read by default
2439 2315 pmf = repo.manifest.read(repo.changelog.read(parent)[0])
2440 2316 if abs in pmf:
2441 2317 if mfentry:
2442 2318 # if version of file is same in parent and target
2443 2319 # manifests, do nothing
2444 2320 if pmf[abs] != mfentry:
2445 2321 handle(revert, False)
2446 2322 else:
2447 2323 handle(remove, False)
2448 2324
2449 2325 if not opts.get('dry_run'):
2450 2326 repo.dirstate.forget(forget[0])
2451 2327 r = hg.revert(repo, node, update.has_key, wlock)
2452 2328 repo.dirstate.update(add[0], 'a')
2453 2329 repo.dirstate.update(undelete[0], 'n')
2454 2330 repo.dirstate.update(remove[0], 'r')
2455 2331 return r
2456 2332
2457 2333 def rollback(ui, repo):
2458 2334 """roll back the last transaction in this repository
2459 2335
2460 2336 Roll back the last transaction in this repository, restoring the
2461 2337 project to its state prior to the transaction.
2462 2338
2463 2339 Transactions are used to encapsulate the effects of all commands
2464 2340 that create new changesets or propagate existing changesets into a
2465 2341 repository. For example, the following commands are transactional,
2466 2342 and their effects can be rolled back:
2467 2343
2468 2344 commit
2469 2345 import
2470 2346 pull
2471 2347 push (with this repository as destination)
2472 2348 unbundle
2473 2349
2474 2350 This command should be used with care. There is only one level of
2475 2351 rollback, and there is no way to undo a rollback.
2476 2352
2477 2353 This command is not intended for use on public repositories. Once
2478 2354 changes are visible for pull by other users, rolling a transaction
2479 2355 back locally is ineffective (someone else may already have pulled
2480 2356 the changes). Furthermore, a race is possible with readers of the
2481 2357 repository; for example an in-progress pull from the repository
2482 2358 may fail if a rollback is performed.
2483 2359 """
2484 2360 repo.rollback()
2485 2361
2486 2362 def root(ui, repo):
2487 2363 """print the root (top) of the current working dir
2488 2364
2489 2365 Print the root directory of the current repository.
2490 2366 """
2491 2367 ui.write(repo.root + "\n")
2492 2368
2493 2369 def serve(ui, repo, **opts):
2494 2370 """export the repository via HTTP
2495 2371
2496 2372 Start a local HTTP repository browser and pull server.
2497 2373
2498 2374 By default, the server logs accesses to stdout and errors to
2499 2375 stderr. Use the "-A" and "-E" options to log to files.
2500 2376 """
2501 2377
2502 2378 if opts["stdio"]:
2503 2379 if repo is None:
2504 2380 raise hg.RepoError(_("There is no Mercurial repository here"
2505 2381 " (.hg not found)"))
2506 2382 s = sshserver.sshserver(ui, repo)
2507 2383 s.serve_forever()
2508 2384
2509 2385 optlist = ("name templates style address port ipv6"
2510 2386 " accesslog errorlog webdir_conf")
2511 2387 for o in optlist.split():
2512 2388 if opts[o]:
2513 2389 ui.setconfig("web", o, str(opts[o]))
2514 2390
2515 2391 if repo is None and not ui.config("web", "webdir_conf"):
2516 2392 raise hg.RepoError(_("There is no Mercurial repository here"
2517 2393 " (.hg not found)"))
2518 2394
2519 2395 if opts['daemon'] and not opts['daemon_pipefds']:
2520 2396 rfd, wfd = os.pipe()
2521 2397 args = sys.argv[:]
2522 2398 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
2523 2399 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
2524 2400 args[0], args)
2525 2401 os.close(wfd)
2526 2402 os.read(rfd, 1)
2527 2403 os._exit(0)
2528 2404
2529 2405 httpd = hgweb.server.create_server(ui, repo)
2530 2406
2531 2407 if ui.verbose:
2532 2408 if httpd.port != 80:
2533 2409 ui.status(_('listening at http://%s:%d/\n') %
2534 2410 (httpd.addr, httpd.port))
2535 2411 else:
2536 2412 ui.status(_('listening at http://%s/\n') % httpd.addr)
2537 2413
2538 2414 if opts['pid_file']:
2539 2415 fp = open(opts['pid_file'], 'w')
2540 2416 fp.write(str(os.getpid()) + '\n')
2541 2417 fp.close()
2542 2418
2543 2419 if opts['daemon_pipefds']:
2544 2420 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
2545 2421 os.close(rfd)
2546 2422 os.write(wfd, 'y')
2547 2423 os.close(wfd)
2548 2424 sys.stdout.flush()
2549 2425 sys.stderr.flush()
2550 2426 fd = os.open(util.nulldev, os.O_RDWR)
2551 2427 if fd != 0: os.dup2(fd, 0)
2552 2428 if fd != 1: os.dup2(fd, 1)
2553 2429 if fd != 2: os.dup2(fd, 2)
2554 2430 if fd not in (0, 1, 2): os.close(fd)
2555 2431
2556 2432 httpd.serve_forever()
2557 2433
2558 2434 def status(ui, repo, *pats, **opts):
2559 2435 """show changed files in the working directory
2560 2436
2561 2437 Show status of files in the repository. If names are given, only
2562 2438 files that match are shown. Files that are clean or ignored, are
2563 2439 not listed unless -c (clean), -i (ignored) or -A is given.
2564 2440
2565 2441 If one revision is given, it is used as the base revision.
2566 2442 If two revisions are given, the difference between them is shown.
2567 2443
2568 2444 The codes used to show the status of files are:
2569 2445 M = modified
2570 2446 A = added
2571 2447 R = removed
2572 2448 C = clean
2573 2449 ! = deleted, but still tracked
2574 2450 ? = not tracked
2575 2451 I = ignored (not shown by default)
2576 2452 = the previous added file was copied from here
2577 2453 """
2578 2454
2579 2455 all = opts['all']
2580 2456 node1, node2 = cmdutil.revpair(ui, repo, opts.get('rev'))
2581 2457
2582 2458 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
2583 2459 cwd = (pats and repo.getcwd()) or ''
2584 2460 modified, added, removed, deleted, unknown, ignored, clean = [
2585 2461 [util.pathto(cwd, x) for x in n]
2586 2462 for n in repo.status(node1=node1, node2=node2, files=files,
2587 2463 match=matchfn,
2588 2464 list_ignored=all or opts['ignored'],
2589 2465 list_clean=all or opts['clean'])]
2590 2466
2591 2467 changetypes = (('modified', 'M', modified),
2592 2468 ('added', 'A', added),
2593 2469 ('removed', 'R', removed),
2594 2470 ('deleted', '!', deleted),
2595 2471 ('unknown', '?', unknown),
2596 2472 ('ignored', 'I', ignored))
2597 2473
2598 2474 explicit_changetypes = changetypes + (('clean', 'C', clean),)
2599 2475
2600 2476 end = opts['print0'] and '\0' or '\n'
2601 2477
2602 2478 for opt, char, changes in ([ct for ct in explicit_changetypes
2603 2479 if all or opts[ct[0]]]
2604 2480 or changetypes):
2605 2481 if opts['no_status']:
2606 2482 format = "%%s%s" % end
2607 2483 else:
2608 2484 format = "%s %%s%s" % (char, end)
2609 2485
2610 2486 for f in changes:
2611 2487 ui.write(format % f)
2612 2488 if ((all or opts.get('copies')) and not opts.get('no_status')):
2613 2489 copied = repo.dirstate.copied(f)
2614 2490 if copied:
2615 2491 ui.write(' %s%s' % (copied, end))
2616 2492
2617 2493 def tag(ui, repo, name, rev_=None, **opts):
2618 2494 """add a tag for the current tip or a given revision
2619 2495
2620 2496 Name a particular revision using <name>.
2621 2497
2622 2498 Tags are used to name particular revisions of the repository and are
2623 2499 very useful to compare different revision, to go back to significant
2624 2500 earlier versions or to mark branch points as releases, etc.
2625 2501
2626 2502 If no revision is given, the parent of the working directory is used.
2627 2503
2628 2504 To facilitate version control, distribution, and merging of tags,
2629 2505 they are stored as a file named ".hgtags" which is managed
2630 2506 similarly to other project files and can be hand-edited if
2631 2507 necessary. The file '.hg/localtags' is used for local tags (not
2632 2508 shared among repositories).
2633 2509 """
2634 2510 if name in ['tip', '.']:
2635 2511 raise util.Abort(_("the name '%s' is reserved") % name)
2636 2512 if rev_ is not None:
2637 2513 ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, "
2638 2514 "please use 'hg tag [-r REV] NAME' instead\n"))
2639 2515 if opts['rev']:
2640 2516 raise util.Abort(_("use only one form to specify the revision"))
2641 2517 if opts['rev']:
2642 2518 rev_ = opts['rev']
2643 2519 if not rev_ and repo.dirstate.parents()[1] != nullid:
2644 2520 raise util.Abort(_('uncommitted merge - please provide a '
2645 2521 'specific revision'))
2646 2522 r = repo.changectx(rev_).node()
2647 2523
2648 2524 message = opts['message']
2649 2525 if not message:
2650 2526 message = _('Added tag %s for changeset %s') % (name, short(r))
2651 2527
2652 2528 repo.tag(name, r, message, opts['local'], opts['user'], opts['date'])
2653 2529
2654 2530 def tags(ui, repo):
2655 2531 """list repository tags
2656 2532
2657 2533 List the repository tags.
2658 2534
2659 2535 This lists both regular and local tags.
2660 2536 """
2661 2537
2662 2538 l = repo.tagslist()
2663 2539 l.reverse()
2664 2540 hexfunc = ui.debugflag and hex or short
2665 2541 for t, n in l:
2666 2542 try:
2667 2543 r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n))
2668 2544 except KeyError:
2669 2545 r = " ?:?"
2670 2546 if ui.quiet:
2671 2547 ui.write("%s\n" % t)
2672 2548 else:
2673 2549 ui.write("%-30s %s\n" % (t, r))
2674 2550
2675 2551 def tip(ui, repo, **opts):
2676 2552 """show the tip revision
2677 2553
2678 2554 Show the tip revision.
2679 2555 """
2680 2556 n = repo.changelog.tip()
2681 2557 br = None
2682 2558 if opts['branches']:
2683 2559 ui.warn(_("the --branches option is deprecated, "
2684 2560 "please use 'hg branches' instead\n"))
2685 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 2563 if opts['patch']:
2688 2564 patch.diff(repo, repo.changelog.parents(n)[0], n)
2689 2565
2690 2566 def unbundle(ui, repo, fname, **opts):
2691 2567 """apply a changegroup file
2692 2568
2693 2569 Apply a compressed changegroup file generated by the bundle
2694 2570 command.
2695 2571 """
2696 2572 f = urllib.urlopen(fname)
2697 2573
2698 2574 header = f.read(6)
2699 2575 if not header.startswith("HG"):
2700 2576 raise util.Abort(_("%s: not a Mercurial bundle file") % fname)
2701 2577 elif not header.startswith("HG10"):
2702 2578 raise util.Abort(_("%s: unknown bundle version") % fname)
2703 2579 elif header == "HG10BZ":
2704 2580 def generator(f):
2705 2581 zd = bz2.BZ2Decompressor()
2706 2582 zd.decompress("BZ")
2707 2583 for chunk in f:
2708 2584 yield zd.decompress(chunk)
2709 2585 elif header == "HG10UN":
2710 2586 def generator(f):
2711 2587 for chunk in f:
2712 2588 yield chunk
2713 2589 else:
2714 2590 raise util.Abort(_("%s: unknown bundle compression type")
2715 2591 % fname)
2716 2592 gen = generator(util.filechunkiter(f, 4096))
2717 2593 modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle',
2718 2594 'bundle:' + fname)
2719 2595 return postincoming(ui, repo, modheads, opts['update'])
2720 2596
2721 2597 def update(ui, repo, node=None, merge=False, clean=False, force=None,
2722 2598 branch=None):
2723 2599 """update or merge working directory
2724 2600
2725 2601 Update the working directory to the specified revision.
2726 2602
2727 2603 If there are no outstanding changes in the working directory and
2728 2604 there is a linear relationship between the current version and the
2729 2605 requested version, the result is the requested version.
2730 2606
2731 2607 To merge the working directory with another revision, use the
2732 2608 merge command.
2733 2609
2734 2610 By default, update will refuse to run if doing so would require
2735 2611 merging or discarding local changes.
2736 2612 """
2737 2613 node = _lookup(repo, node, branch)
2738 2614 if clean:
2739 2615 return hg.clean(repo, node)
2740 2616 else:
2741 2617 return hg.update(repo, node)
2742 2618
2743 2619 def _lookup(repo, node, branch=None):
2744 2620 if branch:
2745 2621 repo.ui.warn(_("the --branch option is deprecated, "
2746 2622 "please use 'hg branch' instead\n"))
2747 2623 br = repo.branchlookup(branch=branch)
2748 2624 found = []
2749 2625 for x in br:
2750 2626 if branch in br[x]:
2751 2627 found.append(x)
2752 2628 if len(found) > 1:
2753 2629 repo.ui.warn(_("Found multiple heads for %s\n") % branch)
2754 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 2633 raise util.Abort("")
2757 2634 if len(found) == 1:
2758 2635 node = found[0]
2759 2636 repo.ui.warn(_("Using head %s for branch %s\n")
2760 2637 % (short(node), branch))
2761 2638 else:
2762 2639 raise util.Abort(_("branch %s not found") % branch)
2763 2640 else:
2764 2641 node = node and repo.lookup(node) or repo.changelog.tip()
2765 2642 return node
2766 2643
2767 2644 def verify(ui, repo):
2768 2645 """verify the integrity of the repository
2769 2646
2770 2647 Verify the integrity of the current repository.
2771 2648
2772 2649 This will perform an extensive check of the repository's
2773 2650 integrity, validating the hashes and checksums of each entry in
2774 2651 the changelog, manifest, and tracked files, as well as the
2775 2652 integrity of their crosslinks and indices.
2776 2653 """
2777 2654 return hg.verify(repo)
2778 2655
2779 2656 # Command options and aliases are listed here, alphabetically
2780 2657
2781 2658 globalopts = [
2782 2659 ('R', 'repository', '',
2783 2660 _('repository root directory or symbolic path name')),
2784 2661 ('', 'cwd', '', _('change working directory')),
2785 2662 ('y', 'noninteractive', None,
2786 2663 _('do not prompt, assume \'yes\' for any required answers')),
2787 2664 ('q', 'quiet', None, _('suppress output')),
2788 2665 ('v', 'verbose', None, _('enable additional output')),
2789 2666 ('', 'config', [], _('set/override config option')),
2790 2667 ('', 'debug', None, _('enable debugging output')),
2791 2668 ('', 'debugger', None, _('start debugger')),
2792 2669 ('', 'lsprof', None, _('print improved command execution profile')),
2793 2670 ('', 'traceback', None, _('print traceback on exception')),
2794 2671 ('', 'time', None, _('time how long the command takes')),
2795 2672 ('', 'profile', None, _('print command execution profile')),
2796 2673 ('', 'version', None, _('output version information and exit')),
2797 2674 ('h', 'help', None, _('display help and exit')),
2798 2675 ]
2799 2676
2800 2677 dryrunopts = [('n', 'dry-run', None,
2801 2678 _('do not perform actions, just print output'))]
2802 2679
2803 2680 remoteopts = [
2804 2681 ('e', 'ssh', '', _('specify ssh command to use')),
2805 2682 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2806 2683 ]
2807 2684
2808 2685 walkopts = [
2809 2686 ('I', 'include', [], _('include names matching the given patterns')),
2810 2687 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2811 2688 ]
2812 2689
2813 2690 table = {
2814 2691 "^add":
2815 2692 (add,
2816 2693 walkopts + dryrunopts,
2817 2694 _('hg add [OPTION]... [FILE]...')),
2818 2695 "addremove":
2819 2696 (addremove,
2820 2697 [('s', 'similarity', '',
2821 2698 _('guess renamed files by similarity (0<=s<=100)')),
2822 2699 ] + walkopts + dryrunopts,
2823 2700 _('hg addremove [OPTION]... [FILE]...')),
2824 2701 "^annotate":
2825 2702 (annotate,
2826 2703 [('r', 'rev', '', _('annotate the specified revision')),
2827 2704 ('f', 'follow', None, _('follow file copies and renames')),
2828 2705 ('a', 'text', None, _('treat all files as text')),
2829 2706 ('u', 'user', None, _('list the author')),
2830 2707 ('d', 'date', None, _('list the date')),
2831 2708 ('n', 'number', None, _('list the revision number (default)')),
2832 2709 ('c', 'changeset', None, _('list the changeset')),
2833 2710 ] + walkopts,
2834 2711 _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')),
2835 2712 "archive":
2836 2713 (archive,
2837 2714 [('', 'no-decode', None, _('do not pass files through decoders')),
2838 2715 ('p', 'prefix', '', _('directory prefix for files in archive')),
2839 2716 ('r', 'rev', '', _('revision to distribute')),
2840 2717 ('t', 'type', '', _('type of distribution to create')),
2841 2718 ] + walkopts,
2842 2719 _('hg archive [OPTION]... DEST')),
2843 2720 "backout":
2844 2721 (backout,
2845 2722 [('', 'merge', None,
2846 2723 _('merge with old dirstate parent after backout')),
2847 2724 ('m', 'message', '', _('use <text> as commit message')),
2848 2725 ('l', 'logfile', '', _('read commit message from <file>')),
2849 2726 ('d', 'date', '', _('record datecode as commit date')),
2850 2727 ('', 'parent', '', _('parent to choose when backing out merge')),
2851 2728 ('u', 'user', '', _('record user as committer')),
2852 2729 ] + walkopts,
2853 2730 _('hg backout [OPTION]... REV')),
2854 2731 "branch": (branch, [], _('hg branch [NAME]')),
2855 2732 "branches": (branches, [], _('hg branches')),
2856 2733 "bundle":
2857 2734 (bundle,
2858 2735 [('f', 'force', None,
2859 2736 _('run even when remote repository is unrelated')),
2860 2737 ('r', 'rev', [],
2861 2738 _('a changeset you would like to bundle')),
2862 2739 ('', 'base', [],
2863 2740 _('a base changeset to specify instead of a destination')),
2864 2741 ] + remoteopts,
2865 2742 _('hg bundle [--base REV]... [--rev REV]... FILE [DEST]')),
2866 2743 "cat":
2867 2744 (cat,
2868 2745 [('o', 'output', '', _('print output to file with formatted name')),
2869 2746 ('r', 'rev', '', _('print the given revision')),
2870 2747 ] + walkopts,
2871 2748 _('hg cat [OPTION]... FILE...')),
2872 2749 "^clone":
2873 2750 (clone,
2874 2751 [('U', 'noupdate', None, _('do not update the new working directory')),
2875 2752 ('r', 'rev', [],
2876 2753 _('a changeset you would like to have after cloning')),
2877 2754 ('', 'pull', None, _('use pull protocol to copy metadata')),
2878 2755 ('', 'uncompressed', None,
2879 2756 _('use uncompressed transfer (fast over LAN)')),
2880 2757 ] + remoteopts,
2881 2758 _('hg clone [OPTION]... SOURCE [DEST]')),
2882 2759 "^commit|ci":
2883 2760 (commit,
2884 2761 [('A', 'addremove', None,
2885 2762 _('mark new/missing files as added/removed before committing')),
2886 2763 ('m', 'message', '', _('use <text> as commit message')),
2887 2764 ('l', 'logfile', '', _('read the commit message from <file>')),
2888 2765 ('d', 'date', '', _('record datecode as commit date')),
2889 2766 ('u', 'user', '', _('record user as commiter')),
2890 2767 ] + walkopts,
2891 2768 _('hg commit [OPTION]... [FILE]...')),
2892 2769 "copy|cp":
2893 2770 (copy,
2894 2771 [('A', 'after', None, _('record a copy that has already occurred')),
2895 2772 ('f', 'force', None,
2896 2773 _('forcibly copy over an existing managed file')),
2897 2774 ] + walkopts + dryrunopts,
2898 2775 _('hg copy [OPTION]... [SOURCE]... DEST')),
2899 2776 "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')),
2900 2777 "debugcomplete":
2901 2778 (debugcomplete,
2902 2779 [('o', 'options', None, _('show the command options'))],
2903 2780 _('debugcomplete [-o] CMD')),
2904 2781 "debugrebuildstate":
2905 2782 (debugrebuildstate,
2906 2783 [('r', 'rev', '', _('revision to rebuild to'))],
2907 2784 _('debugrebuildstate [-r REV] [REV]')),
2908 2785 "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')),
2909 2786 "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')),
2910 2787 "debugstate": (debugstate, [], _('debugstate')),
2911 2788 "debugdata": (debugdata, [], _('debugdata FILE REV')),
2912 2789 "debugindex": (debugindex, [], _('debugindex FILE')),
2913 2790 "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')),
2914 2791 "debugrename": (debugrename, [], _('debugrename FILE [REV]')),
2915 2792 "debugwalk":
2916 2793 (debugwalk, walkopts, _('debugwalk [OPTION]... [FILE]...')),
2917 2794 "^diff":
2918 2795 (diff,
2919 2796 [('r', 'rev', [], _('revision')),
2920 2797 ('a', 'text', None, _('treat all files as text')),
2921 2798 ('p', 'show-function', None,
2922 2799 _('show which function each change is in')),
2923 2800 ('g', 'git', None, _('use git extended diff format')),
2924 2801 ('', 'nodates', None, _("don't include dates in diff headers")),
2925 2802 ('w', 'ignore-all-space', None,
2926 2803 _('ignore white space when comparing lines')),
2927 2804 ('b', 'ignore-space-change', None,
2928 2805 _('ignore changes in the amount of white space')),
2929 2806 ('B', 'ignore-blank-lines', None,
2930 2807 _('ignore changes whose lines are all blank')),
2931 2808 ] + walkopts,
2932 2809 _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')),
2933 2810 "^export":
2934 2811 (export,
2935 2812 [('o', 'output', '', _('print output to file with formatted name')),
2936 2813 ('a', 'text', None, _('treat all files as text')),
2937 2814 ('g', 'git', None, _('use git extended diff format')),
2938 2815 ('', 'nodates', None, _("don't include dates in diff headers")),
2939 2816 ('', 'switch-parent', None, _('diff against the second parent'))],
2940 2817 _('hg export [-a] [-o OUTFILESPEC] REV...')),
2941 2818 "grep":
2942 2819 (grep,
2943 2820 [('0', 'print0', None, _('end fields with NUL')),
2944 2821 ('', 'all', None, _('print all revisions that match')),
2945 2822 ('f', 'follow', None,
2946 2823 _('follow changeset history, or file history across copies and renames')),
2947 2824 ('i', 'ignore-case', None, _('ignore case when matching')),
2948 2825 ('l', 'files-with-matches', None,
2949 2826 _('print only filenames and revs that match')),
2950 2827 ('n', 'line-number', None, _('print matching line numbers')),
2951 2828 ('r', 'rev', [], _('search in given revision range')),
2952 2829 ('u', 'user', None, _('print user who committed change')),
2953 2830 ] + walkopts,
2954 2831 _('hg grep [OPTION]... PATTERN [FILE]...')),
2955 2832 "heads":
2956 2833 (heads,
2957 2834 [('b', 'branches', None, _('show branches (DEPRECATED)')),
2958 2835 ('', 'style', '', _('display using template map file')),
2959 2836 ('r', 'rev', '', _('show only heads which are descendants of rev')),
2960 2837 ('', 'template', '', _('display with template'))],
2961 2838 _('hg heads [-r REV]')),
2962 2839 "help": (help_, [], _('hg help [COMMAND]')),
2963 2840 "identify|id": (identify, [], _('hg identify')),
2964 2841 "import|patch":
2965 2842 (import_,
2966 2843 [('p', 'strip', 1,
2967 2844 _('directory strip option for patch. This has the same\n'
2968 2845 'meaning as the corresponding patch option')),
2969 2846 ('m', 'message', '', _('use <text> as commit message')),
2970 2847 ('b', 'base', '', _('base path (DEPRECATED)')),
2971 2848 ('f', 'force', None,
2972 2849 _('skip check for outstanding uncommitted changes'))],
2973 2850 _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')),
2974 2851 "incoming|in": (incoming,
2975 2852 [('M', 'no-merges', None, _('do not show merges')),
2976 2853 ('f', 'force', None,
2977 2854 _('run even when remote repository is unrelated')),
2978 2855 ('', 'style', '', _('display using template map file')),
2979 2856 ('n', 'newest-first', None, _('show newest record first')),
2980 2857 ('', 'bundle', '', _('file to store the bundles into')),
2981 2858 ('p', 'patch', None, _('show patch')),
2982 2859 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
2983 2860 ('', 'template', '', _('display with template')),
2984 2861 ] + remoteopts,
2985 2862 _('hg incoming [-p] [-n] [-M] [-r REV]...'
2986 2863 ' [--bundle FILENAME] [SOURCE]')),
2987 2864 "^init":
2988 2865 (init, remoteopts, _('hg init [-e FILE] [--remotecmd FILE] [DEST]')),
2989 2866 "locate":
2990 2867 (locate,
2991 2868 [('r', 'rev', '', _('search the repository as it stood at rev')),
2992 2869 ('0', 'print0', None,
2993 2870 _('end filenames with NUL, for use with xargs')),
2994 2871 ('f', 'fullpath', None,
2995 2872 _('print complete paths from the filesystem root')),
2996 2873 ] + walkopts,
2997 2874 _('hg locate [OPTION]... [PATTERN]...')),
2998 2875 "^log|history":
2999 2876 (log,
3000 2877 [('b', 'branches', None, _('show branches (DEPRECATED)')),
3001 2878 ('f', 'follow', None,
3002 2879 _('follow changeset history, or file history across copies and renames')),
3003 2880 ('', 'follow-first', None,
3004 2881 _('only follow the first parent of merge changesets')),
3005 2882 ('C', 'copies', None, _('show copied files')),
3006 2883 ('k', 'keyword', [], _('search for a keyword')),
3007 2884 ('l', 'limit', '', _('limit number of changes displayed')),
3008 2885 ('r', 'rev', [], _('show the specified revision or range')),
3009 2886 ('M', 'no-merges', None, _('do not show merges')),
3010 2887 ('', 'style', '', _('display using template map file')),
3011 2888 ('m', 'only-merges', None, _('show only merges')),
3012 2889 ('p', 'patch', None, _('show patch')),
3013 2890 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3014 2891 ('', 'template', '', _('display with template')),
3015 2892 ] + walkopts,
3016 2893 _('hg log [OPTION]... [FILE]')),
3017 2894 "manifest": (manifest, [], _('hg manifest [REV]')),
3018 2895 "merge":
3019 2896 (merge,
3020 2897 [('b', 'branch', '', _('merge with head of a specific branch (DEPRECATED)')),
3021 2898 ('f', 'force', None, _('force a merge with outstanding changes'))],
3022 2899 _('hg merge [-f] [REV]')),
3023 2900 "outgoing|out": (outgoing,
3024 2901 [('M', 'no-merges', None, _('do not show merges')),
3025 2902 ('f', 'force', None,
3026 2903 _('run even when remote repository is unrelated')),
3027 2904 ('p', 'patch', None, _('show patch')),
3028 2905 ('', 'style', '', _('display using template map file')),
3029 2906 ('r', 'rev', [], _('a specific revision you would like to push')),
3030 2907 ('n', 'newest-first', None, _('show newest record first')),
3031 2908 ('', 'template', '', _('display with template')),
3032 2909 ] + remoteopts,
3033 2910 _('hg outgoing [-M] [-p] [-n] [-r REV]... [DEST]')),
3034 2911 "^parents":
3035 2912 (parents,
3036 2913 [('b', 'branches', None, _('show branches (DEPRECATED)')),
3037 2914 ('r', 'rev', '', _('show parents from the specified rev')),
3038 2915 ('', 'style', '', _('display using template map file')),
3039 2916 ('', 'template', '', _('display with template'))],
3040 2917 _('hg parents [-r REV] [FILE]')),
3041 2918 "paths": (paths, [], _('hg paths [NAME]')),
3042 2919 "^pull":
3043 2920 (pull,
3044 2921 [('u', 'update', None,
3045 2922 _('update to new tip if changesets were pulled')),
3046 2923 ('f', 'force', None,
3047 2924 _('run even when remote repository is unrelated')),
3048 2925 ('r', 'rev', [], _('a specific revision up to which you would like to pull')),
3049 2926 ] + remoteopts,
3050 2927 _('hg pull [-u] [-r REV]... [-e FILE] [--remotecmd FILE] [SOURCE]')),
3051 2928 "^push":
3052 2929 (push,
3053 2930 [('f', 'force', None, _('force push')),
3054 2931 ('r', 'rev', [], _('a specific revision you would like to push')),
3055 2932 ] + remoteopts,
3056 2933 _('hg push [-f] [-r REV]... [-e FILE] [--remotecmd FILE] [DEST]')),
3057 2934 "debugrawcommit|rawcommit":
3058 2935 (rawcommit,
3059 2936 [('p', 'parent', [], _('parent')),
3060 2937 ('d', 'date', '', _('date code')),
3061 2938 ('u', 'user', '', _('user')),
3062 2939 ('F', 'files', '', _('file list')),
3063 2940 ('m', 'message', '', _('commit message')),
3064 2941 ('l', 'logfile', '', _('commit message file'))],
3065 2942 _('hg debugrawcommit [OPTION]... [FILE]...')),
3066 2943 "recover": (recover, [], _('hg recover')),
3067 2944 "^remove|rm":
3068 2945 (remove,
3069 2946 [('A', 'after', None, _('record remove that has already occurred')),
3070 2947 ('f', 'force', None, _('remove file even if modified')),
3071 2948 ] + walkopts,
3072 2949 _('hg remove [OPTION]... FILE...')),
3073 2950 "rename|mv":
3074 2951 (rename,
3075 2952 [('A', 'after', None, _('record a rename that has already occurred')),
3076 2953 ('f', 'force', None,
3077 2954 _('forcibly copy over an existing managed file')),
3078 2955 ] + walkopts + dryrunopts,
3079 2956 _('hg rename [OPTION]... SOURCE... DEST')),
3080 2957 "^revert":
3081 2958 (revert,
3082 2959 [('a', 'all', None, _('revert all changes when no arguments given')),
3083 2960 ('r', 'rev', '', _('revision to revert to')),
3084 2961 ('', 'no-backup', None, _('do not save backup copies of files')),
3085 2962 ] + walkopts + dryrunopts,
3086 2963 _('hg revert [-r REV] [NAME]...')),
3087 2964 "rollback": (rollback, [], _('hg rollback')),
3088 2965 "root": (root, [], _('hg root')),
3089 2966 "showconfig|debugconfig":
3090 2967 (showconfig,
3091 2968 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3092 2969 _('showconfig [-u] [NAME]...')),
3093 2970 "^serve":
3094 2971 (serve,
3095 2972 [('A', 'accesslog', '', _('name of access log file to write to')),
3096 2973 ('d', 'daemon', None, _('run server in background')),
3097 2974 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3098 2975 ('E', 'errorlog', '', _('name of error log file to write to')),
3099 2976 ('p', 'port', 0, _('port to use (default: 8000)')),
3100 2977 ('a', 'address', '', _('address to use')),
3101 2978 ('n', 'name', '',
3102 2979 _('name to show in web pages (default: working dir)')),
3103 2980 ('', 'webdir-conf', '', _('name of the webdir config file'
3104 2981 ' (serve more than one repo)')),
3105 2982 ('', 'pid-file', '', _('name of file to write process ID to')),
3106 2983 ('', 'stdio', None, _('for remote clients')),
3107 2984 ('t', 'templates', '', _('web templates to use')),
3108 2985 ('', 'style', '', _('template style to use')),
3109 2986 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))],
3110 2987 _('hg serve [OPTION]...')),
3111 2988 "^status|st":
3112 2989 (status,
3113 2990 [('A', 'all', None, _('show status of all files')),
3114 2991 ('m', 'modified', None, _('show only modified files')),
3115 2992 ('a', 'added', None, _('show only added files')),
3116 2993 ('r', 'removed', None, _('show only removed files')),
3117 2994 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3118 2995 ('c', 'clean', None, _('show only files without changes')),
3119 2996 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3120 2997 ('i', 'ignored', None, _('show ignored files')),
3121 2998 ('n', 'no-status', None, _('hide status prefix')),
3122 2999 ('C', 'copies', None, _('show source of copied files')),
3123 3000 ('0', 'print0', None,
3124 3001 _('end filenames with NUL, for use with xargs')),
3125 3002 ('', 'rev', [], _('show difference from revision')),
3126 3003 ] + walkopts,
3127 3004 _('hg status [OPTION]... [FILE]...')),
3128 3005 "tag":
3129 3006 (tag,
3130 3007 [('l', 'local', None, _('make the tag local')),
3131 3008 ('m', 'message', '', _('message for tag commit log entry')),
3132 3009 ('d', 'date', '', _('record datecode as commit date')),
3133 3010 ('u', 'user', '', _('record user as commiter')),
3134 3011 ('r', 'rev', '', _('revision to tag'))],
3135 3012 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')),
3136 3013 "tags": (tags, [], _('hg tags')),
3137 3014 "tip":
3138 3015 (tip,
3139 3016 [('b', 'branches', None, _('show branches (DEPRECATED)')),
3140 3017 ('', 'style', '', _('display using template map file')),
3141 3018 ('p', 'patch', None, _('show patch')),
3142 3019 ('', 'template', '', _('display with template'))],
3143 3020 _('hg tip [-p]')),
3144 3021 "unbundle":
3145 3022 (unbundle,
3146 3023 [('u', 'update', None,
3147 3024 _('update to new tip if changesets were unbundled'))],
3148 3025 _('hg unbundle [-u] FILE')),
3149 3026 "^update|up|checkout|co":
3150 3027 (update,
3151 3028 [('b', 'branch', '',
3152 3029 _('checkout the head of a specific branch (DEPRECATED)')),
3153 3030 ('m', 'merge', None, _('allow merging of branches (DEPRECATED)')),
3154 3031 ('C', 'clean', None, _('overwrite locally modified files')),
3155 3032 ('f', 'force', None, _('force a merge with outstanding changes'))],
3156 3033 _('hg update [-C] [-f] [REV]')),
3157 3034 "verify": (verify, [], _('hg verify')),
3158 3035 "version": (show_version, [], _('hg version')),
3159 3036 }
3160 3037
3161 3038 norepo = ("clone init version help debugancestor debugcomplete debugdata"
3162 3039 " debugindex debugindexdot")
3163 3040 optionalrepo = ("paths serve showconfig")
3164 3041
3165 3042 def findpossible(ui, cmd):
3166 3043 """
3167 3044 Return cmd -> (aliases, command table entry)
3168 3045 for each matching command.
3169 3046 Return debug commands (or their aliases) only if no normal command matches.
3170 3047 """
3171 3048 choice = {}
3172 3049 debugchoice = {}
3173 3050 for e in table.keys():
3174 3051 aliases = e.lstrip("^").split("|")
3175 3052 found = None
3176 3053 if cmd in aliases:
3177 3054 found = cmd
3178 3055 elif not ui.config("ui", "strict"):
3179 3056 for a in aliases:
3180 3057 if a.startswith(cmd):
3181 3058 found = a
3182 3059 break
3183 3060 if found is not None:
3184 3061 if aliases[0].startswith("debug") or found.startswith("debug"):
3185 3062 debugchoice[found] = (aliases, table[e])
3186 3063 else:
3187 3064 choice[found] = (aliases, table[e])
3188 3065
3189 3066 if not choice and debugchoice:
3190 3067 choice = debugchoice
3191 3068
3192 3069 return choice
3193 3070
3194 3071 def findcmd(ui, cmd):
3195 3072 """Return (aliases, command table entry) for command string."""
3196 3073 choice = findpossible(ui, cmd)
3197 3074
3198 3075 if choice.has_key(cmd):
3199 3076 return choice[cmd]
3200 3077
3201 3078 if len(choice) > 1:
3202 3079 clist = choice.keys()
3203 3080 clist.sort()
3204 3081 raise AmbiguousCommand(cmd, clist)
3205 3082
3206 3083 if choice:
3207 3084 return choice.values()[0]
3208 3085
3209 3086 raise UnknownCommand(cmd)
3210 3087
3211 3088 def catchterm(*args):
3212 3089 raise util.SignalInterrupt
3213 3090
3214 3091 def run():
3215 3092 sys.exit(dispatch(sys.argv[1:]))
3216 3093
3217 3094 class ParseError(Exception):
3218 3095 """Exception raised on errors in parsing the command line."""
3219 3096
3220 3097 def parse(ui, args):
3221 3098 options = {}
3222 3099 cmdoptions = {}
3223 3100
3224 3101 try:
3225 3102 args = fancyopts.fancyopts(args, globalopts, options)
3226 3103 except fancyopts.getopt.GetoptError, inst:
3227 3104 raise ParseError(None, inst)
3228 3105
3229 3106 if args:
3230 3107 cmd, args = args[0], args[1:]
3231 3108 aliases, i = findcmd(ui, cmd)
3232 3109 cmd = aliases[0]
3233 3110 defaults = ui.config("defaults", cmd)
3234 3111 if defaults:
3235 3112 args = shlex.split(defaults) + args
3236 3113 c = list(i[1])
3237 3114 else:
3238 3115 cmd = None
3239 3116 c = []
3240 3117
3241 3118 # combine global options into local
3242 3119 for o in globalopts:
3243 3120 c.append((o[0], o[1], options[o[1]], o[3]))
3244 3121
3245 3122 try:
3246 3123 args = fancyopts.fancyopts(args, c, cmdoptions)
3247 3124 except fancyopts.getopt.GetoptError, inst:
3248 3125 raise ParseError(cmd, inst)
3249 3126
3250 3127 # separate global options back out
3251 3128 for o in globalopts:
3252 3129 n = o[1]
3253 3130 options[n] = cmdoptions[n]
3254 3131 del cmdoptions[n]
3255 3132
3256 3133 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
3257 3134
3258 3135 external = {}
3259 3136
3260 3137 def findext(name):
3261 3138 '''return module with given extension name'''
3262 3139 try:
3263 3140 return sys.modules[external[name]]
3264 3141 except KeyError:
3265 3142 for k, v in external.iteritems():
3266 3143 if k.endswith('.' + name) or k.endswith('/' + name) or v == name:
3267 3144 return sys.modules[v]
3268 3145 raise KeyError(name)
3269 3146
3270 3147 def load_extensions(ui):
3271 3148 added = []
3272 3149 for ext_name, load_from_name in ui.extensions():
3273 3150 if ext_name in external:
3274 3151 continue
3275 3152 try:
3276 3153 if load_from_name:
3277 3154 # the module will be loaded in sys.modules
3278 3155 # choose an unique name so that it doesn't
3279 3156 # conflicts with other modules
3280 3157 module_name = "hgext_%s" % ext_name.replace('.', '_')
3281 3158 mod = imp.load_source(module_name, load_from_name)
3282 3159 else:
3283 3160 def importh(name):
3284 3161 mod = __import__(name)
3285 3162 components = name.split('.')
3286 3163 for comp in components[1:]:
3287 3164 mod = getattr(mod, comp)
3288 3165 return mod
3289 3166 try:
3290 3167 mod = importh("hgext.%s" % ext_name)
3291 3168 except ImportError:
3292 3169 mod = importh(ext_name)
3293 3170 external[ext_name] = mod.__name__
3294 3171 added.append((mod, ext_name))
3295 3172 except (util.SignalInterrupt, KeyboardInterrupt):
3296 3173 raise
3297 3174 except Exception, inst:
3298 3175 ui.warn(_("*** failed to import extension %s: %s\n") %
3299 3176 (ext_name, inst))
3300 3177 if ui.print_exc():
3301 3178 return 1
3302 3179
3303 3180 for mod, name in added:
3304 3181 uisetup = getattr(mod, 'uisetup', None)
3305 3182 if uisetup:
3306 3183 uisetup(ui)
3307 3184 cmdtable = getattr(mod, 'cmdtable', {})
3308 3185 for t in cmdtable:
3309 3186 if t in table:
3310 3187 ui.warn(_("module %s overrides %s\n") % (name, t))
3311 3188 table.update(cmdtable)
3312 3189
3313 3190 def parseconfig(config):
3314 3191 """parse the --config options from the command line"""
3315 3192 parsed = []
3316 3193 for cfg in config:
3317 3194 try:
3318 3195 name, value = cfg.split('=', 1)
3319 3196 section, name = name.split('.', 1)
3320 3197 if not section or not name:
3321 3198 raise IndexError
3322 3199 parsed.append((section, name, value))
3323 3200 except (IndexError, ValueError):
3324 3201 raise util.Abort(_('malformed --config option: %s') % cfg)
3325 3202 return parsed
3326 3203
3327 3204 def dispatch(args):
3328 3205 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
3329 3206 num = getattr(signal, name, None)
3330 3207 if num: signal.signal(num, catchterm)
3331 3208
3332 3209 try:
3333 3210 u = ui.ui(traceback='--traceback' in sys.argv[1:])
3334 3211 except util.Abort, inst:
3335 3212 sys.stderr.write(_("abort: %s\n") % inst)
3336 3213 return -1
3337 3214
3338 3215 load_extensions(u)
3339 3216 u.addreadhook(load_extensions)
3340 3217
3341 3218 try:
3342 3219 cmd, func, args, options, cmdoptions = parse(u, args)
3343 3220 if options["time"]:
3344 3221 def get_times():
3345 3222 t = os.times()
3346 3223 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
3347 3224 t = (t[0], t[1], t[2], t[3], time.clock())
3348 3225 return t
3349 3226 s = get_times()
3350 3227 def print_time():
3351 3228 t = get_times()
3352 3229 u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
3353 3230 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
3354 3231 atexit.register(print_time)
3355 3232
3356 3233 # enter the debugger before command execution
3357 3234 if options['debugger']:
3358 3235 pdb.set_trace()
3359 3236
3360 3237 try:
3361 3238 if options['cwd']:
3362 3239 try:
3363 3240 os.chdir(options['cwd'])
3364 3241 except OSError, inst:
3365 3242 raise util.Abort('%s: %s' %
3366 3243 (options['cwd'], inst.strerror))
3367 3244
3368 3245 u.updateopts(options["verbose"], options["debug"], options["quiet"],
3369 3246 not options["noninteractive"], options["traceback"],
3370 3247 parseconfig(options["config"]))
3371 3248
3372 3249 path = u.expandpath(options["repository"]) or ""
3373 3250 repo = path and hg.repository(u, path=path) or None
3374 3251 if repo and not repo.local():
3375 3252 raise util.Abort(_("repository '%s' is not local") % path)
3376 3253
3377 3254 if options['help']:
3378 3255 return help_(u, cmd, options['version'])
3379 3256 elif options['version']:
3380 3257 return show_version(u)
3381 3258 elif not cmd:
3382 3259 return help_(u, 'shortlist')
3383 3260
3384 3261 if cmd not in norepo.split():
3385 3262 try:
3386 3263 if not repo:
3387 3264 repo = hg.repository(u, path=path)
3388 3265 u = repo.ui
3389 3266 for name in external.itervalues():
3390 3267 mod = sys.modules[name]
3391 3268 if hasattr(mod, 'reposetup'):
3392 3269 mod.reposetup(u, repo)
3393 3270 hg.repo_setup_hooks.append(mod.reposetup)
3394 3271 except hg.RepoError:
3395 3272 if cmd not in optionalrepo.split():
3396 3273 raise
3397 3274 d = lambda: func(u, repo, *args, **cmdoptions)
3398 3275 else:
3399 3276 d = lambda: func(u, *args, **cmdoptions)
3400 3277
3401 3278 try:
3402 3279 if options['profile']:
3403 3280 import hotshot, hotshot.stats
3404 3281 prof = hotshot.Profile("hg.prof")
3405 3282 try:
3406 3283 try:
3407 3284 return prof.runcall(d)
3408 3285 except:
3409 3286 try:
3410 3287 u.warn(_('exception raised - generating '
3411 3288 'profile anyway\n'))
3412 3289 except:
3413 3290 pass
3414 3291 raise
3415 3292 finally:
3416 3293 prof.close()
3417 3294 stats = hotshot.stats.load("hg.prof")
3418 3295 stats.strip_dirs()
3419 3296 stats.sort_stats('time', 'calls')
3420 3297 stats.print_stats(40)
3421 3298 elif options['lsprof']:
3422 3299 try:
3423 3300 from mercurial import lsprof
3424 3301 except ImportError:
3425 3302 raise util.Abort(_(
3426 3303 'lsprof not available - install from '
3427 3304 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
3428 3305 p = lsprof.Profiler()
3429 3306 p.enable(subcalls=True)
3430 3307 try:
3431 3308 return d()
3432 3309 finally:
3433 3310 p.disable()
3434 3311 stats = lsprof.Stats(p.getstats())
3435 3312 stats.sort()
3436 3313 stats.pprint(top=10, file=sys.stderr, climit=5)
3437 3314 else:
3438 3315 return d()
3439 3316 finally:
3440 3317 u.flush()
3441 3318 except:
3442 3319 # enter the debugger when we hit an exception
3443 3320 if options['debugger']:
3444 3321 pdb.post_mortem(sys.exc_info()[2])
3445 3322 u.print_exc()
3446 3323 raise
3447 3324 except ParseError, inst:
3448 3325 if inst.args[0]:
3449 3326 u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
3450 3327 help_(u, inst.args[0])
3451 3328 else:
3452 3329 u.warn(_("hg: %s\n") % inst.args[1])
3453 3330 help_(u, 'shortlist')
3454 3331 except AmbiguousCommand, inst:
3455 3332 u.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
3456 3333 (inst.args[0], " ".join(inst.args[1])))
3457 3334 except UnknownCommand, inst:
3458 3335 u.warn(_("hg: unknown command '%s'\n") % inst.args[0])
3459 3336 help_(u, 'shortlist')
3460 3337 except hg.RepoError, inst:
3461 3338 u.warn(_("abort: %s!\n") % inst)
3462 3339 except lock.LockHeld, inst:
3463 3340 if inst.errno == errno.ETIMEDOUT:
3464 3341 reason = _('timed out waiting for lock held by %s') % inst.locker
3465 3342 else:
3466 3343 reason = _('lock held by %s') % inst.locker
3467 3344 u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
3468 3345 except lock.LockUnavailable, inst:
3469 3346 u.warn(_("abort: could not lock %s: %s\n") %
3470 3347 (inst.desc or inst.filename, inst.strerror))
3471 3348 except revlog.RevlogError, inst:
3472 3349 u.warn(_("abort: %s!\n") % inst)
3473 3350 except util.SignalInterrupt:
3474 3351 u.warn(_("killed!\n"))
3475 3352 except KeyboardInterrupt:
3476 3353 try:
3477 3354 u.warn(_("interrupted!\n"))
3478 3355 except IOError, inst:
3479 3356 if inst.errno == errno.EPIPE:
3480 3357 if u.debugflag:
3481 3358 u.warn(_("\nbroken pipe\n"))
3482 3359 else:
3483 3360 raise
3484 3361 except IOError, inst:
3485 3362 if hasattr(inst, "code"):
3486 3363 u.warn(_("abort: %s\n") % inst)
3487 3364 elif hasattr(inst, "reason"):
3488 3365 u.warn(_("abort: error: %s\n") % inst.reason[1])
3489 3366 elif hasattr(inst, "args") and inst[0] == errno.EPIPE:
3490 3367 if u.debugflag:
3491 3368 u.warn(_("broken pipe\n"))
3492 3369 elif getattr(inst, "strerror", None):
3493 3370 if getattr(inst, "filename", None):
3494 3371 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3495 3372 else:
3496 3373 u.warn(_("abort: %s\n") % inst.strerror)
3497 3374 else:
3498 3375 raise
3499 3376 except OSError, inst:
3500 3377 if getattr(inst, "filename", None):
3501 3378 u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
3502 3379 else:
3503 3380 u.warn(_("abort: %s\n") % inst.strerror)
3504 3381 except util.UnexpectedOutput, inst:
3505 3382 u.warn(_("abort: %s") % inst[0])
3506 3383 if not isinstance(inst[1], basestring):
3507 3384 u.warn(" %r\n" % (inst[1],))
3508 3385 elif not inst[1]:
3509 3386 u.warn(_(" empty string\n"))
3510 3387 else:
3511 3388 u.warn("\n%r%s\n" %
3512 3389 (inst[1][:400], len(inst[1]) > 400 and '...' or ''))
3513 3390 except util.Abort, inst:
3514 3391 u.warn(_("abort: %s\n") % inst)
3515 3392 except TypeError, inst:
3516 3393 # was this an argument error?
3517 3394 tb = traceback.extract_tb(sys.exc_info()[2])
3518 3395 if len(tb) > 2: # no
3519 3396 raise
3520 3397 u.debug(inst, "\n")
3521 3398 u.warn(_("%s: invalid arguments\n") % cmd)
3522 3399 help_(u, cmd)
3523 3400 except SystemExit, inst:
3524 3401 # Commands shouldn't sys.exit directly, but give a return code.
3525 3402 # Just in case catch this and and pass exit code to caller.
3526 3403 return inst.code
3527 3404 except:
3528 3405 u.warn(_("** unknown exception encountered, details follow\n"))
3529 3406 u.warn(_("** report bug details to "
3530 3407 "http://www.selenic.com/mercurial/bts\n"))
3531 3408 u.warn(_("** or mercurial@selenic.com\n"))
3532 3409 u.warn(_("** Mercurial Distributed SCM (version %s)\n")
3533 3410 % version.get_version())
3534 3411 raise
3535 3412
3536 3413 return -1
@@ -1,493 +1,292 b''
1 1 # templater.py - template expansion for output
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from i18n import gettext as _
10 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 13 def parsestring(s, quoted=True):
14 14 '''parse a string using simple c-like syntax.
15 15 string must be in quotes if quoted is True.'''
16 16 if quoted:
17 17 if len(s) < 2 or s[0] != s[-1]:
18 18 raise SyntaxError(_('unmatched quotes'))
19 19 return s[1:-1].decode('string_escape')
20 20
21 21 return s.decode('string_escape')
22 22
23 23 class templater(object):
24 24 '''template expansion engine.
25 25
26 26 template expansion works like this. a map file contains key=value
27 27 pairs. if value is quoted, it is treated as string. otherwise, it
28 28 is treated as name of template file.
29 29
30 30 templater is asked to expand a key in map. it looks up key, and
31 31 looks for atrings like this: {foo}. it expands {foo} by looking up
32 32 foo in map, and substituting it. expansion is recursive: it stops
33 33 when there is no more {foo} to replace.
34 34
35 35 expansion also allows formatting and filtering.
36 36
37 37 format uses key to expand each item in list. syntax is
38 38 {key%format}.
39 39
40 40 filter uses function to transform value. syntax is
41 41 {key|filter1|filter2|...}.'''
42 42
43 43 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
44 44 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
45 45
46 46 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
47 47 '''set up template engine.
48 48 mapfile is name of file to read map definitions from.
49 49 filters is dict of functions. each transforms a value into another.
50 50 defaults is dict of default map definitions.'''
51 51 self.mapfile = mapfile or 'template'
52 52 self.cache = cache.copy()
53 53 self.map = {}
54 54 self.base = (mapfile and os.path.dirname(mapfile)) or ''
55 55 self.filters = filters
56 56 self.defaults = defaults
57 57
58 58 if not mapfile:
59 59 return
60 60 i = 0
61 61 for l in file(mapfile):
62 62 l = l.strip()
63 63 i += 1
64 64 if not l or l[0] in '#;': continue
65 65 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
66 66 if m:
67 67 key, val = m.groups()
68 68 if val[0] in "'\"":
69 69 try:
70 70 self.cache[key] = parsestring(val)
71 71 except SyntaxError, inst:
72 72 raise SyntaxError('%s:%s: %s' %
73 73 (mapfile, i, inst.args[0]))
74 74 else:
75 75 self.map[key] = os.path.join(self.base, val)
76 76 else:
77 77 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
78 78
79 79 def __contains__(self, key):
80 80 return key in self.cache or key in self.map
81 81
82 82 def __call__(self, t, **map):
83 83 '''perform expansion.
84 84 t is name of map element to expand.
85 85 map is added elements to use during expansion.'''
86 86 if not self.cache.has_key(t):
87 87 try:
88 88 self.cache[t] = file(self.map[t]).read()
89 89 except IOError, inst:
90 90 raise IOError(inst.args[0], _('template file %s: %s') %
91 91 (self.map[t], inst.args[1]))
92 92 tmpl = self.cache[t]
93 93
94 94 while tmpl:
95 95 m = self.template_re.search(tmpl)
96 96 if not m:
97 97 yield tmpl
98 98 break
99 99
100 100 start, end = m.span(0)
101 101 key, format, fl = m.groups()
102 102
103 103 if start:
104 104 yield tmpl[:start]
105 105 tmpl = tmpl[end:]
106 106
107 107 if key in map:
108 108 v = map[key]
109 109 else:
110 110 v = self.defaults.get(key, "")
111 111 if callable(v):
112 112 v = v(**map)
113 113 if format:
114 114 if not hasattr(v, '__iter__'):
115 115 raise SyntaxError(_("Error expanding '%s%s'")
116 116 % (key, format))
117 117 lm = map.copy()
118 118 for i in v:
119 119 lm.update(i)
120 120 yield self(format, **lm)
121 121 else:
122 122 if fl:
123 123 for f in fl.split("|")[1:]:
124 124 v = self.filters[f](v)
125 125 yield v
126 126
127 127 agescales = [("second", 1),
128 128 ("minute", 60),
129 129 ("hour", 3600),
130 130 ("day", 3600 * 24),
131 131 ("week", 3600 * 24 * 7),
132 132 ("month", 3600 * 24 * 30),
133 133 ("year", 3600 * 24 * 365)]
134 134
135 135 agescales.reverse()
136 136
137 137 def age(date):
138 138 '''turn a (timestamp, tzoff) tuple into an age string.'''
139 139
140 140 def plural(t, c):
141 141 if c == 1:
142 142 return t
143 143 return t + "s"
144 144 def fmt(t, c):
145 145 return "%d %s" % (c, plural(t, c))
146 146
147 147 now = time.time()
148 148 then = date[0]
149 149 delta = max(1, int(now - then))
150 150
151 151 for t, s in agescales:
152 152 n = delta / s
153 153 if n >= 2 or s == 1:
154 154 return fmt(t, n)
155 155
156 156 def stringify(thing):
157 157 '''turn nested template iterator into string.'''
158 158 if hasattr(thing, '__iter__'):
159 159 return "".join([stringify(t) for t in thing])
160 160 if thing is None: return ""
161 161 return str(thing)
162 162
163 163 para_re = None
164 164 space_re = None
165 165
166 166 def fill(text, width):
167 167 '''fill many paragraphs.'''
168 168 global para_re, space_re
169 169 if para_re is None:
170 170 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
171 171 space_re = re.compile(r' +')
172 172
173 173 def findparas():
174 174 start = 0
175 175 while True:
176 176 m = para_re.search(text, start)
177 177 if not m:
178 178 w = len(text)
179 179 while w > start and text[w-1].isspace(): w -= 1
180 180 yield text[start:w], text[w:]
181 181 break
182 182 yield text[start:m.start(0)], m.group(1)
183 183 start = m.end(1)
184 184
185 185 return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest
186 186 for para, rest in findparas()])
187 187
188 188 def firstline(text):
189 189 '''return the first line of text'''
190 190 try:
191 191 return text.splitlines(1)[0].rstrip('\r\n')
192 192 except IndexError:
193 193 return ''
194 194
195 195 def isodate(date):
196 196 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
197 197 return util.datestr(date, format='%Y-%m-%d %H:%M')
198 198
199 199 def hgdate(date):
200 200 '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
201 201 return "%d %d" % date
202 202
203 203 def nl2br(text):
204 204 '''replace raw newlines with xhtml line breaks.'''
205 205 return text.replace('\n', '<br/>\n')
206 206
207 207 def obfuscate(text):
208 208 text = unicode(text, 'utf-8', 'replace')
209 209 return ''.join(['&#%d;' % ord(c) for c in text])
210 210
211 211 def domain(author):
212 212 '''get domain of author, or empty string if none.'''
213 213 f = author.find('@')
214 214 if f == -1: return ''
215 215 author = author[f+1:]
216 216 f = author.find('>')
217 217 if f >= 0: author = author[:f]
218 218 return author
219 219
220 220 def email(author):
221 221 '''get email of author.'''
222 222 r = author.find('>')
223 223 if r == -1: r = None
224 224 return author[author.find('<')+1:r]
225 225
226 226 def person(author):
227 227 '''get name of author, or else username.'''
228 228 f = author.find('<')
229 229 if f == -1: return util.shortuser(author)
230 230 return author[:f].rstrip()
231 231
232 232 def shortdate(date):
233 233 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
234 234 return util.datestr(date, format='%Y-%m-%d', timezone=False)
235 235
236 236 def indent(text, prefix):
237 237 '''indent each non-empty line of text after first with prefix.'''
238 238 lines = text.splitlines()
239 239 num_lines = len(lines)
240 240 def indenter():
241 241 for i in xrange(num_lines):
242 242 l = lines[i]
243 243 if i and l.strip():
244 244 yield prefix
245 245 yield l
246 246 if i < num_lines - 1 or text.endswith('\n'):
247 247 yield '\n'
248 248 return "".join(indenter())
249 249
250 250 common_filters = {
251 251 "addbreaks": nl2br,
252 252 "basename": os.path.basename,
253 253 "age": age,
254 254 "date": lambda x: util.datestr(x),
255 255 "domain": domain,
256 256 "email": email,
257 257 "escape": lambda x: cgi.escape(x, True),
258 258 "fill68": lambda x: fill(x, width=68),
259 259 "fill76": lambda x: fill(x, width=76),
260 260 "firstline": firstline,
261 261 "tabindent": lambda x: indent(x, '\t'),
262 262 "hgdate": hgdate,
263 263 "isodate": isodate,
264 264 "obfuscate": obfuscate,
265 265 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
266 266 "person": person,
267 267 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
268 268 "short": lambda x: x[:12],
269 269 "shortdate": shortdate,
270 270 "stringify": stringify,
271 271 "strip": lambda x: x.strip(),
272 272 "urlescape": lambda x: urllib.quote(x),
273 273 "user": lambda x: util.shortuser(x),
274 274 "stringescape": lambda x: x.encode('string_escape'),
275 275 }
276 276
277 277 def templatepath(name=None):
278 278 '''return location of template file or directory (if no name).
279 279 returns None if not found.'''
280 280
281 281 # executable version (py2exe) doesn't support __file__
282 282 if hasattr(sys, 'frozen'):
283 283 module = sys.executable
284 284 else:
285 285 module = __file__
286 286 for f in 'templates', '../templates':
287 287 fl = f.split('/')
288 288 if name: fl.append(name)
289 289 p = os.path.join(os.path.dirname(module), *fl)
290 290 if (name and os.path.exists(p)) or os.path.isdir(p):
291 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