##// END OF EJS Templates
merge with crew
Benoit Boissinot -
r7135:06ca0338 merge default
parent child Browse files
Show More
@@ -1,184 +1,184
1 1 # churn.py - create a graph of revisions count grouped by template
2 2 #
3 3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
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 '''allow graphing the number of lines (or count of revisions) grouped by template'''
8 '''command to show certain statistics about revision history'''
9 9
10 10 from mercurial.i18n import _
11 11 from mercurial import patch, cmdutil, util, templater
12 12 import os, sys
13 13 import time, datetime
14 14
15 15 def get_tty_width():
16 16 if 'COLUMNS' in os.environ:
17 17 try:
18 18 return int(os.environ['COLUMNS'])
19 19 except ValueError:
20 20 pass
21 21 try:
22 22 import termios, array, fcntl
23 23 for dev in (sys.stdout, sys.stdin):
24 24 try:
25 25 fd = dev.fileno()
26 26 if not os.isatty(fd):
27 27 continue
28 28 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
29 29 return array.array('h', arri)[1]
30 30 except ValueError:
31 31 pass
32 32 except ImportError:
33 33 pass
34 34 return 80
35 35
36 36 def maketemplater(ui, repo, tmpl):
37 37 tmpl = templater.parsestring(tmpl, quoted=False)
38 38 try:
39 39 t = cmdutil.changeset_templater(ui, repo, False, None, False)
40 40 except SyntaxError, inst:
41 41 raise util.Abort(inst.args[0])
42 42 t.use_template(tmpl)
43 43 return t
44 44
45 45 def changedlines(ui, repo, ctx1, ctx2):
46 46 lines = 0
47 47 ui.pushbuffer()
48 48 patch.diff(repo, ctx1.node(), ctx2.node())
49 49 diff = ui.popbuffer()
50 50 for l in diff.split('\n'):
51 51 if (l.startswith("+") and not l.startswith("+++ ") or
52 52 l.startswith("-") and not l.startswith("--- ")):
53 53 lines += 1
54 54 return lines
55 55
56 56 def countrate(ui, repo, amap, *pats, **opts):
57 57 """Calculate stats"""
58 58 if opts.get('dateformat'):
59 59 def getkey(ctx):
60 60 t, tz = ctx.date()
61 61 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
62 62 return date.strftime(opts['dateformat'])
63 63 else:
64 64 tmpl = opts.get('template', '{author|email}')
65 65 tmpl = maketemplater(ui, repo, tmpl)
66 66 def getkey(ctx):
67 67 ui.pushbuffer()
68 68 tmpl.show(changenode=ctx.node())
69 69 return ui.popbuffer()
70 70
71 71 count = pct = 0
72 72 rate = {}
73 73 df = False
74 74 if opts.get('date'):
75 75 df = util.matchdate(opts['date'])
76 76
77 77 get = util.cachefunc(lambda r: repo[r].changeset())
78 78 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
79 79 for st, rev, fns in changeiter:
80 80 if not st == 'add':
81 81 continue
82 82 if df and not df(get(rev)[2][0]): # doesn't match date format
83 83 continue
84 84
85 85 ctx = repo[rev]
86 86 key = getkey(ctx)
87 87 key = amap.get(key, key) # alias remap
88 88 if opts.get('changesets'):
89 89 rate[key] = rate.get(key, 0) + 1
90 90 else:
91 91 parents = ctx.parents()
92 92 if len(parents) > 1:
93 93 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
94 94 continue
95 95
96 96 ctx1 = parents[0]
97 97 lines = changedlines(ui, repo, ctx1, ctx)
98 98 rate[key] = rate.get(key, 0) + lines
99 99
100 100 if opts.get('progress'):
101 101 count += 1
102 102 newpct = int(100.0 * count / max(len(repo), 1))
103 103 if pct < newpct:
104 104 pct = newpct
105 105 ui.write(_("\rGenerating stats: %d%%") % pct)
106 106 sys.stdout.flush()
107 107
108 108 if opts.get('progress'):
109 109 ui.write("\r")
110 110 sys.stdout.flush()
111 111
112 112 return rate
113 113
114 114
115 115 def churn(ui, repo, *pats, **opts):
116 116 '''Graph count of revisions grouped by template
117 117
118 118 Will graph count of changed lines or revisions grouped by template or
119 119 alternatively by date, if dateformat is used. In this case it will override
120 120 template.
121 121
122 122 By default statistics are counted for number of changed lines.
123 123
124 124 Examples:
125 125
126 126 # display count of changed lines for every committer
127 127 hg churn -t '{author|email}'
128 128
129 129 # display daily activity graph
130 130 hg churn -f '%H' -s -c
131 131
132 132 # display activity of developers by month
133 133 hg churn -f '%Y-%m' -s -c
134 134
135 135 # display count of lines changed in every year
136 136 hg churn -f '%Y' -s
137 137
138 138 The map file format used to specify aliases is fairly simple:
139 139
140 140 <alias email> <actual email>'''
141 141 def pad(s, l):
142 142 return (s + " " * l)[:l]
143 143
144 144 amap = {}
145 145 aliases = opts.get('aliases')
146 146 if aliases:
147 147 for l in open(aliases, "r"):
148 148 l = l.strip()
149 149 alias, actual = l.split()
150 150 amap[alias] = actual
151 151
152 152 rate = countrate(ui, repo, amap, *pats, **opts).items()
153 153 if not rate:
154 154 return
155 155
156 156 sortfn = ((not opts.get('sort')) and (lambda a, b: cmp(b[1], a[1])) or None)
157 157 rate.sort(sortfn)
158 158
159 159 maxcount = float(max([v for k, v in rate]))
160 160 maxname = max([len(k) for k, v in rate])
161 161
162 162 ttywidth = get_tty_width()
163 163 ui.debug(_("assuming %i character terminal\n") % ttywidth)
164 164 width = ttywidth - maxname - 2 - 6 - 2 - 2
165 165
166 166 for date, count in rate:
167 167 print "%s %6d %s" % (pad(date, maxname), count,
168 168 "*" * int(count * width / maxcount))
169 169
170 170
171 171 cmdtable = {
172 172 "churn":
173 173 (churn,
174 174 [('r', 'rev', [], _('count rate for the specified revision or range')),
175 175 ('d', 'date', '', _('count rate for revs matching date spec')),
176 176 ('t', 'template', '{author|email}', _('template to group changesets')),
177 177 ('f', 'dateformat', '',
178 178 _('strftime-compatible format for grouping by date')),
179 179 ('c', 'changesets', False, _('count rate by number of changesets')),
180 180 ('s', 'sort', False, _('sort by key (default: sort by count)')),
181 181 ('', 'aliases', '', _('file with email aliases')),
182 182 ('', 'progress', None, _('show progress'))],
183 _("hg stats [-d DATE] [-r REV] [--aliases FILE] [--progress] [FILE]")),
183 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [--progress] [FILE]")),
184 184 }
@@ -1,61 +1,61
1 """a mercurial extension for syntax highlighting in hgweb
1 """syntax highlighting in hgweb, based on Pygments
2 2
3 3 It depends on the pygments syntax highlighting library:
4 4 http://pygments.org/
5 5
6 6 To enable the extension add this to hgrc:
7 7
8 8 [extensions]
9 9 hgext.highlight =
10 10
11 11 There is a single configuration option:
12 12
13 13 [web]
14 14 pygments_style = <style>
15 15
16 16 The default is 'colorful'.
17 17
18 18 -- Adam Hupp <adam@hupp.org>
19 19 """
20 20
21 21 import highlight
22 22 from mercurial.hgweb import webcommands, webutil, common
23 23
24 24 web_filerevision = webcommands._filerevision
25 25 web_annotate = webcommands.annotate
26 26
27 27 def filerevision_highlight(web, tmpl, fctx):
28 28 mt = ''.join(tmpl('mimetype', encoding=web.encoding))
29 29 # only pygmentize for mimetype containing 'html' so we both match
30 30 # 'text/html' and possibly 'application/xhtml+xml' in the future
31 31 # so that we don't have to touch the extension when the mimetype
32 32 # for a template changes; also hgweb optimizes the case that a
33 33 # raw file is sent using rawfile() and doesn't call us, so we
34 34 # can't clash with the file's content-type here in case we
35 35 # pygmentize a html file
36 36 if 'html' in mt:
37 37 style = web.config('web', 'pygments_style', 'colorful')
38 38 highlight.pygmentize('fileline', fctx, style, tmpl)
39 39 return web_filerevision(web, tmpl, fctx)
40 40
41 41 def annotate_highlight(web, req, tmpl):
42 42 mt = ''.join(tmpl('mimetype', encoding=web.encoding))
43 43 if 'html' in mt:
44 44 fctx = webutil.filectx(web.repo, req)
45 45 style = web.config('web', 'pygments_style', 'colorful')
46 46 highlight.pygmentize('annotateline', fctx, style, tmpl)
47 47 return web_annotate(web, req, tmpl)
48 48
49 49 def generate_css(web, req, tmpl):
50 50 pg_style = web.config('web', 'pygments_style', 'colorful')
51 51 fmter = highlight.HtmlFormatter(style = pg_style)
52 52 req.respond(common.HTTP_OK, 'text/css')
53 53 return ['/* pygments_style = %s */\n\n' % pg_style, fmter.get_style_defs('')]
54 54
55 55
56 56 # monkeypatch in the new version
57 57
58 58 webcommands._filerevision = filerevision_highlight
59 59 webcommands.annotate = annotate_highlight
60 60 webcommands.highlightcss = generate_css
61 61 webcommands.__all__.append('highlightcss')
@@ -1,288 +1,287
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 #
8 # hook extension to email notifications to people when changesets are
9 # committed to a repo they subscribe to.
10 #
11 # default mode is to print messages to stdout, for testing and
12 # configuring.
13 #
14 # to use, configure notify extension and enable in hgrc like this:
15 #
16 # [extensions]
17 # hgext.notify =
18 #
19 # [hooks]
20 # # one email for each incoming changeset
21 # incoming.notify = python:hgext.notify.hook
22 # # batch emails when many changesets incoming at one time
23 # changegroup.notify = python:hgext.notify.hook
24 #
25 # [notify]
26 # # config items go in here
27 #
28 # config items:
29 #
30 # REQUIRED:
31 # config = /path/to/file # file containing subscriptions
32 #
33 # OPTIONAL:
34 # test = True # print messages to stdout for testing
35 # strip = 3 # number of slashes to strip for url paths
36 # domain = example.com # domain to use if committer missing domain
37 # style = ... # style file to use when formatting email
38 # template = ... # template to use when formatting email
39 # incoming = ... # template to use when run as incoming hook
40 # changegroup = ... # template when run as changegroup hook
41 # maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
42 # maxsubject = 67 # truncate subject line longer than this
43 # diffstat = True # add a diffstat before the diff content
44 # sources = serve # notify if source of incoming changes in this list
45 # # (serve == ssh or http, push, pull, bundle)
46 # [email]
47 # from = user@host.com # email address to send as if none given
48 # [web]
49 # baseurl = http://hgserver/... # root of hg web site for browsing commits
50 #
51 # notify config file has same format as regular hgrc. it has two
52 # sections so you can express subscriptions in whatever way is handier
53 # for you.
54 #
55 # [usersubs]
56 # # key is subscriber email, value is ","-separated list of glob patterns
57 # user@host = pattern
58 #
59 # [reposubs]
60 # # key is glob pattern, value is ","-separated list of subscriber emails
61 # pattern = user@host
62 #
63 # glob patterns are matched against path to repo root.
64 #
65 # if you like, you can put notify config file in repo that users can
66 # push changes to, they can manage their own subscriptions.
7
8 '''hook extension to email notifications on commits/pushes
9
10 Subscriptions can be managed through hgrc. Default mode is to print
11 messages to stdout, for testing and configuring.
12
13 To use, configure notify extension and enable in hgrc like this:
14
15 [extensions]
16 hgext.notify =
17
18 [hooks]
19 # one email for each incoming changeset
20 incoming.notify = python:hgext.notify.hook
21 # batch emails when many changesets incoming at one time
22 changegroup.notify = python:hgext.notify.hook
23
24 [notify]
25 # config items go in here
26
27 config items:
28
29 REQUIRED:
30 config = /path/to/file # file containing subscriptions
31
32 OPTIONAL:
33 test = True # print messages to stdout for testing
34 strip = 3 # number of slashes to strip for url paths
35 domain = example.com # domain to use if committer missing domain
36 style = ... # style file to use when formatting email
37 template = ... # template to use when formatting email
38 incoming = ... # template to use when run as incoming hook
39 changegroup = ... # template when run as changegroup hook
40 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
41 maxsubject = 67 # truncate subject line longer than this
42 diffstat = True # add a diffstat before the diff content
43 sources = serve # notify if source of incoming changes in this list
44 # (serve == ssh or http, push, pull, bundle)
45 [email]
46 from = user@host.com # email address to send as if none given
47 [web]
48 baseurl = http://hgserver/... # root of hg web site for browsing commits
49
50 notify config file has same format as regular hgrc. it has two
51 sections so you can express subscriptions in whatever way is handier
52 for you.
53
54 [usersubs]
55 # key is subscriber email, value is ","-separated list of glob patterns
56 user@host = pattern
57
58 [reposubs]
59 # key is glob pattern, value is ","-separated list of subscriber emails
60 pattern = user@host
61
62 glob patterns are matched against path to repo root.
63
64 if you like, you can put notify config file in repo that users can
65 push changes to, they can manage their own subscriptions.'''
67 66
68 67 from mercurial.i18n import _
69 68 from mercurial.node import bin, short
70 69 from mercurial import patch, cmdutil, templater, util, mail
71 70 import email.Parser, fnmatch, socket, time
72 71
73 72 # template for single changeset can include email headers.
74 73 single_template = '''
75 74 Subject: changeset in {webroot}: {desc|firstline|strip}
76 75 From: {author}
77 76
78 77 changeset {node|short} in {root}
79 78 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
80 79 description:
81 80 \t{desc|tabindent|strip}
82 81 '''.lstrip()
83 82
84 83 # template for multiple changesets should not contain email headers,
85 84 # because only first set of headers will be used and result will look
86 85 # strange.
87 86 multiple_template = '''
88 87 changeset {node|short} in {root}
89 88 details: {baseurl}{webroot}?cmd=changeset;node={node|short}
90 89 summary: {desc|firstline}
91 90 '''
92 91
93 92 deftemplates = {
94 93 'changegroup': multiple_template,
95 94 }
96 95
97 96 class notifier(object):
98 97 '''email notification class.'''
99 98
100 99 def __init__(self, ui, repo, hooktype):
101 100 self.ui = ui
102 101 cfg = self.ui.config('notify', 'config')
103 102 if cfg:
104 103 self.ui.readsections(cfg, 'usersubs', 'reposubs')
105 104 self.repo = repo
106 105 self.stripcount = int(self.ui.config('notify', 'strip', 0))
107 106 self.root = self.strip(self.repo.root)
108 107 self.domain = self.ui.config('notify', 'domain')
109 108 self.charsets = mail._charsets(self.ui)
110 109 self.subs = self.subscribers()
111 110
112 111 mapfile = self.ui.config('notify', 'style')
113 112 template = (self.ui.config('notify', hooktype) or
114 113 self.ui.config('notify', 'template'))
115 114 self.t = cmdutil.changeset_templater(self.ui, self.repo,
116 115 False, mapfile, False)
117 116 if not mapfile and not template:
118 117 template = deftemplates.get(hooktype) or single_template
119 118 if template:
120 119 template = templater.parsestring(template, quoted=False)
121 120 self.t.use_template(template)
122 121
123 122 def strip(self, path):
124 123 '''strip leading slashes from local path, turn into web-safe path.'''
125 124
126 125 path = util.pconvert(path)
127 126 count = self.stripcount
128 127 while count > 0:
129 128 c = path.find('/')
130 129 if c == -1:
131 130 break
132 131 path = path[c+1:]
133 132 count -= 1
134 133 return path
135 134
136 135 def fixmail(self, addr):
137 136 '''try to clean up email addresses.'''
138 137
139 138 addr = util.email(addr.strip())
140 139 if self.domain:
141 140 a = addr.find('@localhost')
142 141 if a != -1:
143 142 addr = addr[:a]
144 143 if '@' not in addr:
145 144 return addr + '@' + self.domain
146 145 return addr
147 146
148 147 def subscribers(self):
149 148 '''return list of email addresses of subscribers to this repo.'''
150 149
151 150 subs = {}
152 151 for user, pats in self.ui.configitems('usersubs'):
153 152 for pat in pats.split(','):
154 153 if fnmatch.fnmatch(self.repo.root, pat.strip()):
155 154 subs[self.fixmail(user)] = 1
156 155 for pat, users in self.ui.configitems('reposubs'):
157 156 if fnmatch.fnmatch(self.repo.root, pat):
158 157 for user in users.split(','):
159 158 subs[self.fixmail(user)] = 1
160 159 subs = util.sort(subs)
161 160 return [mail.addressencode(self.ui, s, self.charsets) for s in subs]
162 161
163 162 def url(self, path=None):
164 163 return self.ui.config('web', 'baseurl') + (path or self.root)
165 164
166 165 def node(self, node):
167 166 '''format one changeset.'''
168 167
169 168 self.t.show(changenode=node, changes=self.repo.changelog.read(node),
170 169 baseurl=self.ui.config('web', 'baseurl'),
171 170 root=self.repo.root,
172 171 webroot=self.root)
173 172
174 173 def skipsource(self, source):
175 174 '''true if incoming changes from this source should be skipped.'''
176 175 ok_sources = self.ui.config('notify', 'sources', 'serve').split()
177 176 return source not in ok_sources
178 177
179 178 def send(self, node, count, data):
180 179 '''send message.'''
181 180
182 181 p = email.Parser.Parser()
183 182 msg = p.parsestr(data)
184 183
185 184 # store sender and subject
186 185 sender, subject = msg['From'], msg['Subject']
187 186 # create fresh mime message from msg body
188 187 text = msg.get_payload()
189 188 # for notification prefer readability over data precision
190 189 msg = mail.mimeencode(self.ui, text, self.charsets)
191 190
192 191 def fix_subject(subject):
193 192 '''try to make subject line exist and be useful.'''
194 193
195 194 if not subject:
196 195 if count > 1:
197 196 subject = _('%s: %d new changesets') % (self.root, count)
198 197 else:
199 198 changes = self.repo.changelog.read(node)
200 199 s = changes[4].lstrip().split('\n', 1)[0].rstrip()
201 200 subject = '%s: %s' % (self.root, s)
202 201 maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
203 202 if maxsubject and len(subject) > maxsubject:
204 203 subject = subject[:maxsubject-3] + '...'
205 204 msg['Subject'] = mail.headencode(self.ui, subject, self.charsets)
206 205
207 206 def fix_sender(sender):
208 207 '''try to make message have proper sender.'''
209 208
210 209 if not sender:
211 210 sender = self.ui.config('email', 'from') or self.ui.username()
212 211 if '@' not in sender or '@localhost' in sender:
213 212 sender = self.fixmail(sender)
214 213 msg['From'] = mail.addressencode(self.ui, sender, self.charsets)
215 214
216 215 msg['Date'] = util.datestr(format="%a, %d %b %Y %H:%M:%S %1%2")
217 216 fix_subject(subject)
218 217 fix_sender(sender)
219 218
220 219 msg['X-Hg-Notification'] = 'changeset ' + short(node)
221 220 if not msg['Message-Id']:
222 221 msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' %
223 222 (short(node), int(time.time()),
224 223 hash(self.repo.root), socket.getfqdn()))
225 224 msg['To'] = ', '.join(self.subs)
226 225
227 226 msgtext = msg.as_string(0)
228 227 if self.ui.configbool('notify', 'test', True):
229 228 self.ui.write(msgtext)
230 229 if not msgtext.endswith('\n'):
231 230 self.ui.write('\n')
232 231 else:
233 232 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
234 233 (len(self.subs), count))
235 234 mail.sendmail(self.ui, util.email(msg['From']),
236 235 self.subs, msgtext)
237 236
238 237 def diff(self, node, ref):
239 238 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
240 239 prev = self.repo.changelog.parents(node)[0]
241 240
242 241 self.ui.pushbuffer()
243 242 patch.diff(self.repo, prev, ref, opts=patch.diffopts(self.ui))
244 243 difflines = self.ui.popbuffer().splitlines()
245 244
246 245 if self.ui.configbool('notify', 'diffstat', True):
247 246 s = patch.diffstat(difflines)
248 247 # s may be nil, don't include the header if it is
249 248 if s:
250 249 self.ui.write('\ndiffstat:\n\n%s' % s)
251 250 if maxdiff == 0:
252 251 return
253 252 if maxdiff > 0 and len(difflines) > maxdiff:
254 253 self.ui.write(_('\ndiffs (truncated from %d to %d lines):\n\n') %
255 254 (len(difflines), maxdiff))
256 255 difflines = difflines[:maxdiff]
257 256 elif difflines:
258 257 self.ui.write(_('\ndiffs (%d lines):\n\n') % len(difflines))
259 258 self.ui.write("\n".join(difflines))
260 259
261 260 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
262 261 '''send email notifications to interested subscribers.
263 262
264 263 if used as changegroup hook, send one email for all changesets in
265 264 changegroup. else send one email per changeset.'''
266 265 n = notifier(ui, repo, hooktype)
267 266 if not n.subs:
268 267 ui.debug(_('notify: no subscribers to repo %s\n') % n.root)
269 268 return
270 269 if n.skipsource(source):
271 270 ui.debug(_('notify: changes have source "%s" - skipping\n') %
272 271 source)
273 272 return
274 273 node = bin(node)
275 274 ui.pushbuffer()
276 275 if hooktype == 'changegroup':
277 276 start = repo[node].rev()
278 277 end = len(repo)
279 278 count = end - start
280 279 for rev in xrange(start, end):
281 280 n.node(repo[rev].node())
282 281 n.diff(node, repo.changelog.tip())
283 282 else:
284 283 count = 1
285 284 n.node(node)
286 285 n.diff(node, node)
287 286 data = ui.popbuffer()
288 287 n.send(node, count, data)
@@ -1,401 +1,403
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot 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 ''' Rebasing feature
8 '''move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial repository.
11 11
12 12 For more information:
13 13 http://www.selenic.com/mercurial/wiki/index.cgi/RebaseProject
14 14 '''
15 15
16 16 from mercurial import util, repair, merge, cmdutil, dispatch, commands
17 17 from mercurial.commands import templateopts
18 18 from mercurial.node import nullrev
19 19 from mercurial.i18n import _
20 20 import os, errno
21 21
22 22 def rebase(ui, repo, **opts):
23 23 """move changeset (and descendants) to a different branch
24 24
25 25 Rebase uses repeated merging to graft changesets from one part of history
26 26 onto another. This can be useful for linearizing local changes relative to
27 27 a master development tree.
28 28
29 29 If a rebase is interrupted to manually resolve a merge, it can be continued
30 30 with --continue or aborted with --abort.
31 31 """
32 32 originalwd = target = source = None
33 33 external = nullrev
34 34 state = skipped = {}
35 35
36 36 lock = wlock = None
37 37 try:
38 38 lock = repo.lock()
39 39 wlock = repo.wlock()
40 40
41 41 # Validate input and define rebasing points
42 42 destf = opts.get('dest', None)
43 43 srcf = opts.get('source', None)
44 44 basef = opts.get('base', None)
45 45 contf = opts.get('continue')
46 46 abortf = opts.get('abort')
47 47 collapsef = opts.get('collapse', False)
48 48 if contf or abortf:
49 49 if contf and abortf:
50 50 raise dispatch.ParseError('rebase',
51 51 _('cannot use both abort and continue'))
52 52 if collapsef:
53 53 raise dispatch.ParseError('rebase',
54 54 _('cannot use collapse with continue or abort'))
55 55
56 56 if (srcf or basef or destf):
57 57 raise dispatch.ParseError('rebase',
58 58 _('abort and continue do not allow specifying revisions'))
59 59
60 60 originalwd, target, state, collapsef, external = restorestatus(repo)
61 61 if abortf:
62 62 abort(repo, originalwd, target, state)
63 63 return
64 64 else:
65 65 if srcf and basef:
66 66 raise dispatch.ParseError('rebase', _('cannot specify both a '
67 67 'revision and a base'))
68 68 cmdutil.bail_if_changed(repo)
69 69 result = buildstate(repo, destf, srcf, basef, collapsef)
70 70 if result:
71 71 originalwd, target, state, external = result
72 72 else: # Empty state built, nothing to rebase
73 73 repo.ui.status(_('nothing to rebase\n'))
74 74 return
75 75
76 76 # Rebase
77 77 targetancestors = list(repo.changelog.ancestors(target))
78 78 targetancestors.append(target)
79 79
80 80 for rev in util.sort(state):
81 81 if state[rev] == -1:
82 82 storestatus(repo, originalwd, target, state, collapsef,
83 83 external)
84 84 rebasenode(repo, rev, target, state, skipped, targetancestors,
85 85 collapsef)
86 86 ui.note(_('rebase merging completed\n'))
87 87
88 88 if collapsef:
89 89 p1, p2 = defineparents(repo, min(state), target,
90 90 state, targetancestors)
91 91 concludenode(repo, rev, p1, external, state, collapsef,
92 92 last=True, skipped=skipped)
93 93
94 94 if 'qtip' in repo.tags():
95 95 updatemq(repo, state, skipped, **opts)
96 96
97 97 if not opts.get('keep'):
98 98 # Remove no more useful revisions
99 99 if (util.set(repo.changelog.descendants(min(state)))
100 100 - util.set(state.keys())):
101 101 ui.warn(_("warning: new changesets detected on source branch, "
102 102 "not stripping\n"))
103 103 else:
104 104 repair.strip(repo.ui, repo, repo[min(state)].node(), "strip")
105 105
106 106 clearstatus(repo)
107 107 ui.status(_("rebase completed\n"))
108 if os.path.exists(repo.sjoin('undo')):
109 util.unlink(repo.sjoin('undo'))
108 110 if skipped:
109 111 ui.note(_("%d revisions have been skipped\n") % len(skipped))
110 112 finally:
111 113 del lock, wlock
112 114
113 115 def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped={}):
114 116 """Skip commit if collapsing has been required and rev is not the last
115 117 revision, commit otherwise
116 118 """
117 119 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
118 120
119 121 if collapse and not last:
120 122 return None
121 123
122 124 # Commit, record the old nodeid
123 125 m, a, r = repo.status()[:3]
124 126 newrev = nullrev
125 127 try:
126 128 if last:
127 129 commitmsg = 'Collapsed revision'
128 130 for rebased in state:
129 131 if rebased not in skipped:
130 132 commitmsg += '\n* %s' % repo[rebased].description()
131 133 commitmsg = repo.ui.edit(commitmsg, repo.ui.username())
132 134 else:
133 135 commitmsg = repo[rev].description()
134 136 # Commit might fail if unresolved files exist
135 137 newrev = repo.commit(m+a+r,
136 138 text=commitmsg,
137 139 user=repo[rev].user(),
138 140 date=repo[rev].date(),
139 141 extra={'rebase_source': repo[rev].hex()})
140 142 return newrev
141 143 except util.Abort:
142 144 # Invalidate the previous setparents
143 145 repo.dirstate.invalidate()
144 146 raise
145 147
146 148 def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse):
147 149 'Rebase a single revision'
148 150 repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev].node()))
149 151
150 152 p1, p2 = defineparents(repo, rev, target, state, targetancestors)
151 153
152 154 # Merge phase
153 155 if len(repo.parents()) != 2:
154 156 # Update to target and merge it with local
155 157 merge.update(repo, p1, False, True, False)
156 158 repo.dirstate.write()
157 159 stats = merge.update(repo, rev, True, False, False)
158 160
159 161 if stats[3] > 0:
160 162 raise util.Abort(_('fix unresolved conflicts with hg resolve then '
161 163 'run hg rebase --continue'))
162 164 else: # we have an interrupted rebase
163 165 repo.ui.debug(_('resuming interrupted rebase\n'))
164 166
165 167
166 168 newrev = concludenode(repo, rev, p1, p2, state, collapse)
167 169
168 170 # Update the state
169 171 if newrev is not None:
170 172 state[rev] = repo[newrev].rev()
171 173 else:
172 174 if not collapse:
173 175 repo.ui.note(_('no changes, revision %d skipped\n') % rev)
174 176 repo.ui.debug(_('next revision set to %s\n') % p1)
175 177 skipped[rev] = True
176 178 state[rev] = p1
177 179
178 180 def defineparents(repo, rev, target, state, targetancestors):
179 181 'Return the new parent relationship of the revision that will be rebased'
180 182 parents = repo[rev].parents()
181 183 p1 = p2 = nullrev
182 184
183 185 P1n = parents[0].rev()
184 186 if P1n in targetancestors:
185 187 p1 = target
186 188 elif P1n in state:
187 189 p1 = state[P1n]
188 190 else: # P1n external
189 191 p1 = target
190 192 p2 = P1n
191 193
192 194 if len(parents) == 2 and parents[1].rev() not in targetancestors:
193 195 P2n = parents[1].rev()
194 196 # interesting second parent
195 197 if P2n in state:
196 198 if p1 == target: # P1n in targetancestors or external
197 199 p1 = state[P2n]
198 200 else:
199 201 p2 = state[P2n]
200 202 else: # P2n external
201 203 if p2 != nullrev: # P1n external too => rev is a merged revision
202 204 raise util.Abort(_('cannot use revision %d as base, result '
203 205 'would have 3 parents') % rev)
204 206 p2 = P2n
205 207 return p1, p2
206 208
207 209 def updatemq(repo, state, skipped, **opts):
208 210 'Update rebased mq patches - finalize and then import them'
209 211 mqrebase = {}
210 212 for p in repo.mq.applied:
211 213 if repo[p.rev].rev() in state:
212 214 repo.ui.debug(_('revision %d is an mq patch (%s), finalize it.\n') %
213 215 (repo[p.rev].rev(), p.name))
214 216 mqrebase[repo[p.rev].rev()] = p.name
215 217
216 218 if mqrebase:
217 219 repo.mq.finish(repo, mqrebase.keys())
218 220
219 221 # We must start import from the newest revision
220 222 mq = mqrebase.keys()
221 223 mq.sort()
222 224 mq.reverse()
223 225 for rev in mq:
224 226 if rev not in skipped:
225 227 repo.ui.debug(_('import mq patch %d (%s)\n')
226 228 % (state[rev], mqrebase[rev]))
227 229 repo.mq.qimport(repo, (), patchname=mqrebase[rev],
228 230 git=opts.get('git', False),rev=[str(state[rev])])
229 231 repo.mq.save_dirty()
230 232
231 233 def storestatus(repo, originalwd, target, state, collapse, external):
232 234 'Store the current status to allow recovery'
233 235 f = repo.opener("rebasestate", "w")
234 236 f.write(repo[originalwd].hex() + '\n')
235 237 f.write(repo[target].hex() + '\n')
236 238 f.write(repo[external].hex() + '\n')
237 239 f.write('%d\n' % int(collapse))
238 240 for d, v in state.items():
239 241 oldrev = repo[d].hex()
240 242 newrev = repo[v].hex()
241 243 f.write("%s:%s\n" % (oldrev, newrev))
242 244 f.close()
243 245 repo.ui.debug(_('rebase status stored\n'))
244 246
245 247 def clearstatus(repo):
246 248 'Remove the status files'
247 249 if os.path.exists(repo.join("rebasestate")):
248 250 util.unlink(repo.join("rebasestate"))
249 251
250 252 def restorestatus(repo):
251 253 'Restore a previously stored status'
252 254 try:
253 255 target = None
254 256 collapse = False
255 257 external = nullrev
256 258 state = {}
257 259 f = repo.opener("rebasestate")
258 260 for i, l in enumerate(f.read().splitlines()):
259 261 if i == 0:
260 262 originalwd = repo[l].rev()
261 263 elif i == 1:
262 264 target = repo[l].rev()
263 265 elif i == 2:
264 266 external = repo[l].rev()
265 267 elif i == 3:
266 268 collapse = bool(int(l))
267 269 else:
268 270 oldrev, newrev = l.split(':')
269 271 state[repo[oldrev].rev()] = repo[newrev].rev()
270 272 repo.ui.debug(_('rebase status resumed\n'))
271 273 return originalwd, target, state, collapse, external
272 274 except IOError, err:
273 275 if err.errno != errno.ENOENT:
274 276 raise
275 277 raise util.Abort(_('no rebase in progress'))
276 278
277 279 def abort(repo, originalwd, target, state):
278 280 'Restore the repository to its original state'
279 281 if util.set(repo.changelog.descendants(target)) - util.set(state.values()):
280 282 repo.ui.warn(_("warning: new changesets detected on target branch, "
281 283 "not stripping\n"))
282 284 else:
283 285 # Strip from the first rebased revision
284 286 merge.update(repo, repo[originalwd].rev(), False, True, False)
285 287 rebased = filter(lambda x: x > -1, state.values())
286 288 if rebased:
287 289 strippoint = min(rebased)
288 290 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
289 291 clearstatus(repo)
290 292 repo.ui.status(_('rebase aborted\n'))
291 293
292 294 def buildstate(repo, dest, src, base, collapse):
293 295 'Define which revisions are going to be rebased and where'
294 296 state = {}
295 297 targetancestors = util.set()
296 298
297 299 if not dest:
298 300 # Destination defaults to the latest revision in the current branch
299 301 branch = repo[None].branch()
300 302 dest = repo[branch].rev()
301 303 else:
302 304 if 'qtip' in repo.tags() and (repo[dest].hex() in
303 305 [s.rev for s in repo.mq.applied]):
304 306 raise util.Abort(_('cannot rebase onto an applied mq patch'))
305 307 dest = repo[dest].rev()
306 308
307 309 if src:
308 310 commonbase = repo[src].ancestor(repo[dest])
309 311 if commonbase == repo[src]:
310 312 raise util.Abort(_('cannot rebase an ancestor'))
311 313 if commonbase == repo[dest]:
312 314 raise util.Abort(_('cannot rebase a descendant'))
313 315 source = repo[src].rev()
314 316 else:
315 317 if base:
316 318 cwd = repo[base].rev()
317 319 else:
318 320 cwd = repo['.'].rev()
319 321
320 322 if cwd == dest:
321 323 repo.ui.debug(_('already working on current\n'))
322 324 return None
323 325
324 326 targetancestors = util.set(repo.changelog.ancestors(dest))
325 327 if cwd in targetancestors:
326 328 repo.ui.debug(_('already working on the current branch\n'))
327 329 return None
328 330
329 331 cwdancestors = util.set(repo.changelog.ancestors(cwd))
330 332 cwdancestors.add(cwd)
331 333 rebasingbranch = cwdancestors - targetancestors
332 334 source = min(rebasingbranch)
333 335
334 336 repo.ui.debug(_('rebase onto %d starting from %d\n') % (dest, source))
335 337 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
336 338 external = nullrev
337 339 if collapse:
338 340 if not targetancestors:
339 341 targetancestors = util.set(repo.changelog.ancestors(dest))
340 342 for rev in state:
341 343 # Check externals and fail if there are more than one
342 344 for p in repo[rev].parents():
343 345 if (p.rev() not in state and p.rev() != source
344 346 and p.rev() not in targetancestors):
345 347 if external != nullrev:
346 348 raise util.Abort(_('unable to collapse, there is more '
347 349 'than one external parent'))
348 350 external = p.rev()
349 351
350 352 state[source] = nullrev
351 353 return repo['.'].rev(), repo[dest].rev(), state, external
352 354
353 355 def pulldelegate(pullfunction, repo, *args, **opts):
354 356 'Call rebase after pull if the latter has been invoked with --rebase'
355 357 if opts.get('rebase'):
356 358 if opts.get('update'):
357 359 raise util.Abort(_('--update and --rebase are not compatible'))
358 360
359 361 cmdutil.bail_if_changed(repo)
360 362 revsprepull = len(repo)
361 363 pullfunction(repo.ui, repo, *args, **opts)
362 364 revspostpull = len(repo)
363 365 if revspostpull > revsprepull:
364 366 rebase(repo.ui, repo, **opts)
365 367 else:
366 368 pullfunction(repo.ui, repo, *args, **opts)
367 369
368 370 def uisetup(ui):
369 371 'Replace pull with a decorator to provide --rebase option'
370 372 # cribbed from color.py
371 373 aliases, entry = cmdutil.findcmd(ui, 'pull', commands.table)
372 374 for candidatekey, candidateentry in commands.table.iteritems():
373 375 if candidateentry is entry:
374 376 cmdkey, cmdentry = candidatekey, entry
375 377 break
376 378
377 379 decorator = lambda ui, repo, *args, **opts: \
378 380 pulldelegate(cmdentry[0], repo, *args, **opts)
379 381 # make sure 'hg help cmd' still works
380 382 decorator.__doc__ = cmdentry[0].__doc__
381 383 decoratorentry = (decorator,) + cmdentry[1:]
382 384 rebaseopt = ('', 'rebase', None,
383 385 _("rebase working directory to branch head"))
384 386 decoratorentry[1].append(rebaseopt)
385 387 commands.table[cmdkey] = decoratorentry
386 388
387 389 cmdtable = {
388 390 "rebase":
389 391 (rebase,
390 392 [
391 393 ('', 'keep', False, _('keep original revisions')),
392 394 ('s', 'source', '', _('rebase from a given revision')),
393 395 ('b', 'base', '', _('rebase from the base of a given revision')),
394 396 ('d', 'dest', '', _('rebase onto a given revision')),
395 397 ('', 'collapse', False, _('collapse the rebased revisions')),
396 398 ('c', 'continue', False, _('continue an interrupted rebase')),
397 399 ('a', 'abort', False, _('abort an interrupted rebase')),] +
398 400 templateopts,
399 401 _('hg rebase [-s rev | -b rev] [-d rev] [--collapse] | [-c] | [-a] | '
400 402 '[--keep]')),
401 403 }
@@ -1,3327 +1,3342
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 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 node import hex, nullid, nullrev, short
9 9 from repo import RepoError, NoCapability
10 10 from i18n import _, gettext
11 11 import os, re, sys, urllib
12 12 import hg, util, revlog, bundlerepo, extensions, copies
13 13 import difflib, patch, time, help, mdiff, tempfile
14 14 import version, socket
15 15 import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
16 16 import merge as merge_
17 17
18 18 # Commands start here, listed alphabetically
19 19
20 20 def add(ui, repo, *pats, **opts):
21 21 """add the specified files on the next commit
22 22
23 23 Schedule files to be version controlled and added to the repository.
24 24
25 25 The files will be added to the repository at the next commit. To
26 26 undo an add before that, see hg revert.
27 27
28 28 If no names are given, add all files in the repository.
29 29 """
30 30
31 31 rejected = None
32 32 exacts = {}
33 33 names = []
34 34 m = cmdutil.match(repo, pats, opts)
35 35 m.bad = lambda x,y: True
36 36 for abs in repo.walk(m):
37 37 if m.exact(abs):
38 38 if ui.verbose:
39 39 ui.status(_('adding %s\n') % m.rel(abs))
40 40 names.append(abs)
41 41 exacts[abs] = 1
42 42 elif abs not in repo.dirstate:
43 43 ui.status(_('adding %s\n') % m.rel(abs))
44 44 names.append(abs)
45 45 if not opts.get('dry_run'):
46 46 rejected = repo.add(names)
47 47 rejected = [p for p in rejected if p in exacts]
48 48 return rejected and 1 or 0
49 49
50 50 def addremove(ui, repo, *pats, **opts):
51 51 """add all new files, delete all missing files
52 52
53 53 Add all new files and remove all missing files from the repository.
54 54
55 55 New files are ignored if they match any of the patterns in .hgignore. As
56 56 with add, these changes take effect at the next commit.
57 57
58 58 Use the -s option to detect renamed files. With a parameter > 0,
59 59 this compares every removed file with every added file and records
60 60 those similar enough as renames. This option takes a percentage
61 61 between 0 (disabled) and 100 (files must be identical) as its
62 62 parameter. Detecting renamed files this way can be expensive.
63 63 """
64 64 try:
65 65 sim = float(opts.get('similarity') or 0)
66 66 except ValueError:
67 67 raise util.Abort(_('similarity must be a number'))
68 68 if sim < 0 or sim > 100:
69 69 raise util.Abort(_('similarity must be between 0 and 100'))
70 70 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
71 71
72 72 def annotate(ui, repo, *pats, **opts):
73 73 """show changeset information per file line
74 74
75 75 List changes in files, showing the revision id responsible for each line
76 76
77 77 This command is useful to discover who did a change or when a change took
78 78 place.
79 79
80 80 Without the -a option, annotate will avoid processing files it
81 81 detects as binary. With -a, annotate will generate an annotation
82 82 anyway, probably with undesirable results.
83 83 """
84 84 datefunc = ui.quiet and util.shortdate or util.datestr
85 85 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
86 86
87 87 if not pats:
88 88 raise util.Abort(_('at least one file name or pattern required'))
89 89
90 90 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
91 91 ('number', lambda x: str(x[0].rev())),
92 92 ('changeset', lambda x: short(x[0].node())),
93 93 ('date', getdate),
94 94 ('follow', lambda x: x[0].path()),
95 95 ]
96 96
97 if (not opts['user'] and not opts['changeset'] and not opts['date']
98 and not opts['follow']):
97 if (not opts.get('user') and not opts.get('changeset') and not opts.get('date')
98 and not opts.get('follow')):
99 99 opts['number'] = 1
100 100
101 101 linenumber = opts.get('line_number') is not None
102 if (linenumber and (not opts['changeset']) and (not opts['number'])):
102 if (linenumber and (not opts.get('changeset')) and (not opts.get('number'))):
103 103 raise util.Abort(_('at least one of -n/-c is required for -l'))
104 104
105 105 funcmap = [func for op, func in opmap if opts.get(op)]
106 106 if linenumber:
107 107 lastfunc = funcmap[-1]
108 108 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
109 109
110 ctx = repo[opts['rev']]
110 ctx = repo[opts.get('rev')]
111 111
112 112 m = cmdutil.match(repo, pats, opts)
113 113 for abs in ctx.walk(m):
114 114 fctx = ctx[abs]
115 if not opts['text'] and util.binary(fctx.data()):
115 if not opts.get('text') and util.binary(fctx.data()):
116 116 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
117 117 continue
118 118
119 119 lines = fctx.annotate(follow=opts.get('follow'),
120 120 linenumber=linenumber)
121 121 pieces = []
122 122
123 123 for f in funcmap:
124 124 l = [f(n) for n, dummy in lines]
125 125 if l:
126 126 ml = max(map(len, l))
127 127 pieces.append(["%*s" % (ml, x) for x in l])
128 128
129 129 if pieces:
130 130 for p, l in zip(zip(*pieces), lines):
131 131 ui.write("%s: %s" % (" ".join(p), l[1]))
132 132
133 133 def archive(ui, repo, dest, **opts):
134 134 '''create unversioned archive of a repository revision
135 135
136 136 By default, the revision used is the parent of the working
137 137 directory; use "-r" to specify a different revision.
138 138
139 139 To specify the type of archive to create, use "-t". Valid
140 140 types are:
141 141
142 142 "files" (default): a directory full of files
143 143 "tar": tar archive, uncompressed
144 144 "tbz2": tar archive, compressed using bzip2
145 145 "tgz": tar archive, compressed using gzip
146 146 "uzip": zip archive, uncompressed
147 147 "zip": zip archive, compressed using deflate
148 148
149 149 The exact name of the destination archive or directory is given
150 150 using a format string; see "hg help export" for details.
151 151
152 152 Each member added to an archive file has a directory prefix
153 153 prepended. Use "-p" to specify a format string for the prefix.
154 154 The default is the basename of the archive, with suffixes removed.
155 155 '''
156 156
157 ctx = repo[opts['rev']]
157 ctx = repo[opts.get('rev')]
158 158 if not ctx:
159 159 raise util.Abort(_('repository has no revisions'))
160 160 node = ctx.node()
161 161 dest = cmdutil.make_filename(repo, dest, node)
162 162 if os.path.realpath(dest) == repo.root:
163 163 raise util.Abort(_('repository root cannot be destination'))
164 164 matchfn = cmdutil.match(repo, [], opts)
165 165 kind = opts.get('type') or 'files'
166 prefix = opts['prefix']
166 prefix = opts.get('prefix')
167 167 if dest == '-':
168 168 if kind == 'files':
169 169 raise util.Abort(_('cannot archive plain files to stdout'))
170 170 dest = sys.stdout
171 171 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
172 172 prefix = cmdutil.make_filename(repo, prefix, node)
173 archival.archive(repo, dest, node, kind, not opts['no_decode'],
173 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
174 174 matchfn, prefix)
175 175
176 176 def backout(ui, repo, node=None, rev=None, **opts):
177 177 '''reverse effect of earlier changeset
178 178
179 179 Commit the backed out changes as a new changeset. The new
180 180 changeset is a child of the backed out changeset.
181 181
182 182 If you back out a changeset other than the tip, a new head is
183 183 created. This head will be the new tip and you should merge this
184 184 backout changeset with another head (current one by default).
185 185
186 186 The --merge option remembers the parent of the working directory
187 187 before starting the backout, then merges the new head with that
188 188 changeset afterwards. This saves you from doing the merge by
189 189 hand. The result of this merge is not committed, as for a normal
190 190 merge.
191 191
192 192 See \'hg help dates\' for a list of formats valid for -d/--date.
193 193 '''
194 194 if rev and node:
195 195 raise util.Abort(_("please specify just one revision"))
196 196
197 197 if not rev:
198 198 rev = node
199 199
200 200 if not rev:
201 201 raise util.Abort(_("please specify a revision to backout"))
202 202
203 203 date = opts.get('date')
204 204 if date:
205 205 opts['date'] = util.parsedate(date)
206 206
207 207 cmdutil.bail_if_changed(repo)
208 208 node = repo.lookup(rev)
209 209
210 210 op1, op2 = repo.dirstate.parents()
211 211 a = repo.changelog.ancestor(op1, node)
212 212 if a != node:
213 213 raise util.Abort(_('cannot back out change on a different branch'))
214 214
215 215 p1, p2 = repo.changelog.parents(node)
216 216 if p1 == nullid:
217 217 raise util.Abort(_('cannot back out a change with no parents'))
218 218 if p2 != nullid:
219 if not opts['parent']:
219 if not opts.get('parent'):
220 220 raise util.Abort(_('cannot back out a merge changeset without '
221 221 '--parent'))
222 222 p = repo.lookup(opts['parent'])
223 223 if p not in (p1, p2):
224 224 raise util.Abort(_('%s is not a parent of %s') %
225 225 (short(p), short(node)))
226 226 parent = p
227 227 else:
228 if opts['parent']:
228 if opts.get('parent'):
229 229 raise util.Abort(_('cannot use --parent on non-merge changeset'))
230 230 parent = p1
231 231
232 232 # the backout should appear on the same branch
233 233 branch = repo.dirstate.branch()
234 234 hg.clean(repo, node, show_stats=False)
235 235 repo.dirstate.setbranch(branch)
236 236 revert_opts = opts.copy()
237 237 revert_opts['date'] = None
238 238 revert_opts['all'] = True
239 239 revert_opts['rev'] = hex(parent)
240 240 revert_opts['no_backup'] = None
241 241 revert(ui, repo, **revert_opts)
242 242 commit_opts = opts.copy()
243 243 commit_opts['addremove'] = False
244 244 if not commit_opts['message'] and not commit_opts['logfile']:
245 245 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
246 246 commit_opts['force_editor'] = True
247 247 commit(ui, repo, **commit_opts)
248 248 def nice(node):
249 249 return '%d:%s' % (repo.changelog.rev(node), short(node))
250 250 ui.status(_('changeset %s backs out changeset %s\n') %
251 251 (nice(repo.changelog.tip()), nice(node)))
252 252 if op1 != node:
253 253 hg.clean(repo, op1, show_stats=False)
254 if opts['merge']:
254 if opts.get('merge'):
255 255 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
256 256 hg.merge(repo, hex(repo.changelog.tip()))
257 257 else:
258 258 ui.status(_('the backout changeset is a new head - '
259 259 'do not forget to merge\n'))
260 260 ui.status(_('(use "backout --merge" '
261 261 'if you want to auto-merge)\n'))
262 262
263 263 def bisect(ui, repo, rev=None, extra=None,
264 264 reset=None, good=None, bad=None, skip=None, noupdate=None):
265 265 """subdivision search of changesets
266 266
267 267 This command helps to find changesets which introduce problems.
268 268 To use, mark the earliest changeset you know exhibits the problem
269 269 as bad, then mark the latest changeset which is free from the
270 270 problem as good. Bisect will update your working directory to a
271 271 revision for testing (unless the --noupdate option is specified).
272 272 Once you have performed tests, mark the working directory as bad
273 273 or good and bisect will either update to another candidate changeset
274 274 or announce that it has found the bad revision.
275 275
276 276 As a shortcut, you can also use the revision argument to mark a
277 277 revision as good or bad without checking it out first.
278 278 """
279 279 # backward compatibility
280 280 if rev in "good bad reset init".split():
281 281 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
282 282 cmd, rev, extra = rev, extra, None
283 283 if cmd == "good":
284 284 good = True
285 285 elif cmd == "bad":
286 286 bad = True
287 287 else:
288 288 reset = True
289 289 elif extra or good + bad + skip + reset > 1:
290 290 raise util.Abort(_('incompatible arguments'))
291 291
292 292 if reset:
293 293 p = repo.join("bisect.state")
294 294 if os.path.exists(p):
295 295 os.unlink(p)
296 296 return
297 297
298 298 # load state
299 299 state = {'good': [], 'bad': [], 'skip': []}
300 300 if os.path.exists(repo.join("bisect.state")):
301 301 for l in repo.opener("bisect.state"):
302 302 kind, node = l[:-1].split()
303 303 node = repo.lookup(node)
304 304 if kind not in state:
305 305 raise util.Abort(_("unknown bisect kind %s") % kind)
306 306 state[kind].append(node)
307 307
308 308 # update state
309 309 node = repo.lookup(rev or '.')
310 310 if good:
311 311 state['good'].append(node)
312 312 elif bad:
313 313 state['bad'].append(node)
314 314 elif skip:
315 315 state['skip'].append(node)
316 316
317 317 # save state
318 318 f = repo.opener("bisect.state", "w", atomictemp=True)
319 319 wlock = repo.wlock()
320 320 try:
321 321 for kind in state:
322 322 for node in state[kind]:
323 323 f.write("%s %s\n" % (kind, hex(node)))
324 324 f.rename()
325 325 finally:
326 326 del wlock
327 327
328 328 if not state['good'] or not state['bad']:
329 329 if (good or bad or skip or reset):
330 330 return
331 331 if not state['good']:
332 332 raise util.Abort(_('cannot bisect (no known good revisions)'))
333 333 else:
334 334 raise util.Abort(_('cannot bisect (no known bad revisions)'))
335 335
336 336 # actually bisect
337 337 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
338 338 if changesets == 0:
339 339 displayer = cmdutil.show_changeset(ui, repo, {})
340 340 transition = (good and "good" or "bad")
341 341 if len(nodes) == 1:
342 342 # narrowed it down to a single revision
343 343 ui.write(_("The first %s revision is:\n") % transition)
344 344 displayer.show(changenode=nodes[0])
345 345 else:
346 346 # multiple possible revisions
347 347 ui.write(_("Due to skipped revisions, the first "
348 348 "%s revision could be any of:\n") % transition)
349 349 for n in nodes:
350 350 displayer.show(changenode=n)
351 351 else:
352 352 assert len(nodes) == 1 # only a single node can be tested next
353 353 node = nodes[0]
354 354 # compute the approximate number of remaining tests
355 355 tests, size = 0, 2
356 356 while size <= changesets:
357 357 tests, size = tests + 1, size * 2
358 358 rev = repo.changelog.rev(node)
359 359 ui.write(_("Testing changeset %s:%s "
360 360 "(%s changesets remaining, ~%s tests)\n")
361 361 % (rev, short(node), changesets, tests))
362 362 if not noupdate:
363 363 cmdutil.bail_if_changed(repo)
364 364 return hg.clean(repo, node)
365 365
366 366 def branch(ui, repo, label=None, **opts):
367 367 """set or show the current branch name
368 368
369 369 With no argument, show the current branch name. With one argument,
370 370 set the working directory branch name (the branch does not exist in
371 371 the repository until the next commit).
372 372
373 373 Unless --force is specified, branch will not let you set a
374 374 branch name that shadows an existing branch.
375 375
376 376 Use --clean to reset the working directory branch to that of the
377 377 parent of the working directory, negating a previous branch change.
378 378
379 379 Use the command 'hg update' to switch to an existing branch.
380 380 """
381 381
382 382 if opts.get('clean'):
383 383 label = repo[None].parents()[0].branch()
384 384 repo.dirstate.setbranch(label)
385 385 ui.status(_('reset working directory to branch %s\n') % label)
386 386 elif label:
387 387 if not opts.get('force') and label in repo.branchtags():
388 388 if label not in [p.branch() for p in repo.parents()]:
389 389 raise util.Abort(_('a branch of the same name already exists'
390 390 ' (use --force to override)'))
391 391 repo.dirstate.setbranch(util.fromlocal(label))
392 392 ui.status(_('marked working directory as branch %s\n') % label)
393 393 else:
394 394 ui.write("%s\n" % util.tolocal(repo.dirstate.branch()))
395 395
396 396 def branches(ui, repo, active=False):
397 397 """list repository named branches
398 398
399 399 List the repository's named branches, indicating which ones are
400 400 inactive. If active is specified, only show active branches.
401 401
402 402 A branch is considered active if it contains repository heads.
403 403
404 404 Use the command 'hg update' to switch to an existing branch.
405 405 """
406 406 hexfunc = ui.debugflag and hex or short
407 407 activebranches = [util.tolocal(repo[n].branch())
408 408 for n in repo.heads()]
409 409 branches = util.sort([(tag in activebranches, repo.changelog.rev(node), tag)
410 410 for tag, node in repo.branchtags().items()])
411 411 branches.reverse()
412 412
413 413 for isactive, node, tag in branches:
414 414 if (not active) or isactive:
415 415 if ui.quiet:
416 416 ui.write("%s\n" % tag)
417 417 else:
418 418 rev = str(node).rjust(31 - util.locallen(tag))
419 419 isinactive = ((not isactive) and " (inactive)") or ''
420 420 data = tag, rev, hexfunc(repo.lookup(node)), isinactive
421 421 ui.write("%s %s:%s%s\n" % data)
422 422
423 423 def bundle(ui, repo, fname, dest=None, **opts):
424 424 """create a changegroup file
425 425
426 426 Generate a compressed changegroup file collecting changesets not
427 427 found in the other repository.
428 428
429 429 If no destination repository is specified the destination is
430 430 assumed to have all the nodes specified by one or more --base
431 431 parameters. To create a bundle containing all changesets, use
432 432 --all (or --base null). To change the compression method applied,
433 433 use the -t option (by default, bundles are compressed using bz2).
434 434
435 435 The bundle file can then be transferred using conventional means and
436 436 applied to another repository with the unbundle or pull command.
437 437 This is useful when direct push and pull are not available or when
438 438 exporting an entire repository is undesirable.
439 439
440 440 Applying bundles preserves all changeset contents including
441 441 permissions, copy/rename information, and revision history.
442 442 """
443 443 revs = opts.get('rev') or None
444 444 if revs:
445 445 revs = [repo.lookup(rev) for rev in revs]
446 446 if opts.get('all'):
447 447 base = ['null']
448 448 else:
449 449 base = opts.get('base')
450 450 if base:
451 451 if dest:
452 452 raise util.Abort(_("--base is incompatible with specifiying "
453 453 "a destination"))
454 454 base = [repo.lookup(rev) for rev in base]
455 455 # create the right base
456 456 # XXX: nodesbetween / changegroup* should be "fixed" instead
457 457 o = []
458 458 has = {nullid: None}
459 459 for n in base:
460 460 has.update(repo.changelog.reachable(n))
461 461 if revs:
462 462 visit = list(revs)
463 463 else:
464 464 visit = repo.changelog.heads()
465 465 seen = {}
466 466 while visit:
467 467 n = visit.pop(0)
468 468 parents = [p for p in repo.changelog.parents(n) if p not in has]
469 469 if len(parents) == 0:
470 470 o.insert(0, n)
471 471 else:
472 472 for p in parents:
473 473 if p not in seen:
474 474 seen[p] = 1
475 475 visit.append(p)
476 476 else:
477 477 cmdutil.setremoteconfig(ui, opts)
478 478 dest, revs, checkout = hg.parseurl(
479 479 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
480 480 other = hg.repository(ui, dest)
481 o = repo.findoutgoing(other, force=opts['force'])
481 o = repo.findoutgoing(other, force=opts.get('force'))
482 482
483 483 if revs:
484 484 cg = repo.changegroupsubset(o, revs, 'bundle')
485 485 else:
486 486 cg = repo.changegroup(o, 'bundle')
487 487
488 488 bundletype = opts.get('type', 'bzip2').lower()
489 489 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
490 490 bundletype = btypes.get(bundletype)
491 491 if bundletype not in changegroup.bundletypes:
492 492 raise util.Abort(_('unknown bundle type specified with --type'))
493 493
494 494 changegroup.writebundle(cg, fname, bundletype)
495 495
496 496 def cat(ui, repo, file1, *pats, **opts):
497 497 """output the current or given revision of files
498 498
499 499 Print the specified files as they were at the given revision.
500 500 If no revision is given, the parent of the working directory is used,
501 501 or tip if no revision is checked out.
502 502
503 503 Output may be to a file, in which case the name of the file is
504 504 given using a format string. The formatting rules are the same as
505 505 for the export command, with the following additions:
506 506
507 507 %s basename of file being printed
508 508 %d dirname of file being printed, or '.' if in repo root
509 509 %p root-relative path name of file being printed
510 510 """
511 ctx = repo[opts['rev']]
511 ctx = repo[opts.get('rev')]
512 512 err = 1
513 513 m = cmdutil.match(repo, (file1,) + pats, opts)
514 514 for abs in ctx.walk(m):
515 fp = cmdutil.make_file(repo, opts['output'], ctx.node(), pathname=abs)
515 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
516 516 data = ctx[abs].data()
517 517 if opts.get('decode'):
518 518 data = repo.wwritedata(abs, data)
519 519 fp.write(data)
520 520 err = 0
521 521 return err
522 522
523 523 def clone(ui, source, dest=None, **opts):
524 524 """make a copy of an existing repository
525 525
526 526 Create a copy of an existing repository in a new directory.
527 527
528 528 If no destination directory name is specified, it defaults to the
529 529 basename of the source.
530 530
531 531 The location of the source is added to the new repository's
532 532 .hg/hgrc file, as the default to be used for future pulls.
533 533
534 534 For efficiency, hardlinks are used for cloning whenever the source
535 535 and destination are on the same filesystem (note this applies only
536 536 to the repository data, not to the checked out files). Some
537 537 filesystems, such as AFS, implement hardlinking incorrectly, but
538 538 do not report errors. In these cases, use the --pull option to
539 539 avoid hardlinking.
540 540
541 541 In some cases, you can clone repositories and checked out files
542 542 using full hardlinks with
543 543
544 544 $ cp -al REPO REPOCLONE
545 545
546 546 This is the fastest way to clone, but it is not always safe. The
547 547 operation is not atomic (making sure REPO is not modified during
548 548 the operation is up to you) and you have to make sure your editor
549 549 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
550 550 this is not compatible with certain extensions that place their
551 551 metadata under the .hg directory, such as mq.
552 552
553 553 If you use the -r option to clone up to a specific revision, no
554 554 subsequent revisions will be present in the cloned repository.
555 555 This option implies --pull, even on local repositories.
556 556
557 557 If the -U option is used, the new clone will contain only a repository
558 558 (.hg) and no working copy (the working copy parent is the null revision).
559 559
560 560 See pull for valid source format details.
561 561
562 562 It is possible to specify an ssh:// URL as the destination, but no
563 563 .hg/hgrc and working directory will be created on the remote side.
564 564 Look at the help text for the pull command for important details
565 565 about ssh:// URLs.
566 566 """
567 567 cmdutil.setremoteconfig(ui, opts)
568 568 hg.clone(ui, source, dest,
569 pull=opts['pull'],
570 stream=opts['uncompressed'],
571 rev=opts['rev'],
572 update=not opts['noupdate'])
569 pull=opts.get('pull'),
570 stream=opts.get('uncompressed'),
571 rev=opts.get('rev'),
572 update=not opts.get('noupdate'))
573 573
574 574 def commit(ui, repo, *pats, **opts):
575 575 """commit the specified files or all outstanding changes
576 576
577 577 Commit changes to the given files into the repository.
578 578
579 579 If a list of files is omitted, all changes reported by "hg status"
580 580 will be committed.
581 581
582 582 If you are committing the result of a merge, do not provide any
583 583 file names or -I/-X filters.
584 584
585 585 If no commit message is specified, the configured editor is started to
586 586 enter a message.
587 587
588 588 See 'hg help dates' for a list of formats valid for -d/--date.
589 589 """
590 590 def commitfunc(ui, repo, message, match, opts):
591 return repo.commit(match.files(), message, opts['user'], opts['date'],
591 return repo.commit(match.files(), message, opts.get('user'), opts.get('date'),
592 592 match, force_editor=opts.get('force_editor'))
593 593
594 594 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
595 595 if not node:
596 596 return
597 597 cl = repo.changelog
598 598 rev = cl.rev(node)
599 599 parents = cl.parentrevs(rev)
600 600 if rev - 1 in parents:
601 601 # one of the parents was the old tip
602 602 pass
603 603 elif (parents == (nullrev, nullrev) or
604 604 len(cl.heads(cl.node(parents[0]))) > 1 and
605 605 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
606 606 ui.status(_('created new head\n'))
607 607
608 608 if ui.debugflag:
609 609 ui.write(_('committed changeset %d:%s\n') % (rev,hex(node)))
610 610 elif ui.verbose:
611 611 ui.write(_('committed changeset %d:%s\n') % (rev,short(node)))
612 612
613 613 def copy(ui, repo, *pats, **opts):
614 614 """mark files as copied for the next commit
615 615
616 616 Mark dest as having copies of source files. If dest is a
617 617 directory, copies are put in that directory. If dest is a file,
618 618 there can only be one source.
619 619
620 620 By default, this command copies the contents of files as they
621 621 stand in the working directory. If invoked with --after, the
622 622 operation is recorded, but no copying is performed.
623 623
624 624 This command takes effect in the next commit. To undo a copy
625 625 before that, see hg revert.
626 626 """
627 627 wlock = repo.wlock(False)
628 628 try:
629 629 return cmdutil.copy(ui, repo, pats, opts)
630 630 finally:
631 631 del wlock
632 632
633 633 def debugancestor(ui, repo, *args):
634 634 """find the ancestor revision of two revisions in a given index"""
635 635 if len(args) == 3:
636 636 index, rev1, rev2 = args
637 637 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
638 638 lookup = r.lookup
639 639 elif len(args) == 2:
640 640 if not repo:
641 641 raise util.Abort(_("There is no Mercurial repository here "
642 642 "(.hg not found)"))
643 643 rev1, rev2 = args
644 644 r = repo.changelog
645 645 lookup = repo.lookup
646 646 else:
647 647 raise util.Abort(_('either two or three arguments required'))
648 648 a = r.ancestor(lookup(rev1), lookup(rev2))
649 649 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
650 650
651 651 def debugcomplete(ui, cmd='', **opts):
652 652 """returns the completion list associated with the given command"""
653 653
654 if opts['options']:
654 if opts.get('options'):
655 655 options = []
656 656 otables = [globalopts]
657 657 if cmd:
658 658 aliases, entry = cmdutil.findcmd(ui, cmd, table)
659 659 otables.append(entry[1])
660 660 for t in otables:
661 661 for o in t:
662 662 if o[0]:
663 663 options.append('-%s' % o[0])
664 664 options.append('--%s' % o[1])
665 665 ui.write("%s\n" % "\n".join(options))
666 666 return
667 667
668 668 ui.write("%s\n" % "\n".join(util.sort(cmdutil.findpossible(ui, cmd, table))))
669 669
670 670 def debugfsinfo(ui, path = "."):
671 671 file('.debugfsinfo', 'w').write('')
672 672 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
673 673 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
674 674 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
675 675 and 'yes' or 'no'))
676 676 os.unlink('.debugfsinfo')
677 677
678 678 def debugrebuildstate(ui, repo, rev="tip"):
679 679 """rebuild the dirstate as it would look like for the given revision"""
680 680 ctx = repo[rev]
681 681 wlock = repo.wlock()
682 682 try:
683 683 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
684 684 finally:
685 685 del wlock
686 686
687 687 def debugcheckstate(ui, repo):
688 688 """validate the correctness of the current dirstate"""
689 689 parent1, parent2 = repo.dirstate.parents()
690 690 m1 = repo[parent1].manifest()
691 691 m2 = repo[parent2].manifest()
692 692 errors = 0
693 693 for f in repo.dirstate:
694 694 state = repo.dirstate[f]
695 695 if state in "nr" and f not in m1:
696 696 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
697 697 errors += 1
698 698 if state in "a" and f in m1:
699 699 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
700 700 errors += 1
701 701 if state in "m" and f not in m1 and f not in m2:
702 702 ui.warn(_("%s in state %s, but not in either manifest\n") %
703 703 (f, state))
704 704 errors += 1
705 705 for f in m1:
706 706 state = repo.dirstate[f]
707 707 if state not in "nrm":
708 708 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
709 709 errors += 1
710 710 if errors:
711 711 error = _(".hg/dirstate inconsistent with current parent's manifest")
712 712 raise util.Abort(error)
713 713
714 714 def showconfig(ui, repo, *values, **opts):
715 715 """show combined config settings from all hgrc files
716 716
717 717 With no args, print names and values of all config items.
718 718
719 719 With one arg of the form section.name, print just the value of
720 720 that config item.
721 721
722 722 With multiple args, print names and values of all config items
723 723 with matching section names."""
724 724
725 725 untrusted = bool(opts.get('untrusted'))
726 726 if values:
727 727 if len([v for v in values if '.' in v]) > 1:
728 728 raise util.Abort(_('only one config item permitted'))
729 729 for section, name, value in ui.walkconfig(untrusted=untrusted):
730 730 sectname = section + '.' + name
731 731 if values:
732 732 for v in values:
733 733 if v == section:
734 734 ui.write('%s=%s\n' % (sectname, value))
735 735 elif v == sectname:
736 736 ui.write(value, '\n')
737 737 else:
738 738 ui.write('%s=%s\n' % (sectname, value))
739 739
740 740 def debugsetparents(ui, repo, rev1, rev2=None):
741 741 """manually set the parents of the current working directory
742 742
743 743 This is useful for writing repository conversion tools, but should
744 744 be used with care.
745 745 """
746 746
747 747 if not rev2:
748 748 rev2 = hex(nullid)
749 749
750 750 wlock = repo.wlock()
751 751 try:
752 752 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
753 753 finally:
754 754 del wlock
755 755
756 756 def debugstate(ui, repo, nodates=None):
757 757 """show the contents of the current dirstate"""
758 758 timestr = ""
759 759 showdate = not nodates
760 760 for file_, ent in util.sort(repo.dirstate._map.items()):
761 761 if showdate:
762 762 if ent[3] == -1:
763 763 # Pad or slice to locale representation
764 764 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
765 765 timestr = 'unset'
766 766 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
767 767 else:
768 768 timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
769 769 if ent[1] & 020000:
770 770 mode = 'lnk'
771 771 else:
772 772 mode = '%3o' % (ent[1] & 0777)
773 773 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
774 774 for f in repo.dirstate.copies():
775 775 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
776 776
777 777 def debugdata(ui, file_, rev):
778 778 """dump the contents of a data file revision"""
779 779 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
780 780 try:
781 781 ui.write(r.revision(r.lookup(rev)))
782 782 except KeyError:
783 783 raise util.Abort(_('invalid revision identifier %s') % rev)
784 784
785 785 def debugdate(ui, date, range=None, **opts):
786 786 """parse and display a date"""
787 787 if opts["extended"]:
788 788 d = util.parsedate(date, util.extendeddateformats)
789 789 else:
790 790 d = util.parsedate(date)
791 791 ui.write("internal: %s %s\n" % d)
792 792 ui.write("standard: %s\n" % util.datestr(d))
793 793 if range:
794 794 m = util.matchdate(range)
795 795 ui.write("match: %s\n" % m(d[0]))
796 796
797 797 def debugindex(ui, file_):
798 798 """dump the contents of an index file"""
799 799 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
800 800 ui.write(" rev offset length base linkrev" +
801 801 " nodeid p1 p2\n")
802 802 for i in r:
803 803 node = r.node(i)
804 804 try:
805 805 pp = r.parents(node)
806 806 except:
807 807 pp = [nullid, nullid]
808 808 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
809 809 i, r.start(i), r.length(i), r.base(i), r.linkrev(node),
810 810 short(node), short(pp[0]), short(pp[1])))
811 811
812 812 def debugindexdot(ui, file_):
813 813 """dump an index DAG as a .dot file"""
814 814 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
815 815 ui.write("digraph G {\n")
816 816 for i in r:
817 817 node = r.node(i)
818 818 pp = r.parents(node)
819 819 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
820 820 if pp[1] != nullid:
821 821 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
822 822 ui.write("}\n")
823 823
824 824 def debuginstall(ui):
825 825 '''test Mercurial installation'''
826 826
827 827 def writetemp(contents):
828 828 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
829 829 f = os.fdopen(fd, "wb")
830 830 f.write(contents)
831 831 f.close()
832 832 return name
833 833
834 834 problems = 0
835 835
836 836 # encoding
837 837 ui.status(_("Checking encoding (%s)...\n") % util._encoding)
838 838 try:
839 839 util.fromlocal("test")
840 840 except util.Abort, inst:
841 841 ui.write(" %s\n" % inst)
842 842 ui.write(_(" (check that your locale is properly set)\n"))
843 843 problems += 1
844 844
845 845 # compiled modules
846 846 ui.status(_("Checking extensions...\n"))
847 847 try:
848 848 import bdiff, mpatch, base85
849 849 except Exception, inst:
850 850 ui.write(" %s\n" % inst)
851 851 ui.write(_(" One or more extensions could not be found"))
852 852 ui.write(_(" (check that you compiled the extensions)\n"))
853 853 problems += 1
854 854
855 855 # templates
856 856 ui.status(_("Checking templates...\n"))
857 857 try:
858 858 import templater
859 859 t = templater.templater(templater.templatepath("map-cmdline.default"))
860 860 except Exception, inst:
861 861 ui.write(" %s\n" % inst)
862 862 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
863 863 problems += 1
864 864
865 865 # patch
866 866 ui.status(_("Checking patch...\n"))
867 867 patchproblems = 0
868 868 a = "1\n2\n3\n4\n"
869 869 b = "1\n2\n3\ninsert\n4\n"
870 870 fa = writetemp(a)
871 871 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
872 872 os.path.basename(fa))
873 873 fd = writetemp(d)
874 874
875 875 files = {}
876 876 try:
877 877 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
878 878 except util.Abort, e:
879 879 ui.write(_(" patch call failed:\n"))
880 880 ui.write(" " + str(e) + "\n")
881 881 patchproblems += 1
882 882 else:
883 883 if list(files) != [os.path.basename(fa)]:
884 884 ui.write(_(" unexpected patch output!\n"))
885 885 patchproblems += 1
886 886 a = file(fa).read()
887 887 if a != b:
888 888 ui.write(_(" patch test failed!\n"))
889 889 patchproblems += 1
890 890
891 891 if patchproblems:
892 892 if ui.config('ui', 'patch'):
893 893 ui.write(_(" (Current patch tool may be incompatible with patch,"
894 894 " or misconfigured. Please check your .hgrc file)\n"))
895 895 else:
896 896 ui.write(_(" Internal patcher failure, please report this error"
897 897 " to http://www.selenic.com/mercurial/bts\n"))
898 898 problems += patchproblems
899 899
900 900 os.unlink(fa)
901 901 os.unlink(fd)
902 902
903 903 # editor
904 904 ui.status(_("Checking commit editor...\n"))
905 905 editor = ui.geteditor()
906 906 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
907 907 if not cmdpath:
908 908 if editor == 'vi':
909 909 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
910 910 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
911 911 else:
912 912 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
913 913 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
914 914 problems += 1
915 915
916 916 # check username
917 917 ui.status(_("Checking username...\n"))
918 918 user = os.environ.get("HGUSER")
919 919 if user is None:
920 920 user = ui.config("ui", "username")
921 921 if user is None:
922 922 user = os.environ.get("EMAIL")
923 923 if not user:
924 924 ui.warn(" ")
925 925 ui.username()
926 926 ui.write(_(" (specify a username in your .hgrc file)\n"))
927 927
928 928 if not problems:
929 929 ui.status(_("No problems detected\n"))
930 930 else:
931 931 ui.write(_("%s problems detected,"
932 932 " please check your install!\n") % problems)
933 933
934 934 return problems
935 935
936 936 def debugrename(ui, repo, file1, *pats, **opts):
937 937 """dump rename information"""
938 938
939 939 ctx = repo[opts.get('rev')]
940 940 m = cmdutil.match(repo, (file1,) + pats, opts)
941 941 for abs in ctx.walk(m):
942 942 fctx = ctx[abs]
943 943 o = fctx.filelog().renamed(fctx.filenode())
944 944 rel = m.rel(abs)
945 945 if o:
946 946 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
947 947 else:
948 948 ui.write(_("%s not renamed\n") % rel)
949 949
950 950 def debugwalk(ui, repo, *pats, **opts):
951 951 """show how files match on given patterns"""
952 952 m = cmdutil.match(repo, pats, opts)
953 953 items = list(repo.walk(m))
954 954 if not items:
955 955 return
956 956 fmt = 'f %%-%ds %%-%ds %%s' % (
957 957 max([len(abs) for abs in items]),
958 958 max([len(m.rel(abs)) for abs in items]))
959 959 for abs in items:
960 960 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
961 961 ui.write("%s\n" % line.rstrip())
962 962
963 963 def diff(ui, repo, *pats, **opts):
964 964 """diff repository (or selected files)
965 965
966 966 Show differences between revisions for the specified files.
967 967
968 968 Differences between files are shown using the unified diff format.
969 969
970 970 NOTE: diff may generate unexpected results for merges, as it will
971 971 default to comparing against the working directory's first parent
972 972 changeset if no revisions are specified.
973 973
974 974 When two revision arguments are given, then changes are shown
975 975 between those revisions. If only one revision is specified then
976 976 that revision is compared to the working directory, and, when no
977 977 revisions are specified, the working directory files are compared
978 978 to its parent.
979 979
980 980 Without the -a option, diff will avoid generating diffs of files
981 981 it detects as binary. With -a, diff will generate a diff anyway,
982 982 probably with undesirable results.
983 983 """
984 node1, node2 = cmdutil.revpair(repo, opts['rev'])
984 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
985 985
986 986 m = cmdutil.match(repo, pats, opts)
987 987 patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts))
988 988
989 989 def export(ui, repo, *changesets, **opts):
990 990 """dump the header and diffs for one or more changesets
991 991
992 992 Print the changeset header and diffs for one or more revisions.
993 993
994 994 The information shown in the changeset header is: author,
995 995 changeset hash, parent(s) and commit comment.
996 996
997 997 NOTE: export may generate unexpected diff output for merge changesets,
998 998 as it will compare the merge changeset against its first parent only.
999 999
1000 1000 Output may be to a file, in which case the name of the file is
1001 1001 given using a format string. The formatting rules are as follows:
1002 1002
1003 1003 %% literal "%" character
1004 1004 %H changeset hash (40 bytes of hexadecimal)
1005 1005 %N number of patches being generated
1006 1006 %R changeset revision number
1007 1007 %b basename of the exporting repository
1008 1008 %h short-form changeset hash (12 bytes of hexadecimal)
1009 1009 %n zero-padded sequence number, starting at 1
1010 1010 %r zero-padded changeset revision number
1011 1011
1012 1012 Without the -a option, export will avoid generating diffs of files
1013 1013 it detects as binary. With -a, export will generate a diff anyway,
1014 1014 probably with undesirable results.
1015 1015
1016 1016 With the --switch-parent option, the diff will be against the second
1017 1017 parent. It can be useful to review a merge.
1018 1018 """
1019 1019 if not changesets:
1020 1020 raise util.Abort(_("export requires at least one changeset"))
1021 1021 revs = cmdutil.revrange(repo, changesets)
1022 1022 if len(revs) > 1:
1023 1023 ui.note(_('exporting patches:\n'))
1024 1024 else:
1025 1025 ui.note(_('exporting patch:\n'))
1026 patch.export(repo, revs, template=opts['output'],
1027 switch_parent=opts['switch_parent'],
1026 patch.export(repo, revs, template=opts.get('output'),
1027 switch_parent=opts.get('switch_parent'),
1028 1028 opts=patch.diffopts(ui, opts))
1029 1029
1030 1030 def grep(ui, repo, pattern, *pats, **opts):
1031 1031 """search for a pattern in specified files and revisions
1032 1032
1033 1033 Search revisions of files for a regular expression.
1034 1034
1035 1035 This command behaves differently than Unix grep. It only accepts
1036 1036 Python/Perl regexps. It searches repository history, not the
1037 1037 working directory. It always prints the revision number in which
1038 1038 a match appears.
1039 1039
1040 1040 By default, grep only prints output for the first revision of a
1041 1041 file in which it finds a match. To get it to print every revision
1042 1042 that contains a change in match status ("-" for a match that
1043 1043 becomes a non-match, or "+" for a non-match that becomes a match),
1044 1044 use the --all flag.
1045 1045 """
1046 1046 reflags = 0
1047 if opts['ignore_case']:
1047 if opts.get('ignore_case'):
1048 1048 reflags |= re.I
1049 1049 try:
1050 1050 regexp = re.compile(pattern, reflags)
1051 1051 except Exception, inst:
1052 1052 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1053 1053 return None
1054 1054 sep, eol = ':', '\n'
1055 if opts['print0']:
1055 if opts.get('print0'):
1056 1056 sep = eol = '\0'
1057 1057
1058 1058 fcache = {}
1059 1059 def getfile(fn):
1060 1060 if fn not in fcache:
1061 1061 fcache[fn] = repo.file(fn)
1062 1062 return fcache[fn]
1063 1063
1064 1064 def matchlines(body):
1065 1065 begin = 0
1066 1066 linenum = 0
1067 1067 while True:
1068 1068 match = regexp.search(body, begin)
1069 1069 if not match:
1070 1070 break
1071 1071 mstart, mend = match.span()
1072 1072 linenum += body.count('\n', begin, mstart) + 1
1073 1073 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1074 1074 lend = body.find('\n', mend)
1075 1075 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1076 1076 begin = lend + 1
1077 1077
1078 1078 class linestate(object):
1079 1079 def __init__(self, line, linenum, colstart, colend):
1080 1080 self.line = line
1081 1081 self.linenum = linenum
1082 1082 self.colstart = colstart
1083 1083 self.colend = colend
1084 1084
1085 1085 def __hash__(self):
1086 1086 return hash((self.linenum, self.line))
1087 1087
1088 1088 def __eq__(self, other):
1089 1089 return self.line == other.line
1090 1090
1091 1091 matches = {}
1092 1092 copies = {}
1093 1093 def grepbody(fn, rev, body):
1094 1094 matches[rev].setdefault(fn, [])
1095 1095 m = matches[rev][fn]
1096 1096 for lnum, cstart, cend, line in matchlines(body):
1097 1097 s = linestate(line, lnum, cstart, cend)
1098 1098 m.append(s)
1099 1099
1100 1100 def difflinestates(a, b):
1101 1101 sm = difflib.SequenceMatcher(None, a, b)
1102 1102 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1103 1103 if tag == 'insert':
1104 1104 for i in xrange(blo, bhi):
1105 1105 yield ('+', b[i])
1106 1106 elif tag == 'delete':
1107 1107 for i in xrange(alo, ahi):
1108 1108 yield ('-', a[i])
1109 1109 elif tag == 'replace':
1110 1110 for i in xrange(alo, ahi):
1111 1111 yield ('-', a[i])
1112 1112 for i in xrange(blo, bhi):
1113 1113 yield ('+', b[i])
1114 1114
1115 1115 prev = {}
1116 1116 def display(fn, rev, states, prevstates):
1117 1117 datefunc = ui.quiet and util.shortdate or util.datestr
1118 1118 found = False
1119 1119 filerevmatches = {}
1120 1120 r = prev.get(fn, -1)
1121 if opts['all']:
1121 if opts.get('all'):
1122 1122 iter = difflinestates(states, prevstates)
1123 1123 else:
1124 1124 iter = [('', l) for l in prevstates]
1125 1125 for change, l in iter:
1126 1126 cols = [fn, str(r)]
1127 if opts['line_number']:
1127 if opts.get('line_number'):
1128 1128 cols.append(str(l.linenum))
1129 if opts['all']:
1129 if opts.get('all'):
1130 1130 cols.append(change)
1131 if opts['user']:
1131 if opts.get('user'):
1132 1132 cols.append(ui.shortuser(get(r)[1]))
1133 1133 if opts.get('date'):
1134 1134 cols.append(datefunc(get(r)[2]))
1135 if opts['files_with_matches']:
1135 if opts.get('files_with_matches'):
1136 1136 c = (fn, r)
1137 1137 if c in filerevmatches:
1138 1138 continue
1139 1139 filerevmatches[c] = 1
1140 1140 else:
1141 1141 cols.append(l.line)
1142 1142 ui.write(sep.join(cols), eol)
1143 1143 found = True
1144 1144 return found
1145 1145
1146 1146 fstate = {}
1147 1147 skip = {}
1148 1148 get = util.cachefunc(lambda r: repo[r].changeset())
1149 1149 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1150 1150 found = False
1151 1151 follow = opts.get('follow')
1152 1152 for st, rev, fns in changeiter:
1153 1153 if st == 'window':
1154 1154 matches.clear()
1155 1155 elif st == 'add':
1156 1156 ctx = repo[rev]
1157 1157 matches[rev] = {}
1158 1158 for fn in fns:
1159 1159 if fn in skip:
1160 1160 continue
1161 1161 try:
1162 1162 grepbody(fn, rev, getfile(fn).read(ctx.filenode(fn)))
1163 1163 fstate.setdefault(fn, [])
1164 1164 if follow:
1165 1165 copied = getfile(fn).renamed(ctx.filenode(fn))
1166 1166 if copied:
1167 1167 copies.setdefault(rev, {})[fn] = copied[0]
1168 1168 except revlog.LookupError:
1169 1169 pass
1170 1170 elif st == 'iter':
1171 1171 for fn, m in util.sort(matches[rev].items()):
1172 1172 copy = copies.get(rev, {}).get(fn)
1173 1173 if fn in skip:
1174 1174 if copy:
1175 1175 skip[copy] = True
1176 1176 continue
1177 1177 if fn in prev or fstate[fn]:
1178 1178 r = display(fn, rev, m, fstate[fn])
1179 1179 found = found or r
1180 if r and not opts['all']:
1180 if r and not opts.get('all'):
1181 1181 skip[fn] = True
1182 1182 if copy:
1183 1183 skip[copy] = True
1184 1184 fstate[fn] = m
1185 1185 if copy:
1186 1186 fstate[copy] = m
1187 1187 prev[fn] = rev
1188 1188
1189 1189 for fn, state in util.sort(fstate.items()):
1190 1190 if fn in skip:
1191 1191 continue
1192 1192 if fn not in copies.get(prev[fn], {}):
1193 1193 found = display(fn, rev, {}, state) or found
1194 1194 return (not found and 1) or 0
1195 1195
1196 1196 def heads(ui, repo, *branchrevs, **opts):
1197 1197 """show current repository heads or show branch heads
1198 1198
1199 1199 With no arguments, show all repository head changesets.
1200 1200
1201 1201 If branch or revisions names are given this will show the heads of
1202 1202 the specified branches or the branches those revisions are tagged
1203 1203 with.
1204 1204
1205 1205 Repository "heads" are changesets that don't have child
1206 1206 changesets. They are where development generally takes place and
1207 1207 are the usual targets for update and merge operations.
1208 1208
1209 1209 Branch heads are changesets that have a given branch tag, but have
1210 1210 no child changesets with that tag. They are usually where
1211 1211 development on the given branch takes place.
1212 1212 """
1213 if opts['rev']:
1213 if opts.get('rev'):
1214 1214 start = repo.lookup(opts['rev'])
1215 1215 else:
1216 1216 start = None
1217 1217 if not branchrevs:
1218 1218 # Assume we're looking repo-wide heads if no revs were specified.
1219 1219 heads = repo.heads(start)
1220 1220 else:
1221 1221 heads = []
1222 1222 visitedset = util.set()
1223 1223 for branchrev in branchrevs:
1224 1224 branch = repo[branchrev].branch()
1225 1225 if branch in visitedset:
1226 1226 continue
1227 1227 visitedset.add(branch)
1228 1228 bheads = repo.branchheads(branch, start)
1229 1229 if not bheads:
1230 1230 if branch != branchrev:
1231 1231 ui.warn(_("no changes on branch %s containing %s are "
1232 1232 "reachable from %s\n")
1233 % (branch, branchrev, opts['rev']))
1233 % (branch, branchrev, opts.get('rev')))
1234 1234 else:
1235 1235 ui.warn(_("no changes on branch %s are reachable from %s\n")
1236 % (branch, opts['rev']))
1236 % (branch, opts.get('rev')))
1237 1237 heads.extend(bheads)
1238 1238 if not heads:
1239 1239 return 1
1240 1240 displayer = cmdutil.show_changeset(ui, repo, opts)
1241 1241 for n in heads:
1242 1242 displayer.show(changenode=n)
1243 1243
1244 1244 def help_(ui, name=None, with_version=False):
1245 1245 """show help for a command, extension, or list of commands
1246 1246
1247 1247 With no arguments, print a list of commands and short help.
1248 1248
1249 1249 Given a command name, print help for that command.
1250 1250
1251 1251 Given an extension name, print help for that extension, and the
1252 1252 commands it provides."""
1253 1253 option_lists = []
1254 1254
1255 1255 def addglobalopts(aliases):
1256 1256 if ui.verbose:
1257 1257 option_lists.append((_("global options:"), globalopts))
1258 1258 if name == 'shortlist':
1259 1259 option_lists.append((_('use "hg help" for the full list '
1260 1260 'of commands'), ()))
1261 1261 else:
1262 1262 if name == 'shortlist':
1263 1263 msg = _('use "hg help" for the full list of commands '
1264 1264 'or "hg -v" for details')
1265 1265 elif aliases:
1266 1266 msg = _('use "hg -v help%s" to show aliases and '
1267 1267 'global options') % (name and " " + name or "")
1268 1268 else:
1269 1269 msg = _('use "hg -v help %s" to show global options') % name
1270 1270 option_lists.append((msg, ()))
1271 1271
1272 1272 def helpcmd(name):
1273 1273 if with_version:
1274 1274 version_(ui)
1275 1275 ui.write('\n')
1276 1276
1277 1277 try:
1278 1278 aliases, i = cmdutil.findcmd(ui, name, table)
1279 1279 except cmdutil.AmbiguousCommand, inst:
1280 1280 select = lambda c: c.lstrip('^').startswith(inst.args[0])
1281 1281 helplist(_('list of commands:\n\n'), select)
1282 1282 return
1283 1283
1284 1284 # synopsis
1285 1285 ui.write("%s\n" % i[2])
1286 1286
1287 1287 # aliases
1288 1288 if not ui.quiet and len(aliases) > 1:
1289 1289 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1290 1290
1291 1291 # description
1292 1292 doc = gettext(i[0].__doc__)
1293 1293 if not doc:
1294 1294 doc = _("(No help text available)")
1295 1295 if ui.quiet:
1296 1296 doc = doc.splitlines(0)[0]
1297 1297 ui.write("\n%s\n" % doc.rstrip())
1298 1298
1299 1299 if not ui.quiet:
1300 1300 # options
1301 1301 if i[1]:
1302 1302 option_lists.append((_("options:\n"), i[1]))
1303 1303
1304 1304 addglobalopts(False)
1305 1305
1306 1306 def helplist(header, select=None):
1307 1307 h = {}
1308 1308 cmds = {}
1309 1309 for c, e in table.items():
1310 1310 f = c.split("|", 1)[0]
1311 1311 if select and not select(f):
1312 1312 continue
1313 if select is None and e[0].__module__ != __name__:
1314 continue
1313 1315 if name == "shortlist" and not f.startswith("^"):
1314 1316 continue
1315 1317 f = f.lstrip("^")
1316 1318 if not ui.debugflag and f.startswith("debug"):
1317 1319 continue
1318 1320 doc = gettext(e[0].__doc__)
1319 1321 if not doc:
1320 1322 doc = _("(No help text available)")
1321 1323 h[f] = doc.splitlines(0)[0].rstrip()
1322 1324 cmds[f] = c.lstrip("^")
1323 1325
1324 1326 if not h:
1325 1327 ui.status(_('no commands defined\n'))
1326 1328 return
1327 1329
1328 1330 ui.status(header)
1329 1331 fns = util.sort(h)
1330 1332 m = max(map(len, fns))
1331 1333 for f in fns:
1332 1334 if ui.verbose:
1333 1335 commands = cmds[f].replace("|",", ")
1334 1336 ui.write(" %s:\n %s\n"%(commands, h[f]))
1335 1337 else:
1336 1338 ui.write(' %-*s %s\n' % (m, f, h[f]))
1337 1339
1340 exts = list(extensions.extensions())
1341 if exts:
1342 ui.write(_('\nenabled extensions:\n\n'))
1343 maxlength = 0
1344 exthelps = []
1345 for ename, ext in exts:
1346 doc = (ext.__doc__ or _('(no help text available)'))
1347 ename = ename.split('.')[-1]
1348 maxlength = max(len(ename), maxlength)
1349 exthelps.append((ename, doc.splitlines(0)[0].strip()))
1350 for ename, text in exthelps:
1351 ui.write(_(' %s %s\n') % (ename.ljust(maxlength), text))
1352
1338 1353 if not ui.quiet:
1339 1354 addglobalopts(True)
1340 1355
1341 1356 def helptopic(name):
1342 1357 for names, header, doc in help.helptable:
1343 1358 if name in names:
1344 1359 break
1345 1360 else:
1346 1361 raise cmdutil.UnknownCommand(name)
1347 1362
1348 1363 # description
1349 1364 if not doc:
1350 1365 doc = _("(No help text available)")
1351 1366 if callable(doc):
1352 1367 doc = doc()
1353 1368
1354 1369 ui.write("%s\n" % header)
1355 1370 ui.write("%s\n" % doc.rstrip())
1356 1371
1357 1372 def helpext(name):
1358 1373 try:
1359 1374 mod = extensions.find(name)
1360 1375 except KeyError:
1361 1376 raise cmdutil.UnknownCommand(name)
1362 1377
1363 1378 doc = gettext(mod.__doc__) or _('No help text available')
1364 1379 doc = doc.splitlines(0)
1365 1380 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1366 1381 for d in doc[1:]:
1367 1382 ui.write(d, '\n')
1368 1383
1369 1384 ui.status('\n')
1370 1385
1371 1386 try:
1372 1387 ct = mod.cmdtable
1373 1388 except AttributeError:
1374 1389 ct = {}
1375 1390
1376 1391 modcmds = dict.fromkeys([c.split('|', 1)[0] for c in ct])
1377 1392 helplist(_('list of commands:\n\n'), modcmds.has_key)
1378 1393
1379 1394 if name and name != 'shortlist':
1380 1395 i = None
1381 1396 for f in (helpcmd, helptopic, helpext):
1382 1397 try:
1383 1398 f(name)
1384 1399 i = None
1385 1400 break
1386 1401 except cmdutil.UnknownCommand, inst:
1387 1402 i = inst
1388 1403 if i:
1389 1404 raise i
1390 1405
1391 1406 else:
1392 1407 # program name
1393 1408 if ui.verbose or with_version:
1394 1409 version_(ui)
1395 1410 else:
1396 1411 ui.status(_("Mercurial Distributed SCM\n"))
1397 1412 ui.status('\n')
1398 1413
1399 1414 # list of commands
1400 1415 if name == "shortlist":
1401 1416 header = _('basic commands:\n\n')
1402 1417 else:
1403 1418 header = _('list of commands:\n\n')
1404 1419
1405 1420 helplist(header)
1406 1421
1407 1422 # list all option lists
1408 1423 opt_output = []
1409 1424 for title, options in option_lists:
1410 1425 opt_output.append(("\n%s" % title, None))
1411 1426 for shortopt, longopt, default, desc in options:
1412 1427 if "DEPRECATED" in desc and not ui.verbose: continue
1413 1428 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1414 1429 longopt and " --%s" % longopt),
1415 1430 "%s%s" % (desc,
1416 1431 default
1417 1432 and _(" (default: %s)") % default
1418 1433 or "")))
1419 1434
1420 1435 if ui.verbose:
1421 1436 ui.write(_("\nspecial help topics:\n"))
1422 1437 topics = []
1423 1438 for names, header, doc in help.helptable:
1424 1439 topics.append((", ".join(names), header))
1425 1440 topics_len = max([len(s[0]) for s in topics])
1426 1441 for t, desc in topics:
1427 1442 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1428 1443
1429 1444 if opt_output:
1430 1445 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1431 1446 for first, second in opt_output:
1432 1447 if second:
1433 1448 ui.write(" %-*s %s\n" % (opts_len, first, second))
1434 1449 else:
1435 1450 ui.write("%s\n" % first)
1436 1451
1437 1452 def identify(ui, repo, source=None,
1438 1453 rev=None, num=None, id=None, branch=None, tags=None):
1439 1454 """identify the working copy or specified revision
1440 1455
1441 1456 With no revision, print a summary of the current state of the repo.
1442 1457
1443 1458 With a path, do a lookup in another repository.
1444 1459
1445 1460 This summary identifies the repository state using one or two parent
1446 1461 hash identifiers, followed by a "+" if there are uncommitted changes
1447 1462 in the working directory, a list of tags for this revision and a branch
1448 1463 name for non-default branches.
1449 1464 """
1450 1465
1451 1466 if not repo and not source:
1452 1467 raise util.Abort(_("There is no Mercurial repository here "
1453 1468 "(.hg not found)"))
1454 1469
1455 1470 hexfunc = ui.debugflag and hex or short
1456 1471 default = not (num or id or branch or tags)
1457 1472 output = []
1458 1473
1459 1474 if source:
1460 1475 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1461 1476 srepo = hg.repository(ui, source)
1462 1477 if not rev and revs:
1463 1478 rev = revs[0]
1464 1479 if not rev:
1465 1480 rev = "tip"
1466 1481 if num or branch or tags:
1467 1482 raise util.Abort(
1468 1483 "can't query remote revision number, branch, or tags")
1469 1484 output = [hexfunc(srepo.lookup(rev))]
1470 1485 elif not rev:
1471 1486 ctx = repo[None]
1472 1487 parents = ctx.parents()
1473 1488 changed = False
1474 1489 if default or id or num:
1475 1490 changed = ctx.files() + ctx.deleted()
1476 1491 if default or id:
1477 1492 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1478 1493 (changed) and "+" or "")]
1479 1494 if num:
1480 1495 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1481 1496 (changed) and "+" or ""))
1482 1497 else:
1483 1498 ctx = repo[rev]
1484 1499 if default or id:
1485 1500 output = [hexfunc(ctx.node())]
1486 1501 if num:
1487 1502 output.append(str(ctx.rev()))
1488 1503
1489 1504 if not source and default and not ui.quiet:
1490 1505 b = util.tolocal(ctx.branch())
1491 1506 if b != 'default':
1492 1507 output.append("(%s)" % b)
1493 1508
1494 1509 # multiple tags for a single parent separated by '/'
1495 1510 t = "/".join(ctx.tags())
1496 1511 if t:
1497 1512 output.append(t)
1498 1513
1499 1514 if branch:
1500 1515 output.append(util.tolocal(ctx.branch()))
1501 1516
1502 1517 if tags:
1503 1518 output.extend(ctx.tags())
1504 1519
1505 1520 ui.write("%s\n" % ' '.join(output))
1506 1521
1507 1522 def import_(ui, repo, patch1, *patches, **opts):
1508 1523 """import an ordered set of patches
1509 1524
1510 1525 Import a list of patches and commit them individually.
1511 1526
1512 1527 If there are outstanding changes in the working directory, import
1513 1528 will abort unless given the -f flag.
1514 1529
1515 1530 You can import a patch straight from a mail message. Even patches
1516 1531 as attachments work (body part must be type text/plain or
1517 1532 text/x-patch to be used). From and Subject headers of email
1518 1533 message are used as default committer and commit message. All
1519 1534 text/plain body parts before first diff are added to commit
1520 1535 message.
1521 1536
1522 1537 If the imported patch was generated by hg export, user and description
1523 1538 from patch override values from message headers and body. Values
1524 1539 given on command line with -m and -u override these.
1525 1540
1526 1541 If --exact is specified, import will set the working directory
1527 1542 to the parent of each patch before applying it, and will abort
1528 1543 if the resulting changeset has a different ID than the one
1529 1544 recorded in the patch. This may happen due to character set
1530 1545 problems or other deficiencies in the text patch format.
1531 1546
1532 1547 To read a patch from standard input, use patch name "-".
1533 1548 See 'hg help dates' for a list of formats valid for -d/--date.
1534 1549 """
1535 1550 patches = (patch1,) + patches
1536 1551
1537 1552 date = opts.get('date')
1538 1553 if date:
1539 1554 opts['date'] = util.parsedate(date)
1540 1555
1541 if opts.get('exact') or not opts['force']:
1556 if opts.get('exact') or not opts.get('force'):
1542 1557 cmdutil.bail_if_changed(repo)
1543 1558
1544 1559 d = opts["base"]
1545 1560 strip = opts["strip"]
1546 1561 wlock = lock = None
1547 1562 try:
1548 1563 wlock = repo.wlock()
1549 1564 lock = repo.lock()
1550 1565 for p in patches:
1551 1566 pf = os.path.join(d, p)
1552 1567
1553 1568 if pf == '-':
1554 1569 ui.status(_("applying patch from stdin\n"))
1555 1570 data = patch.extract(ui, sys.stdin)
1556 1571 else:
1557 1572 ui.status(_("applying %s\n") % p)
1558 1573 if os.path.exists(pf):
1559 1574 data = patch.extract(ui, file(pf, 'rb'))
1560 1575 else:
1561 1576 data = patch.extract(ui, urllib.urlopen(pf))
1562 1577 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1563 1578
1564 1579 if tmpname is None:
1565 1580 raise util.Abort(_('no diffs found'))
1566 1581
1567 1582 try:
1568 1583 cmdline_message = cmdutil.logmessage(opts)
1569 1584 if cmdline_message:
1570 1585 # pickup the cmdline msg
1571 1586 message = cmdline_message
1572 1587 elif message:
1573 1588 # pickup the patch msg
1574 1589 message = message.strip()
1575 1590 else:
1576 1591 # launch the editor
1577 1592 message = None
1578 1593 ui.debug(_('message:\n%s\n') % message)
1579 1594
1580 1595 wp = repo.parents()
1581 1596 if opts.get('exact'):
1582 1597 if not nodeid or not p1:
1583 1598 raise util.Abort(_('not a mercurial patch'))
1584 1599 p1 = repo.lookup(p1)
1585 1600 p2 = repo.lookup(p2 or hex(nullid))
1586 1601
1587 1602 if p1 != wp[0].node():
1588 1603 hg.clean(repo, p1)
1589 1604 repo.dirstate.setparents(p1, p2)
1590 1605 elif p2:
1591 1606 try:
1592 1607 p1 = repo.lookup(p1)
1593 1608 p2 = repo.lookup(p2)
1594 1609 if p1 == wp[0].node():
1595 1610 repo.dirstate.setparents(p1, p2)
1596 1611 except RepoError:
1597 1612 pass
1598 1613 if opts.get('exact') or opts.get('import_branch'):
1599 1614 repo.dirstate.setbranch(branch or 'default')
1600 1615
1601 1616 files = {}
1602 1617 try:
1603 1618 fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1604 1619 files=files)
1605 1620 finally:
1606 1621 files = patch.updatedir(ui, repo, files)
1607 1622 if not opts.get('no_commit'):
1608 1623 n = repo.commit(files, message, opts.get('user') or user,
1609 1624 opts.get('date') or date)
1610 1625 if opts.get('exact'):
1611 1626 if hex(n) != nodeid:
1612 1627 repo.rollback()
1613 1628 raise util.Abort(_('patch is damaged'
1614 1629 ' or loses information'))
1615 1630 # Force a dirstate write so that the next transaction
1616 1631 # backups an up-do-date file.
1617 1632 repo.dirstate.write()
1618 1633 finally:
1619 1634 os.unlink(tmpname)
1620 1635 finally:
1621 1636 del lock, wlock
1622 1637
1623 1638 def incoming(ui, repo, source="default", **opts):
1624 1639 """show new changesets found in source
1625 1640
1626 1641 Show new changesets found in the specified path/URL or the default
1627 1642 pull location. These are the changesets that would be pulled if a pull
1628 1643 was requested.
1629 1644
1630 1645 For remote repository, using --bundle avoids downloading the changesets
1631 1646 twice if the incoming is followed by a pull.
1632 1647
1633 1648 See pull for valid source format details.
1634 1649 """
1635 1650 limit = cmdutil.loglimit(opts)
1636 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
1651 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
1637 1652 cmdutil.setremoteconfig(ui, opts)
1638 1653
1639 1654 other = hg.repository(ui, source)
1640 1655 ui.status(_('comparing with %s\n') % util.hidepassword(source))
1641 1656 if revs:
1642 1657 revs = [other.lookup(rev) for rev in revs]
1643 1658 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
1644 1659 if not incoming:
1645 1660 try:
1646 1661 os.unlink(opts["bundle"])
1647 1662 except:
1648 1663 pass
1649 1664 ui.status(_("no changes found\n"))
1650 1665 return 1
1651 1666
1652 1667 cleanup = None
1653 1668 try:
1654 1669 fname = opts["bundle"]
1655 1670 if fname or not other.local():
1656 1671 # create a bundle (uncompressed if other repo is not local)
1657 1672 if revs is None:
1658 1673 cg = other.changegroup(incoming, "incoming")
1659 1674 else:
1660 1675 cg = other.changegroupsubset(incoming, revs, 'incoming')
1661 1676 bundletype = other.local() and "HG10BZ" or "HG10UN"
1662 1677 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1663 1678 # keep written bundle?
1664 1679 if opts["bundle"]:
1665 1680 cleanup = None
1666 1681 if not other.local():
1667 1682 # use the created uncompressed bundlerepo
1668 1683 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1669 1684
1670 1685 o = other.changelog.nodesbetween(incoming, revs)[0]
1671 if opts['newest_first']:
1686 if opts.get('newest_first'):
1672 1687 o.reverse()
1673 1688 displayer = cmdutil.show_changeset(ui, other, opts)
1674 1689 count = 0
1675 1690 for n in o:
1676 1691 if count >= limit:
1677 1692 break
1678 1693 parents = [p for p in other.changelog.parents(n) if p != nullid]
1679 if opts['no_merges'] and len(parents) == 2:
1694 if opts.get('no_merges') and len(parents) == 2:
1680 1695 continue
1681 1696 count += 1
1682 1697 displayer.show(changenode=n)
1683 1698 finally:
1684 1699 if hasattr(other, 'close'):
1685 1700 other.close()
1686 1701 if cleanup:
1687 1702 os.unlink(cleanup)
1688 1703
1689 1704 def init(ui, dest=".", **opts):
1690 1705 """create a new repository in the given directory
1691 1706
1692 1707 Initialize a new repository in the given directory. If the given
1693 1708 directory does not exist, it is created.
1694 1709
1695 1710 If no directory is given, the current directory is used.
1696 1711
1697 1712 It is possible to specify an ssh:// URL as the destination.
1698 1713 Look at the help text for the pull command for important details
1699 1714 about ssh:// URLs.
1700 1715 """
1701 1716 cmdutil.setremoteconfig(ui, opts)
1702 1717 hg.repository(ui, dest, create=1)
1703 1718
1704 1719 def locate(ui, repo, *pats, **opts):
1705 1720 """locate files matching specific patterns
1706 1721
1707 1722 Print all files under Mercurial control whose names match the
1708 1723 given patterns.
1709 1724
1710 1725 This command searches the entire repository by default. To search
1711 1726 just the current directory and its subdirectories, use
1712 1727 "--include .".
1713 1728
1714 1729 If no patterns are given to match, this command prints all file
1715 1730 names.
1716 1731
1717 1732 If you want to feed the output of this command into the "xargs"
1718 1733 command, use the "-0" option to both this command and "xargs".
1719 1734 This will avoid the problem of "xargs" treating single filenames
1720 1735 that contain white space as multiple filenames.
1721 1736 """
1722 end = opts['print0'] and '\0' or '\n'
1737 end = opts.get('print0') and '\0' or '\n'
1723 1738 rev = opts.get('rev') or None
1724 1739
1725 1740 ret = 1
1726 1741 m = cmdutil.match(repo, pats, opts, default='relglob')
1727 1742 m.bad = lambda x,y: False
1728 1743 for abs in repo[rev].walk(m):
1729 1744 if not rev and abs not in repo.dirstate:
1730 1745 continue
1731 if opts['fullpath']:
1746 if opts.get('fullpath'):
1732 1747 ui.write(os.path.join(repo.root, abs), end)
1733 1748 else:
1734 1749 ui.write(((pats and m.rel(abs)) or abs), end)
1735 1750 ret = 0
1736 1751
1737 1752 return ret
1738 1753
1739 1754 def log(ui, repo, *pats, **opts):
1740 1755 """show revision history of entire repository or files
1741 1756
1742 1757 Print the revision history of the specified files or the entire
1743 1758 project.
1744 1759
1745 1760 File history is shown without following rename or copy history of
1746 1761 files. Use -f/--follow with a file name to follow history across
1747 1762 renames and copies. --follow without a file name will only show
1748 1763 ancestors or descendants of the starting revision. --follow-first
1749 1764 only follows the first parent of merge revisions.
1750 1765
1751 1766 If no revision range is specified, the default is tip:0 unless
1752 1767 --follow is set, in which case the working directory parent is
1753 1768 used as the starting revision.
1754 1769
1755 1770 See 'hg help dates' for a list of formats valid for -d/--date.
1756 1771
1757 1772 By default this command outputs: changeset id and hash, tags,
1758 1773 non-trivial parents, user, date and time, and a summary for each
1759 1774 commit. When the -v/--verbose switch is used, the list of changed
1760 1775 files and full commit message is shown.
1761 1776
1762 1777 NOTE: log -p may generate unexpected diff output for merge
1763 1778 changesets, as it will compare the merge changeset against its
1764 1779 first parent only. Also, the files: list will only reflect files
1765 1780 that are different from BOTH parents.
1766 1781
1767 1782 """
1768 1783
1769 1784 get = util.cachefunc(lambda r: repo[r].changeset())
1770 1785 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1771 1786
1772 1787 limit = cmdutil.loglimit(opts)
1773 1788 count = 0
1774 1789
1775 if opts['copies'] and opts['rev']:
1776 endrev = max(cmdutil.revrange(repo, opts['rev'])) + 1
1790 if opts.get('copies') and opts.get('rev'):
1791 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
1777 1792 else:
1778 1793 endrev = len(repo)
1779 1794 rcache = {}
1780 1795 ncache = {}
1781 1796 def getrenamed(fn, rev):
1782 1797 '''looks up all renames for a file (up to endrev) the first
1783 1798 time the file is given. It indexes on the changerev and only
1784 1799 parses the manifest if linkrev != changerev.
1785 1800 Returns rename info for fn at changerev rev.'''
1786 1801 if fn not in rcache:
1787 1802 rcache[fn] = {}
1788 1803 ncache[fn] = {}
1789 1804 fl = repo.file(fn)
1790 1805 for i in fl:
1791 1806 node = fl.node(i)
1792 1807 lr = fl.linkrev(node)
1793 1808 renamed = fl.renamed(node)
1794 1809 rcache[fn][lr] = renamed
1795 1810 if renamed:
1796 1811 ncache[fn][node] = renamed
1797 1812 if lr >= endrev:
1798 1813 break
1799 1814 if rev in rcache[fn]:
1800 1815 return rcache[fn][rev]
1801 1816
1802 1817 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1803 1818 # filectx logic.
1804 1819
1805 1820 try:
1806 1821 return repo[rev][fn].renamed()
1807 1822 except revlog.LookupError:
1808 1823 pass
1809 1824 return None
1810 1825
1811 1826 df = False
1812 1827 if opts["date"]:
1813 1828 df = util.matchdate(opts["date"])
1814 1829
1815 only_branches = opts['only_branch']
1830 only_branches = opts.get('only_branch')
1816 1831
1817 1832 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1818 1833 for st, rev, fns in changeiter:
1819 1834 if st == 'add':
1820 1835 changenode = repo.changelog.node(rev)
1821 1836 parents = [p for p in repo.changelog.parentrevs(rev)
1822 1837 if p != nullrev]
1823 if opts['no_merges'] and len(parents) == 2:
1838 if opts.get('no_merges') and len(parents) == 2:
1824 1839 continue
1825 if opts['only_merges'] and len(parents) != 2:
1840 if opts.get('only_merges') and len(parents) != 2:
1826 1841 continue
1827 1842
1828 1843 if only_branches:
1829 1844 revbranch = get(rev)[5]['branch']
1830 1845 if revbranch not in only_branches:
1831 1846 continue
1832 1847
1833 1848 if df:
1834 1849 changes = get(rev)
1835 1850 if not df(changes[2][0]):
1836 1851 continue
1837 1852
1838 if opts['keyword']:
1853 if opts.get('keyword'):
1839 1854 changes = get(rev)
1840 1855 miss = 0
1841 1856 for k in [kw.lower() for kw in opts['keyword']]:
1842 1857 if not (k in changes[1].lower() or
1843 1858 k in changes[4].lower() or
1844 1859 k in " ".join(changes[3]).lower()):
1845 1860 miss = 1
1846 1861 break
1847 1862 if miss:
1848 1863 continue
1849 1864
1850 1865 copies = []
1851 1866 if opts.get('copies') and rev:
1852 1867 for fn in get(rev)[3]:
1853 1868 rename = getrenamed(fn, rev)
1854 1869 if rename:
1855 1870 copies.append((fn, rename[0]))
1856 1871 displayer.show(rev, changenode, copies=copies)
1857 1872 elif st == 'iter':
1858 1873 if count == limit: break
1859 1874 if displayer.flush(rev):
1860 1875 count += 1
1861 1876
1862 1877 def manifest(ui, repo, node=None, rev=None):
1863 1878 """output the current or given revision of the project manifest
1864 1879
1865 1880 Print a list of version controlled files for the given revision.
1866 1881 If no revision is given, the parent of the working directory is used,
1867 1882 or tip if no revision is checked out.
1868 1883
1869 1884 The manifest is the list of files being version controlled. If no revision
1870 1885 is given then the first parent of the working directory is used.
1871 1886
1872 1887 With -v flag, print file permissions, symlink and executable bits. With
1873 1888 --debug flag, print file revision hashes.
1874 1889 """
1875 1890
1876 1891 if rev and node:
1877 1892 raise util.Abort(_("please specify just one revision"))
1878 1893
1879 1894 if not node:
1880 1895 node = rev
1881 1896
1882 1897 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
1883 1898 ctx = repo[node]
1884 1899 for f in ctx:
1885 1900 if ui.debugflag:
1886 1901 ui.write("%40s " % hex(ctx.manifest()[f]))
1887 1902 if ui.verbose:
1888 1903 ui.write(decor[ctx.flags(f)])
1889 1904 ui.write("%s\n" % f)
1890 1905
1891 1906 def merge(ui, repo, node=None, force=None, rev=None):
1892 1907 """merge working directory with another revision
1893 1908
1894 1909 Merge the contents of the current working directory and the
1895 1910 requested revision. Files that changed between either parent are
1896 1911 marked as changed for the next commit and a commit must be
1897 1912 performed before any further updates are allowed.
1898 1913
1899 1914 If no revision is specified, the working directory's parent is a
1900 1915 head revision, and the current branch contains exactly one other head,
1901 1916 the other head is merged with by default. Otherwise, an explicit
1902 1917 revision to merge with must be provided.
1903 1918 """
1904 1919
1905 1920 if rev and node:
1906 1921 raise util.Abort(_("please specify just one revision"))
1907 1922 if not node:
1908 1923 node = rev
1909 1924
1910 1925 if not node:
1911 1926 branch = repo.changectx(None).branch()
1912 1927 bheads = repo.branchheads(branch)
1913 1928 if len(bheads) > 2:
1914 1929 raise util.Abort(_("branch '%s' has %d heads - "
1915 1930 "please merge with an explicit rev") %
1916 1931 (branch, len(bheads)))
1917 1932
1918 1933 parent = repo.dirstate.parents()[0]
1919 1934 if len(bheads) == 1:
1920 1935 if len(repo.heads()) > 1:
1921 1936 raise util.Abort(_("branch '%s' has one head - "
1922 1937 "please merge with an explicit rev") %
1923 1938 branch)
1924 1939 msg = _('there is nothing to merge')
1925 1940 if parent != repo.lookup(repo[None].branch()):
1926 1941 msg = _('%s - use "hg update" instead') % msg
1927 1942 raise util.Abort(msg)
1928 1943
1929 1944 if parent not in bheads:
1930 1945 raise util.Abort(_('working dir not at a head rev - '
1931 1946 'use "hg update" or merge with an explicit rev'))
1932 1947 node = parent == bheads[0] and bheads[-1] or bheads[0]
1933 1948 return hg.merge(repo, node, force=force)
1934 1949
1935 1950 def outgoing(ui, repo, dest=None, **opts):
1936 1951 """show changesets not found in destination
1937 1952
1938 1953 Show changesets not found in the specified destination repository or
1939 1954 the default push location. These are the changesets that would be pushed
1940 1955 if a push was requested.
1941 1956
1942 1957 See pull for valid destination format details.
1943 1958 """
1944 1959 limit = cmdutil.loglimit(opts)
1945 1960 dest, revs, checkout = hg.parseurl(
1946 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
1961 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
1947 1962 cmdutil.setremoteconfig(ui, opts)
1948 1963 if revs:
1949 1964 revs = [repo.lookup(rev) for rev in revs]
1950 1965
1951 1966 other = hg.repository(ui, dest)
1952 1967 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
1953 o = repo.findoutgoing(other, force=opts['force'])
1968 o = repo.findoutgoing(other, force=opts.get('force'))
1954 1969 if not o:
1955 1970 ui.status(_("no changes found\n"))
1956 1971 return 1
1957 1972 o = repo.changelog.nodesbetween(o, revs)[0]
1958 if opts['newest_first']:
1973 if opts.get('newest_first'):
1959 1974 o.reverse()
1960 1975 displayer = cmdutil.show_changeset(ui, repo, opts)
1961 1976 count = 0
1962 1977 for n in o:
1963 1978 if count >= limit:
1964 1979 break
1965 1980 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1966 if opts['no_merges'] and len(parents) == 2:
1981 if opts.get('no_merges') and len(parents) == 2:
1967 1982 continue
1968 1983 count += 1
1969 1984 displayer.show(changenode=n)
1970 1985
1971 1986 def parents(ui, repo, file_=None, **opts):
1972 1987 """show the parents of the working dir or revision
1973 1988
1974 1989 Print the working directory's parent revisions. If a
1975 1990 revision is given via --rev, the parent of that revision
1976 1991 will be printed. If a file argument is given, revision in
1977 1992 which the file was last changed (before the working directory
1978 1993 revision or the argument to --rev if given) is printed.
1979 1994 """
1980 1995 rev = opts.get('rev')
1981 1996 if rev:
1982 1997 ctx = repo[rev]
1983 1998 else:
1984 1999 ctx = repo[None]
1985 2000
1986 2001 if file_:
1987 2002 m = cmdutil.match(repo, (file_,), opts)
1988 2003 if m.anypats() or len(m.files()) != 1:
1989 2004 raise util.Abort(_('can only specify an explicit file name'))
1990 2005 file_ = m.files()[0]
1991 2006 filenodes = []
1992 2007 for cp in ctx.parents():
1993 2008 if not cp:
1994 2009 continue
1995 2010 try:
1996 2011 filenodes.append(cp.filenode(file_))
1997 2012 except revlog.LookupError:
1998 2013 pass
1999 2014 if not filenodes:
2000 2015 raise util.Abort(_("'%s' not found in manifest!") % file_)
2001 2016 fl = repo.file(file_)
2002 2017 p = [repo.lookup(fl.linkrev(fn)) for fn in filenodes]
2003 2018 else:
2004 2019 p = [cp.node() for cp in ctx.parents()]
2005 2020
2006 2021 displayer = cmdutil.show_changeset(ui, repo, opts)
2007 2022 for n in p:
2008 2023 if n != nullid:
2009 2024 displayer.show(changenode=n)
2010 2025
2011 2026 def paths(ui, repo, search=None):
2012 2027 """show definition of symbolic path names
2013 2028
2014 2029 Show definition of symbolic path name NAME. If no name is given, show
2015 2030 definition of available names.
2016 2031
2017 2032 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2018 2033 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2019 2034 """
2020 2035 if search:
2021 2036 for name, path in ui.configitems("paths"):
2022 2037 if name == search:
2023 2038 ui.write("%s\n" % util.hidepassword(path))
2024 2039 return
2025 2040 ui.warn(_("not found!\n"))
2026 2041 return 1
2027 2042 else:
2028 2043 for name, path in ui.configitems("paths"):
2029 2044 ui.write("%s = %s\n" % (name, util.hidepassword(path)))
2030 2045
2031 2046 def postincoming(ui, repo, modheads, optupdate, checkout):
2032 2047 if modheads == 0:
2033 2048 return
2034 2049 if optupdate:
2035 2050 if modheads <= 1 or checkout:
2036 2051 return hg.update(repo, checkout)
2037 2052 else:
2038 2053 ui.status(_("not updating, since new heads added\n"))
2039 2054 if modheads > 1:
2040 2055 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2041 2056 else:
2042 2057 ui.status(_("(run 'hg update' to get a working copy)\n"))
2043 2058
2044 2059 def pull(ui, repo, source="default", **opts):
2045 2060 """pull changes from the specified source
2046 2061
2047 2062 Pull changes from a remote repository to a local one.
2048 2063
2049 2064 This finds all changes from the repository at the specified path
2050 2065 or URL and adds them to the local repository. By default, this
2051 2066 does not update the copy of the project in the working directory.
2052 2067
2053 2068 Valid URLs are of the form:
2054 2069
2055 2070 local/filesystem/path (or file://local/filesystem/path)
2056 2071 http://[user[:pass]@]host[:port]/[path]
2057 2072 https://[user[:pass]@]host[:port]/[path]
2058 2073 ssh://[user[:pass]@]host[:port]/[path]
2059 2074 static-http://host[:port]/[path]
2060 2075
2061 2076 Paths in the local filesystem can either point to Mercurial
2062 2077 repositories or to bundle files (as created by 'hg bundle' or
2063 2078 'hg incoming --bundle'). The static-http:// protocol, albeit slow,
2064 2079 allows access to a Mercurial repository where you simply use a web
2065 2080 server to publish the .hg directory as static content.
2066 2081
2067 2082 An optional identifier after # indicates a particular branch, tag,
2068 2083 or changeset to pull.
2069 2084
2070 2085 Some notes about using SSH with Mercurial:
2071 2086 - SSH requires an accessible shell account on the destination machine
2072 2087 and a copy of hg in the remote path or specified with as remotecmd.
2073 2088 - path is relative to the remote user's home directory by default.
2074 2089 Use an extra slash at the start of a path to specify an absolute path:
2075 2090 ssh://example.com//tmp/repository
2076 2091 - Mercurial doesn't use its own compression via SSH; the right thing
2077 2092 to do is to configure it in your ~/.ssh/config, e.g.:
2078 2093 Host *.mylocalnetwork.example.com
2079 2094 Compression no
2080 2095 Host *
2081 2096 Compression yes
2082 2097 Alternatively specify "ssh -C" as your ssh command in your hgrc or
2083 2098 with the --ssh command line option.
2084 2099 """
2085 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts['rev'])
2100 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
2086 2101 cmdutil.setremoteconfig(ui, opts)
2087 2102
2088 2103 other = hg.repository(ui, source)
2089 2104 ui.status(_('pulling from %s\n') % util.hidepassword(source))
2090 2105 if revs:
2091 2106 try:
2092 2107 revs = [other.lookup(rev) for rev in revs]
2093 2108 except NoCapability:
2094 2109 error = _("Other repository doesn't support revision lookup, "
2095 2110 "so a rev cannot be specified.")
2096 2111 raise util.Abort(error)
2097 2112
2098 modheads = repo.pull(other, heads=revs, force=opts['force'])
2099 return postincoming(ui, repo, modheads, opts['update'], checkout)
2113 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2114 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2100 2115
2101 2116 def push(ui, repo, dest=None, **opts):
2102 2117 """push changes to the specified destination
2103 2118
2104 2119 Push changes from the local repository to the given destination.
2105 2120
2106 2121 This is the symmetrical operation for pull. It helps to move
2107 2122 changes from the current repository to a different one. If the
2108 2123 destination is local this is identical to a pull in that directory
2109 2124 from the current one.
2110 2125
2111 2126 By default, push will refuse to run if it detects the result would
2112 2127 increase the number of remote heads. This generally indicates the
2113 2128 the client has forgotten to pull and merge before pushing.
2114 2129
2115 2130 Valid URLs are of the form:
2116 2131
2117 2132 local/filesystem/path (or file://local/filesystem/path)
2118 2133 ssh://[user[:pass]@]host[:port]/[path]
2119 2134 http://[user[:pass]@]host[:port]/[path]
2120 2135 https://[user[:pass]@]host[:port]/[path]
2121 2136
2122 2137 An optional identifier after # indicates a particular branch, tag,
2123 2138 or changeset to push. If -r is used, the named changeset and all its
2124 2139 ancestors will be pushed to the remote repository.
2125 2140
2126 2141 Look at the help text for the pull command for important details
2127 2142 about ssh:// URLs.
2128 2143
2129 2144 Pushing to http:// and https:// URLs is only possible, if this
2130 2145 feature is explicitly enabled on the remote Mercurial server.
2131 2146 """
2132 2147 dest, revs, checkout = hg.parseurl(
2133 ui.expandpath(dest or 'default-push', dest or 'default'), opts['rev'])
2148 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
2134 2149 cmdutil.setremoteconfig(ui, opts)
2135 2150
2136 2151 other = hg.repository(ui, dest)
2137 2152 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
2138 2153 if revs:
2139 2154 revs = [repo.lookup(rev) for rev in revs]
2140 r = repo.push(other, opts['force'], revs=revs)
2155 r = repo.push(other, opts.get('force'), revs=revs)
2141 2156 return r == 0
2142 2157
2143 2158 def rawcommit(ui, repo, *pats, **opts):
2144 2159 """raw commit interface (DEPRECATED)
2145 2160
2146 2161 (DEPRECATED)
2147 2162 Lowlevel commit, for use in helper scripts.
2148 2163
2149 2164 This command is not intended to be used by normal users, as it is
2150 2165 primarily useful for importing from other SCMs.
2151 2166
2152 2167 This command is now deprecated and will be removed in a future
2153 2168 release, please use debugsetparents and commit instead.
2154 2169 """
2155 2170
2156 2171 ui.warn(_("(the rawcommit command is deprecated)\n"))
2157 2172
2158 2173 message = cmdutil.logmessage(opts)
2159 2174
2160 2175 files = cmdutil.match(repo, pats, opts).files()
2161 if opts['files']:
2176 if opts.get('files'):
2162 2177 files += open(opts['files']).read().splitlines()
2163 2178
2164 2179 parents = [repo.lookup(p) for p in opts['parent']]
2165 2180
2166 2181 try:
2167 2182 repo.rawcommit(files, message, opts['user'], opts['date'], *parents)
2168 2183 except ValueError, inst:
2169 2184 raise util.Abort(str(inst))
2170 2185
2171 2186 def recover(ui, repo):
2172 2187 """roll back an interrupted transaction
2173 2188
2174 2189 Recover from an interrupted commit or pull.
2175 2190
2176 2191 This command tries to fix the repository status after an interrupted
2177 2192 operation. It should only be necessary when Mercurial suggests it.
2178 2193 """
2179 2194 if repo.recover():
2180 2195 return hg.verify(repo)
2181 2196 return 1
2182 2197
2183 2198 def remove(ui, repo, *pats, **opts):
2184 2199 """remove the specified files on the next commit
2185 2200
2186 2201 Schedule the indicated files for removal from the repository.
2187 2202
2188 2203 This only removes files from the current branch, not from the entire
2189 2204 project history. -A can be used to remove only files that have already
2190 2205 been deleted, -f can be used to force deletion, and -Af can be used
2191 2206 to remove files from the next revision without deleting them.
2192 2207
2193 2208 The following table details the behavior of remove for different file
2194 2209 states (columns) and option combinations (rows). The file states are
2195 2210 Added, Clean, Modified and Missing (as reported by hg status). The
2196 2211 actions are Warn, Remove (from branch) and Delete (from disk).
2197 2212
2198 2213 A C M !
2199 2214 none W RD W R
2200 2215 -f R RD RD R
2201 2216 -A W W W R
2202 2217 -Af R R R R
2203 2218
2204 2219 This command schedules the files to be removed at the next commit.
2205 2220 To undo a remove before that, see hg revert.
2206 2221 """
2207 2222
2208 2223 after, force = opts.get('after'), opts.get('force')
2209 2224 if not pats and not after:
2210 2225 raise util.Abort(_('no files specified'))
2211 2226
2212 2227 m = cmdutil.match(repo, pats, opts)
2213 2228 s = repo.status(match=m, clean=True)
2214 2229 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2215 2230
2216 2231 def warn(files, reason):
2217 2232 for f in files:
2218 2233 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2219 2234 % (m.rel(f), reason))
2220 2235
2221 2236 if force:
2222 2237 remove, forget = modified + deleted + clean, added
2223 2238 elif after:
2224 2239 remove, forget = deleted, []
2225 2240 warn(modified + added + clean, _('still exists'))
2226 2241 else:
2227 2242 remove, forget = deleted + clean, []
2228 2243 warn(modified, _('is modified'))
2229 2244 warn(added, _('has been marked for add'))
2230 2245
2231 2246 for f in util.sort(remove + forget):
2232 2247 if ui.verbose or not m.exact(f):
2233 2248 ui.status(_('removing %s\n') % m.rel(f))
2234 2249
2235 2250 repo.forget(forget)
2236 2251 repo.remove(remove, unlink=not after)
2237 2252
2238 2253 def rename(ui, repo, *pats, **opts):
2239 2254 """rename files; equivalent of copy + remove
2240 2255
2241 2256 Mark dest as copies of sources; mark sources for deletion. If
2242 2257 dest is a directory, copies are put in that directory. If dest is
2243 2258 a file, there can only be one source.
2244 2259
2245 2260 By default, this command copies the contents of files as they
2246 2261 stand in the working directory. If invoked with --after, the
2247 2262 operation is recorded, but no copying is performed.
2248 2263
2249 2264 This command takes effect in the next commit. To undo a rename
2250 2265 before that, see hg revert.
2251 2266 """
2252 2267 wlock = repo.wlock(False)
2253 2268 try:
2254 2269 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2255 2270 finally:
2256 2271 del wlock
2257 2272
2258 2273 def resolve(ui, repo, *pats, **opts):
2259 2274 """resolve file merges from a branch merge or update
2260 2275
2261 2276 This command will attempt to resolve unresolved merges from the
2262 2277 last update or merge command. This will use the local file
2263 2278 revision preserved at the last update or merge to cleanly retry
2264 2279 the file merge attempt. With no file or options specified, this
2265 2280 command will attempt to resolve all unresolved files.
2266 2281
2267 2282 The codes used to show the status of files are:
2268 2283 U = unresolved
2269 2284 R = resolved
2270 2285 """
2271 2286
2272 2287 if len([x for x in opts if opts[x]]) > 1:
2273 2288 raise util.Abort(_("too many options specified"))
2274 2289
2275 2290 ms = merge_.mergestate(repo)
2276 2291 m = cmdutil.match(repo, pats, opts)
2277 2292
2278 2293 for f in ms:
2279 2294 if m(f):
2280 2295 if opts.get("list"):
2281 2296 ui.write("%s %s\n" % (ms[f].upper(), f))
2282 2297 elif opts.get("mark"):
2283 2298 ms.mark(f, "r")
2284 2299 elif opts.get("unmark"):
2285 2300 ms.mark(f, "u")
2286 2301 else:
2287 2302 wctx = repo[None]
2288 2303 mctx = wctx.parents()[-1]
2289 2304 ms.resolve(f, wctx, mctx)
2290 2305
2291 2306 def revert(ui, repo, *pats, **opts):
2292 2307 """restore individual files or dirs to an earlier state
2293 2308
2294 2309 (use update -r to check out earlier revisions, revert does not
2295 2310 change the working dir parents)
2296 2311
2297 2312 With no revision specified, revert the named files or directories
2298 2313 to the contents they had in the parent of the working directory.
2299 2314 This restores the contents of the affected files to an unmodified
2300 2315 state and unschedules adds, removes, copies, and renames. If the
2301 2316 working directory has two parents, you must explicitly specify the
2302 2317 revision to revert to.
2303 2318
2304 2319 Using the -r option, revert the given files or directories to their
2305 2320 contents as of a specific revision. This can be helpful to "roll
2306 2321 back" some or all of an earlier change.
2307 2322 See 'hg help dates' for a list of formats valid for -d/--date.
2308 2323
2309 2324 Revert modifies the working directory. It does not commit any
2310 2325 changes, or change the parent of the working directory. If you
2311 2326 revert to a revision other than the parent of the working
2312 2327 directory, the reverted files will thus appear modified
2313 2328 afterwards.
2314 2329
2315 2330 If a file has been deleted, it is restored. If the executable
2316 2331 mode of a file was changed, it is reset.
2317 2332
2318 2333 If names are given, all files matching the names are reverted.
2319 2334 If no arguments are given, no files are reverted.
2320 2335
2321 2336 Modified files are saved with a .orig suffix before reverting.
2322 2337 To disable these backups, use --no-backup.
2323 2338 """
2324 2339
2325 2340 if opts["date"]:
2326 2341 if opts["rev"]:
2327 2342 raise util.Abort(_("you can't specify a revision and a date"))
2328 2343 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2329 2344
2330 if not pats and not opts['all']:
2345 if not pats and not opts.get('all'):
2331 2346 raise util.Abort(_('no files or directories specified; '
2332 2347 'use --all to revert the whole repo'))
2333 2348
2334 2349 parent, p2 = repo.dirstate.parents()
2335 if not opts['rev'] and p2 != nullid:
2350 if not opts.get('rev') and p2 != nullid:
2336 2351 raise util.Abort(_('uncommitted merge - please provide a '
2337 2352 'specific revision'))
2338 ctx = repo[opts['rev']]
2353 ctx = repo[opts.get('rev')]
2339 2354 node = ctx.node()
2340 2355 mf = ctx.manifest()
2341 2356 if node == parent:
2342 2357 pmf = mf
2343 2358 else:
2344 2359 pmf = None
2345 2360
2346 2361 # need all matching names in dirstate and manifest of target rev,
2347 2362 # so have to walk both. do not print errors if files exist in one
2348 2363 # but not other.
2349 2364
2350 2365 names = {}
2351 2366
2352 2367 wlock = repo.wlock()
2353 2368 try:
2354 2369 # walk dirstate.
2355 2370 files = []
2356 2371
2357 2372 m = cmdutil.match(repo, pats, opts)
2358 2373 m.bad = lambda x,y: False
2359 2374 for abs in repo.walk(m):
2360 2375 names[abs] = m.rel(abs), m.exact(abs)
2361 2376
2362 2377 # walk target manifest.
2363 2378
2364 2379 def badfn(path, msg):
2365 2380 if path in names:
2366 2381 return False
2367 2382 path_ = path + '/'
2368 2383 for f in names:
2369 2384 if f.startswith(path_):
2370 2385 return False
2371 2386 repo.ui.warn("%s: %s\n" % (m.rel(path), msg))
2372 2387 return False
2373 2388
2374 2389 m = cmdutil.match(repo, pats, opts)
2375 2390 m.bad = badfn
2376 2391 for abs in repo[node].walk(m):
2377 2392 if abs not in names:
2378 2393 names[abs] = m.rel(abs), m.exact(abs)
2379 2394
2380 2395 m = cmdutil.matchfiles(repo, names)
2381 2396 changes = repo.status(match=m)[:4]
2382 2397 modified, added, removed, deleted = map(dict.fromkeys, changes)
2383 2398
2384 2399 # if f is a rename, also revert the source
2385 2400 cwd = repo.getcwd()
2386 2401 for f in added:
2387 2402 src = repo.dirstate.copied(f)
2388 2403 if src and src not in names and repo.dirstate[src] == 'r':
2389 2404 removed[src] = None
2390 2405 names[src] = (repo.pathto(src, cwd), True)
2391 2406
2392 2407 def removeforget(abs):
2393 2408 if repo.dirstate[abs] == 'a':
2394 2409 return _('forgetting %s\n')
2395 2410 return _('removing %s\n')
2396 2411
2397 2412 revert = ([], _('reverting %s\n'))
2398 2413 add = ([], _('adding %s\n'))
2399 2414 remove = ([], removeforget)
2400 2415 undelete = ([], _('undeleting %s\n'))
2401 2416
2402 2417 disptable = (
2403 2418 # dispatch table:
2404 2419 # file state
2405 2420 # action if in target manifest
2406 2421 # action if not in target manifest
2407 2422 # make backup if in target manifest
2408 2423 # make backup if not in target manifest
2409 2424 (modified, revert, remove, True, True),
2410 2425 (added, revert, remove, True, False),
2411 2426 (removed, undelete, None, False, False),
2412 2427 (deleted, revert, remove, False, False),
2413 2428 )
2414 2429
2415 2430 for abs, (rel, exact) in util.sort(names.items()):
2416 2431 mfentry = mf.get(abs)
2417 2432 target = repo.wjoin(abs)
2418 2433 def handle(xlist, dobackup):
2419 2434 xlist[0].append(abs)
2420 if dobackup and not opts['no_backup'] and util.lexists(target):
2435 if dobackup and not opts.get('no_backup') and util.lexists(target):
2421 2436 bakname = "%s.orig" % rel
2422 2437 ui.note(_('saving current version of %s as %s\n') %
2423 2438 (rel, bakname))
2424 2439 if not opts.get('dry_run'):
2425 2440 util.copyfile(target, bakname)
2426 2441 if ui.verbose or not exact:
2427 2442 msg = xlist[1]
2428 2443 if not isinstance(msg, basestring):
2429 2444 msg = msg(abs)
2430 2445 ui.status(msg % rel)
2431 2446 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2432 2447 if abs not in table: continue
2433 2448 # file has changed in dirstate
2434 2449 if mfentry:
2435 2450 handle(hitlist, backuphit)
2436 2451 elif misslist is not None:
2437 2452 handle(misslist, backupmiss)
2438 2453 break
2439 2454 else:
2440 2455 if abs not in repo.dirstate:
2441 2456 if mfentry:
2442 2457 handle(add, True)
2443 2458 elif exact:
2444 2459 ui.warn(_('file not managed: %s\n') % rel)
2445 2460 continue
2446 2461 # file has not changed in dirstate
2447 2462 if node == parent:
2448 2463 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2449 2464 continue
2450 2465 if pmf is None:
2451 2466 # only need parent manifest in this unlikely case,
2452 2467 # so do not read by default
2453 2468 pmf = repo[parent].manifest()
2454 2469 if abs in pmf:
2455 2470 if mfentry:
2456 2471 # if version of file is same in parent and target
2457 2472 # manifests, do nothing
2458 2473 if (pmf[abs] != mfentry or
2459 2474 pmf.flags(abs) != mf.flags(abs)):
2460 2475 handle(revert, False)
2461 2476 else:
2462 2477 handle(remove, False)
2463 2478
2464 2479 if not opts.get('dry_run'):
2465 2480 def checkout(f):
2466 2481 fc = ctx[f]
2467 2482 repo.wwrite(f, fc.data(), fc.flags())
2468 2483
2469 2484 audit_path = util.path_auditor(repo.root)
2470 2485 for f in remove[0]:
2471 2486 if repo.dirstate[f] == 'a':
2472 2487 repo.dirstate.forget(f)
2473 2488 continue
2474 2489 audit_path(f)
2475 2490 try:
2476 2491 util.unlink(repo.wjoin(f))
2477 2492 except OSError:
2478 2493 pass
2479 2494 repo.dirstate.remove(f)
2480 2495
2481 2496 normal = None
2482 2497 if node == parent:
2483 2498 # We're reverting to our parent. If possible, we'd like status
2484 2499 # to report the file as clean. We have to use normallookup for
2485 2500 # merges to avoid losing information about merged/dirty files.
2486 2501 if p2 != nullid:
2487 2502 normal = repo.dirstate.normallookup
2488 2503 else:
2489 2504 normal = repo.dirstate.normal
2490 2505 for f in revert[0]:
2491 2506 checkout(f)
2492 2507 if normal:
2493 2508 normal(f)
2494 2509
2495 2510 for f in add[0]:
2496 2511 checkout(f)
2497 2512 repo.dirstate.add(f)
2498 2513
2499 2514 normal = repo.dirstate.normallookup
2500 2515 if node == parent and p2 == nullid:
2501 2516 normal = repo.dirstate.normal
2502 2517 for f in undelete[0]:
2503 2518 checkout(f)
2504 2519 normal(f)
2505 2520
2506 2521 finally:
2507 2522 del wlock
2508 2523
2509 2524 def rollback(ui, repo):
2510 2525 """roll back the last transaction
2511 2526
2512 2527 This command should be used with care. There is only one level of
2513 2528 rollback, and there is no way to undo a rollback. It will also
2514 2529 restore the dirstate at the time of the last transaction, losing
2515 2530 any dirstate changes since that time.
2516 2531
2517 2532 Transactions are used to encapsulate the effects of all commands
2518 2533 that create new changesets or propagate existing changesets into a
2519 2534 repository. For example, the following commands are transactional,
2520 2535 and their effects can be rolled back:
2521 2536
2522 2537 commit
2523 2538 import
2524 2539 pull
2525 2540 push (with this repository as destination)
2526 2541 unbundle
2527 2542
2528 2543 This command is not intended for use on public repositories. Once
2529 2544 changes are visible for pull by other users, rolling a transaction
2530 2545 back locally is ineffective (someone else may already have pulled
2531 2546 the changes). Furthermore, a race is possible with readers of the
2532 2547 repository; for example an in-progress pull from the repository
2533 2548 may fail if a rollback is performed.
2534 2549 """
2535 2550 repo.rollback()
2536 2551
2537 2552 def root(ui, repo):
2538 2553 """print the root (top) of the current working dir
2539 2554
2540 2555 Print the root directory of the current repository.
2541 2556 """
2542 2557 ui.write(repo.root + "\n")
2543 2558
2544 2559 def serve(ui, repo, **opts):
2545 2560 """export the repository via HTTP
2546 2561
2547 2562 Start a local HTTP repository browser and pull server.
2548 2563
2549 2564 By default, the server logs accesses to stdout and errors to
2550 2565 stderr. Use the "-A" and "-E" options to log to files.
2551 2566 """
2552 2567
2553 2568 if opts["stdio"]:
2554 2569 if repo is None:
2555 2570 raise RepoError(_("There is no Mercurial repository here"
2556 2571 " (.hg not found)"))
2557 2572 s = sshserver.sshserver(ui, repo)
2558 2573 s.serve_forever()
2559 2574
2560 2575 parentui = ui.parentui or ui
2561 2576 optlist = ("name templates style address port prefix ipv6"
2562 2577 " accesslog errorlog webdir_conf certificate")
2563 2578 for o in optlist.split():
2564 2579 if opts[o]:
2565 2580 parentui.setconfig("web", o, str(opts[o]))
2566 2581 if (repo is not None) and (repo.ui != parentui):
2567 2582 repo.ui.setconfig("web", o, str(opts[o]))
2568 2583
2569 2584 if repo is None and not ui.config("web", "webdir_conf"):
2570 2585 raise RepoError(_("There is no Mercurial repository here"
2571 2586 " (.hg not found)"))
2572 2587
2573 2588 class service:
2574 2589 def init(self):
2575 2590 util.set_signal_handler()
2576 2591 self.httpd = hgweb.server.create_server(parentui, repo)
2577 2592
2578 2593 if not ui.verbose: return
2579 2594
2580 2595 if self.httpd.prefix:
2581 2596 prefix = self.httpd.prefix.strip('/') + '/'
2582 2597 else:
2583 2598 prefix = ''
2584 2599
2585 2600 port = ':%d' % self.httpd.port
2586 2601 if port == ':80':
2587 2602 port = ''
2588 2603
2589 2604 bindaddr = self.httpd.addr
2590 2605 if bindaddr == '0.0.0.0':
2591 2606 bindaddr = '*'
2592 2607 elif ':' in bindaddr: # IPv6
2593 2608 bindaddr = '[%s]' % bindaddr
2594 2609
2595 2610 fqaddr = self.httpd.fqaddr
2596 2611 if ':' in fqaddr:
2597 2612 fqaddr = '[%s]' % fqaddr
2598 2613 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2599 2614 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2600 2615
2601 2616 def run(self):
2602 2617 self.httpd.serve_forever()
2603 2618
2604 2619 service = service()
2605 2620
2606 2621 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2607 2622
2608 2623 def status(ui, repo, *pats, **opts):
2609 2624 """show changed files in the working directory
2610 2625
2611 2626 Show status of files in the repository. If names are given, only
2612 2627 files that match are shown. Files that are clean or ignored or
2613 2628 source of a copy/move operation, are not listed unless -c (clean),
2614 2629 -i (ignored), -C (copies) or -A is given. Unless options described
2615 2630 with "show only ..." are given, the options -mardu are used.
2616 2631
2617 2632 Option -q/--quiet hides untracked (unknown and ignored) files
2618 2633 unless explicitly requested with -u/--unknown or -i/-ignored.
2619 2634
2620 2635 NOTE: status may appear to disagree with diff if permissions have
2621 2636 changed or a merge has occurred. The standard diff format does not
2622 2637 report permission changes and diff only reports changes relative
2623 2638 to one merge parent.
2624 2639
2625 2640 If one revision is given, it is used as the base revision.
2626 2641 If two revisions are given, the difference between them is shown.
2627 2642
2628 2643 The codes used to show the status of files are:
2629 2644 M = modified
2630 2645 A = added
2631 2646 R = removed
2632 2647 C = clean
2633 2648 ! = deleted, but still tracked
2634 2649 ? = not tracked
2635 2650 I = ignored
2636 2651 = the previous added file was copied from here
2637 2652 """
2638 2653
2639 2654 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2640 2655 cwd = (pats and repo.getcwd()) or ''
2641 end = opts['print0'] and '\0' or '\n'
2656 end = opts.get('print0') and '\0' or '\n'
2642 2657 copy = {}
2643 2658 states = 'modified added removed deleted unknown ignored clean'.split()
2644 2659 show = [k for k in states if opts[k]]
2645 if opts['all']:
2660 if opts.get('all'):
2646 2661 show += ui.quiet and (states[:4] + ['clean']) or states
2647 2662 if not show:
2648 2663 show = ui.quiet and states[:4] or states[:5]
2649 2664
2650 2665 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2651 2666 'ignored' in show, 'clean' in show, 'unknown' in show)
2652 2667 changestates = zip(states, 'MAR!?IC', stat)
2653 2668
2654 if (opts['all'] or opts['copies']) and not opts['no_status']:
2669 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
2655 2670 ctxn = repo[nullid]
2656 2671 ctx1 = repo[node1]
2657 2672 ctx2 = repo[node2]
2658 2673 added = stat[1]
2659 2674 if node2 is None:
2660 2675 added = stat[0] + stat[1] # merged?
2661 2676
2662 2677 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].items():
2663 2678 if k in added:
2664 2679 copy[k] = v
2665 2680 elif v in added:
2666 2681 copy[v] = k
2667 2682
2668 2683 for state, char, files in changestates:
2669 2684 if state in show:
2670 2685 format = "%s %%s%s" % (char, end)
2671 if opts['no_status']:
2686 if opts.get('no_status'):
2672 2687 format = "%%s%s" % end
2673 2688
2674 2689 for f in files:
2675 2690 ui.write(format % repo.pathto(f, cwd))
2676 2691 if f in copy:
2677 2692 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2678 2693
2679 2694 def tag(ui, repo, name1, *names, **opts):
2680 2695 """add one or more tags for the current or given revision
2681 2696
2682 2697 Name a particular revision using <name>.
2683 2698
2684 2699 Tags are used to name particular revisions of the repository and are
2685 2700 very useful to compare different revisions, to go back to significant
2686 2701 earlier versions or to mark branch points as releases, etc.
2687 2702
2688 2703 If no revision is given, the parent of the working directory is used,
2689 2704 or tip if no revision is checked out.
2690 2705
2691 2706 To facilitate version control, distribution, and merging of tags,
2692 2707 they are stored as a file named ".hgtags" which is managed
2693 2708 similarly to other project files and can be hand-edited if
2694 2709 necessary. The file '.hg/localtags' is used for local tags (not
2695 2710 shared among repositories).
2696 2711
2697 2712 See 'hg help dates' for a list of formats valid for -d/--date.
2698 2713 """
2699 2714
2700 2715 rev_ = "."
2701 2716 names = (name1,) + names
2702 2717 if len(names) != len(dict.fromkeys(names)):
2703 2718 raise util.Abort(_('tag names must be unique'))
2704 2719 for n in names:
2705 2720 if n in ['tip', '.', 'null']:
2706 2721 raise util.Abort(_('the name \'%s\' is reserved') % n)
2707 if opts['rev'] and opts['remove']:
2722 if opts.get('rev') and opts.get('remove'):
2708 2723 raise util.Abort(_("--rev and --remove are incompatible"))
2709 if opts['rev']:
2724 if opts.get('rev'):
2710 2725 rev_ = opts['rev']
2711 message = opts['message']
2712 if opts['remove']:
2713 expectedtype = opts['local'] and 'local' or 'global'
2726 message = opts.get('message')
2727 if opts.get('remove'):
2728 expectedtype = opts.get('local') and 'local' or 'global'
2714 2729 for n in names:
2715 2730 if not repo.tagtype(n):
2716 2731 raise util.Abort(_('tag \'%s\' does not exist') % n)
2717 2732 if repo.tagtype(n) != expectedtype:
2718 2733 raise util.Abort(_('tag \'%s\' is not a %s tag') %
2719 2734 (n, expectedtype))
2720 2735 rev_ = nullid
2721 2736 if not message:
2722 2737 message = _('Removed tag %s') % ', '.join(names)
2723 elif not opts['force']:
2738 elif not opts.get('force'):
2724 2739 for n in names:
2725 2740 if n in repo.tags():
2726 2741 raise util.Abort(_('tag \'%s\' already exists '
2727 2742 '(use -f to force)') % n)
2728 2743 if not rev_ and repo.dirstate.parents()[1] != nullid:
2729 2744 raise util.Abort(_('uncommitted merge - please provide a '
2730 2745 'specific revision'))
2731 2746 r = repo[rev_].node()
2732 2747
2733 2748 if not message:
2734 2749 message = (_('Added tag %s for changeset %s') %
2735 2750 (', '.join(names), short(r)))
2736 2751
2737 2752 date = opts.get('date')
2738 2753 if date:
2739 2754 date = util.parsedate(date)
2740 2755
2741 repo.tag(names, r, message, opts['local'], opts['user'], date)
2756 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
2742 2757
2743 2758 def tags(ui, repo):
2744 2759 """list repository tags
2745 2760
2746 2761 List the repository tags.
2747 2762
2748 2763 This lists both regular and local tags. When the -v/--verbose switch
2749 2764 is used, a third column "local" is printed for local tags.
2750 2765 """
2751 2766
2752 2767 l = repo.tagslist()
2753 2768 l.reverse()
2754 2769 hexfunc = ui.debugflag and hex or short
2755 2770 tagtype = ""
2756 2771
2757 2772 for t, n in l:
2758 2773 if ui.quiet:
2759 2774 ui.write("%s\n" % t)
2760 2775 continue
2761 2776
2762 2777 try:
2763 2778 hn = hexfunc(n)
2764 2779 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2765 2780 except revlog.LookupError:
2766 2781 r = " ?:%s" % hn
2767 2782 else:
2768 2783 spaces = " " * (30 - util.locallen(t))
2769 2784 if ui.verbose:
2770 2785 if repo.tagtype(t) == 'local':
2771 2786 tagtype = " local"
2772 2787 else:
2773 2788 tagtype = ""
2774 2789 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2775 2790
2776 2791 def tip(ui, repo, **opts):
2777 2792 """show the tip revision
2778 2793
2779 2794 The tip revision (usually just called the tip) is the most
2780 2795 recently added changeset in the repository, the most recently
2781 2796 changed head.
2782 2797
2783 2798 If you have just made a commit, that commit will be the tip. If
2784 2799 you have just pulled changes from another repository, the tip of
2785 2800 that repository becomes the current tip. The "tip" tag is special
2786 2801 and cannot be renamed or assigned to a different changeset.
2787 2802 """
2788 2803 cmdutil.show_changeset(ui, repo, opts).show(len(repo) - 1)
2789 2804
2790 2805 def unbundle(ui, repo, fname1, *fnames, **opts):
2791 2806 """apply one or more changegroup files
2792 2807
2793 2808 Apply one or more compressed changegroup files generated by the
2794 2809 bundle command.
2795 2810 """
2796 2811 fnames = (fname1,) + fnames
2797 2812
2798 2813 lock = None
2799 2814 try:
2800 2815 lock = repo.lock()
2801 2816 for fname in fnames:
2802 2817 if os.path.exists(fname):
2803 2818 f = open(fname, "rb")
2804 2819 else:
2805 2820 f = urllib.urlopen(fname)
2806 2821 gen = changegroup.readbundle(f, fname)
2807 2822 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2808 2823 finally:
2809 2824 del lock
2810 2825
2811 return postincoming(ui, repo, modheads, opts['update'], None)
2826 return postincoming(ui, repo, modheads, opts.get('update'), None)
2812 2827
2813 2828 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2814 2829 """update working directory
2815 2830
2816 2831 Update the repository's working directory to the specified revision,
2817 2832 or the tip of the current branch if none is specified. Use null as
2818 2833 the revision to remove the working copy (like 'hg clone -U').
2819 2834
2820 2835 If the requested revision is a descendant of the working
2821 2836 directory, any outstanding changes in the working directory will
2822 2837 be merged into the result. If it is not directly descended but is
2823 2838 on the same named branch, update aborts with a suggestion to use
2824 2839 merge or update -C instead.
2825 2840
2826 2841 If the requested revision is on a different named branch and the
2827 2842 working directory is clean, update quietly switches branches.
2828 2843
2829 2844 If you want to update just one file to an older revision, use revert.
2830 2845
2831 2846 See 'hg help dates' for a list of formats valid for --date.
2832 2847 """
2833 2848 if rev and node:
2834 2849 raise util.Abort(_("please specify just one revision"))
2835 2850
2836 2851 if not rev:
2837 2852 rev = node
2838 2853
2839 2854 if date:
2840 2855 if rev:
2841 2856 raise util.Abort(_("you can't specify a revision and a date"))
2842 2857 rev = cmdutil.finddate(ui, repo, date)
2843 2858
2844 2859 if clean:
2845 2860 return hg.clean(repo, rev)
2846 2861 else:
2847 2862 return hg.update(repo, rev)
2848 2863
2849 2864 def verify(ui, repo):
2850 2865 """verify the integrity of the repository
2851 2866
2852 2867 Verify the integrity of the current repository.
2853 2868
2854 2869 This will perform an extensive check of the repository's
2855 2870 integrity, validating the hashes and checksums of each entry in
2856 2871 the changelog, manifest, and tracked files, as well as the
2857 2872 integrity of their crosslinks and indices.
2858 2873 """
2859 2874 return hg.verify(repo)
2860 2875
2861 2876 def version_(ui):
2862 2877 """output version and copyright information"""
2863 2878 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2864 2879 % version.get_version())
2865 2880 ui.status(_(
2866 2881 "\nCopyright (C) 2005-2008 Matt Mackall <mpm@selenic.com> and others\n"
2867 2882 "This is free software; see the source for copying conditions. "
2868 2883 "There is NO\nwarranty; "
2869 2884 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2870 2885 ))
2871 2886
2872 2887 # Command options and aliases are listed here, alphabetically
2873 2888
2874 2889 globalopts = [
2875 2890 ('R', 'repository', '',
2876 2891 _('repository root directory or symbolic path name')),
2877 2892 ('', 'cwd', '', _('change working directory')),
2878 2893 ('y', 'noninteractive', None,
2879 2894 _('do not prompt, assume \'yes\' for any required answers')),
2880 2895 ('q', 'quiet', None, _('suppress output')),
2881 2896 ('v', 'verbose', None, _('enable additional output')),
2882 2897 ('', 'config', [], _('set/override config option')),
2883 2898 ('', 'debug', None, _('enable debugging output')),
2884 2899 ('', 'debugger', None, _('start debugger')),
2885 2900 ('', 'encoding', util._encoding, _('set the charset encoding')),
2886 2901 ('', 'encodingmode', util._encodingmode, _('set the charset encoding mode')),
2887 2902 ('', 'lsprof', None, _('print improved command execution profile')),
2888 2903 ('', 'traceback', None, _('print traceback on exception')),
2889 2904 ('', 'time', None, _('time how long the command takes')),
2890 2905 ('', 'profile', None, _('print command execution profile')),
2891 2906 ('', 'version', None, _('output version information and exit')),
2892 2907 ('h', 'help', None, _('display help and exit')),
2893 2908 ]
2894 2909
2895 2910 dryrunopts = [('n', 'dry-run', None,
2896 2911 _('do not perform actions, just print output'))]
2897 2912
2898 2913 remoteopts = [
2899 2914 ('e', 'ssh', '', _('specify ssh command to use')),
2900 2915 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
2901 2916 ]
2902 2917
2903 2918 walkopts = [
2904 2919 ('I', 'include', [], _('include names matching the given patterns')),
2905 2920 ('X', 'exclude', [], _('exclude names matching the given patterns')),
2906 2921 ]
2907 2922
2908 2923 commitopts = [
2909 2924 ('m', 'message', '', _('use <text> as commit message')),
2910 2925 ('l', 'logfile', '', _('read commit message from <file>')),
2911 2926 ]
2912 2927
2913 2928 commitopts2 = [
2914 2929 ('d', 'date', '', _('record datecode as commit date')),
2915 2930 ('u', 'user', '', _('record user as committer')),
2916 2931 ]
2917 2932
2918 2933 templateopts = [
2919 2934 ('', 'style', '', _('display using template map file')),
2920 2935 ('', 'template', '', _('display with template')),
2921 2936 ]
2922 2937
2923 2938 logopts = [
2924 2939 ('p', 'patch', None, _('show patch')),
2925 2940 ('l', 'limit', '', _('limit number of changes displayed')),
2926 2941 ('M', 'no-merges', None, _('do not show merges')),
2927 2942 ] + templateopts
2928 2943
2929 2944 diffopts = [
2930 2945 ('a', 'text', None, _('treat all files as text')),
2931 2946 ('g', 'git', None, _('use git extended diff format')),
2932 2947 ('', 'nodates', None, _("don't include dates in diff headers"))
2933 2948 ]
2934 2949
2935 2950 diffopts2 = [
2936 2951 ('p', 'show-function', None, _('show which function each change is in')),
2937 2952 ('w', 'ignore-all-space', None,
2938 2953 _('ignore white space when comparing lines')),
2939 2954 ('b', 'ignore-space-change', None,
2940 2955 _('ignore changes in the amount of white space')),
2941 2956 ('B', 'ignore-blank-lines', None,
2942 2957 _('ignore changes whose lines are all blank')),
2943 2958 ('U', 'unified', '', _('number of lines of context to show'))
2944 2959 ]
2945 2960
2946 2961 table = {
2947 2962 "^add": (add, walkopts + dryrunopts, _('hg add [OPTION]... [FILE]...')),
2948 2963 "addremove":
2949 2964 (addremove,
2950 2965 [('s', 'similarity', '',
2951 2966 _('guess renamed files by similarity (0<=s<=100)')),
2952 2967 ] + walkopts + dryrunopts,
2953 2968 _('hg addremove [OPTION]... [FILE]...')),
2954 2969 "^annotate|blame":
2955 2970 (annotate,
2956 2971 [('r', 'rev', '', _('annotate the specified revision')),
2957 2972 ('f', 'follow', None, _('follow file copies and renames')),
2958 2973 ('a', 'text', None, _('treat all files as text')),
2959 2974 ('u', 'user', None, _('list the author (long with -v)')),
2960 2975 ('d', 'date', None, _('list the date (short with -q)')),
2961 2976 ('n', 'number', None, _('list the revision number (default)')),
2962 2977 ('c', 'changeset', None, _('list the changeset')),
2963 2978 ('l', 'line-number', None,
2964 2979 _('show line number at the first appearance'))
2965 2980 ] + walkopts,
2966 2981 _('hg annotate [-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
2967 2982 "archive":
2968 2983 (archive,
2969 2984 [('', 'no-decode', None, _('do not pass files through decoders')),
2970 2985 ('p', 'prefix', '', _('directory prefix for files in archive')),
2971 2986 ('r', 'rev', '', _('revision to distribute')),
2972 2987 ('t', 'type', '', _('type of distribution to create')),
2973 2988 ] + walkopts,
2974 2989 _('hg archive [OPTION]... DEST')),
2975 2990 "backout":
2976 2991 (backout,
2977 2992 [('', 'merge', None,
2978 2993 _('merge with old dirstate parent after backout')),
2979 2994 ('', 'parent', '', _('parent to choose when backing out merge')),
2980 2995 ('r', 'rev', '', _('revision to backout')),
2981 2996 ] + walkopts + commitopts + commitopts2,
2982 2997 _('hg backout [OPTION]... [-r] REV')),
2983 2998 "bisect":
2984 2999 (bisect,
2985 3000 [('r', 'reset', False, _('reset bisect state')),
2986 3001 ('g', 'good', False, _('mark changeset good')),
2987 3002 ('b', 'bad', False, _('mark changeset bad')),
2988 3003 ('s', 'skip', False, _('skip testing changeset')),
2989 3004 ('U', 'noupdate', False, _('do not update to target'))],
2990 3005 _("hg bisect [-gbsr] [REV]")),
2991 3006 "branch":
2992 3007 (branch,
2993 3008 [('f', 'force', None,
2994 3009 _('set branch name even if it shadows an existing branch')),
2995 3010 ('C', 'clean', None, _('reset branch name to parent branch name'))],
2996 3011 _('hg branch [-fC] [NAME]')),
2997 3012 "branches":
2998 3013 (branches,
2999 3014 [('a', 'active', False,
3000 3015 _('show only branches that have unmerged heads'))],
3001 3016 _('hg branches [-a]')),
3002 3017 "bundle":
3003 3018 (bundle,
3004 3019 [('f', 'force', None,
3005 3020 _('run even when remote repository is unrelated')),
3006 3021 ('r', 'rev', [],
3007 3022 _('a changeset up to which you would like to bundle')),
3008 3023 ('', 'base', [],
3009 3024 _('a base changeset to specify instead of a destination')),
3010 3025 ('a', 'all', None, _('bundle all changesets in the repository')),
3011 3026 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3012 3027 ] + remoteopts,
3013 3028 _('hg bundle [-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3014 3029 "cat":
3015 3030 (cat,
3016 3031 [('o', 'output', '', _('print output to file with formatted name')),
3017 3032 ('r', 'rev', '', _('print the given revision')),
3018 3033 ('', 'decode', None, _('apply any matching decode filter')),
3019 3034 ] + walkopts,
3020 3035 _('hg cat [OPTION]... FILE...')),
3021 3036 "^clone":
3022 3037 (clone,
3023 3038 [('U', 'noupdate', None,
3024 3039 _('the clone will only contain a repository (no working copy)')),
3025 3040 ('r', 'rev', [],
3026 3041 _('a changeset you would like to have after cloning')),
3027 3042 ('', 'pull', None, _('use pull protocol to copy metadata')),
3028 3043 ('', 'uncompressed', None,
3029 3044 _('use uncompressed transfer (fast over LAN)')),
3030 3045 ] + remoteopts,
3031 3046 _('hg clone [OPTION]... SOURCE [DEST]')),
3032 3047 "^commit|ci":
3033 3048 (commit,
3034 3049 [('A', 'addremove', None,
3035 3050 _('mark new/missing files as added/removed before committing')),
3036 3051 ] + walkopts + commitopts + commitopts2,
3037 3052 _('hg commit [OPTION]... [FILE]...')),
3038 3053 "copy|cp":
3039 3054 (copy,
3040 3055 [('A', 'after', None, _('record a copy that has already occurred')),
3041 3056 ('f', 'force', None,
3042 3057 _('forcibly copy over an existing managed file')),
3043 3058 ] + walkopts + dryrunopts,
3044 3059 _('hg copy [OPTION]... [SOURCE]... DEST')),
3045 3060 "debugancestor": (debugancestor, [],
3046 3061 _('hg debugancestor [INDEX] REV1 REV2')),
3047 3062 "debugcheckstate": (debugcheckstate, [], _('hg debugcheckstate')),
3048 3063 "debugcomplete":
3049 3064 (debugcomplete,
3050 3065 [('o', 'options', None, _('show the command options'))],
3051 3066 _('hg debugcomplete [-o] CMD')),
3052 3067 "debugdate":
3053 3068 (debugdate,
3054 3069 [('e', 'extended', None, _('try extended date formats'))],
3055 3070 _('hg debugdate [-e] DATE [RANGE]')),
3056 3071 "debugdata": (debugdata, [], _('hg debugdata FILE REV')),
3057 3072 "debugfsinfo": (debugfsinfo, [], _('hg debugfsinfo [PATH]')),
3058 3073 "debugindex": (debugindex, [], _('hg debugindex FILE')),
3059 3074 "debugindexdot": (debugindexdot, [], _('hg debugindexdot FILE')),
3060 3075 "debuginstall": (debuginstall, [], _('hg debuginstall')),
3061 3076 "debugrawcommit|rawcommit":
3062 3077 (rawcommit,
3063 3078 [('p', 'parent', [], _('parent')),
3064 3079 ('F', 'files', '', _('file list'))
3065 3080 ] + commitopts + commitopts2,
3066 3081 _('hg debugrawcommit [OPTION]... [FILE]...')),
3067 3082 "debugrebuildstate":
3068 3083 (debugrebuildstate,
3069 3084 [('r', 'rev', '', _('revision to rebuild to'))],
3070 3085 _('hg debugrebuildstate [-r REV] [REV]')),
3071 3086 "debugrename":
3072 3087 (debugrename,
3073 3088 [('r', 'rev', '', _('revision to debug'))],
3074 3089 _('hg debugrename [-r REV] FILE')),
3075 3090 "debugsetparents":
3076 3091 (debugsetparents,
3077 3092 [],
3078 3093 _('hg debugsetparents REV1 [REV2]')),
3079 3094 "debugstate":
3080 3095 (debugstate,
3081 3096 [('', 'nodates', None, _('do not display the saved mtime'))],
3082 3097 _('hg debugstate [OPTS]')),
3083 3098 "debugwalk": (debugwalk, walkopts, _('hg debugwalk [OPTION]... [FILE]...')),
3084 3099 "^diff":
3085 3100 (diff,
3086 3101 [('r', 'rev', [], _('revision'))
3087 3102 ] + diffopts + diffopts2 + walkopts,
3088 3103 _('hg diff [OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3089 3104 "^export":
3090 3105 (export,
3091 3106 [('o', 'output', '', _('print output to file with formatted name')),
3092 3107 ('', 'switch-parent', None, _('diff against the second parent'))
3093 3108 ] + diffopts,
3094 3109 _('hg export [OPTION]... [-o OUTFILESPEC] REV...')),
3095 3110 "grep":
3096 3111 (grep,
3097 3112 [('0', 'print0', None, _('end fields with NUL')),
3098 3113 ('', 'all', None, _('print all revisions that match')),
3099 3114 ('f', 'follow', None,
3100 3115 _('follow changeset history, or file history across copies and renames')),
3101 3116 ('i', 'ignore-case', None, _('ignore case when matching')),
3102 3117 ('l', 'files-with-matches', None,
3103 3118 _('print only filenames and revs that match')),
3104 3119 ('n', 'line-number', None, _('print matching line numbers')),
3105 3120 ('r', 'rev', [], _('search in given revision range')),
3106 3121 ('u', 'user', None, _('list the author (long with -v)')),
3107 3122 ('d', 'date', None, _('list the date (short with -q)')),
3108 3123 ] + walkopts,
3109 3124 _('hg grep [OPTION]... PATTERN [FILE]...')),
3110 3125 "heads":
3111 3126 (heads,
3112 3127 [('r', 'rev', '', _('show only heads which are descendants of rev')),
3113 3128 ] + templateopts,
3114 3129 _('hg heads [-r REV] [REV]...')),
3115 3130 "help": (help_, [], _('hg help [COMMAND]')),
3116 3131 "identify|id":
3117 3132 (identify,
3118 3133 [('r', 'rev', '', _('identify the specified rev')),
3119 3134 ('n', 'num', None, _('show local revision number')),
3120 3135 ('i', 'id', None, _('show global revision id')),
3121 3136 ('b', 'branch', None, _('show branch')),
3122 3137 ('t', 'tags', None, _('show tags'))],
3123 3138 _('hg identify [-nibt] [-r REV] [SOURCE]')),
3124 3139 "import|patch":
3125 3140 (import_,
3126 3141 [('p', 'strip', 1,
3127 3142 _('directory strip option for patch. This has the same\n'
3128 3143 'meaning as the corresponding patch option')),
3129 3144 ('b', 'base', '', _('base path')),
3130 3145 ('f', 'force', None,
3131 3146 _('skip check for outstanding uncommitted changes')),
3132 3147 ('', 'no-commit', None, _("don't commit, just update the working directory")),
3133 3148 ('', 'exact', None,
3134 3149 _('apply patch to the nodes from which it was generated')),
3135 3150 ('', 'import-branch', None,
3136 3151 _('Use any branch information in patch (implied by --exact)'))] +
3137 3152 commitopts + commitopts2,
3138 3153 _('hg import [OPTION]... PATCH...')),
3139 3154 "incoming|in":
3140 3155 (incoming,
3141 3156 [('f', 'force', None,
3142 3157 _('run even when remote repository is unrelated')),
3143 3158 ('n', 'newest-first', None, _('show newest record first')),
3144 3159 ('', 'bundle', '', _('file to store the bundles into')),
3145 3160 ('r', 'rev', [],
3146 3161 _('a specific revision up to which you would like to pull')),
3147 3162 ] + logopts + remoteopts,
3148 3163 _('hg incoming [-p] [-n] [-M] [-f] [-r REV]...'
3149 3164 ' [--bundle FILENAME] [SOURCE]')),
3150 3165 "^init":
3151 3166 (init,
3152 3167 remoteopts,
3153 3168 _('hg init [-e CMD] [--remotecmd CMD] [DEST]')),
3154 3169 "locate":
3155 3170 (locate,
3156 3171 [('r', 'rev', '', _('search the repository as it stood at rev')),
3157 3172 ('0', 'print0', None,
3158 3173 _('end filenames with NUL, for use with xargs')),
3159 3174 ('f', 'fullpath', None,
3160 3175 _('print complete paths from the filesystem root')),
3161 3176 ] + walkopts,
3162 3177 _('hg locate [OPTION]... [PATTERN]...')),
3163 3178 "^log|history":
3164 3179 (log,
3165 3180 [('f', 'follow', None,
3166 3181 _('follow changeset history, or file history across copies and renames')),
3167 3182 ('', 'follow-first', None,
3168 3183 _('only follow the first parent of merge changesets')),
3169 3184 ('d', 'date', '', _('show revs matching date spec')),
3170 3185 ('C', 'copies', None, _('show copied files')),
3171 3186 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3172 3187 ('r', 'rev', [], _('show the specified revision or range')),
3173 3188 ('', 'removed', None, _('include revs where files were removed')),
3174 3189 ('m', 'only-merges', None, _('show only merges')),
3175 3190 ('b', 'only-branch', [],
3176 3191 _('show only changesets within the given named branch')),
3177 3192 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3178 3193 ] + logopts + walkopts,
3179 3194 _('hg log [OPTION]... [FILE]')),
3180 3195 "manifest":
3181 3196 (manifest,
3182 3197 [('r', 'rev', '', _('revision to display'))],
3183 3198 _('hg manifest [-r REV]')),
3184 3199 "^merge":
3185 3200 (merge,
3186 3201 [('f', 'force', None, _('force a merge with outstanding changes')),
3187 3202 ('r', 'rev', '', _('revision to merge')),
3188 3203 ],
3189 3204 _('hg merge [-f] [[-r] REV]')),
3190 3205 "outgoing|out":
3191 3206 (outgoing,
3192 3207 [('f', 'force', None,
3193 3208 _('run even when remote repository is unrelated')),
3194 3209 ('r', 'rev', [],
3195 3210 _('a specific revision up to which you would like to push')),
3196 3211 ('n', 'newest-first', None, _('show newest record first')),
3197 3212 ] + logopts + remoteopts,
3198 3213 _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3199 3214 "^parents":
3200 3215 (parents,
3201 3216 [('r', 'rev', '', _('show parents from the specified rev')),
3202 3217 ] + templateopts,
3203 3218 _('hg parents [-r REV] [FILE]')),
3204 3219 "paths": (paths, [], _('hg paths [NAME]')),
3205 3220 "^pull":
3206 3221 (pull,
3207 3222 [('u', 'update', None,
3208 3223 _('update to new tip if changesets were pulled')),
3209 3224 ('f', 'force', None,
3210 3225 _('run even when remote repository is unrelated')),
3211 3226 ('r', 'rev', [],
3212 3227 _('a specific revision up to which you would like to pull')),
3213 3228 ] + remoteopts,
3214 3229 _('hg pull [-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3215 3230 "^push":
3216 3231 (push,
3217 3232 [('f', 'force', None, _('force push')),
3218 3233 ('r', 'rev', [],
3219 3234 _('a specific revision up to which you would like to push')),
3220 3235 ] + remoteopts,
3221 3236 _('hg push [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3222 3237 "recover": (recover, [], _('hg recover')),
3223 3238 "^remove|rm":
3224 3239 (remove,
3225 3240 [('A', 'after', None, _('record delete for missing files')),
3226 3241 ('f', 'force', None,
3227 3242 _('remove (and delete) file even if added or modified')),
3228 3243 ] + walkopts,
3229 3244 _('hg remove [OPTION]... FILE...')),
3230 3245 "rename|mv":
3231 3246 (rename,
3232 3247 [('A', 'after', None, _('record a rename that has already occurred')),
3233 3248 ('f', 'force', None,
3234 3249 _('forcibly copy over an existing managed file')),
3235 3250 ] + walkopts + dryrunopts,
3236 3251 _('hg rename [OPTION]... SOURCE... DEST')),
3237 3252 "resolve":
3238 3253 (resolve,
3239 3254 [('l', 'list', None, _('list state of files needing merge')),
3240 3255 ('m', 'mark', None, _('mark files as resolved')),
3241 3256 ('u', 'unmark', None, _('unmark files as resolved'))],
3242 3257 _('hg resolve [OPTION] [FILES...]')),
3243 3258 "revert":
3244 3259 (revert,
3245 3260 [('a', 'all', None, _('revert all changes when no arguments given')),
3246 3261 ('d', 'date', '', _('tipmost revision matching date')),
3247 3262 ('r', 'rev', '', _('revision to revert to')),
3248 3263 ('', 'no-backup', None, _('do not save backup copies of files')),
3249 3264 ] + walkopts + dryrunopts,
3250 3265 _('hg revert [OPTION]... [-r REV] [NAME]...')),
3251 3266 "rollback": (rollback, [], _('hg rollback')),
3252 3267 "root": (root, [], _('hg root')),
3253 3268 "^serve":
3254 3269 (serve,
3255 3270 [('A', 'accesslog', '', _('name of access log file to write to')),
3256 3271 ('d', 'daemon', None, _('run server in background')),
3257 3272 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3258 3273 ('E', 'errorlog', '', _('name of error log file to write to')),
3259 3274 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3260 3275 ('a', 'address', '', _('address to listen on (default: all interfaces)')),
3261 3276 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3262 3277 ('n', 'name', '',
3263 3278 _('name to show in web pages (default: working dir)')),
3264 3279 ('', 'webdir-conf', '', _('name of the webdir config file'
3265 3280 ' (serve more than one repo)')),
3266 3281 ('', 'pid-file', '', _('name of file to write process ID to')),
3267 3282 ('', 'stdio', None, _('for remote clients')),
3268 3283 ('t', 'templates', '', _('web templates to use')),
3269 3284 ('', 'style', '', _('template style to use')),
3270 3285 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3271 3286 ('', 'certificate', '', _('SSL certificate file'))],
3272 3287 _('hg serve [OPTION]...')),
3273 3288 "showconfig|debugconfig":
3274 3289 (showconfig,
3275 3290 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3276 3291 _('hg showconfig [-u] [NAME]...')),
3277 3292 "^status|st":
3278 3293 (status,
3279 3294 [('A', 'all', None, _('show status of all files')),
3280 3295 ('m', 'modified', None, _('show only modified files')),
3281 3296 ('a', 'added', None, _('show only added files')),
3282 3297 ('r', 'removed', None, _('show only removed files')),
3283 3298 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3284 3299 ('c', 'clean', None, _('show only files without changes')),
3285 3300 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3286 3301 ('i', 'ignored', None, _('show only ignored files')),
3287 3302 ('n', 'no-status', None, _('hide status prefix')),
3288 3303 ('C', 'copies', None, _('show source of copied files')),
3289 3304 ('0', 'print0', None,
3290 3305 _('end filenames with NUL, for use with xargs')),
3291 3306 ('', 'rev', [], _('show difference from revision')),
3292 3307 ] + walkopts,
3293 3308 _('hg status [OPTION]... [FILE]...')),
3294 3309 "tag":
3295 3310 (tag,
3296 3311 [('f', 'force', None, _('replace existing tag')),
3297 3312 ('l', 'local', None, _('make the tag local')),
3298 3313 ('r', 'rev', '', _('revision to tag')),
3299 3314 ('', 'remove', None, _('remove a tag')),
3300 3315 # -l/--local is already there, commitopts cannot be used
3301 3316 ('m', 'message', '', _('use <text> as commit message')),
3302 3317 ] + commitopts2,
3303 3318 _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3304 3319 "tags": (tags, [], _('hg tags')),
3305 3320 "tip":
3306 3321 (tip,
3307 3322 [('p', 'patch', None, _('show patch')),
3308 3323 ] + templateopts,
3309 3324 _('hg tip [-p]')),
3310 3325 "unbundle":
3311 3326 (unbundle,
3312 3327 [('u', 'update', None,
3313 3328 _('update to new tip if changesets were unbundled'))],
3314 3329 _('hg unbundle [-u] FILE...')),
3315 3330 "^update|up|checkout|co":
3316 3331 (update,
3317 3332 [('C', 'clean', None, _('overwrite locally modified files (no backup)')),
3318 3333 ('d', 'date', '', _('tipmost revision matching date')),
3319 3334 ('r', 'rev', '', _('revision'))],
3320 3335 _('hg update [-C] [-d DATE] [[-r] REV]')),
3321 3336 "verify": (verify, [], _('hg verify')),
3322 3337 "version": (version_, [], _('hg version')),
3323 3338 }
3324 3339
3325 3340 norepo = ("clone init version help debugcomplete debugdata"
3326 3341 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3327 3342 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,584 +1,584
1 1 """
2 2 dirstate.py - working directory tracking for mercurial
3 3
4 4 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
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
10 10 from node import nullid
11 11 from i18n import _
12 12 import struct, os, stat, util, errno, ignore
13 13 import cStringIO, osutil, sys, parsers
14 14
15 15 _unknown = ('?', 0, 0, 0)
16 16 _format = ">cllll"
17 17
18 18 def _finddirs(path):
19 19 pos = path.rfind('/')
20 20 while pos != -1:
21 21 yield path[:pos]
22 22 pos = path.rfind('/', 0, pos)
23 23
24 24 def _incdirs(dirs, path):
25 25 for base in _finddirs(path):
26 26 if base in dirs:
27 27 dirs[base] += 1
28 28 return
29 29 dirs[base] = 1
30 30
31 31 def _decdirs(dirs, path):
32 32 for base in _finddirs(path):
33 33 if dirs[base] > 1:
34 34 dirs[base] -= 1
35 35 return
36 36 del dirs[base]
37 37
38 38 class dirstate(object):
39 39
40 40 def __init__(self, opener, ui, root):
41 41 self._opener = opener
42 42 self._root = root
43 43 self._rootdir = os.path.join(root, '')
44 44 self._dirty = False
45 45 self._dirtypl = False
46 46 self._ui = ui
47 47
48 48 def __getattr__(self, name):
49 49 if name == '_map':
50 50 self._read()
51 51 return self._map
52 52 elif name == '_copymap':
53 53 self._read()
54 54 return self._copymap
55 55 elif name == '_foldmap':
56 56 _foldmap = {}
57 57 for name in self._map:
58 58 norm = os.path.normcase(name)
59 59 _foldmap[norm] = name
60 60 self._foldmap = _foldmap
61 61 return self._foldmap
62 62 elif name == '_branch':
63 63 try:
64 64 self._branch = (self._opener("branch").read().strip()
65 65 or "default")
66 66 except IOError:
67 67 self._branch = "default"
68 68 return self._branch
69 69 elif name == '_pl':
70 70 self._pl = [nullid, nullid]
71 71 try:
72 72 st = self._opener("dirstate").read(40)
73 73 if len(st) == 40:
74 74 self._pl = st[:20], st[20:40]
75 75 except IOError, err:
76 76 if err.errno != errno.ENOENT: raise
77 77 return self._pl
78 78 elif name == '_dirs':
79 79 dirs = {}
80 80 for f,s in self._map.iteritems():
81 81 if s[0] != 'r':
82 82 _incdirs(dirs, f)
83 83 self._dirs = dirs
84 84 return self._dirs
85 85 elif name == '_ignore':
86 86 files = [self._join('.hgignore')]
87 87 for name, path in self._ui.configitems("ui"):
88 88 if name == 'ignore' or name.startswith('ignore.'):
89 89 files.append(os.path.expanduser(path))
90 90 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
91 91 return self._ignore
92 92 elif name == '_slash':
93 93 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
94 94 return self._slash
95 95 elif name == '_checklink':
96 96 self._checklink = util.checklink(self._root)
97 97 return self._checklink
98 98 elif name == '_checkexec':
99 99 self._checkexec = util.checkexec(self._root)
100 100 return self._checkexec
101 101 elif name == '_checkcase':
102 102 self._checkcase = not util.checkcase(self._join('.hg'))
103 103 return self._checkcase
104 104 elif name == 'normalize':
105 105 if self._checkcase:
106 106 self.normalize = self._normalize
107 107 else:
108 108 self.normalize = lambda x, y=False: x
109 109 return self.normalize
110 110 else:
111 111 raise AttributeError(name)
112 112
113 113 def _join(self, f):
114 114 # much faster than os.path.join()
115 115 # it's safe because f is always a relative path
116 116 return self._rootdir + f
117 117
118 118 def flagfunc(self, fallback):
119 119 if self._checklink:
120 120 if self._checkexec:
121 121 def f(x):
122 122 p = self._join(x)
123 123 if os.path.islink(p):
124 124 return 'l'
125 125 if util.is_exec(p):
126 126 return 'x'
127 127 return ''
128 128 return f
129 129 def f(x):
130 130 if os.path.islink(self._join(x)):
131 131 return 'l'
132 132 if 'x' in fallback(x):
133 133 return 'x'
134 134 return ''
135 135 return f
136 136 if self._checkexec:
137 137 def f(x):
138 138 if 'l' in fallback(x):
139 139 return 'l'
140 140 if util.is_exec(self._join(x)):
141 141 return 'x'
142 142 return ''
143 143 return f
144 144 return fallback
145 145
146 146 def getcwd(self):
147 147 cwd = os.getcwd()
148 148 if cwd == self._root: return ''
149 149 # self._root ends with a path separator if self._root is '/' or 'C:\'
150 150 rootsep = self._root
151 151 if not util.endswithsep(rootsep):
152 152 rootsep += os.sep
153 153 if cwd.startswith(rootsep):
154 154 return cwd[len(rootsep):]
155 155 else:
156 156 # we're outside the repo. return an absolute path.
157 157 return cwd
158 158
159 159 def pathto(self, f, cwd=None):
160 160 if cwd is None:
161 161 cwd = self.getcwd()
162 162 path = util.pathto(self._root, cwd, f)
163 163 if self._slash:
164 164 return util.normpath(path)
165 165 return path
166 166
167 167 def __getitem__(self, key):
168 168 ''' current states:
169 169 n normal
170 170 m needs merging
171 171 r marked for removal
172 172 a marked for addition
173 173 ? not tracked'''
174 174 return self._map.get(key, ("?",))[0]
175 175
176 176 def __contains__(self, key):
177 177 return key in self._map
178 178
179 179 def __iter__(self):
180 180 for x in util.sort(self._map):
181 181 yield x
182 182
183 183 def parents(self):
184 184 return self._pl
185 185
186 186 def branch(self):
187 187 return self._branch
188 188
189 189 def setparents(self, p1, p2=nullid):
190 190 self._dirty = self._dirtypl = True
191 191 self._pl = p1, p2
192 192
193 193 def setbranch(self, branch):
194 194 self._branch = branch
195 195 self._opener("branch", "w").write(branch + '\n')
196 196
197 197 def _read(self):
198 198 self._map = {}
199 199 self._copymap = {}
200 200 try:
201 201 st = self._opener("dirstate").read()
202 202 except IOError, err:
203 203 if err.errno != errno.ENOENT: raise
204 204 return
205 205 if not st:
206 206 return
207 207
208 p = parsers.parse_dirstate(self._map, self._copymap, st);
208 p = parsers.parse_dirstate(self._map, self._copymap, st)
209 209 if not self._dirtypl:
210 210 self._pl = p
211 211
212 212 def invalidate(self):
213 213 for a in "_map _copymap _foldmap _branch _pl _dirs _ignore".split():
214 214 if a in self.__dict__:
215 215 delattr(self, a)
216 216 self._dirty = False
217 217
218 218 def copy(self, source, dest):
219 219 if source == dest:
220 220 return
221 221 self._dirty = True
222 222 self._copymap[dest] = source
223 223
224 224 def copied(self, file):
225 225 return self._copymap.get(file, None)
226 226
227 227 def copies(self):
228 228 return self._copymap
229 229
230 230 def _droppath(self, f):
231 231 if self[f] not in "?r" and "_dirs" in self.__dict__:
232 232 _decdirs(self._dirs, f)
233 233
234 234 def _addpath(self, f, check=False):
235 235 oldstate = self[f]
236 236 if check or oldstate == "r":
237 237 if '\r' in f or '\n' in f:
238 238 raise util.Abort(
239 239 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
240 240 if f in self._dirs:
241 241 raise util.Abort(_('directory %r already in dirstate') % f)
242 242 # shadows
243 243 for d in _finddirs(f):
244 244 if d in self._dirs:
245 245 break
246 246 if d in self._map and self[d] != 'r':
247 247 raise util.Abort(
248 248 _('file %r in dirstate clashes with %r') % (d, f))
249 249 if oldstate in "?r" and "_dirs" in self.__dict__:
250 250 _incdirs(self._dirs, f)
251 251
252 252 def normal(self, f):
253 253 'mark a file normal and clean'
254 254 self._dirty = True
255 255 self._addpath(f)
256 256 s = os.lstat(self._join(f))
257 257 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
258 258 if f in self._copymap:
259 259 del self._copymap[f]
260 260
261 261 def normallookup(self, f):
262 262 'mark a file normal, but possibly dirty'
263 263 if self._pl[1] != nullid and f in self._map:
264 264 # if there is a merge going on and the file was either
265 265 # in state 'm' or dirty before being removed, restore that state.
266 266 entry = self._map[f]
267 267 if entry[0] == 'r' and entry[2] in (-1, -2):
268 268 source = self._copymap.get(f)
269 269 if entry[2] == -1:
270 270 self.merge(f)
271 271 elif entry[2] == -2:
272 272 self.normaldirty(f)
273 273 if source:
274 274 self.copy(source, f)
275 275 return
276 276 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
277 277 return
278 278 self._dirty = True
279 279 self._addpath(f)
280 280 self._map[f] = ('n', 0, -1, -1)
281 281 if f in self._copymap:
282 282 del self._copymap[f]
283 283
284 284 def normaldirty(self, f):
285 285 'mark a file normal, but dirty'
286 286 self._dirty = True
287 287 self._addpath(f)
288 288 self._map[f] = ('n', 0, -2, -1)
289 289 if f in self._copymap:
290 290 del self._copymap[f]
291 291
292 292 def add(self, f):
293 293 'mark a file added'
294 294 self._dirty = True
295 295 self._addpath(f, True)
296 296 self._map[f] = ('a', 0, -1, -1)
297 297 if f in self._copymap:
298 298 del self._copymap[f]
299 299
300 300 def remove(self, f):
301 301 'mark a file removed'
302 302 self._dirty = True
303 303 self._droppath(f)
304 304 size = 0
305 305 if self._pl[1] != nullid and f in self._map:
306 306 entry = self._map[f]
307 307 if entry[0] == 'm':
308 308 size = -1
309 309 elif entry[0] == 'n' and entry[2] == -2:
310 310 size = -2
311 311 self._map[f] = ('r', 0, size, 0)
312 312 if size == 0 and f in self._copymap:
313 313 del self._copymap[f]
314 314
315 315 def merge(self, f):
316 316 'mark a file merged'
317 317 self._dirty = True
318 318 s = os.lstat(self._join(f))
319 319 self._addpath(f)
320 320 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
321 321 if f in self._copymap:
322 322 del self._copymap[f]
323 323
324 324 def forget(self, f):
325 325 'forget a file'
326 326 self._dirty = True
327 327 try:
328 328 self._droppath(f)
329 329 del self._map[f]
330 330 except KeyError:
331 331 self._ui.warn(_("not in dirstate: %s\n") % f)
332 332
333 333 def _normalize(self, path, knownpath=False):
334 334 norm_path = os.path.normcase(path)
335 335 fold_path = self._foldmap.get(norm_path, None)
336 336 if fold_path is None:
337 337 if knownpath or not os.path.exists(os.path.join(self._root, path)):
338 338 fold_path = path
339 339 else:
340 340 fold_path = self._foldmap.setdefault(norm_path,
341 341 util.fspath(path, self._root))
342 342 return fold_path
343 343
344 344 def clear(self):
345 345 self._map = {}
346 346 if "_dirs" in self.__dict__:
347 347 delattr(self, "_dirs");
348 348 self._copymap = {}
349 349 self._pl = [nullid, nullid]
350 350 self._dirty = True
351 351
352 352 def rebuild(self, parent, files):
353 353 self.clear()
354 354 for f in files:
355 355 if 'x' in files.flags(f):
356 356 self._map[f] = ('n', 0777, -1, 0)
357 357 else:
358 358 self._map[f] = ('n', 0666, -1, 0)
359 359 self._pl = (parent, nullid)
360 360 self._dirty = True
361 361
362 362 def write(self):
363 363 if not self._dirty:
364 364 return
365 365 st = self._opener("dirstate", "w", atomictemp=True)
366 366
367 367 try:
368 368 gran = int(self._ui.config('dirstate', 'granularity', 1))
369 369 except ValueError:
370 370 gran = 1
371 371 limit = sys.maxint
372 372 if gran > 0:
373 373 limit = util.fstat(st).st_mtime - gran
374 374
375 375 cs = cStringIO.StringIO()
376 376 copymap = self._copymap
377 377 pack = struct.pack
378 378 write = cs.write
379 379 write("".join(self._pl))
380 380 for f, e in self._map.iteritems():
381 381 if f in copymap:
382 382 f = "%s\0%s" % (f, copymap[f])
383 383 if e[3] > limit and e[0] == 'n':
384 384 e = (e[0], 0, -1, -1)
385 385 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
386 386 write(e)
387 387 write(f)
388 388 st.write(cs.getvalue())
389 389 st.rename()
390 390 self._dirty = self._dirtypl = False
391 391
392 392 def _dirignore(self, f):
393 393 if f == '.':
394 394 return False
395 395 if self._ignore(f):
396 396 return True
397 397 for p in _finddirs(f):
398 398 if self._ignore(p):
399 399 return True
400 400 return False
401 401
402 402 def walk(self, match, unknown, ignored):
403 403 '''
404 404 walk recursively through the directory tree, finding all files
405 405 matched by the match function
406 406
407 407 results are yielded in a tuple (filename, stat), where stat
408 408 and st is the stat result if the file was found in the directory.
409 409 '''
410 410
411 411 def fwarn(f, msg):
412 412 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
413 413 return False
414 414 badfn = fwarn
415 415 if hasattr(match, 'bad'):
416 416 badfn = match.bad
417 417
418 418 def badtype(f, mode):
419 419 kind = 'unknown'
420 420 if stat.S_ISCHR(mode): kind = _('character device')
421 421 elif stat.S_ISBLK(mode): kind = _('block device')
422 422 elif stat.S_ISFIFO(mode): kind = _('fifo')
423 423 elif stat.S_ISSOCK(mode): kind = _('socket')
424 424 elif stat.S_ISDIR(mode): kind = _('directory')
425 425 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
426 426 % (self.pathto(f), kind))
427 427
428 428 ignore = self._ignore
429 429 dirignore = self._dirignore
430 430 if ignored:
431 431 ignore = util.never
432 432 dirignore = util.never
433 433 elif not unknown:
434 434 # if unknown and ignored are False, skip step 2
435 435 ignore = util.always
436 436 dirignore = util.always
437 437
438 438 matchfn = match.matchfn
439 439 dmap = self._map
440 440 normpath = util.normpath
441 441 normalize = self.normalize
442 442 listdir = osutil.listdir
443 443 lstat = os.lstat
444 444 pconvert = util.pconvert
445 445 getkind = stat.S_IFMT
446 446 dirkind = stat.S_IFDIR
447 447 regkind = stat.S_IFREG
448 448 lnkkind = stat.S_IFLNK
449 449 join = self._join
450 450 work = []
451 451 wadd = work.append
452 452
453 453 files = util.unique(match.files())
454 454 if not files or '.' in files:
455 455 files = ['']
456 456 results = {'.hg': None}
457 457
458 458 # step 1: find all explicit files
459 459 for ff in util.sort(files):
460 460 nf = normalize(normpath(ff))
461 461 if nf in results:
462 462 continue
463 463
464 464 try:
465 465 st = lstat(join(nf))
466 466 kind = getkind(st.st_mode)
467 467 if kind == dirkind:
468 468 if not dirignore(nf):
469 469 wadd(nf)
470 470 elif kind == regkind or kind == lnkkind:
471 471 results[nf] = st
472 472 else:
473 473 badtype(ff, kind)
474 474 if nf in dmap:
475 475 results[nf] = None
476 476 except OSError, inst:
477 477 keep = False
478 478 prefix = nf + "/"
479 479 for fn in dmap:
480 480 if nf == fn or fn.startswith(prefix):
481 481 keep = True
482 482 break
483 483 if not keep:
484 484 if inst.errno != errno.ENOENT:
485 485 fwarn(ff, inst.strerror)
486 486 elif badfn(ff, inst.strerror):
487 487 if (nf in dmap or not ignore(nf)) and matchfn(nf):
488 488 results[nf] = None
489 489
490 490 # step 2: visit subdirectories
491 491 while work:
492 492 nd = work.pop()
493 493 if hasattr(match, 'dir'):
494 494 match.dir(nd)
495 495 skip = None
496 496 if nd == '.':
497 497 nd = ''
498 498 else:
499 499 skip = '.hg'
500 500 try:
501 501 entries = listdir(join(nd), stat=True, skip=skip)
502 502 except OSError, inst:
503 503 if inst.errno == errno.EACCES:
504 504 fwarn(nd, inst.strerror)
505 505 continue
506 506 raise
507 507 for f, kind, st in entries:
508 508 nf = normalize(nd and (nd + "/" + f) or f, True)
509 509 if nf not in results:
510 510 if kind == dirkind:
511 511 if not ignore(nf):
512 512 wadd(nf)
513 513 if nf in dmap and matchfn(nf):
514 514 results[nf] = None
515 515 elif kind == regkind or kind == lnkkind:
516 516 if nf in dmap:
517 517 if matchfn(nf):
518 518 results[nf] = st
519 519 elif matchfn(nf) and not ignore(nf):
520 520 results[nf] = st
521 521 elif nf in dmap and matchfn(nf):
522 522 results[nf] = None
523 523
524 524 # step 3: report unseen items in the dmap hash
525 525 visit = util.sort([f for f in dmap if f not in results and match(f)])
526 526 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
527 527 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
528 528 st = None
529 529 results[nf] = st
530 530
531 531 del results['.hg']
532 532 return results
533 533
534 534 def status(self, match, ignored, clean, unknown):
535 535 listignored, listclean, listunknown = ignored, clean, unknown
536 536 lookup, modified, added, unknown, ignored = [], [], [], [], []
537 537 removed, deleted, clean = [], [], []
538 538
539 539 _join = self._join
540 540 lstat = os.lstat
541 541 cmap = self._copymap
542 542 dmap = self._map
543 543 ladd = lookup.append
544 544 madd = modified.append
545 545 aadd = added.append
546 546 uadd = unknown.append
547 547 iadd = ignored.append
548 548 radd = removed.append
549 549 dadd = deleted.append
550 550 cadd = clean.append
551 551
552 552 for fn, st in self.walk(match, listunknown, listignored).iteritems():
553 553 if fn not in dmap:
554 554 if (listignored or match.exact(fn)) and self._dirignore(fn):
555 555 if listignored:
556 556 iadd(fn)
557 557 elif listunknown:
558 558 uadd(fn)
559 559 continue
560 560
561 561 state, mode, size, time = dmap[fn]
562 562
563 563 if not st and state in "nma":
564 564 dadd(fn)
565 565 elif state == 'n':
566 566 if (size >= 0 and
567 567 (size != st.st_size
568 568 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
569 569 or size == -2
570 570 or fn in self._copymap):
571 571 madd(fn)
572 572 elif time != int(st.st_mtime):
573 573 ladd(fn)
574 574 elif listclean:
575 575 cadd(fn)
576 576 elif state == 'm':
577 577 madd(fn)
578 578 elif state == 'a':
579 579 aadd(fn)
580 580 elif state == 'r':
581 581 radd(fn)
582 582
583 583 return (lookup, modified, added, removed, deleted, unknown, ignored,
584 584 clean)
@@ -1,413 +1,414
1 1 /*
2 2 parsers.c - efficient content parsing
3 3
4 4 Copyright 2008 Matt Mackall <mpm@selenic.com> and others
5 5
6 6 This software may be used and distributed according to the terms of
7 7 the GNU General Public License, incorporated herein by reference.
8 8 */
9 9
10 10 #include <Python.h>
11 11 #include <ctype.h>
12 12 #include <string.h>
13 13
14 14 static int hexdigit(char c)
15 15 {
16 16 if (c >= '0' && c <= '9')
17 17 return c - '0';
18 18 if (c >= 'a' && c <= 'f')
19 19 return c - 'a' + 10;
20 20 if (c >= 'A' && c <= 'F')
21 21 return c - 'A' + 10;
22 22
23 23 PyErr_SetString(PyExc_ValueError, "input contains non-hex character");
24 24 return 0;
25 25 }
26 26
27 27 /*
28 28 * Turn a hex-encoded string into binary.
29 29 */
30 30 static PyObject *unhexlify(const char *str, int len)
31 31 {
32 32 PyObject *ret;
33 33 const char *c;
34 34 char *d;
35 35
36 36 ret = PyString_FromStringAndSize(NULL, len / 2);
37 37 if (!ret)
38 38 return NULL;
39 39
40 40 d = PyString_AS_STRING(ret);
41 41 for (c = str; c < str + len;) {
42 42 int hi = hexdigit(*c++);
43 43 int lo = hexdigit(*c++);
44 44 *d++ = (hi << 4) | lo;
45 45 }
46 46
47 47 return ret;
48 48 }
49 49
50 50 /*
51 51 * This code assumes that a manifest is stitched together with newline
52 52 * ('\n') characters.
53 53 */
54 54 static PyObject *parse_manifest(PyObject *self, PyObject *args)
55 55 {
56 56 PyObject *mfdict, *fdict;
57 57 char *str, *cur, *start, *zero;
58 58 int len;
59 59
60 60 if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
61 61 &PyDict_Type, &mfdict,
62 62 &PyDict_Type, &fdict,
63 63 &str, &len))
64 64 goto quit;
65 65
66 66 for (start = cur = str, zero = NULL; cur < str + len; cur++) {
67 67 PyObject *file = NULL, *node = NULL;
68 68 PyObject *flags = NULL;
69 69 int nlen;
70 70
71 71 if (!*cur) {
72 72 zero = cur;
73 73 continue;
74 74 }
75 75 else if (*cur != '\n')
76 76 continue;
77 77
78 78 if (!zero) {
79 79 PyErr_SetString(PyExc_ValueError,
80 80 "manifest entry has no separator");
81 81 goto quit;
82 82 }
83 83
84 84 file = PyString_FromStringAndSize(start, zero - start);
85 85 if (!file)
86 86 goto bail;
87 87
88 88 nlen = cur - zero - 1;
89 89
90 90 node = unhexlify(zero + 1, nlen > 40 ? 40 : nlen);
91 91 if (!node)
92 92 goto bail;
93 93
94 94 if (nlen > 40) {
95 95 PyObject *flags;
96 96
97 97 flags = PyString_FromStringAndSize(zero + 41,
98 98 nlen - 40);
99 99 if (!flags)
100 100 goto bail;
101 101
102 102 if (PyDict_SetItem(fdict, file, flags) == -1)
103 103 goto bail;
104 104 }
105 105
106 106 if (PyDict_SetItem(mfdict, file, node) == -1)
107 107 goto bail;
108 108
109 109 start = cur + 1;
110 110 zero = NULL;
111 111
112 112 Py_XDECREF(flags);
113 113 Py_XDECREF(node);
114 114 Py_XDECREF(file);
115 115 continue;
116 116 bail:
117 117 Py_XDECREF(flags);
118 118 Py_XDECREF(node);
119 119 Py_XDECREF(file);
120 120 goto quit;
121 121 }
122 122
123 123 if (len > 0 && *(cur - 1) != '\n') {
124 124 PyErr_SetString(PyExc_ValueError,
125 125 "manifest contains trailing garbage");
126 126 goto quit;
127 127 }
128 128
129 129 Py_INCREF(Py_None);
130 130 return Py_None;
131 131 quit:
132 132 return NULL;
133 133 }
134 134
135 135 #ifdef _WIN32
136 136 # ifdef _MSC_VER
137 137 /* msvc 6.0 has problems */
138 138 # define inline __inline
139 139 typedef unsigned long uint32_t;
140 typedef unsigned __int64 uint64_t;
140 141 # else
141 142 # include <stdint.h>
142 143 # endif
143 144 static uint32_t ntohl(uint32_t x)
144 145 {
145 146 return ((x & 0x000000ffUL) << 24) |
146 147 ((x & 0x0000ff00UL) << 8) |
147 148 ((x & 0x00ff0000UL) >> 8) |
148 149 ((x & 0xff000000UL) >> 24);
149 150 }
150 151 #else
151 152 /* not windows */
152 153 # include <sys/types.h>
153 154 # if defined __BEOS__ && !defined __HAIKU__
154 155 # include <ByteOrder.h>
155 156 # else
156 157 # include <arpa/inet.h>
157 158 # endif
158 159 # include <inttypes.h>
159 160 #endif
160 161
161 162 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
162 163 {
163 164 PyObject *dmap, *cmap, *parents = NULL, *ret = NULL;
164 165 PyObject *fname = NULL, *cname = NULL, *entry = NULL;
165 166 char *str, *cur, *end, *cpos;
166 167 int state, mode, size, mtime, flen;
167 168 int len;
168 169 char decode[16]; /* for alignment */
169 170
170 171 if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
171 172 &PyDict_Type, &dmap,
172 173 &PyDict_Type, &cmap,
173 174 &str, &len))
174 175 goto quit;
175 176
176 177 /* read parents */
177 178 if (len < 40)
178 179 goto quit;
179 180
180 181 parents = Py_BuildValue("s#s#", str, 20, str + 20, 20);
181 182 if (!parents)
182 183 goto quit;
183 184
184 185 /* read filenames */
185 186 cur = str + 40;
186 187 end = str + len;
187 188
188 189 while (cur < end - 17) {
189 190 /* unpack header */
190 191 state = *cur;
191 192 memcpy(decode, cur + 1, 16);
192 193 mode = ntohl(*(uint32_t *)(decode));
193 194 size = ntohl(*(uint32_t *)(decode + 4));
194 195 mtime = ntohl(*(uint32_t *)(decode + 8));
195 196 flen = ntohl(*(uint32_t *)(decode + 12));
196 197 cur += 17;
197 198 if (cur + flen > end)
198 199 goto quit;
199 200
200 201 entry = Py_BuildValue("ciii", state, mode, size, mtime);
201 202 PyObject_GC_UnTrack(entry); /* don't waste time with this */
202 203 if (!entry)
203 204 goto quit;
204 205
205 206 cpos = memchr(cur, 0, flen);
206 207 if (cpos) {
207 208 fname = PyString_FromStringAndSize(cur, cpos - cur);
208 209 cname = PyString_FromStringAndSize(cpos + 1,
209 210 flen - (cpos - cur) - 1);
210 211 if (!fname || !cname ||
211 212 PyDict_SetItem(cmap, fname, cname) == -1 ||
212 213 PyDict_SetItem(dmap, fname, entry) == -1)
213 214 goto quit;
214 215 Py_DECREF(cname);
215 216 } else {
216 217 fname = PyString_FromStringAndSize(cur, flen);
217 218 if (!fname ||
218 219 PyDict_SetItem(dmap, fname, entry) == -1)
219 220 goto quit;
220 221 }
221 222 cur += flen;
222 223 Py_DECREF(fname);
223 224 Py_DECREF(entry);
224 225 fname = cname = entry = NULL;
225 226 }
226 227
227 228 ret = parents;
228 229 Py_INCREF(ret);
229 230 quit:
230 231 Py_XDECREF(fname);
231 232 Py_XDECREF(cname);
232 233 Py_XDECREF(entry);
233 234 Py_XDECREF(parents);
234 235 return ret;
235 236 }
236 237
237 238 const char nullid[20];
238 239 const int nullrev = -1;
239 240
240 241 /* RevlogNG format (all in big endian, data may be inlined):
241 242 * 6 bytes: offset
242 243 * 2 bytes: flags
243 244 * 4 bytes: compressed length
244 245 * 4 bytes: uncompressed length
245 246 * 4 bytes: base revision
246 247 * 4 bytes: link revision
247 248 * 4 bytes: parent 1 revision
248 249 * 4 bytes: parent 2 revision
249 250 * 32 bytes: nodeid (only 20 bytes used)
250 251 */
251 252 static int _parse_index_ng (const char *data, int size, int inlined,
252 253 PyObject *index, PyObject *nodemap)
253 254 {
254 255 PyObject *entry = NULL, *node_id = NULL, *n_obj = NULL;
255 256 PyObject *nullrev_obj = NULL, *nullid_obj = NULL;
256 257 int comp_len, uncomp_len, base_rev, link_rev, parent_1, parent_2;
257 258 uint64_t offset_flags;
258 259 int n = 0;
259 260 const char *end = data + size;
260 261
261 262 while (data < end) {
262 263 offset_flags = ntohl(*((uint32_t *) (data + 4)));
263 264 if (n == 0) /* mask out version number for the first entry */
264 265 offset_flags &= 0xFFFF;
265 266 else {
266 267 uint32_t offset_high = ntohl(*((uint32_t *) data));
267 268 offset_flags |= ((uint64_t) offset_high) << 32;
268 269 }
269 270
270 271
271 272 comp_len = ntohl(*((uint32_t *) (data + 8)));
272 273 uncomp_len = ntohl(*((uint32_t *) (data + 12)));
273 274 base_rev = ntohl(*((uint32_t *) (data + 16)));
274 275 link_rev = ntohl(*((uint32_t *) (data + 20)));
275 276 parent_1 = ntohl(*((uint32_t *) (data + 24)));
276 277 parent_2 = ntohl(*((uint32_t *) (data + 28)));
277 278 node_id = PyString_FromStringAndSize(data + 32, 20);
278 279 n_obj = PyInt_FromLong(n);
279 280 if (!node_id || !n_obj ||
280 281 PyDict_SetItem(nodemap, node_id, n_obj) != 0)
281 282 goto quit;
282 283 Py_DECREF(n_obj);
283 284
284 285 entry = Py_BuildValue("LiiiiiiN", offset_flags, comp_len,
285 286 uncomp_len, base_rev, link_rev,
286 287 parent_1, parent_2, node_id);
287 288 PyObject_GC_UnTrack(entry); /* don't waste time with this */
288 289 if (!entry)
289 290 goto quit;
290 291
291 292 /* append to or set value in the index list */
292 293 if (inlined) {
293 294 if (PyList_Append(index, entry) != 0)
294 295 goto quit;
295 296 Py_DECREF(entry);
296 297 } else {
297 298 PyList_SET_ITEM(index, n, entry); /* steals reference */
298 299 }
299 300
300 301 data += 64 + (inlined ? comp_len : 0);
301 302 n++;
302 303 }
303 304 if (data > end) {
304 305 if (!PyErr_Occurred())
305 306 PyErr_SetString(PyExc_ValueError, "corrupt index file");
306 307 goto quit;
307 308 }
308 309
309 310 /* create the nullid/nullrev entry in the nodemap and the
310 311 * magic nullid entry in the index at [-1] */
311 312 nullid_obj = PyString_FromStringAndSize(nullid, 20);
312 313 nullrev_obj = PyInt_FromLong(nullrev);
313 314 if (!nodemap || !nullid_obj || !nullrev_obj ||
314 315 PyDict_SetItem(nodemap, nullid_obj, nullrev_obj) != 0)
315 316 goto quit;
316 317 Py_DECREF(nullrev_obj);
317 318
318 319 entry = Py_BuildValue("iiiiiiiN", 0, 0, 0, -1, -1, -1, -1, nullid_obj);
319 320 PyObject_GC_UnTrack(entry); /* don't waste time with this */
320 321 if (!entry)
321 322 goto quit;
322 323 if (inlined) {
323 324 if (PyList_Append(index, entry) != 0)
324 325 goto quit;
325 326 Py_DECREF(entry);
326 327 } else {
327 328 PyList_SET_ITEM(index, n, entry); /* steals reference */
328 329 }
329 330
330 331 return 1;
331 332
332 333 quit:
333 334 Py_XDECREF(n_obj);
334 335 Py_XDECREF(node_id);
335 336 Py_XDECREF(entry);
336 337 Py_XDECREF(nullrev_obj);
337 338 Py_XDECREF(nullid_obj);
338 339 return 0;
339 340 }
340 341
341 342
342 343
343 344 /* This function parses a index file and returns a Python tuple of the
344 345 * following format: (index, nodemap, cache)
345 346 *
346 347 * index: a list of tuples containing the RevlogNG records
347 348 * nodemap: a dict mapping node ids to indices in the index list
348 349 * cache: if data is inlined, a tuple (index_file_content, 0) else None
349 350 */
350 351 static PyObject *parse_index(PyObject *self, PyObject *args)
351 352 {
352 353 const char *data;
353 354 int size, inlined;
354 355 PyObject *rval = NULL, *index = NULL, *nodemap = NULL, *cache = NULL;
355 356 PyObject *data_obj = NULL, *inlined_obj;
356 357
357 358 if (!PyArg_ParseTuple(args, "s#O", &data, &size, &inlined_obj))
358 359 return NULL;
359 360 inlined = inlined_obj && PyObject_IsTrue(inlined_obj);
360 361
361 362 /* If no data is inlined, we know the size of the index list in
362 363 * advance: size divided by size of one one revlog record (64 bytes)
363 364 * plus one for the nullid */
364 365 index = inlined ? PyList_New(0) : PyList_New(size / 64 + 1);
365 366 if (!index)
366 367 goto quit;
367 368
368 369 nodemap = PyDict_New();
369 370
370 371 /* set up the cache return value */
371 372 if (inlined) {
372 373 /* Note that the reference to data_obj is only borrowed */
373 374 data_obj = PyTuple_GET_ITEM(args, 0);
374 375 cache = Py_BuildValue("iO", 0, data_obj);
375 376 if (!cache)
376 377 goto quit;
377 378 } else {
378 379 cache = Py_None;
379 380 Py_INCREF(Py_None);
380 381 }
381 382
382 383 /* actually populate the index and the nodemap with data */
383 384 if (!_parse_index_ng (data, size, inlined, index, nodemap))
384 385 goto quit;
385 386
386 387 rval = Py_BuildValue("NNN", index, nodemap, cache);
387 388 if (!rval)
388 389 goto quit;
389 390 return rval;
390 391
391 392 quit:
392 393 Py_XDECREF(index);
393 394 Py_XDECREF(nodemap);
394 395 Py_XDECREF(cache);
395 396 Py_XDECREF(rval);
396 397 Py_XDECREF(data_obj);
397 398 return NULL;
398 399 }
399 400
400 401
401 402 static char parsers_doc[] = "Efficient content parsing.";
402 403
403 404 static PyMethodDef methods[] = {
404 405 {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
405 406 {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
406 407 {"parse_index", parse_index, METH_VARARGS, "parse a revlog index\n"},
407 408 {NULL, NULL}
408 409 };
409 410
410 411 PyMODINIT_FUNC initparsers(void)
411 412 {
412 413 Py_InitModule3("parsers", methods, parsers_doc);
413 414 }
@@ -1,1939 +1,1943
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import _
16 16 import cStringIO, errno, getpass, re, shutil, sys, tempfile
17 17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
18 18 import imp, urlparse
19 19
20 20 # Python compatibility
21 21
22 22 try:
23 23 set = set
24 24 frozenset = frozenset
25 25 except NameError:
26 26 from sets import Set as set, ImmutableSet as frozenset
27 27
28 28 _md5 = None
29 29 def md5(s):
30 30 global _md5
31 31 if _md5 is None:
32 32 try:
33 33 import hashlib
34 34 _md5 = hashlib.md5
35 35 except ImportError:
36 36 import md5
37 37 _md5 = md5.md5
38 38 return _md5(s)
39 39
40 40 _sha1 = None
41 41 def sha1(s):
42 42 global _sha1
43 43 if _sha1 is None:
44 44 try:
45 45 import hashlib
46 46 _sha1 = hashlib.sha1
47 47 except ImportError:
48 48 import sha
49 49 _sha1 = sha.sha
50 50 return _sha1(s)
51 51
52 52 try:
53 53 import subprocess
54 closefds = os.name == 'posix'
54 55 def popen2(cmd, mode='t', bufsize=-1):
55 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize, close_fds=True,
56 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
57 close_fds=closefds,
56 58 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
57 59 return p.stdin, p.stdout
58 60 def popen3(cmd, mode='t', bufsize=-1):
59 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize, close_fds=True,
61 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
62 close_fds=closefds,
60 63 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 64 stderr=subprocess.PIPE)
62 65 return p.stdin, p.stdout, p.stderr
63 66 def Popen3(cmd, capturestderr=False, bufsize=-1):
64 67 stderr = capturestderr and subprocess.PIPE or None
65 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize, close_fds=True,
68 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
69 close_fds=closefds,
66 70 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
67 71 stderr=stderr)
68 72 p.fromchild = p.stdout
69 73 p.tochild = p.stdin
70 74 p.childerr = p.stderr
71 75 return p
72 76 except ImportError:
73 77 subprocess = None
74 78 import popen2 as _popen2
75 79 popen2 = _popen2.popen2
76 80 Popen3 = _popen2.Popen3
77 81
78 82
79 83 try:
80 84 _encoding = os.environ.get("HGENCODING")
81 85 if sys.platform == 'darwin' and not _encoding:
82 86 # On darwin, getpreferredencoding ignores the locale environment and
83 87 # always returns mac-roman. We override this if the environment is
84 88 # not C (has been customized by the user).
85 89 locale.setlocale(locale.LC_CTYPE, '')
86 90 _encoding = locale.getlocale()[1]
87 91 if not _encoding:
88 92 _encoding = locale.getpreferredencoding() or 'ascii'
89 93 except locale.Error:
90 94 _encoding = 'ascii'
91 95 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
92 96 _fallbackencoding = 'ISO-8859-1'
93 97
94 98 def tolocal(s):
95 99 """
96 100 Convert a string from internal UTF-8 to local encoding
97 101
98 102 All internal strings should be UTF-8 but some repos before the
99 103 implementation of locale support may contain latin1 or possibly
100 104 other character sets. We attempt to decode everything strictly
101 105 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
102 106 replace unknown characters.
103 107 """
104 108 for e in ('UTF-8', _fallbackencoding):
105 109 try:
106 110 u = s.decode(e) # attempt strict decoding
107 111 return u.encode(_encoding, "replace")
108 112 except LookupError, k:
109 113 raise Abort(_("%s, please check your locale settings") % k)
110 114 except UnicodeDecodeError:
111 115 pass
112 116 u = s.decode("utf-8", "replace") # last ditch
113 117 return u.encode(_encoding, "replace")
114 118
115 119 def fromlocal(s):
116 120 """
117 121 Convert a string from the local character encoding to UTF-8
118 122
119 123 We attempt to decode strings using the encoding mode set by
120 124 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
121 125 characters will cause an error message. Other modes include
122 126 'replace', which replaces unknown characters with a special
123 127 Unicode character, and 'ignore', which drops the character.
124 128 """
125 129 try:
126 130 return s.decode(_encoding, _encodingmode).encode("utf-8")
127 131 except UnicodeDecodeError, inst:
128 132 sub = s[max(0, inst.start-10):inst.start+10]
129 133 raise Abort("decoding near '%s': %s!" % (sub, inst))
130 134 except LookupError, k:
131 135 raise Abort(_("%s, please check your locale settings") % k)
132 136
133 137 def locallen(s):
134 138 """Find the length in characters of a local string"""
135 139 return len(s.decode(_encoding, "replace"))
136 140
137 141 # used by parsedate
138 142 defaultdateformats = (
139 143 '%Y-%m-%d %H:%M:%S',
140 144 '%Y-%m-%d %I:%M:%S%p',
141 145 '%Y-%m-%d %H:%M',
142 146 '%Y-%m-%d %I:%M%p',
143 147 '%Y-%m-%d',
144 148 '%m-%d',
145 149 '%m/%d',
146 150 '%m/%d/%y',
147 151 '%m/%d/%Y',
148 152 '%a %b %d %H:%M:%S %Y',
149 153 '%a %b %d %I:%M:%S%p %Y',
150 154 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
151 155 '%b %d %H:%M:%S %Y',
152 156 '%b %d %I:%M:%S%p %Y',
153 157 '%b %d %H:%M:%S',
154 158 '%b %d %I:%M:%S%p',
155 159 '%b %d %H:%M',
156 160 '%b %d %I:%M%p',
157 161 '%b %d %Y',
158 162 '%b %d',
159 163 '%H:%M:%S',
160 164 '%I:%M:%SP',
161 165 '%H:%M',
162 166 '%I:%M%p',
163 167 )
164 168
165 169 extendeddateformats = defaultdateformats + (
166 170 "%Y",
167 171 "%Y-%m",
168 172 "%b",
169 173 "%b %Y",
170 174 )
171 175
172 176 class SignalInterrupt(Exception):
173 177 """Exception raised on SIGTERM and SIGHUP."""
174 178
175 179 # differences from SafeConfigParser:
176 180 # - case-sensitive keys
177 181 # - allows values that are not strings (this means that you may not
178 182 # be able to save the configuration to a file)
179 183 class configparser(ConfigParser.SafeConfigParser):
180 184 def optionxform(self, optionstr):
181 185 return optionstr
182 186
183 187 def set(self, section, option, value):
184 188 return ConfigParser.ConfigParser.set(self, section, option, value)
185 189
186 190 def _interpolate(self, section, option, rawval, vars):
187 191 if not isinstance(rawval, basestring):
188 192 return rawval
189 193 return ConfigParser.SafeConfigParser._interpolate(self, section,
190 194 option, rawval, vars)
191 195
192 196 def cachefunc(func):
193 197 '''cache the result of function calls'''
194 198 # XXX doesn't handle keywords args
195 199 cache = {}
196 200 if func.func_code.co_argcount == 1:
197 201 # we gain a small amount of time because
198 202 # we don't need to pack/unpack the list
199 203 def f(arg):
200 204 if arg not in cache:
201 205 cache[arg] = func(arg)
202 206 return cache[arg]
203 207 else:
204 208 def f(*args):
205 209 if args not in cache:
206 210 cache[args] = func(*args)
207 211 return cache[args]
208 212
209 213 return f
210 214
211 215 def pipefilter(s, cmd):
212 216 '''filter string S through command CMD, returning its output'''
213 217 (pin, pout) = popen2(cmd, 'b')
214 218 def writer():
215 219 try:
216 220 pin.write(s)
217 221 pin.close()
218 222 except IOError, inst:
219 223 if inst.errno != errno.EPIPE:
220 224 raise
221 225
222 226 # we should use select instead on UNIX, but this will work on most
223 227 # systems, including Windows
224 228 w = threading.Thread(target=writer)
225 229 w.start()
226 230 f = pout.read()
227 231 pout.close()
228 232 w.join()
229 233 return f
230 234
231 235 def tempfilter(s, cmd):
232 236 '''filter string S through a pair of temporary files with CMD.
233 237 CMD is used as a template to create the real command to be run,
234 238 with the strings INFILE and OUTFILE replaced by the real names of
235 239 the temporary files generated.'''
236 240 inname, outname = None, None
237 241 try:
238 242 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
239 243 fp = os.fdopen(infd, 'wb')
240 244 fp.write(s)
241 245 fp.close()
242 246 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
243 247 os.close(outfd)
244 248 cmd = cmd.replace('INFILE', inname)
245 249 cmd = cmd.replace('OUTFILE', outname)
246 250 code = os.system(cmd)
247 251 if sys.platform == 'OpenVMS' and code & 1:
248 252 code = 0
249 253 if code: raise Abort(_("command '%s' failed: %s") %
250 254 (cmd, explain_exit(code)))
251 255 return open(outname, 'rb').read()
252 256 finally:
253 257 try:
254 258 if inname: os.unlink(inname)
255 259 except: pass
256 260 try:
257 261 if outname: os.unlink(outname)
258 262 except: pass
259 263
260 264 filtertable = {
261 265 'tempfile:': tempfilter,
262 266 'pipe:': pipefilter,
263 267 }
264 268
265 269 def filter(s, cmd):
266 270 "filter a string through a command that transforms its input to its output"
267 271 for name, fn in filtertable.iteritems():
268 272 if cmd.startswith(name):
269 273 return fn(s, cmd[len(name):].lstrip())
270 274 return pipefilter(s, cmd)
271 275
272 276 def binary(s):
273 277 """return true if a string is binary data"""
274 278 if s and '\0' in s:
275 279 return True
276 280 return False
277 281
278 282 def unique(g):
279 283 """return the uniq elements of iterable g"""
280 284 return dict.fromkeys(g).keys()
281 285
282 286 def sort(l):
283 287 if not isinstance(l, list):
284 288 l = list(l)
285 289 l.sort()
286 290 return l
287 291
288 292 class Abort(Exception):
289 293 """Raised if a command needs to print an error and exit."""
290 294
291 295 class UnexpectedOutput(Abort):
292 296 """Raised to print an error with part of output and exit."""
293 297
294 298 def always(fn): return True
295 299 def never(fn): return False
296 300
297 301 def expand_glob(pats):
298 302 '''On Windows, expand the implicit globs in a list of patterns'''
299 303 if os.name != 'nt':
300 304 return list(pats)
301 305 ret = []
302 306 for p in pats:
303 307 kind, name = patkind(p, None)
304 308 if kind is None:
305 309 globbed = glob.glob(name)
306 310 if globbed:
307 311 ret.extend(globbed)
308 312 continue
309 313 # if we couldn't expand the glob, just keep it around
310 314 ret.append(p)
311 315 return ret
312 316
313 317 def patkind(name, default):
314 318 """Split a string into an optional pattern kind prefix and the
315 319 actual pattern."""
316 320 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
317 321 if name.startswith(prefix + ':'): return name.split(':', 1)
318 322 return default, name
319 323
320 324 def globre(pat, head='^', tail='$'):
321 325 "convert a glob pattern into a regexp"
322 326 i, n = 0, len(pat)
323 327 res = ''
324 328 group = 0
325 329 def peek(): return i < n and pat[i]
326 330 while i < n:
327 331 c = pat[i]
328 332 i = i+1
329 333 if c == '*':
330 334 if peek() == '*':
331 335 i += 1
332 336 res += '.*'
333 337 else:
334 338 res += '[^/]*'
335 339 elif c == '?':
336 340 res += '.'
337 341 elif c == '[':
338 342 j = i
339 343 if j < n and pat[j] in '!]':
340 344 j += 1
341 345 while j < n and pat[j] != ']':
342 346 j += 1
343 347 if j >= n:
344 348 res += '\\['
345 349 else:
346 350 stuff = pat[i:j].replace('\\','\\\\')
347 351 i = j + 1
348 352 if stuff[0] == '!':
349 353 stuff = '^' + stuff[1:]
350 354 elif stuff[0] == '^':
351 355 stuff = '\\' + stuff
352 356 res = '%s[%s]' % (res, stuff)
353 357 elif c == '{':
354 358 group += 1
355 359 res += '(?:'
356 360 elif c == '}' and group:
357 361 res += ')'
358 362 group -= 1
359 363 elif c == ',' and group:
360 364 res += '|'
361 365 elif c == '\\':
362 366 p = peek()
363 367 if p:
364 368 i += 1
365 369 res += re.escape(p)
366 370 else:
367 371 res += re.escape(c)
368 372 else:
369 373 res += re.escape(c)
370 374 return head + res + tail
371 375
372 376 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
373 377
374 378 def pathto(root, n1, n2):
375 379 '''return the relative path from one place to another.
376 380 root should use os.sep to separate directories
377 381 n1 should use os.sep to separate directories
378 382 n2 should use "/" to separate directories
379 383 returns an os.sep-separated path.
380 384
381 385 If n1 is a relative path, it's assumed it's
382 386 relative to root.
383 387 n2 should always be relative to root.
384 388 '''
385 389 if not n1: return localpath(n2)
386 390 if os.path.isabs(n1):
387 391 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
388 392 return os.path.join(root, localpath(n2))
389 393 n2 = '/'.join((pconvert(root), n2))
390 394 a, b = splitpath(n1), n2.split('/')
391 395 a.reverse()
392 396 b.reverse()
393 397 while a and b and a[-1] == b[-1]:
394 398 a.pop()
395 399 b.pop()
396 400 b.reverse()
397 401 return os.sep.join((['..'] * len(a)) + b) or '.'
398 402
399 403 def canonpath(root, cwd, myname):
400 404 """return the canonical path of myname, given cwd and root"""
401 405 if root == os.sep:
402 406 rootsep = os.sep
403 407 elif endswithsep(root):
404 408 rootsep = root
405 409 else:
406 410 rootsep = root + os.sep
407 411 name = myname
408 412 if not os.path.isabs(name):
409 413 name = os.path.join(root, cwd, name)
410 414 name = os.path.normpath(name)
411 415 audit_path = path_auditor(root)
412 416 if name != rootsep and name.startswith(rootsep):
413 417 name = name[len(rootsep):]
414 418 audit_path(name)
415 419 return pconvert(name)
416 420 elif name == root:
417 421 return ''
418 422 else:
419 423 # Determine whether `name' is in the hierarchy at or beneath `root',
420 424 # by iterating name=dirname(name) until that causes no change (can't
421 425 # check name == '/', because that doesn't work on windows). For each
422 426 # `name', compare dev/inode numbers. If they match, the list `rel'
423 427 # holds the reversed list of components making up the relative file
424 428 # name we want.
425 429 root_st = os.stat(root)
426 430 rel = []
427 431 while True:
428 432 try:
429 433 name_st = os.stat(name)
430 434 except OSError:
431 435 break
432 436 if samestat(name_st, root_st):
433 437 if not rel:
434 438 # name was actually the same as root (maybe a symlink)
435 439 return ''
436 440 rel.reverse()
437 441 name = os.path.join(*rel)
438 442 audit_path(name)
439 443 return pconvert(name)
440 444 dirname, basename = os.path.split(name)
441 445 rel.append(basename)
442 446 if dirname == name:
443 447 break
444 448 name = dirname
445 449
446 450 raise Abort('%s not under root' % myname)
447 451
448 452 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
449 453 """build a function to match a set of file patterns
450 454
451 455 arguments:
452 456 canonroot - the canonical root of the tree you're matching against
453 457 cwd - the current working directory, if relevant
454 458 names - patterns to find
455 459 inc - patterns to include
456 460 exc - patterns to exclude
457 461 dflt_pat - if a pattern in names has no explicit type, assume this one
458 462 src - where these patterns came from (e.g. .hgignore)
459 463
460 464 a pattern is one of:
461 465 'glob:<glob>' - a glob relative to cwd
462 466 're:<regexp>' - a regular expression
463 467 'path:<path>' - a path relative to canonroot
464 468 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
465 469 'relpath:<path>' - a path relative to cwd
466 470 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
467 471 '<something>' - one of the cases above, selected by the dflt_pat argument
468 472
469 473 returns:
470 474 a 3-tuple containing
471 475 - list of roots (places where one should start a recursive walk of the fs);
472 476 this often matches the explicit non-pattern names passed in, but also
473 477 includes the initial part of glob: patterns that has no glob characters
474 478 - a bool match(filename) function
475 479 - a bool indicating if any patterns were passed in
476 480 """
477 481
478 482 # a common case: no patterns at all
479 483 if not names and not inc and not exc:
480 484 return [], always, False
481 485
482 486 def contains_glob(name):
483 487 for c in name:
484 488 if c in _globchars: return True
485 489 return False
486 490
487 491 def regex(kind, name, tail):
488 492 '''convert a pattern into a regular expression'''
489 493 if not name:
490 494 return ''
491 495 if kind == 're':
492 496 return name
493 497 elif kind == 'path':
494 498 return '^' + re.escape(name) + '(?:/|$)'
495 499 elif kind == 'relglob':
496 500 return globre(name, '(?:|.*/)', tail)
497 501 elif kind == 'relpath':
498 502 return re.escape(name) + '(?:/|$)'
499 503 elif kind == 'relre':
500 504 if name.startswith('^'):
501 505 return name
502 506 return '.*' + name
503 507 return globre(name, '', tail)
504 508
505 509 def matchfn(pats, tail):
506 510 """build a matching function from a set of patterns"""
507 511 if not pats:
508 512 return
509 513 try:
510 514 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
511 515 if len(pat) > 20000:
512 516 raise OverflowError()
513 517 return re.compile(pat).match
514 518 except OverflowError:
515 519 # We're using a Python with a tiny regex engine and we
516 520 # made it explode, so we'll divide the pattern list in two
517 521 # until it works
518 522 l = len(pats)
519 523 if l < 2:
520 524 raise
521 525 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
522 526 return lambda s: a(s) or b(s)
523 527 except re.error:
524 528 for k, p in pats:
525 529 try:
526 530 re.compile('(?:%s)' % regex(k, p, tail))
527 531 except re.error:
528 532 if src:
529 533 raise Abort("%s: invalid pattern (%s): %s" %
530 534 (src, k, p))
531 535 else:
532 536 raise Abort("invalid pattern (%s): %s" % (k, p))
533 537 raise Abort("invalid pattern")
534 538
535 539 def globprefix(pat):
536 540 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
537 541 root = []
538 542 for p in pat.split('/'):
539 543 if contains_glob(p): break
540 544 root.append(p)
541 545 return '/'.join(root) or '.'
542 546
543 547 def normalizepats(names, default):
544 548 pats = []
545 549 roots = []
546 550 anypats = False
547 551 for kind, name in [patkind(p, default) for p in names]:
548 552 if kind in ('glob', 'relpath'):
549 553 name = canonpath(canonroot, cwd, name)
550 554 elif kind in ('relglob', 'path'):
551 555 name = normpath(name)
552 556
553 557 pats.append((kind, name))
554 558
555 559 if kind in ('glob', 're', 'relglob', 'relre'):
556 560 anypats = True
557 561
558 562 if kind == 'glob':
559 563 root = globprefix(name)
560 564 roots.append(root)
561 565 elif kind in ('relpath', 'path'):
562 566 roots.append(name or '.')
563 567 elif kind == 'relglob':
564 568 roots.append('.')
565 569 return roots, pats, anypats
566 570
567 571 roots, pats, anypats = normalizepats(names, dflt_pat)
568 572
569 573 patmatch = matchfn(pats, '$') or always
570 574 incmatch = always
571 575 if inc:
572 576 dummy, inckinds, dummy = normalizepats(inc, 'glob')
573 577 incmatch = matchfn(inckinds, '(?:/|$)')
574 578 excmatch = lambda fn: False
575 579 if exc:
576 580 dummy, exckinds, dummy = normalizepats(exc, 'glob')
577 581 excmatch = matchfn(exckinds, '(?:/|$)')
578 582
579 583 if not names and inc and not exc:
580 584 # common case: hgignore patterns
581 585 match = incmatch
582 586 else:
583 587 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
584 588
585 589 return (roots, match, (inc or exc or anypats) and True)
586 590
587 591 _hgexecutable = None
588 592
589 593 def main_is_frozen():
590 594 """return True if we are a frozen executable.
591 595
592 596 The code supports py2exe (most common, Windows only) and tools/freeze
593 597 (portable, not much used).
594 598 """
595 599 return (hasattr(sys, "frozen") or # new py2exe
596 600 hasattr(sys, "importers") or # old py2exe
597 601 imp.is_frozen("__main__")) # tools/freeze
598 602
599 603 def hgexecutable():
600 604 """return location of the 'hg' executable.
601 605
602 606 Defaults to $HG or 'hg' in the search path.
603 607 """
604 608 if _hgexecutable is None:
605 609 hg = os.environ.get('HG')
606 610 if hg:
607 611 set_hgexecutable(hg)
608 612 elif main_is_frozen():
609 613 set_hgexecutable(sys.executable)
610 614 else:
611 615 set_hgexecutable(find_exe('hg', 'hg'))
612 616 return _hgexecutable
613 617
614 618 def set_hgexecutable(path):
615 619 """set location of the 'hg' executable"""
616 620 global _hgexecutable
617 621 _hgexecutable = path
618 622
619 623 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
620 624 '''enhanced shell command execution.
621 625 run with environment maybe modified, maybe in different dir.
622 626
623 627 if command fails and onerr is None, return status. if ui object,
624 628 print error message and return status, else raise onerr object as
625 629 exception.'''
626 630 def py2shell(val):
627 631 'convert python object into string that is useful to shell'
628 632 if val in (None, False):
629 633 return '0'
630 634 if val == True:
631 635 return '1'
632 636 return str(val)
633 637 oldenv = {}
634 638 for k in environ:
635 639 oldenv[k] = os.environ.get(k)
636 640 if cwd is not None:
637 641 oldcwd = os.getcwd()
638 642 origcmd = cmd
639 643 if os.name == 'nt':
640 644 cmd = '"%s"' % cmd
641 645 try:
642 646 for k, v in environ.iteritems():
643 647 os.environ[k] = py2shell(v)
644 648 os.environ['HG'] = hgexecutable()
645 649 if cwd is not None and oldcwd != cwd:
646 650 os.chdir(cwd)
647 651 rc = os.system(cmd)
648 652 if sys.platform == 'OpenVMS' and rc & 1:
649 653 rc = 0
650 654 if rc and onerr:
651 655 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
652 656 explain_exit(rc)[0])
653 657 if errprefix:
654 658 errmsg = '%s: %s' % (errprefix, errmsg)
655 659 try:
656 660 onerr.warn(errmsg + '\n')
657 661 except AttributeError:
658 662 raise onerr(errmsg)
659 663 return rc
660 664 finally:
661 665 for k, v in oldenv.iteritems():
662 666 if v is None:
663 667 del os.environ[k]
664 668 else:
665 669 os.environ[k] = v
666 670 if cwd is not None and oldcwd != cwd:
667 671 os.chdir(oldcwd)
668 672
669 673 # os.path.lexists is not available on python2.3
670 674 def lexists(filename):
671 675 "test whether a file with this name exists. does not follow symlinks"
672 676 try:
673 677 os.lstat(filename)
674 678 except:
675 679 return False
676 680 return True
677 681
678 682 def rename(src, dst):
679 683 """forcibly rename a file"""
680 684 try:
681 685 os.rename(src, dst)
682 686 except OSError, err: # FIXME: check err (EEXIST ?)
683 687 # on windows, rename to existing file is not allowed, so we
684 688 # must delete destination first. but if file is open, unlink
685 689 # schedules it for delete but does not delete it. rename
686 690 # happens immediately even for open files, so we create
687 691 # temporary file, delete it, rename destination to that name,
688 692 # then delete that. then rename is safe to do.
689 693 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
690 694 os.close(fd)
691 695 os.unlink(temp)
692 696 os.rename(dst, temp)
693 697 os.unlink(temp)
694 698 os.rename(src, dst)
695 699
696 700 def unlink(f):
697 701 """unlink and remove the directory if it is empty"""
698 702 os.unlink(f)
699 703 # try removing directories that might now be empty
700 704 try:
701 705 os.removedirs(os.path.dirname(f))
702 706 except OSError:
703 707 pass
704 708
705 709 def copyfile(src, dest):
706 710 "copy a file, preserving mode"
707 711 if os.path.islink(src):
708 712 try:
709 713 os.unlink(dest)
710 714 except:
711 715 pass
712 716 os.symlink(os.readlink(src), dest)
713 717 else:
714 718 try:
715 719 shutil.copyfile(src, dest)
716 720 shutil.copymode(src, dest)
717 721 except shutil.Error, inst:
718 722 raise Abort(str(inst))
719 723
720 724 def copyfiles(src, dst, hardlink=None):
721 725 """Copy a directory tree using hardlinks if possible"""
722 726
723 727 if hardlink is None:
724 728 hardlink = (os.stat(src).st_dev ==
725 729 os.stat(os.path.dirname(dst)).st_dev)
726 730
727 731 if os.path.isdir(src):
728 732 os.mkdir(dst)
729 733 for name, kind in osutil.listdir(src):
730 734 srcname = os.path.join(src, name)
731 735 dstname = os.path.join(dst, name)
732 736 copyfiles(srcname, dstname, hardlink)
733 737 else:
734 738 if hardlink:
735 739 try:
736 740 os_link(src, dst)
737 741 except (IOError, OSError):
738 742 hardlink = False
739 743 shutil.copy(src, dst)
740 744 else:
741 745 shutil.copy(src, dst)
742 746
743 747 class path_auditor(object):
744 748 '''ensure that a filesystem path contains no banned components.
745 749 the following properties of a path are checked:
746 750
747 751 - under top-level .hg
748 752 - starts at the root of a windows drive
749 753 - contains ".."
750 754 - traverses a symlink (e.g. a/symlink_here/b)
751 755 - inside a nested repository'''
752 756
753 757 def __init__(self, root):
754 758 self.audited = set()
755 759 self.auditeddir = set()
756 760 self.root = root
757 761
758 762 def __call__(self, path):
759 763 if path in self.audited:
760 764 return
761 765 normpath = os.path.normcase(path)
762 766 parts = splitpath(normpath)
763 767 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
764 768 or os.pardir in parts):
765 769 raise Abort(_("path contains illegal component: %s") % path)
766 770 def check(prefix):
767 771 curpath = os.path.join(self.root, prefix)
768 772 try:
769 773 st = os.lstat(curpath)
770 774 except OSError, err:
771 775 # EINVAL can be raised as invalid path syntax under win32.
772 776 # They must be ignored for patterns can be checked too.
773 777 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
774 778 raise
775 779 else:
776 780 if stat.S_ISLNK(st.st_mode):
777 781 raise Abort(_('path %r traverses symbolic link %r') %
778 782 (path, prefix))
779 783 elif (stat.S_ISDIR(st.st_mode) and
780 784 os.path.isdir(os.path.join(curpath, '.hg'))):
781 785 raise Abort(_('path %r is inside repo %r') %
782 786 (path, prefix))
783 787 parts.pop()
784 788 prefixes = []
785 789 for n in range(len(parts)):
786 790 prefix = os.sep.join(parts)
787 791 if prefix in self.auditeddir:
788 792 break
789 793 check(prefix)
790 794 prefixes.append(prefix)
791 795 parts.pop()
792 796
793 797 self.audited.add(path)
794 798 # only add prefixes to the cache after checking everything: we don't
795 799 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
796 800 self.auditeddir.update(prefixes)
797 801
798 802 def _makelock_file(info, pathname):
799 803 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
800 804 os.write(ld, info)
801 805 os.close(ld)
802 806
803 807 def _readlock_file(pathname):
804 808 return posixfile(pathname).read()
805 809
806 810 def nlinks(pathname):
807 811 """Return number of hardlinks for the given file."""
808 812 return os.lstat(pathname).st_nlink
809 813
810 814 if hasattr(os, 'link'):
811 815 os_link = os.link
812 816 else:
813 817 def os_link(src, dst):
814 818 raise OSError(0, _("Hardlinks not supported"))
815 819
816 820 def fstat(fp):
817 821 '''stat file object that may not have fileno method.'''
818 822 try:
819 823 return os.fstat(fp.fileno())
820 824 except AttributeError:
821 825 return os.stat(fp.name)
822 826
823 827 posixfile = file
824 828
825 829 def openhardlinks():
826 830 '''return true if it is safe to hold open file handles to hardlinks'''
827 831 return True
828 832
829 833 def _statfiles(files):
830 834 'Stat each file in files and yield stat or None if file does not exist.'
831 835 lstat = os.lstat
832 836 for nf in files:
833 837 try:
834 838 st = lstat(nf)
835 839 except OSError, err:
836 840 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
837 841 raise
838 842 st = None
839 843 yield st
840 844
841 845 def _statfiles_clustered(files):
842 846 '''Stat each file in files and yield stat or None if file does not exist.
843 847 Cluster and cache stat per directory to minimize number of OS stat calls.'''
844 848 lstat = os.lstat
845 849 ncase = os.path.normcase
846 850 sep = os.sep
847 851 dircache = {} # dirname -> filename -> status | None if file does not exist
848 852 for nf in files:
849 853 nf = ncase(nf)
850 854 pos = nf.rfind(sep)
851 855 if pos == -1:
852 856 dir, base = '.', nf
853 857 else:
854 858 dir, base = nf[:pos], nf[pos+1:]
855 859 cache = dircache.get(dir, None)
856 860 if cache is None:
857 861 try:
858 862 dmap = dict([(ncase(n), s)
859 863 for n, k, s in osutil.listdir(dir, True)])
860 864 except OSError, err:
861 865 # handle directory not found in Python version prior to 2.5
862 866 # Python <= 2.4 returns native Windows code 3 in errno
863 867 # Python >= 2.5 returns ENOENT and adds winerror field
864 868 if err.errno not in (3, errno.ENOENT, errno.ENOTDIR):
865 869 raise
866 870 dmap = {}
867 871 cache = dircache.setdefault(dir, dmap)
868 872 yield cache.get(base, None)
869 873
870 874 if sys.platform == 'win32':
871 875 statfiles = _statfiles_clustered
872 876 else:
873 877 statfiles = _statfiles
874 878
875 879 getuser_fallback = None
876 880
877 881 def getuser():
878 882 '''return name of current user'''
879 883 try:
880 884 return getpass.getuser()
881 885 except ImportError:
882 886 # import of pwd will fail on windows - try fallback
883 887 if getuser_fallback:
884 888 return getuser_fallback()
885 889 # raised if win32api not available
886 890 raise Abort(_('user name not available - set USERNAME '
887 891 'environment variable'))
888 892
889 893 def username(uid=None):
890 894 """Return the name of the user with the given uid.
891 895
892 896 If uid is None, return the name of the current user."""
893 897 try:
894 898 import pwd
895 899 if uid is None:
896 900 uid = os.getuid()
897 901 try:
898 902 return pwd.getpwuid(uid)[0]
899 903 except KeyError:
900 904 return str(uid)
901 905 except ImportError:
902 906 return None
903 907
904 908 def groupname(gid=None):
905 909 """Return the name of the group with the given gid.
906 910
907 911 If gid is None, return the name of the current group."""
908 912 try:
909 913 import grp
910 914 if gid is None:
911 915 gid = os.getgid()
912 916 try:
913 917 return grp.getgrgid(gid)[0]
914 918 except KeyError:
915 919 return str(gid)
916 920 except ImportError:
917 921 return None
918 922
919 923 # File system features
920 924
921 925 def checkcase(path):
922 926 """
923 927 Check whether the given path is on a case-sensitive filesystem
924 928
925 929 Requires a path (like /foo/.hg) ending with a foldable final
926 930 directory component.
927 931 """
928 932 s1 = os.stat(path)
929 933 d, b = os.path.split(path)
930 934 p2 = os.path.join(d, b.upper())
931 935 if path == p2:
932 936 p2 = os.path.join(d, b.lower())
933 937 try:
934 938 s2 = os.stat(p2)
935 939 if s2 == s1:
936 940 return False
937 941 return True
938 942 except:
939 943 return True
940 944
941 945 _fspathcache = {}
942 946 def fspath(name, root):
943 947 '''Get name in the case stored in the filesystem
944 948
945 949 The name is either relative to root, or it is an absolute path starting
946 950 with root. Note that this function is unnecessary, and should not be
947 951 called, for case-sensitive filesystems (simply because it's expensive).
948 952 '''
949 953 # If name is absolute, make it relative
950 954 if name.lower().startswith(root.lower()):
951 955 l = len(root)
952 956 if name[l] == os.sep or name[l] == os.altsep:
953 957 l = l + 1
954 958 name = name[l:]
955 959
956 960 if not os.path.exists(os.path.join(root, name)):
957 961 return None
958 962
959 963 seps = os.sep
960 964 if os.altsep:
961 965 seps = seps + os.altsep
962 966 # Protect backslashes. This gets silly very quickly.
963 967 seps.replace('\\','\\\\')
964 968 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
965 969 dir = os.path.normcase(os.path.normpath(root))
966 970 result = []
967 971 for part, sep in pattern.findall(name):
968 972 if sep:
969 973 result.append(sep)
970 974 continue
971 975
972 976 if dir not in _fspathcache:
973 977 _fspathcache[dir] = os.listdir(dir)
974 978 contents = _fspathcache[dir]
975 979
976 980 lpart = part.lower()
977 981 for n in contents:
978 982 if n.lower() == lpart:
979 983 result.append(n)
980 984 break
981 985 else:
982 986 # Cannot happen, as the file exists!
983 987 result.append(part)
984 988 dir = os.path.join(dir, lpart)
985 989
986 990 return ''.join(result)
987 991
988 992 def checkexec(path):
989 993 """
990 994 Check whether the given path is on a filesystem with UNIX-like exec flags
991 995
992 996 Requires a directory (like /foo/.hg)
993 997 """
994 998
995 999 # VFAT on some Linux versions can flip mode but it doesn't persist
996 1000 # a FS remount. Frequently we can detect it if files are created
997 1001 # with exec bit on.
998 1002
999 1003 try:
1000 1004 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
1001 1005 fh, fn = tempfile.mkstemp("", "", path)
1002 1006 try:
1003 1007 os.close(fh)
1004 1008 m = os.stat(fn).st_mode & 0777
1005 1009 new_file_has_exec = m & EXECFLAGS
1006 1010 os.chmod(fn, m ^ EXECFLAGS)
1007 1011 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
1008 1012 finally:
1009 1013 os.unlink(fn)
1010 1014 except (IOError, OSError):
1011 1015 # we don't care, the user probably won't be able to commit anyway
1012 1016 return False
1013 1017 return not (new_file_has_exec or exec_flags_cannot_flip)
1014 1018
1015 1019 def checklink(path):
1016 1020 """check whether the given path is on a symlink-capable filesystem"""
1017 1021 # mktemp is not racy because symlink creation will fail if the
1018 1022 # file already exists
1019 1023 name = tempfile.mktemp(dir=path)
1020 1024 try:
1021 1025 os.symlink(".", name)
1022 1026 os.unlink(name)
1023 1027 return True
1024 1028 except (OSError, AttributeError):
1025 1029 return False
1026 1030
1027 1031 _umask = os.umask(0)
1028 1032 os.umask(_umask)
1029 1033
1030 1034 def needbinarypatch():
1031 1035 """return True if patches should be applied in binary mode by default."""
1032 1036 return os.name == 'nt'
1033 1037
1034 1038 def endswithsep(path):
1035 1039 '''Check path ends with os.sep or os.altsep.'''
1036 1040 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1037 1041
1038 1042 def splitpath(path):
1039 1043 '''Split path by os.sep.
1040 1044 Note that this function does not use os.altsep because this is
1041 1045 an alternative of simple "xxx.split(os.sep)".
1042 1046 It is recommended to use os.path.normpath() before using this
1043 1047 function if need.'''
1044 1048 return path.split(os.sep)
1045 1049
1046 1050 def gui():
1047 1051 '''Are we running in a GUI?'''
1048 1052 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
1049 1053
1050 1054 def lookup_reg(key, name=None, scope=None):
1051 1055 return None
1052 1056
1053 1057 # Platform specific variants
1054 1058 if os.name == 'nt':
1055 1059 import msvcrt
1056 1060 nulldev = 'NUL:'
1057 1061
1058 1062 class winstdout:
1059 1063 '''stdout on windows misbehaves if sent through a pipe'''
1060 1064
1061 1065 def __init__(self, fp):
1062 1066 self.fp = fp
1063 1067
1064 1068 def __getattr__(self, key):
1065 1069 return getattr(self.fp, key)
1066 1070
1067 1071 def close(self):
1068 1072 try:
1069 1073 self.fp.close()
1070 1074 except: pass
1071 1075
1072 1076 def write(self, s):
1073 1077 try:
1074 1078 # This is workaround for "Not enough space" error on
1075 1079 # writing large size of data to console.
1076 1080 limit = 16000
1077 1081 l = len(s)
1078 1082 start = 0
1079 1083 while start < l:
1080 1084 end = start + limit
1081 1085 self.fp.write(s[start:end])
1082 1086 start = end
1083 1087 except IOError, inst:
1084 1088 if inst.errno != 0: raise
1085 1089 self.close()
1086 1090 raise IOError(errno.EPIPE, 'Broken pipe')
1087 1091
1088 1092 def flush(self):
1089 1093 try:
1090 1094 return self.fp.flush()
1091 1095 except IOError, inst:
1092 1096 if inst.errno != errno.EINVAL: raise
1093 1097 self.close()
1094 1098 raise IOError(errno.EPIPE, 'Broken pipe')
1095 1099
1096 1100 sys.stdout = winstdout(sys.stdout)
1097 1101
1098 1102 def _is_win_9x():
1099 1103 '''return true if run on windows 95, 98 or me.'''
1100 1104 try:
1101 1105 return sys.getwindowsversion()[3] == 1
1102 1106 except AttributeError:
1103 1107 return 'command' in os.environ.get('comspec', '')
1104 1108
1105 1109 def openhardlinks():
1106 1110 return not _is_win_9x and "win32api" in locals()
1107 1111
1108 1112 def system_rcpath():
1109 1113 try:
1110 1114 return system_rcpath_win32()
1111 1115 except:
1112 1116 return [r'c:\mercurial\mercurial.ini']
1113 1117
1114 1118 def user_rcpath():
1115 1119 '''return os-specific hgrc search path to the user dir'''
1116 1120 try:
1117 1121 path = user_rcpath_win32()
1118 1122 except:
1119 1123 home = os.path.expanduser('~')
1120 1124 path = [os.path.join(home, 'mercurial.ini'),
1121 1125 os.path.join(home, '.hgrc')]
1122 1126 userprofile = os.environ.get('USERPROFILE')
1123 1127 if userprofile:
1124 1128 path.append(os.path.join(userprofile, 'mercurial.ini'))
1125 1129 path.append(os.path.join(userprofile, '.hgrc'))
1126 1130 return path
1127 1131
1128 1132 def parse_patch_output(output_line):
1129 1133 """parses the output produced by patch and returns the file name"""
1130 1134 pf = output_line[14:]
1131 1135 if pf[0] == '`':
1132 1136 pf = pf[1:-1] # Remove the quotes
1133 1137 return pf
1134 1138
1135 1139 def sshargs(sshcmd, host, user, port):
1136 1140 '''Build argument list for ssh or Plink'''
1137 1141 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
1138 1142 args = user and ("%s@%s" % (user, host)) or host
1139 1143 return port and ("%s %s %s" % (args, pflag, port)) or args
1140 1144
1141 1145 def testpid(pid):
1142 1146 '''return False if pid dead, True if running or not known'''
1143 1147 return True
1144 1148
1145 1149 def set_flags(f, l, x):
1146 1150 pass
1147 1151
1148 1152 def set_binary(fd):
1149 1153 # When run without console, pipes may expose invalid
1150 1154 # fileno(), usually set to -1.
1151 1155 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
1152 1156 msvcrt.setmode(fd.fileno(), os.O_BINARY)
1153 1157
1154 1158 def pconvert(path):
1155 1159 return '/'.join(splitpath(path))
1156 1160
1157 1161 def localpath(path):
1158 1162 return path.replace('/', '\\')
1159 1163
1160 1164 def normpath(path):
1161 1165 return pconvert(os.path.normpath(path))
1162 1166
1163 1167 makelock = _makelock_file
1164 1168 readlock = _readlock_file
1165 1169
1166 1170 def samestat(s1, s2):
1167 1171 return False
1168 1172
1169 1173 # A sequence of backslashes is special iff it precedes a double quote:
1170 1174 # - if there's an even number of backslashes, the double quote is not
1171 1175 # quoted (i.e. it ends the quoted region)
1172 1176 # - if there's an odd number of backslashes, the double quote is quoted
1173 1177 # - in both cases, every pair of backslashes is unquoted into a single
1174 1178 # backslash
1175 1179 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1176 1180 # So, to quote a string, we must surround it in double quotes, double
1177 1181 # the number of backslashes that preceed double quotes and add another
1178 1182 # backslash before every double quote (being careful with the double
1179 1183 # quote we've appended to the end)
1180 1184 _quotere = None
1181 1185 def shellquote(s):
1182 1186 global _quotere
1183 1187 if _quotere is None:
1184 1188 _quotere = re.compile(r'(\\*)("|\\$)')
1185 1189 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1186 1190
1187 1191 def quotecommand(cmd):
1188 1192 """Build a command string suitable for os.popen* calls."""
1189 1193 # The extra quotes are needed because popen* runs the command
1190 1194 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1191 1195 return '"' + cmd + '"'
1192 1196
1193 1197 def popen(command, mode='r'):
1194 1198 # Work around "popen spawned process may not write to stdout
1195 1199 # under windows"
1196 1200 # http://bugs.python.org/issue1366
1197 1201 command += " 2> %s" % nulldev
1198 1202 return os.popen(quotecommand(command), mode)
1199 1203
1200 1204 def explain_exit(code):
1201 1205 return _("exited with status %d") % code, code
1202 1206
1203 1207 # if you change this stub into a real check, please try to implement the
1204 1208 # username and groupname functions above, too.
1205 1209 def isowner(fp, st=None):
1206 1210 return True
1207 1211
1208 1212 def find_in_path(name, path, default=None):
1209 1213 '''find name in search path. path can be string (will be split
1210 1214 with os.pathsep), or iterable thing that returns strings. if name
1211 1215 found, return path to name. else return default. name is looked up
1212 1216 using cmd.exe rules, using PATHEXT.'''
1213 1217 if isinstance(path, str):
1214 1218 path = path.split(os.pathsep)
1215 1219
1216 1220 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1217 1221 pathext = pathext.lower().split(os.pathsep)
1218 1222 isexec = os.path.splitext(name)[1].lower() in pathext
1219 1223
1220 1224 for p in path:
1221 1225 p_name = os.path.join(p, name)
1222 1226
1223 1227 if isexec and os.path.exists(p_name):
1224 1228 return p_name
1225 1229
1226 1230 for ext in pathext:
1227 1231 p_name_ext = p_name + ext
1228 1232 if os.path.exists(p_name_ext):
1229 1233 return p_name_ext
1230 1234 return default
1231 1235
1232 1236 def set_signal_handler():
1233 1237 try:
1234 1238 set_signal_handler_win32()
1235 1239 except NameError:
1236 1240 pass
1237 1241
1238 1242 try:
1239 1243 # override functions with win32 versions if possible
1240 1244 from util_win32 import *
1241 1245 if not _is_win_9x():
1242 1246 posixfile = posixfile_nt
1243 1247 except ImportError:
1244 1248 pass
1245 1249
1246 1250 else:
1247 1251 nulldev = '/dev/null'
1248 1252
1249 1253 def rcfiles(path):
1250 1254 rcs = [os.path.join(path, 'hgrc')]
1251 1255 rcdir = os.path.join(path, 'hgrc.d')
1252 1256 try:
1253 1257 rcs.extend([os.path.join(rcdir, f)
1254 1258 for f, kind in osutil.listdir(rcdir)
1255 1259 if f.endswith(".rc")])
1256 1260 except OSError:
1257 1261 pass
1258 1262 return rcs
1259 1263
1260 1264 def system_rcpath():
1261 1265 path = []
1262 1266 # old mod_python does not set sys.argv
1263 1267 if len(getattr(sys, 'argv', [])) > 0:
1264 1268 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1265 1269 '/../etc/mercurial'))
1266 1270 path.extend(rcfiles('/etc/mercurial'))
1267 1271 return path
1268 1272
1269 1273 def user_rcpath():
1270 1274 return [os.path.expanduser('~/.hgrc')]
1271 1275
1272 1276 def parse_patch_output(output_line):
1273 1277 """parses the output produced by patch and returns the file name"""
1274 1278 pf = output_line[14:]
1275 1279 if os.sys.platform == 'OpenVMS':
1276 1280 if pf[0] == '`':
1277 1281 pf = pf[1:-1] # Remove the quotes
1278 1282 else:
1279 1283 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1280 1284 pf = pf[1:-1] # Remove the quotes
1281 1285 return pf
1282 1286
1283 1287 def sshargs(sshcmd, host, user, port):
1284 1288 '''Build argument list for ssh'''
1285 1289 args = user and ("%s@%s" % (user, host)) or host
1286 1290 return port and ("%s -p %s" % (args, port)) or args
1287 1291
1288 1292 def is_exec(f):
1289 1293 """check whether a file is executable"""
1290 1294 return (os.lstat(f).st_mode & 0100 != 0)
1291 1295
1292 1296 def set_flags(f, l, x):
1293 1297 s = os.lstat(f).st_mode
1294 1298 if l:
1295 1299 if not stat.S_ISLNK(s):
1296 1300 # switch file to link
1297 1301 data = file(f).read()
1298 1302 os.unlink(f)
1299 1303 try:
1300 1304 os.symlink(data, f)
1301 1305 except:
1302 1306 # failed to make a link, rewrite file
1303 1307 file(f, "w").write(data)
1304 1308 # no chmod needed at this point
1305 1309 return
1306 1310 if stat.S_ISLNK(s):
1307 1311 # switch link to file
1308 1312 data = os.readlink(f)
1309 1313 os.unlink(f)
1310 1314 file(f, "w").write(data)
1311 1315 s = 0666 & ~_umask # avoid restatting for chmod
1312 1316
1313 1317 sx = s & 0100
1314 1318 if x and not sx:
1315 1319 # Turn on +x for every +r bit when making a file executable
1316 1320 # and obey umask.
1317 1321 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1318 1322 elif not x and sx:
1319 1323 # Turn off all +x bits
1320 1324 os.chmod(f, s & 0666)
1321 1325
1322 1326 def set_binary(fd):
1323 1327 pass
1324 1328
1325 1329 def pconvert(path):
1326 1330 return path
1327 1331
1328 1332 def localpath(path):
1329 1333 return path
1330 1334
1331 1335 normpath = os.path.normpath
1332 1336 samestat = os.path.samestat
1333 1337
1334 1338 def makelock(info, pathname):
1335 1339 try:
1336 1340 os.symlink(info, pathname)
1337 1341 except OSError, why:
1338 1342 if why.errno == errno.EEXIST:
1339 1343 raise
1340 1344 else:
1341 1345 _makelock_file(info, pathname)
1342 1346
1343 1347 def readlock(pathname):
1344 1348 try:
1345 1349 return os.readlink(pathname)
1346 1350 except OSError, why:
1347 1351 if why.errno in (errno.EINVAL, errno.ENOSYS):
1348 1352 return _readlock_file(pathname)
1349 1353 else:
1350 1354 raise
1351 1355
1352 1356 def shellquote(s):
1353 1357 if os.sys.platform == 'OpenVMS':
1354 1358 return '"%s"' % s
1355 1359 else:
1356 1360 return "'%s'" % s.replace("'", "'\\''")
1357 1361
1358 1362 def quotecommand(cmd):
1359 1363 return cmd
1360 1364
1361 1365 def popen(command, mode='r'):
1362 1366 return os.popen(command, mode)
1363 1367
1364 1368 def testpid(pid):
1365 1369 '''return False if pid dead, True if running or not sure'''
1366 1370 if os.sys.platform == 'OpenVMS':
1367 1371 return True
1368 1372 try:
1369 1373 os.kill(pid, 0)
1370 1374 return True
1371 1375 except OSError, inst:
1372 1376 return inst.errno != errno.ESRCH
1373 1377
1374 1378 def explain_exit(code):
1375 1379 """return a 2-tuple (desc, code) describing a process's status"""
1376 1380 if os.WIFEXITED(code):
1377 1381 val = os.WEXITSTATUS(code)
1378 1382 return _("exited with status %d") % val, val
1379 1383 elif os.WIFSIGNALED(code):
1380 1384 val = os.WTERMSIG(code)
1381 1385 return _("killed by signal %d") % val, val
1382 1386 elif os.WIFSTOPPED(code):
1383 1387 val = os.WSTOPSIG(code)
1384 1388 return _("stopped by signal %d") % val, val
1385 1389 raise ValueError(_("invalid exit code"))
1386 1390
1387 1391 def isowner(fp, st=None):
1388 1392 """Return True if the file object f belongs to the current user.
1389 1393
1390 1394 The return value of a util.fstat(f) may be passed as the st argument.
1391 1395 """
1392 1396 if st is None:
1393 1397 st = fstat(fp)
1394 1398 return st.st_uid == os.getuid()
1395 1399
1396 1400 def find_in_path(name, path, default=None):
1397 1401 '''find name in search path. path can be string (will be split
1398 1402 with os.pathsep), or iterable thing that returns strings. if name
1399 1403 found, return path to name. else return default.'''
1400 1404 if isinstance(path, str):
1401 1405 path = path.split(os.pathsep)
1402 1406 for p in path:
1403 1407 p_name = os.path.join(p, name)
1404 1408 if os.path.exists(p_name):
1405 1409 return p_name
1406 1410 return default
1407 1411
1408 1412 def set_signal_handler():
1409 1413 pass
1410 1414
1411 1415 def find_exe(name, default=None):
1412 1416 '''find path of an executable.
1413 1417 if name contains a path component, return it as is. otherwise,
1414 1418 use normal executable search path.'''
1415 1419
1416 1420 if os.sep in name or sys.platform == 'OpenVMS':
1417 1421 # don't check the executable bit. if the file isn't
1418 1422 # executable, whoever tries to actually run it will give a
1419 1423 # much more useful error message.
1420 1424 return name
1421 1425 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1422 1426
1423 1427 def mktempcopy(name, emptyok=False, createmode=None):
1424 1428 """Create a temporary file with the same contents from name
1425 1429
1426 1430 The permission bits are copied from the original file.
1427 1431
1428 1432 If the temporary file is going to be truncated immediately, you
1429 1433 can use emptyok=True as an optimization.
1430 1434
1431 1435 Returns the name of the temporary file.
1432 1436 """
1433 1437 d, fn = os.path.split(name)
1434 1438 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1435 1439 os.close(fd)
1436 1440 # Temporary files are created with mode 0600, which is usually not
1437 1441 # what we want. If the original file already exists, just copy
1438 1442 # its mode. Otherwise, manually obey umask.
1439 1443 try:
1440 1444 st_mode = os.lstat(name).st_mode & 0777
1441 1445 except OSError, inst:
1442 1446 if inst.errno != errno.ENOENT:
1443 1447 raise
1444 1448 st_mode = createmode
1445 1449 if st_mode is None:
1446 1450 st_mode = ~_umask
1447 1451 st_mode &= 0666
1448 1452 os.chmod(temp, st_mode)
1449 1453 if emptyok:
1450 1454 return temp
1451 1455 try:
1452 1456 try:
1453 1457 ifp = posixfile(name, "rb")
1454 1458 except IOError, inst:
1455 1459 if inst.errno == errno.ENOENT:
1456 1460 return temp
1457 1461 if not getattr(inst, 'filename', None):
1458 1462 inst.filename = name
1459 1463 raise
1460 1464 ofp = posixfile(temp, "wb")
1461 1465 for chunk in filechunkiter(ifp):
1462 1466 ofp.write(chunk)
1463 1467 ifp.close()
1464 1468 ofp.close()
1465 1469 except:
1466 1470 try: os.unlink(temp)
1467 1471 except: pass
1468 1472 raise
1469 1473 return temp
1470 1474
1471 1475 class atomictempfile(posixfile):
1472 1476 """file-like object that atomically updates a file
1473 1477
1474 1478 All writes will be redirected to a temporary copy of the original
1475 1479 file. When rename is called, the copy is renamed to the original
1476 1480 name, making the changes visible.
1477 1481 """
1478 1482 def __init__(self, name, mode, createmode):
1479 1483 self.__name = name
1480 1484 self.temp = mktempcopy(name, emptyok=('w' in mode),
1481 1485 createmode=createmode)
1482 1486 posixfile.__init__(self, self.temp, mode)
1483 1487
1484 1488 def rename(self):
1485 1489 if not self.closed:
1486 1490 posixfile.close(self)
1487 1491 rename(self.temp, localpath(self.__name))
1488 1492
1489 1493 def __del__(self):
1490 1494 if not self.closed:
1491 1495 try:
1492 1496 os.unlink(self.temp)
1493 1497 except: pass
1494 1498 posixfile.close(self)
1495 1499
1496 1500 def makedirs(name, mode=None):
1497 1501 """recursive directory creation with parent mode inheritance"""
1498 1502 try:
1499 1503 os.mkdir(name)
1500 1504 if mode is not None:
1501 1505 os.chmod(name, mode)
1502 1506 return
1503 1507 except OSError, err:
1504 1508 if err.errno == errno.EEXIST:
1505 1509 return
1506 1510 if err.errno != errno.ENOENT:
1507 1511 raise
1508 1512 parent = os.path.abspath(os.path.dirname(name))
1509 1513 makedirs(parent, mode)
1510 1514 makedirs(name, mode)
1511 1515
1512 1516 class opener(object):
1513 1517 """Open files relative to a base directory
1514 1518
1515 1519 This class is used to hide the details of COW semantics and
1516 1520 remote file access from higher level code.
1517 1521 """
1518 1522 def __init__(self, base, audit=True):
1519 1523 self.base = base
1520 1524 if audit:
1521 1525 self.audit_path = path_auditor(base)
1522 1526 else:
1523 1527 self.audit_path = always
1524 1528 self.createmode = None
1525 1529
1526 1530 def __getattr__(self, name):
1527 1531 if name == '_can_symlink':
1528 1532 self._can_symlink = checklink(self.base)
1529 1533 return self._can_symlink
1530 1534 raise AttributeError(name)
1531 1535
1532 1536 def _fixfilemode(self, name):
1533 1537 if self.createmode is None:
1534 1538 return
1535 1539 os.chmod(name, self.createmode & 0666)
1536 1540
1537 1541 def __call__(self, path, mode="r", text=False, atomictemp=False):
1538 1542 self.audit_path(path)
1539 1543 f = os.path.join(self.base, path)
1540 1544
1541 1545 if not text and "b" not in mode:
1542 1546 mode += "b" # for that other OS
1543 1547
1544 1548 nlink = -1
1545 1549 if mode not in ("r", "rb"):
1546 1550 try:
1547 1551 nlink = nlinks(f)
1548 1552 except OSError:
1549 1553 nlink = 0
1550 1554 d = os.path.dirname(f)
1551 1555 if not os.path.isdir(d):
1552 1556 makedirs(d, self.createmode)
1553 1557 if atomictemp:
1554 1558 return atomictempfile(f, mode, self.createmode)
1555 1559 if nlink > 1:
1556 1560 rename(mktempcopy(f), f)
1557 1561 fp = posixfile(f, mode)
1558 1562 if nlink == 0:
1559 1563 self._fixfilemode(f)
1560 1564 return fp
1561 1565
1562 1566 def symlink(self, src, dst):
1563 1567 self.audit_path(dst)
1564 1568 linkname = os.path.join(self.base, dst)
1565 1569 try:
1566 1570 os.unlink(linkname)
1567 1571 except OSError:
1568 1572 pass
1569 1573
1570 1574 dirname = os.path.dirname(linkname)
1571 1575 if not os.path.exists(dirname):
1572 1576 makedirs(dirname, self.createmode)
1573 1577
1574 1578 if self._can_symlink:
1575 1579 try:
1576 1580 os.symlink(src, linkname)
1577 1581 except OSError, err:
1578 1582 raise OSError(err.errno, _('could not symlink to %r: %s') %
1579 1583 (src, err.strerror), linkname)
1580 1584 else:
1581 1585 f = self(dst, "w")
1582 1586 f.write(src)
1583 1587 f.close()
1584 1588 self._fixfilemode(dst)
1585 1589
1586 1590 class chunkbuffer(object):
1587 1591 """Allow arbitrary sized chunks of data to be efficiently read from an
1588 1592 iterator over chunks of arbitrary size."""
1589 1593
1590 1594 def __init__(self, in_iter):
1591 1595 """in_iter is the iterator that's iterating over the input chunks.
1592 1596 targetsize is how big a buffer to try to maintain."""
1593 1597 self.iter = iter(in_iter)
1594 1598 self.buf = ''
1595 1599 self.targetsize = 2**16
1596 1600
1597 1601 def read(self, l):
1598 1602 """Read L bytes of data from the iterator of chunks of data.
1599 1603 Returns less than L bytes if the iterator runs dry."""
1600 1604 if l > len(self.buf) and self.iter:
1601 1605 # Clamp to a multiple of self.targetsize
1602 1606 targetsize = max(l, self.targetsize)
1603 1607 collector = cStringIO.StringIO()
1604 1608 collector.write(self.buf)
1605 1609 collected = len(self.buf)
1606 1610 for chunk in self.iter:
1607 1611 collector.write(chunk)
1608 1612 collected += len(chunk)
1609 1613 if collected >= targetsize:
1610 1614 break
1611 1615 if collected < targetsize:
1612 1616 self.iter = False
1613 1617 self.buf = collector.getvalue()
1614 1618 if len(self.buf) == l:
1615 1619 s, self.buf = str(self.buf), ''
1616 1620 else:
1617 1621 s, self.buf = self.buf[:l], buffer(self.buf, l)
1618 1622 return s
1619 1623
1620 1624 def filechunkiter(f, size=65536, limit=None):
1621 1625 """Create a generator that produces the data in the file size
1622 1626 (default 65536) bytes at a time, up to optional limit (default is
1623 1627 to read all data). Chunks may be less than size bytes if the
1624 1628 chunk is the last chunk in the file, or the file is a socket or
1625 1629 some other type of file that sometimes reads less data than is
1626 1630 requested."""
1627 1631 assert size >= 0
1628 1632 assert limit is None or limit >= 0
1629 1633 while True:
1630 1634 if limit is None: nbytes = size
1631 1635 else: nbytes = min(limit, size)
1632 1636 s = nbytes and f.read(nbytes)
1633 1637 if not s: break
1634 1638 if limit: limit -= len(s)
1635 1639 yield s
1636 1640
1637 1641 def makedate():
1638 1642 lt = time.localtime()
1639 1643 if lt[8] == 1 and time.daylight:
1640 1644 tz = time.altzone
1641 1645 else:
1642 1646 tz = time.timezone
1643 1647 return time.mktime(lt), tz
1644 1648
1645 1649 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1646 1650 """represent a (unixtime, offset) tuple as a localized time.
1647 1651 unixtime is seconds since the epoch, and offset is the time zone's
1648 1652 number of seconds away from UTC. if timezone is false, do not
1649 1653 append time zone to string."""
1650 1654 t, tz = date or makedate()
1651 1655 if "%1" in format or "%2" in format:
1652 1656 sign = (tz > 0) and "-" or "+"
1653 1657 minutes = abs(tz) / 60
1654 1658 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1655 1659 format = format.replace("%2", "%02d" % (minutes % 60))
1656 1660 s = time.strftime(format, time.gmtime(float(t) - tz))
1657 1661 return s
1658 1662
1659 1663 def shortdate(date=None):
1660 1664 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1661 1665 return datestr(date, format='%Y-%m-%d')
1662 1666
1663 1667 def strdate(string, format, defaults=[]):
1664 1668 """parse a localized time string and return a (unixtime, offset) tuple.
1665 1669 if the string cannot be parsed, ValueError is raised."""
1666 1670 def timezone(string):
1667 1671 tz = string.split()[-1]
1668 1672 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1669 1673 sign = (tz[0] == "+") and 1 or -1
1670 1674 hours = int(tz[1:3])
1671 1675 minutes = int(tz[3:5])
1672 1676 return -sign * (hours * 60 + minutes) * 60
1673 1677 if tz == "GMT" or tz == "UTC":
1674 1678 return 0
1675 1679 return None
1676 1680
1677 1681 # NOTE: unixtime = localunixtime + offset
1678 1682 offset, date = timezone(string), string
1679 1683 if offset != None:
1680 1684 date = " ".join(string.split()[:-1])
1681 1685
1682 1686 # add missing elements from defaults
1683 1687 for part in defaults:
1684 1688 found = [True for p in part if ("%"+p) in format]
1685 1689 if not found:
1686 1690 date += "@" + defaults[part]
1687 1691 format += "@%" + part[0]
1688 1692
1689 1693 timetuple = time.strptime(date, format)
1690 1694 localunixtime = int(calendar.timegm(timetuple))
1691 1695 if offset is None:
1692 1696 # local timezone
1693 1697 unixtime = int(time.mktime(timetuple))
1694 1698 offset = unixtime - localunixtime
1695 1699 else:
1696 1700 unixtime = localunixtime + offset
1697 1701 return unixtime, offset
1698 1702
1699 1703 def parsedate(date, formats=None, defaults=None):
1700 1704 """parse a localized date/time string and return a (unixtime, offset) tuple.
1701 1705
1702 1706 The date may be a "unixtime offset" string or in one of the specified
1703 1707 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1704 1708 """
1705 1709 if not date:
1706 1710 return 0, 0
1707 1711 if isinstance(date, tuple) and len(date) == 2:
1708 1712 return date
1709 1713 if not formats:
1710 1714 formats = defaultdateformats
1711 1715 date = date.strip()
1712 1716 try:
1713 1717 when, offset = map(int, date.split(' '))
1714 1718 except ValueError:
1715 1719 # fill out defaults
1716 1720 if not defaults:
1717 1721 defaults = {}
1718 1722 now = makedate()
1719 1723 for part in "d mb yY HI M S".split():
1720 1724 if part not in defaults:
1721 1725 if part[0] in "HMS":
1722 1726 defaults[part] = "00"
1723 1727 else:
1724 1728 defaults[part] = datestr(now, "%" + part[0])
1725 1729
1726 1730 for format in formats:
1727 1731 try:
1728 1732 when, offset = strdate(date, format, defaults)
1729 1733 except (ValueError, OverflowError):
1730 1734 pass
1731 1735 else:
1732 1736 break
1733 1737 else:
1734 1738 raise Abort(_('invalid date: %r ') % date)
1735 1739 # validate explicit (probably user-specified) date and
1736 1740 # time zone offset. values must fit in signed 32 bits for
1737 1741 # current 32-bit linux runtimes. timezones go from UTC-12
1738 1742 # to UTC+14
1739 1743 if abs(when) > 0x7fffffff:
1740 1744 raise Abort(_('date exceeds 32 bits: %d') % when)
1741 1745 if offset < -50400 or offset > 43200:
1742 1746 raise Abort(_('impossible time zone offset: %d') % offset)
1743 1747 return when, offset
1744 1748
1745 1749 def matchdate(date):
1746 1750 """Return a function that matches a given date match specifier
1747 1751
1748 1752 Formats include:
1749 1753
1750 1754 '{date}' match a given date to the accuracy provided
1751 1755
1752 1756 '<{date}' on or before a given date
1753 1757
1754 1758 '>{date}' on or after a given date
1755 1759
1756 1760 """
1757 1761
1758 1762 def lower(date):
1759 1763 d = dict(mb="1", d="1")
1760 1764 return parsedate(date, extendeddateformats, d)[0]
1761 1765
1762 1766 def upper(date):
1763 1767 d = dict(mb="12", HI="23", M="59", S="59")
1764 1768 for days in "31 30 29".split():
1765 1769 try:
1766 1770 d["d"] = days
1767 1771 return parsedate(date, extendeddateformats, d)[0]
1768 1772 except:
1769 1773 pass
1770 1774 d["d"] = "28"
1771 1775 return parsedate(date, extendeddateformats, d)[0]
1772 1776
1773 1777 if date[0] == "<":
1774 1778 when = upper(date[1:])
1775 1779 return lambda x: x <= when
1776 1780 elif date[0] == ">":
1777 1781 when = lower(date[1:])
1778 1782 return lambda x: x >= when
1779 1783 elif date[0] == "-":
1780 1784 try:
1781 1785 days = int(date[1:])
1782 1786 except ValueError:
1783 1787 raise Abort(_("invalid day spec: %s") % date[1:])
1784 1788 when = makedate()[0] - days * 3600 * 24
1785 1789 return lambda x: x >= when
1786 1790 elif " to " in date:
1787 1791 a, b = date.split(" to ")
1788 1792 start, stop = lower(a), upper(b)
1789 1793 return lambda x: x >= start and x <= stop
1790 1794 else:
1791 1795 start, stop = lower(date), upper(date)
1792 1796 return lambda x: x >= start and x <= stop
1793 1797
1794 1798 def shortuser(user):
1795 1799 """Return a short representation of a user name or email address."""
1796 1800 f = user.find('@')
1797 1801 if f >= 0:
1798 1802 user = user[:f]
1799 1803 f = user.find('<')
1800 1804 if f >= 0:
1801 1805 user = user[f+1:]
1802 1806 f = user.find(' ')
1803 1807 if f >= 0:
1804 1808 user = user[:f]
1805 1809 f = user.find('.')
1806 1810 if f >= 0:
1807 1811 user = user[:f]
1808 1812 return user
1809 1813
1810 1814 def email(author):
1811 1815 '''get email of author.'''
1812 1816 r = author.find('>')
1813 1817 if r == -1: r = None
1814 1818 return author[author.find('<')+1:r]
1815 1819
1816 1820 def ellipsis(text, maxlength=400):
1817 1821 """Trim string to at most maxlength (default: 400) characters."""
1818 1822 if len(text) <= maxlength:
1819 1823 return text
1820 1824 else:
1821 1825 return "%s..." % (text[:maxlength-3])
1822 1826
1823 1827 def walkrepos(path, followsym=False, seen_dirs=None):
1824 1828 '''yield every hg repository under path, recursively.'''
1825 1829 def errhandler(err):
1826 1830 if err.filename == path:
1827 1831 raise err
1828 1832 if followsym and hasattr(os.path, 'samestat'):
1829 1833 def _add_dir_if_not_there(dirlst, dirname):
1830 1834 match = False
1831 1835 samestat = os.path.samestat
1832 1836 dirstat = os.stat(dirname)
1833 1837 for lstdirstat in dirlst:
1834 1838 if samestat(dirstat, lstdirstat):
1835 1839 match = True
1836 1840 break
1837 1841 if not match:
1838 1842 dirlst.append(dirstat)
1839 1843 return not match
1840 1844 else:
1841 1845 followsym = False
1842 1846
1843 1847 if (seen_dirs is None) and followsym:
1844 1848 seen_dirs = []
1845 1849 _add_dir_if_not_there(seen_dirs, path)
1846 1850 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1847 1851 if '.hg' in dirs:
1848 1852 dirs[:] = [] # don't descend further
1849 1853 yield root # found a repository
1850 1854 qroot = os.path.join(root, '.hg', 'patches')
1851 1855 if os.path.isdir(os.path.join(qroot, '.hg')):
1852 1856 yield qroot # we have a patch queue repo here
1853 1857 elif followsym:
1854 1858 newdirs = []
1855 1859 for d in dirs:
1856 1860 fname = os.path.join(root, d)
1857 1861 if _add_dir_if_not_there(seen_dirs, fname):
1858 1862 if os.path.islink(fname):
1859 1863 for hgname in walkrepos(fname, True, seen_dirs):
1860 1864 yield hgname
1861 1865 else:
1862 1866 newdirs.append(d)
1863 1867 dirs[:] = newdirs
1864 1868
1865 1869 _rcpath = None
1866 1870
1867 1871 def os_rcpath():
1868 1872 '''return default os-specific hgrc search path'''
1869 1873 path = system_rcpath()
1870 1874 path.extend(user_rcpath())
1871 1875 path = [os.path.normpath(f) for f in path]
1872 1876 return path
1873 1877
1874 1878 def rcpath():
1875 1879 '''return hgrc search path. if env var HGRCPATH is set, use it.
1876 1880 for each item in path, if directory, use files ending in .rc,
1877 1881 else use item.
1878 1882 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1879 1883 if no HGRCPATH, use default os-specific path.'''
1880 1884 global _rcpath
1881 1885 if _rcpath is None:
1882 1886 if 'HGRCPATH' in os.environ:
1883 1887 _rcpath = []
1884 1888 for p in os.environ['HGRCPATH'].split(os.pathsep):
1885 1889 if not p: continue
1886 1890 if os.path.isdir(p):
1887 1891 for f, kind in osutil.listdir(p):
1888 1892 if f.endswith('.rc'):
1889 1893 _rcpath.append(os.path.join(p, f))
1890 1894 else:
1891 1895 _rcpath.append(p)
1892 1896 else:
1893 1897 _rcpath = os_rcpath()
1894 1898 return _rcpath
1895 1899
1896 1900 def bytecount(nbytes):
1897 1901 '''return byte count formatted as readable string, with units'''
1898 1902
1899 1903 units = (
1900 1904 (100, 1<<30, _('%.0f GB')),
1901 1905 (10, 1<<30, _('%.1f GB')),
1902 1906 (1, 1<<30, _('%.2f GB')),
1903 1907 (100, 1<<20, _('%.0f MB')),
1904 1908 (10, 1<<20, _('%.1f MB')),
1905 1909 (1, 1<<20, _('%.2f MB')),
1906 1910 (100, 1<<10, _('%.0f KB')),
1907 1911 (10, 1<<10, _('%.1f KB')),
1908 1912 (1, 1<<10, _('%.2f KB')),
1909 1913 (1, 1, _('%.0f bytes')),
1910 1914 )
1911 1915
1912 1916 for multiplier, divisor, format in units:
1913 1917 if nbytes >= divisor * multiplier:
1914 1918 return format % (nbytes / float(divisor))
1915 1919 return units[-1][2] % nbytes
1916 1920
1917 1921 def drop_scheme(scheme, path):
1918 1922 sc = scheme + ':'
1919 1923 if path.startswith(sc):
1920 1924 path = path[len(sc):]
1921 1925 if path.startswith('//'):
1922 1926 path = path[2:]
1923 1927 return path
1924 1928
1925 1929 def uirepr(s):
1926 1930 # Avoid double backslash in Windows path repr()
1927 1931 return repr(s).replace('\\\\', '\\')
1928 1932
1929 1933 def hidepassword(url):
1930 1934 '''hide user credential in a url string'''
1931 1935 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
1932 1936 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
1933 1937 return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
1934 1938
1935 1939 def removeauth(url):
1936 1940 '''remove all authentication information from a url string'''
1937 1941 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
1938 1942 netloc = netloc[netloc.find('@')+1:]
1939 1943 return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
@@ -1,113 +1,111
1 1 {header}
2 2 <title>{repo|escape}: revision graph</title>
3 3 <link rel="alternate" type="application/atom+xml"
4 4 href="{url}atom-log" title="Atom feed for {repo|escape}: log">
5 5 <link rel="alternate" type="application/rss+xml"
6 6 href="{url}rss-log" title="RSS feed for {repo|escape}: log">
7 7 <!--[if IE]><script type="text/javascript" src="{staticurl}excanvas.js"></script><![endif]-->
8 8 </head>
9 9 <body>
10 10
11 11 <div class="container">
12 12 <div class="menu">
13 13 <div class="logo">
14 14 <a href="http://www.selenic.com/mercurial/">
15 15 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
16 16 </div>
17 17 <ul>
18 18 <li><a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">log</a></li>
19 19 <li class="active">graph</li>
20 20 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
21 21 </ul>
22 22 <ul>
23 23 <li><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
24 24 <li><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
25 25 </ul>
26 26 </div>
27 27
28 28 <div class="main">
29 29 <h2>{repo|escape}</h2>
30 30 <h3>graph</h3>
31 31
32 32 <form class="search" action="{url}log">
33 33 {sessionvars%hiddenformentry}
34 34 <p><input name="rev" id="search1" type="text" size="30"></p>
35 35 </form>
36 36
37 37 <div class="navigate">
38 38 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountless}">less</a>
39 39 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountmore}">more</a>
40 40 | {changenav%navgraphentry}
41 41 </div>
42 42
43 <div id="noscript">The revision graph only works with JavaScript-enabled browsers.</div>
43 <noscript>The revision graph only works with JavaScript-enabled browsers.</noscript>
44 44
45 45 <div id="wrapper">
46 46 <ul id="nodebgs"></ul>
47 47 <canvas id="graph" width="224" height="{canvasheight}"></canvas>
48 48 <ul id="graphnodes"></ul>
49 49 </div>
50 50
51 51 <script type="text/javascript" src="{staticurl}graph.js"></script>
52 52 <script type="text/javascript">
53 53 <!-- hide script content
54 54
55 document.getElementById('noscript').style.display = 'none';
56
57 55 var data = {jsdata|json};
58 56 var graph = new Graph();
59 57 graph.scale({bg_height});
60 58
61 59 graph.edge = function(x0, y0, x1, y1, color) {
62 60
63 61 this.setColor(color, 0.0, 0.65);
64 62 this.ctx.beginPath();
65 63 this.ctx.moveTo(x0, y0);
66 64 this.ctx.lineTo(x1, y1);
67 65 this.ctx.stroke();
68 66
69 67 }
70 68
71 69 var revlink = '<li style="_STYLE"><span class="desc">';
72 70 revlink += '<a href="{url}rev/_NODEID{sessionvars%urlparameter}" title="_NODEID">_DESC</a>';
73 71 revlink += '</span><span class="tag">_TAGS</span>';
74 72 revlink += '<span class="info">_DATE ago, by _USER</span></li>';
75 73
76 74 graph.vertex = function(x, y, color, parity, cur) {
77 75
78 76 this.ctx.beginPath();
79 77 color = this.setColor(color, 0.25, 0.75);
80 78 this.ctx.arc(x, y, radius, 0, Math.PI * 2, true);
81 79 this.ctx.fill();
82 80
83 81 var bg = '<li class="bg parity' + parity + '"></li>';
84 82 var left = (this.columns + 1) * this.bg_height;
85 83 var nstyle = 'padding-left: ' + left + 'px;';
86 84 var item = revlink.replace(/_STYLE/, nstyle);
87 85 item = item.replace(/_PARITY/, 'parity' + parity);
88 86 item = item.replace(/_NODEID/, cur[0]);
89 87 item = item.replace(/_NODEID/, cur[0]);
90 88 item = item.replace(/_DESC/, cur[3]);
91 89 item = item.replace(/_USER/, cur[4]);
92 90 item = item.replace(/_DATE/, cur[5]);
93 91 item = item.replace(/_TAGS/, cur[7].join('&nbsp; '));
94 92
95 93 return [bg, item];
96 94
97 95 }
98 96
99 97 graph.render(data);
100 98
101 99 // stop hiding script -->
102 100 </script>
103 101
104 102 <div class="navigate">
105 103 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountless}">less</a>
106 104 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountmore}">more</a>
107 105 | {changenav%navgraphentry}
108 106 </div>
109 107
110 108 </div>
111 109 </div>
112 110
113 111 {footer}
@@ -1,121 +1,119
1 1 #header#
2 2 <title>#repo|escape#: Graph</title>
3 3 <link rel="alternate" type="application/atom+xml"
4 4 href="{url}atom-log" title="Atom feed for #repo|escape#"/>
5 5 <link rel="alternate" type="application/rss+xml"
6 6 href="{url}rss-log" title="RSS feed for #repo|escape#"/>
7 7 </head>
8 8 <body>
9 9
10 10 <div class="page_header">
11 11 <a href="http://www.selenic.com/mercurial/" title="Mercurial" style="float: right;">Mercurial</a><a href="{url}summary{sessionvars%urlparameter}">#repo|escape#</a> / graph
12 12 </div>
13 13
14 14 <form action="{url}log">
15 15 {sessionvars%hiddenformentry}
16 16 <div class="search">
17 17 <input type="text" name="rev" />
18 18 </div>
19 19 </form>
20 20 <div class="page_nav">
21 21 <a href="{url}summary{sessionvars%urlparameter}">summary</a> |
22 22 <a href="{url}shortlog{sessionvars%urlparameter}">shortlog</a> |
23 23 <a href="{url}log/#rev#{sessionvars%urlparameter}">changelog</a> |
24 24 graph |
25 25 <a href="{url}tags{sessionvars%urlparameter}">tags</a> |
26 26 <a href="{url}file/#node|short#{sessionvars%urlparameter}">files</a>
27 27 <br/>
28 28 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountless}">less</a>
29 29 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountmore}">more</a>
30 30 | #changenav%navgraphentry#<br/>
31 31 </div>
32 32
33 33 <div class="title">&nbsp;</div>
34 34
35 <div id="noscript">The revision graph only works with JavaScript-enabled browsers.</div>
35 <noscript>The revision graph only works with JavaScript-enabled browsers.</noscript>
36 36
37 37 <div id="wrapper">
38 38 <ul id="nodebgs"></ul>
39 39 <canvas id="graph" width="224" height="#canvasheight#"></canvas>
40 40 <ul id="graphnodes"></ul>
41 41 </div>
42 42
43 43 <script type="text/javascript" src="#staticurl#graph.js"></script>
44 44 <script>
45 45 <!-- hide script content
46 46
47 document.getElementById('noscript').style.display = 'none';
48
49 47 var data = {jsdata|json};
50 48 var graph = new Graph();
51 49 graph.scale({bg_height});
52 50
53 51 graph.edge = function(x0, y0, x1, y1, color) {
54 52
55 53 this.setColor(color, 0.0, 0.65);
56 54 this.ctx.beginPath();
57 55 this.ctx.moveTo(x0, y0);
58 56 this.ctx.lineTo(x1, y1);
59 57 this.ctx.stroke();
60 58
61 59 }
62 60
63 61 var revlink = '<li style="_STYLE"><span class="desc">';
64 62 revlink += '<a class="list" href="{url}rev/_NODEID{sessionvars%urlparameter}" title="_NODEID"><b>_DESC</b></a>';
65 63 revlink += '</span> _TAGS';
66 64 revlink += '<span class="info">_DATE ago, by _USER</span></li>';
67 65
68 66 graph.vertex = function(x, y, color, parity, cur) {
69 67
70 68 this.ctx.beginPath();
71 69 color = this.setColor(color, 0.25, 0.75);
72 70 this.ctx.arc(x, y, radius, 0, Math.PI * 2, true);
73 71 this.ctx.fill();
74 72
75 73 var bg = '<li class="bg parity' + parity + '"></li>';
76 74 var left = (this.columns + 1) * this.bg_height;
77 75 var nstyle = 'padding-left: ' + left + 'px;';
78 76 var item = revlink.replace(/_STYLE/, nstyle);
79 77 item = item.replace(/_PARITY/, 'parity' + parity);
80 78 item = item.replace(/_NODEID/, cur[0]);
81 79 item = item.replace(/_NODEID/, cur[0]);
82 80 item = item.replace(/_DESC/, cur[3]);
83 81 item = item.replace(/_USER/, cur[4]);
84 82 item = item.replace(/_DATE/, cur[5]);
85 83
86 84 var tagspan = '';
87 85 if (cur[7].length || (cur[6][0] != 'default' || cur[6][1])) {
88 86 tagspan = '<span class="logtags">';
89 87 if (cur[6][1]) {
90 88 tagspan += '<span class="branchtag" title="' + cur[6][0] + '">';
91 89 tagspan += cur[6][0] + '</span> ';
92 90 } else if (!cur[6][1] && cur[6][0] != 'default') {
93 91 tagspan += '<span class="inbranchtag" title="' + cur[6][0] + '">';
94 92 tagspan += cur[6][0] + '</span> ';
95 93 }
96 94 if (cur[7].length) {
97 95 for (var t in cur[7]) {
98 96 var tag = cur[7][t];
99 97 tagspan += '<span class="tagtag">' + tag + '</span> ';
100 98 }
101 99 }
102 100 tagspan += '</span>';
103 101 }
104 102
105 103 item = item.replace(/_TAGS/, tagspan);
106 104 return [bg, item];
107 105
108 106 }
109 107
110 108 graph.render(data);
111 109
112 110 // stop hiding script -->
113 111 </script>
114 112
115 113 <div class="page_nav">
116 114 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountless}">less</a>
117 115 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountmore}">more</a>
118 116 | {changenav%navgraphentry}
119 117 </div>
120 118
121 119 #footer#
@@ -1,97 +1,95
1 1 #header#
2 2 <title>#repo|escape#: graph</title>
3 3 <link rel="alternate" type="application/atom+xml"
4 4 href="#url#atom-tags" title="Atom feed for #repo|escape#: tags">
5 5 <link rel="alternate" type="application/rss+xml"
6 6 href="#url#rss-tags" title="RSS feed for #repo|escape#: tags">
7 7 <!--[if IE]><script type="text/javascript" src="#staticurl#excanvas.js"></script><![endif]-->
8 8 </head>
9 9 <body>
10 10
11 11 <div class="buttons">
12 12 <a href="#url#log{sessionvars%urlparameter}">changelog</a>
13 13 <a href="#url#shortlog{sessionvars%urlparameter}">shortlog</a>
14 14 <a href="#url#tags{sessionvars%urlparameter}">tags</a>
15 15 <a href="#url#file/#node|short#/{sessionvars%urlparameter}">files</a>
16 16 </div>
17 17
18 18 <h2>graph</h2>
19 19
20 20 <form action="#url#log">
21 21 {sessionvars%hiddenformentry}
22 22 <p>
23 23 <label for="search1">search:</label>
24 24 <input name="rev" id="search1" type="text" size="30">
25 25 navigate: <small class="navigate">#changenav%navgraphentry#</small>
26 26 </p>
27 27 </form>
28 28
29 <div id="noscript">The revision graph only works with JavaScript-enabled browsers.</div>
29 <noscript>The revision graph only works with JavaScript-enabled browsers.</noscript>
30 30
31 31 <div id="wrapper">
32 32 <ul id="nodebgs"></ul>
33 33 <canvas id="graph" width="224" height="#canvasheight#"></canvas>
34 34 <ul id="graphnodes"></ul>
35 35 </div>
36 36
37 37 <script type="text/javascript" src="#staticurl#graph.js"></script>
38 38 <script type="text/javascript">
39 39 <!-- hide script content
40 40
41 document.getElementById('noscript').style.display = 'none';
42
43 41 var data = {jsdata|json};
44 42 var graph = new Graph();
45 43 graph.scale({bg_height});
46 44
47 45 graph.edge = function(x0, y0, x1, y1, color) {
48 46
49 47 this.setColor(color, 0.0, 0.65);
50 48 this.ctx.beginPath();
51 49 this.ctx.moveTo(x0, y0);
52 50 this.ctx.lineTo(x1, y1);
53 51 this.ctx.stroke();
54 52
55 53 }
56 54
57 55 var revlink = '<li style="_STYLE"><span class="desc">';
58 56 revlink += '<a href="{url}rev/_NODEID{sessionvars%urlparameter}" title="_NODEID">_DESC</a>';
59 57 revlink += '</span><span class="info">_DATE ago, by _USER</span></li>';
60 58
61 59 graph.vertex = function(x, y, color, parity, cur) {
62 60
63 61 this.ctx.beginPath();
64 62 color = this.setColor(color, 0.25, 0.75);
65 63 this.ctx.arc(x, y, radius, 0, Math.PI * 2, true);
66 64 this.ctx.fill();
67 65
68 66 var bg = '<li class="bg parity' + parity + '"></li>';
69 67 var left = (this.columns + 1) * this.bg_height;
70 68 var nstyle = 'padding-left: ' + left + 'px;';
71 69 var item = revlink.replace(/_STYLE/, nstyle);
72 70 item = item.replace(/_PARITY/, 'parity' + parity);
73 71 item = item.replace(/_NODEID/, cur[0]);
74 72 item = item.replace(/_NODEID/, cur[0]);
75 73 item = item.replace(/_DESC/, cur[3]);
76 74 item = item.replace(/_USER/, cur[4]);
77 75 item = item.replace(/_DATE/, cur[5]);
78 76
79 77 return [bg, item];
80 78
81 79 }
82 80
83 81 graph.render(data);
84 82
85 83 // stop hiding script -->
86 84 </script>
87 85
88 86 <form action="#url#log">
89 87 {sessionvars%hiddenformentry}
90 88 <p>
91 89 <label for="search1">search:</label>
92 90 <input name="rev" id="search1" type="text" size="30">
93 91 navigate: <small class="navigate">#changenav%navgraphentry#</small>
94 92 </p>
95 93 </form>
96 94
97 95 #footer#
@@ -1,63 +1,67
1 1 uisetup called
2 2 ui.parentui isnot None
3 3 reposetup called for a
4 4 ui == repo.ui
5 5 Foo
6 6 uisetup called
7 7 ui.parentui is None
8 8 reposetup called for a
9 9 ui == repo.ui
10 10 reposetup called for b
11 11 ui == repo.ui
12 12 updating working directory
13 13 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
14 14 uisetup called
15 15 ui.parentui is None
16 16 Bar
17 17 % module/__init__.py-style
18 18 uisetup called
19 19 ui.parentui isnot None
20 20 reposetup called for a
21 21 ui == repo.ui
22 22 Foo
23 23 empty extension - empty cmdtable
24 24
25 25 no commands defined
26 26 debugextension extension - only debugcommands
27 27
28 28 no commands defined
29 29 debugextension extension - only debugcommands
30 30
31 31 list of commands:
32 32
33 33 debugfoobar:
34 34 yet another debug command
35 35
36 enabled extensions:
37
38 debugextension only debugcommands
39
36 40 special help topics:
37 41 dates Date Formats
38 42 patterns File Name Patterns
39 43 environment, env Environment Variables
40 44 revs, revisions Specifying Single Revisions
41 45 mrevs, multirevs Specifying Multiple Revisions
42 46
43 47 global options:
44 48 -R --repository repository root directory or symbolic path name
45 49 --cwd change working directory
46 50 -y --noninteractive do not prompt, assume 'yes' for any required answers
47 51 -q --quiet suppress output
48 52 -v --verbose enable additional output
49 53 --config set/override config option
50 54 --debug enable debugging output
51 55 --debugger start debugger
52 56 --encoding set the charset encoding (default: ascii)
53 57 --encodingmode set the charset encoding mode (default: strict)
54 58 --lsprof print improved command execution profile
55 59 --traceback print traceback on exception
56 60 --time time how long the command takes
57 61 --profile print command execution profile
58 62 --version output version information and exit
59 63 -h --help display help and exit
60 64 % issue811
61 65 % show extensions
62 66 debugissue811
63 67 mq
1 NO CONTENT: modified file, binary diff hidden
@@ -1,496 +1,502
1 1 % help
2 2 keyword extension - keyword expansion in local repositories
3 3
4 4 This extension expands RCS/CVS-like or self-customized $Keywords$
5 5 in tracked text files selected by your configuration.
6 6
7 7 Keywords are only expanded in local repositories and not stored in
8 8 the change history. The mechanism can be regarded as a convenience
9 9 for the current user or for archive distribution.
10 10
11 11 Configuration is done in the [keyword] and [keywordmaps] sections
12 12 of hgrc files.
13 13
14 14 Example:
15 15
16 16 [keyword]
17 17 # expand keywords in every python file except those matching "x*"
18 18 **.py =
19 19 x* = ignore
20 20
21 21 Note: the more specific you are in your filename patterns
22 22 the less you lose speed in huge repos.
23 23
24 24 For [keywordmaps] template mapping and expansion demonstration and
25 25 control run "hg kwdemo".
26 26
27 27 An additional date template filter {date|utcdate} is provided.
28 28
29 29 The default template mappings (view with "hg kwdemo -d") can be replaced
30 30 with customized keywords and templates.
31 31 Again, run "hg kwdemo" to control the results of your config changes.
32 32
33 33 Before changing/disabling active keywords, run "hg kwshrink" to avoid
34 34 the risk of inadvertedly storing expanded keywords in the change history.
35 35
36 36 To force expansion after enabling it, or a configuration change, run
37 37 "hg kwexpand".
38 38
39 39 Also, when committing with the record extension or using mq's qrecord, be aware
40 40 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
41 41 question to update keyword expansions after all changes have been checked in.
42 42
43 43 Expansions spanning more than one line and incremental expansions,
44 44 like CVS' $Log$, are not supported. A keyword template map
45 45 "Log = {desc}" expands to the first line of the changeset description.
46 46
47 47 list of commands:
48 48
49 49 kwdemo print [keywordmaps] configuration and an expansion example
50 50 kwexpand expand keywords in working directory
51 51 kwfiles print files currently configured for keyword expansion
52 52 kwshrink revert expanded keywords in working directory
53 53
54 enabled extensions:
55
56 keyword keyword expansion in local repositories
57 mq patch management and development
58 notify hook extension to email notifications on commits/pushes
59
54 60 use "hg -v help keyword" to show aliases and global options
55 61 % hg kwdemo
56 62 [extensions]
57 63 hgext.keyword =
58 64 [keyword]
59 65 * =
60 66 b = ignore
61 67 demo.txt =
62 68 [keywordmaps]
63 69 RCSFile = {file|basename},v
64 70 Author = {author|user}
65 71 Header = {root}/{file},v {node|short} {date|utcdate} {author|user}
66 72 Source = {root}/{file},v
67 73 Date = {date|utcdate}
68 74 Id = {file|basename},v {node|short} {date|utcdate} {author|user}
69 75 Revision = {node|short}
70 76 $RCSFile: demo.txt,v $
71 77 $Author: test $
72 78 $Header: /TMP/demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
73 79 $Source: /TMP/demo.txt,v $
74 80 $Date: 2000/00/00 00:00:00 $
75 81 $Id: demo.txt,v xxxxxxxxxxxx 2000/00/00 00:00:00 test $
76 82 $Revision: xxxxxxxxxxxx $
77 83 [extensions]
78 84 hgext.keyword =
79 85 [keyword]
80 86 * =
81 87 b = ignore
82 88 demo.txt =
83 89 [keywordmaps]
84 90 Branch = {branches}
85 91 $Branch: demobranch $
86 92 % kwshrink should exit silently in empty/invalid repo
87 93 pulling from test-keyword.hg
88 94 requesting all changes
89 95 adding changesets
90 96 adding manifests
91 97 adding file changes
92 98 added 1 changesets with 1 changes to 1 files
93 99 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 100 % cat
95 101 expand $Id$
96 102 do not process $Id:
97 103 xxx $
98 104 ignore $Id$
99 105 % addremove
100 106 adding a
101 107 adding b
102 108 % status
103 109 A a
104 110 A b
105 111 % default keyword expansion including commit hook
106 112 % interrupted commit should not change state or run commit hook
107 113 a
108 114 b
109 115 transaction abort!
110 116 rollback completed
111 117 abort: empty commit message
112 118 % status
113 119 A a
114 120 A b
115 121 % commit
116 122 a
117 123 b
118 124 overwriting a expanding keywords
119 125 running hook commit.test: cp a hooktest
120 126 committed changeset 1:ef63ca68695bc9495032c6fda1350c71e6d256e9
121 127 % status
122 128 ? hooktest
123 129 % identify
124 130 ef63ca68695b
125 131 % cat
126 132 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
127 133 do not process $Id:
128 134 xxx $
129 135 ignore $Id$
130 136 % hg cat
131 137 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
132 138 do not process $Id:
133 139 xxx $
134 140 ignore $Id$
135 141 a
136 142 % diff a hooktest
137 143 % removing commit hook from config
138 144 % bundle
139 145 2 changesets found
140 146 % notify on pull to check whether keywords stay as is in email
141 147 % ie. if patch.diff wrapper acts as it should
142 148 % pull from bundle
143 149 pulling from ../kw.hg
144 150 requesting all changes
145 151 adding changesets
146 152 adding manifests
147 153 adding file changes
148 154 added 2 changesets with 3 changes to 3 files
149 155
150 156 diff -r 000000000000 -r a2392c293916 sym
151 157 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
152 158 +++ b/sym Sat Feb 09 20:25:47 2008 +0100
153 159 @@ -0,0 +1,1 @@
154 160 +a
155 161 \ No newline at end of file
156 162
157 163 diff -r a2392c293916 -r ef63ca68695b a
158 164 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
159 165 +++ b/a Thu Jan 01 00:00:00 1970 +0000
160 166 @@ -0,0 +1,3 @@
161 167 +expand $Id$
162 168 +do not process $Id:
163 169 +xxx $
164 170 diff -r a2392c293916 -r ef63ca68695b b
165 171 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
166 172 +++ b/b Thu Jan 01 00:00:00 1970 +0000
167 173 @@ -0,0 +1,1 @@
168 174 +ignore $Id$
169 175 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
170 176 % remove notify config
171 177 % touch
172 178 % status
173 179 % update
174 180 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
175 181 % cat
176 182 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
177 183 do not process $Id:
178 184 xxx $
179 185 ignore $Id$
180 186 % check whether expansion is filewise
181 187 % commit c
182 188 adding c
183 189 % force expansion
184 190 overwriting a expanding keywords
185 191 overwriting c expanding keywords
186 192 % compare changenodes in a c
187 193 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
188 194 do not process $Id:
189 195 xxx $
190 196 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
191 197 tests for different changenodes
192 198 % qinit -c
193 199 % qimport
194 200 % qcommit
195 201 % keywords should not be expanded in patch
196 202 # HG changeset patch
197 203 # User User Name <user@example.com>
198 204 # Date 1 0
199 205 # Node ID 40a904bbbe4cd4ab0a1f28411e35db26341a40ad
200 206 # Parent ef63ca68695bc9495032c6fda1350c71e6d256e9
201 207 cndiff
202 208
203 209 diff -r ef63ca68695b -r 40a904bbbe4c c
204 210 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
205 211 +++ b/c Thu Jan 01 00:00:01 1970 +0000
206 212 @@ -0,0 +1,2 @@
207 213 +$Id$
208 214 +tests for different changenodes
209 215 % qpop
210 216 Patch queue now empty
211 217 % qgoto - should imply qpush
212 218 applying mqtest.diff
213 219 Now at: mqtest.diff
214 220 % cat
215 221 $Id: c,v 40a904bbbe4c 1970/01/01 00:00:01 user $
216 222 tests for different changenodes
217 223 % qpop and move on
218 224 Patch queue now empty
219 225 % copy
220 226 % kwfiles added
221 227 a
222 228 c
223 229 % commit
224 230 c
225 231 c: copy a:0045e12f6c5791aac80ca6cbfd97709a88307292
226 232 overwriting c expanding keywords
227 233 committed changeset 2:e22d299ac0c2bd8897b3df5114374b9e4d4ca62f
228 234 % cat a c
229 235 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
230 236 do not process $Id:
231 237 xxx $
232 238 expand $Id: c,v e22d299ac0c2 1970/01/01 00:00:01 user $
233 239 do not process $Id:
234 240 xxx $
235 241 % touch copied c
236 242 % status
237 243 % kwfiles
238 244 a
239 245 c
240 246 % diff --rev
241 247 diff -r ef63ca68695b c
242 248 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
243 249 @@ -0,0 +1,3 @@
244 250 +expand $Id$
245 251 +do not process $Id:
246 252 +xxx $
247 253 % rollback
248 254 rolling back last transaction
249 255 % status
250 256 A c
251 257 % update -C
252 258 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
253 259 % custom keyword expansion
254 260 % try with kwdemo
255 261 [extensions]
256 262 hgext.keyword =
257 263 [keyword]
258 264 * =
259 265 b = ignore
260 266 demo.txt =
261 267 [keywordmaps]
262 268 Xinfo = {author}: {desc}
263 269 $Xinfo: test: hg keyword config and expansion example $
264 270 % cat
265 271 expand $Id: a,v ef63ca68695b 1970/01/01 00:00:00 user $
266 272 do not process $Id:
267 273 xxx $
268 274 ignore $Id$
269 275 % hg cat
270 276 expand $Id: a ef63ca68695b Thu, 01 Jan 1970 00:00:00 +0000 user $
271 277 do not process $Id:
272 278 xxx $
273 279 ignore $Id$
274 280 a
275 281 % interrupted commit should not change state
276 282 transaction abort!
277 283 rollback completed
278 284 abort: empty commit message
279 285 % status
280 286 M a
281 287 ? log
282 288 % commit
283 289 a
284 290 overwriting a expanding keywords
285 291 committed changeset 2:bb948857c743469b22bbf51f7ec8112279ca5d83
286 292 % status
287 293 % verify
288 294 checking changesets
289 295 checking manifests
290 296 crosschecking files in changesets and manifests
291 297 checking files
292 298 3 files, 3 changesets, 4 total revisions
293 299 % cat
294 300 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
295 301 do not process $Id:
296 302 xxx $
297 303 $Xinfo: User Name <user@example.com>: firstline $
298 304 ignore $Id$
299 305 % hg cat
300 306 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
301 307 do not process $Id:
302 308 xxx $
303 309 $Xinfo: User Name <user@example.com>: firstline $
304 310 ignore $Id$
305 311 a
306 312 % annotate
307 313 1: expand $Id$
308 314 1: do not process $Id:
309 315 1: xxx $
310 316 2: $Xinfo$
311 317 % remove
312 318 committed changeset 3:d14c712653769de926994cf7fbb06c8fbd68f012
313 319 % status
314 320 % rollback
315 321 rolling back last transaction
316 322 % status
317 323 R a
318 324 % revert a
319 325 % cat a
320 326 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
321 327 do not process $Id:
322 328 xxx $
323 329 $Xinfo: User Name <user@example.com>: firstline $
324 330 % clone to test incoming
325 331 requesting all changes
326 332 adding changesets
327 333 adding manifests
328 334 adding file changes
329 335 added 2 changesets with 3 changes to 3 files
330 336 updating working directory
331 337 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
332 338 % incoming
333 339 comparing with test-keyword/Test
334 340 searching for changes
335 341 changeset: 2:bb948857c743
336 342 tag: tip
337 343 user: User Name <user@example.com>
338 344 date: Thu Jan 01 00:00:02 1970 +0000
339 345 summary: firstline
340 346
341 347 % commit rejecttest
342 348 a
343 349 overwriting a expanding keywords
344 350 committed changeset 2:85e279d709ffc28c9fdd1b868570985fc3d87082
345 351 % export
346 352 % import
347 353 applying ../rejecttest.diff
348 354 % cat
349 355 expand $Id: a 4e0994474d25 Thu, 01 Jan 1970 00:00:03 +0000 user $ rejecttest
350 356 do not process $Id: rejecttest
351 357 xxx $
352 358 $Xinfo: User Name <user@example.com>: rejects? $
353 359 ignore $Id$
354 360
355 361 % rollback
356 362 rolling back last transaction
357 363 % clean update
358 364 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
359 365 % kwexpand/kwshrink on selected files
360 366 % copy a x/a
361 367 % kwexpand a
362 368 overwriting a expanding keywords
363 369 % kwexpand x/a should abort
364 370 abort: outstanding uncommitted changes
365 371 x/a
366 372 x/a: copy a:779c764182ce5d43e2b1eb66ce06d7b47bfe342e
367 373 overwriting x/a expanding keywords
368 374 committed changeset 3:cfa68229c1167443337266ebac453c73b1d5d16e
369 375 % cat a
370 376 expand $Id: x/a cfa68229c116 Thu, 01 Jan 1970 00:00:03 +0000 user $
371 377 do not process $Id:
372 378 xxx $
373 379 $Xinfo: User Name <user@example.com>: xa $
374 380 % kwshrink a inside directory x
375 381 overwriting x/a shrinking keywords
376 382 % cat a
377 383 expand $Id$
378 384 do not process $Id:
379 385 xxx $
380 386 $Xinfo$
381 387 % kwexpand nonexistent
382 388 nonexistent:
383 389 % hg serve
384 390 % expansion
385 391 % hgweb file
386 392 200 Script output follows
387 393
388 394 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
389 395 do not process $Id:
390 396 xxx $
391 397 $Xinfo: User Name <user@example.com>: firstline $
392 398 % no expansion
393 399 % hgweb annotate
394 400 200 Script output follows
395 401
396 402
397 403 user@1: expand $Id$
398 404 user@1: do not process $Id:
399 405 user@1: xxx $
400 406 user@2: $Xinfo$
401 407
402 408
403 409
404 410
405 411 % hgweb changeset
406 412 200 Script output follows
407 413
408 414
409 415 # HG changeset patch
410 416 # User User Name <user@example.com>
411 417 # Date 3 0
412 418 # Node ID cfa68229c1167443337266ebac453c73b1d5d16e
413 419 # Parent bb948857c743469b22bbf51f7ec8112279ca5d83
414 420 xa
415 421
416 422 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
417 423 +++ b/x/a Thu Jan 01 00:00:03 1970 +0000
418 424 @@ -0,0 +1,4 @@
419 425 +expand $Id$
420 426 +do not process $Id:
421 427 +xxx $
422 428 +$Xinfo$
423 429
424 430 % hgweb filediff
425 431 200 Script output follows
426 432
427 433
428 434 --- a/a Thu Jan 01 00:00:00 1970 +0000
429 435 +++ b/a Thu Jan 01 00:00:02 1970 +0000
430 436 @@ -1,3 +1,4 @@
431 437 expand $Id$
432 438 do not process $Id:
433 439 xxx $
434 440 +$Xinfo$
435 441
436 442
437 443
438 444
439 445 % errors encountered
440 446 % merge/resolve
441 447 % simplemerge
442 448 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 449 created new head
444 450 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
445 451 (branch merge, don't forget to commit)
446 452 $Id: m 8731e1dadc99 Thu, 01 Jan 1970 00:00:00 +0000 test $
447 453 foo
448 454 % conflict
449 455 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
450 456 created new head
451 457 merging m
452 458 warning: conflicts during merge.
453 459 merging m failed!
454 460 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
455 461 use 'hg resolve' to retry unresolved file merges
456 462 % keyword stays outside conflict zone
457 463 $Id$
458 464 <<<<<<< local
459 465 bar
460 466 =======
461 467 foo
462 468 >>>>>>> other
463 469 % resolve to local
464 470 $Id: m 43dfd2854b5b Thu, 01 Jan 1970 00:00:00 +0000 test $
465 471 bar
466 472 % switch off expansion
467 473 % kwshrink with unknown file u
468 474 overwriting a shrinking keywords
469 475 overwriting m shrinking keywords
470 476 overwriting x/a shrinking keywords
471 477 % cat
472 478 expand $Id$
473 479 do not process $Id:
474 480 xxx $
475 481 $Xinfo$
476 482 ignore $Id$
477 483 % hg cat
478 484 expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
479 485 do not process $Id:
480 486 xxx $
481 487 $Xinfo: User Name <user@example.com>: firstline $
482 488 ignore $Id$
483 489 a
484 490 % cat
485 491 expand $Id$
486 492 do not process $Id:
487 493 xxx $
488 494 $Xinfo$
489 495 ignore $Id$
490 496 % hg cat
491 497 expand $Id$
492 498 do not process $Id:
493 499 xxx $
494 500 $Xinfo$
495 501 ignore $Id$
496 502 a
@@ -1,554 +1,558
1 1 % help
2 2 mq extension - patch management and development
3 3
4 4 This extension lets you work with a stack of patches in a Mercurial
5 5 repository. It manages two stacks of patches - all known patches, and
6 6 applied patches (subset of known patches).
7 7
8 8 Known patches are represented as patch files in the .hg/patches
9 9 directory. Applied patches are both patch files and changesets.
10 10
11 11 Common tasks (use "hg help command" for more details):
12 12
13 13 prepare repository to work with patches qinit
14 14 create new patch qnew
15 15 import existing patch qimport
16 16
17 17 print patch series qseries
18 18 print applied patches qapplied
19 19 print name of top applied patch qtop
20 20
21 21 add known patch to applied stack qpush
22 22 remove patch from applied stack qpop
23 23 refresh contents of top applied patch qrefresh
24 24
25 25 list of commands:
26 26
27 27 qapplied print the patches already applied
28 28 qclone clone main and patch repository at same time
29 29 qcommit commit changes in the queue repository
30 30 qdelete remove patches from queue
31 31 qdiff diff of the current patch and subsequent modifications
32 32 qfinish move applied patches into repository history
33 33 qfold fold the named patches into the current patch
34 34 qgoto push or pop patches until named patch is at top of stack
35 35 qguard set or print guards for a patch
36 36 qheader Print the header of the topmost or specified patch
37 37 qimport import a patch
38 38 qinit init a new queue repository
39 39 qnew create a new patch
40 40 qnext print the name of the next patch
41 41 qpop pop the current patch off the stack
42 42 qprev print the name of the previous patch
43 43 qpush push the next patch onto the stack
44 44 qrefresh update the current patch
45 45 qrename rename a patch
46 46 qrestore restore the queue state saved by a rev
47 47 qsave save current queue state
48 48 qselect set or print guarded patches to push
49 49 qseries print the entire series file
50 50 qtop print the name of the current patch
51 51 qunapplied print the patches not yet applied
52 52 strip strip a revision and all its descendants from the repository
53 53
54 enabled extensions:
55
56 mq patch management and development
57
54 58 use "hg -v help mq" to show aliases and global options
55 59 adding a
56 60 updating working directory
57 61 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 62 adding b/z
59 63 % qinit
60 64 % -R qinit
61 65 % qinit -c
62 66 A .hgignore
63 67 A series
64 68 % qnew should refuse bad patch names
65 69 abort: "series" cannot be used as the name of a patch
66 70 abort: "status" cannot be used as the name of a patch
67 71 abort: "guards" cannot be used as the name of a patch
68 72 abort: ".hgignore" cannot be used as the name of a patch
69 73 % qnew implies add
70 74 A .hgignore
71 75 A series
72 76 A test.patch
73 77 % qinit; qinit -c
74 78 .hgignore:
75 79 ^\.hg
76 80 ^\.mq
77 81 syntax: glob
78 82 status
79 83 guards
80 84 series:
81 85 abort: repository already exists!
82 86 % qinit; <stuff>; qinit -c
83 87 adding .hg/patches/A
84 88 adding .hg/patches/B
85 89 A .hgignore
86 90 A A
87 91 A B
88 92 A series
89 93 .hgignore:
90 94 status
91 95 bleh
92 96 series:
93 97 A
94 98 B
95 99 % qnew with uncommitted changes
96 100 abort: local changes found, refresh first
97 101 A somefile
98 102 % qnew with uncommitted changes and missing file (issue 803)
99 103 someotherfile: No such file or directory
100 104 someotherfile: No such file or directory
101 105 A somefile
102 106 issue803.patch
103 107 Patch queue now empty
104 108 % qnew -m
105 109 foo bar
106 110 % qrefresh
107 111 foo bar
108 112
109 113 diff -r xa
110 114 --- a/a
111 115 +++ b/a
112 116 @@ -1,1 +1,2 @@
113 117 a
114 118 +a
115 119 % empty qrefresh
116 120 revision:
117 121 patch:
118 122 foo bar
119 123
120 124 working dir diff:
121 125 --- a/a
122 126 +++ b/a
123 127 @@ -1,1 +1,2 @@
124 128 a
125 129 +a
126 130 % qpop
127 131 Patch queue now empty
128 132 % qpush
129 133 applying test.patch
130 134 Now at: test.patch
131 135 % pop/push outside repo
132 136 Patch queue now empty
133 137 applying test.patch
134 138 Now at: test.patch
135 139 % qrefresh in subdir
136 140 % pop/push -a in subdir
137 141 Patch queue now empty
138 142 applying test.patch
139 143 applying test2.patch
140 144 Now at: test2.patch
141 145 % qseries
142 146 test.patch
143 147 test2.patch
144 148 Now at: test.patch
145 149 0 A test.patch: foo bar
146 150 1 U test2.patch:
147 151 applying test2.patch
148 152 Now at: test2.patch
149 153 % qapplied
150 154 test.patch
151 155 test2.patch
152 156 % qtop
153 157 test2.patch
154 158 % qprev
155 159 test.patch
156 160 % qnext
157 161 All patches applied
158 162 % pop, qnext, qprev, qapplied
159 163 Now at: test.patch
160 164 test2.patch
161 165 Only one patch applied
162 166 test.patch
163 167 % commit should fail
164 168 abort: cannot commit over an applied mq patch
165 169 % push should fail
166 170 pushing to ../../k
167 171 abort: source has mq patches applied
168 172 % qunapplied
169 173 test2.patch
170 174 % qpush/qpop with index
171 175 applying test2.patch
172 176 Now at: test2.patch
173 177 Now at: test.patch
174 178 applying test1b.patch
175 179 Now at: test1b.patch
176 180 applying test2.patch
177 181 Now at: test2.patch
178 182 Now at: test1b.patch
179 183 Now at: test.patch
180 184 applying test1b.patch
181 185 applying test2.patch
182 186 Now at: test2.patch
183 187 % push should succeed
184 188 Patch queue now empty
185 189 pushing to ../../k
186 190 searching for changes
187 191 adding changesets
188 192 adding manifests
189 193 adding file changes
190 194 added 1 changesets with 1 changes to 1 files
191 195 % qpush/qpop error codes
192 196 applying test.patch
193 197 applying test1b.patch
194 198 applying test2.patch
195 199 Now at: test2.patch
196 200 % pops all patches and succeeds
197 201 Patch queue now empty
198 202 qpop -a succeeds
199 203 % does nothing and succeeds
200 204 no patches applied
201 205 qpop -a succeeds
202 206 % fails - nothing else to pop
203 207 no patches applied
204 208 qpop fails
205 209 % pushes a patch and succeeds
206 210 applying test.patch
207 211 Now at: test.patch
208 212 qpush succeeds
209 213 % pops a patch and succeeds
210 214 Patch queue now empty
211 215 qpop succeeds
212 216 % pushes up to test1b.patch and succeeds
213 217 applying test.patch
214 218 applying test1b.patch
215 219 Now at: test1b.patch
216 220 qpush test1b.patch succeeds
217 221 % does nothing and succeeds
218 222 qpush: test1b.patch is already at the top
219 223 qpush test1b.patch succeeds
220 224 % does nothing and succeeds
221 225 qpop: test1b.patch is already at the top
222 226 qpop test1b.patch succeeds
223 227 % fails - can't push to this patch
224 228 abort: cannot push to a previous patch: test.patch
225 229 qpush test.patch fails
226 230 % fails - can't pop to this patch
227 231 abort: patch test2.patch is not applied
228 232 qpop test2.patch fails
229 233 % pops up to test.patch and succeeds
230 234 Now at: test.patch
231 235 qpop test.patch succeeds
232 236 % pushes all patches and succeeds
233 237 applying test1b.patch
234 238 applying test2.patch
235 239 Now at: test2.patch
236 240 qpush -a succeeds
237 241 % does nothing and succeeds
238 242 all patches are currently applied
239 243 qpush -a succeeds
240 244 % fails - nothing else to push
241 245 patch series already fully applied
242 246 qpush fails
243 247 % does nothing and succeeds
244 248 all patches are currently applied
245 249 qpush test2.patch succeeds
246 250 % strip
247 251 adding x
248 252 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
249 253 saving bundle to
250 254 adding changesets
251 255 adding manifests
252 256 adding file changes
253 257 added 1 changesets with 1 changes to 1 files
254 258 (run 'hg update' to get a working copy)
255 259 % strip with local changes, should complain
256 260 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 261 abort: local changes found
258 262 % --force strip with local changes
259 263 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
260 264 saving bundle to
261 265 % cd b; hg qrefresh
262 266 adding a
263 267 foo
264 268
265 269 diff -r cb9a9f314b8b a
266 270 --- a/a
267 271 +++ b/a
268 272 @@ -1,1 +1,2 @@
269 273 a
270 274 +a
271 275 diff -r cb9a9f314b8b b/f
272 276 --- /dev/null
273 277 +++ b/b/f
274 278 @@ -0,0 +1,1 @@
275 279 +f
276 280 % hg qrefresh .
277 281 foo
278 282
279 283 diff -r cb9a9f314b8b b/f
280 284 --- /dev/null
281 285 +++ b/b/f
282 286 @@ -0,0 +1,1 @@
283 287 +f
284 288 M a
285 289 % qpush failure
286 290 Patch queue now empty
287 291 applying foo
288 292 applying bar
289 293 file foo already exists
290 294 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
291 295 patch failed, unable to continue (try -v)
292 296 patch failed, rejects left in working dir
293 297 Errors during apply, please fix and refresh bar
294 298 ? foo
295 299 ? foo.rej
296 300 % mq tags
297 301 0 qparent
298 302 1 qbase foo
299 303 2 qtip bar tip
300 304 % bad node in status
301 305 Now at: foo
302 306 changeset: 0:cb9a9f314b8b
303 307 mq status file refers to unknown node
304 308 tag: tip
305 309 user: test
306 310 date: Thu Jan 01 00:00:00 1970 +0000
307 311 summary: a
308 312
309 313 mq status file refers to unknown node
310 314 default 0:cb9a9f314b8b
311 315 abort: working directory revision is not qtip
312 316 new file
313 317
314 318 diff --git a/new b/new
315 319 new file mode 100755
316 320 --- /dev/null
317 321 +++ b/new
318 322 @@ -0,0 +1,1 @@
319 323 +foo
320 324 copy file
321 325
322 326 diff --git a/new b/copy
323 327 copy from new
324 328 copy to copy
325 329 Now at: new
326 330 applying copy
327 331 Now at: copy
328 332 diff --git a/new b/copy
329 333 copy from new
330 334 copy to copy
331 335 diff --git a/new b/copy
332 336 copy from new
333 337 copy to copy
334 338 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
335 339 created new head
336 340 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
337 341 adding branch
338 342 adding changesets
339 343 adding manifests
340 344 adding file changes
341 345 added 1 changesets with 1 changes to 1 files
342 346 Patch queue now empty
343 347 (working directory not at tip)
344 348 applying bar
345 349 Now at: bar
346 350 diff --git a/bar b/bar
347 351 new file mode 100644
348 352 --- /dev/null
349 353 +++ b/bar
350 354 @@ -0,0 +1,1 @@
351 355 +bar
352 356 diff --git a/foo b/baz
353 357 rename from foo
354 358 rename to baz
355 359 2 baz (foo)
356 360 diff --git a/bar b/bar
357 361 new file mode 100644
358 362 --- /dev/null
359 363 +++ b/bar
360 364 @@ -0,0 +1,1 @@
361 365 +bar
362 366 diff --git a/foo b/baz
363 367 rename from foo
364 368 rename to baz
365 369 2 baz (foo)
366 370 diff --git a/bar b/bar
367 371 diff --git a/foo b/baz
368 372
369 373 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
370 374 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
371 375 adding branch
372 376 adding changesets
373 377 adding manifests
374 378 adding file changes
375 379 added 1 changesets with 1 changes to 1 files
376 380 Patch queue now empty
377 381 (working directory not at tip)
378 382 applying bar
379 383 Now at: bar
380 384 diff --git a/foo b/bleh
381 385 rename from foo
382 386 rename to bleh
383 387 diff --git a/quux b/quux
384 388 new file mode 100644
385 389 --- /dev/null
386 390 +++ b/quux
387 391 @@ -0,0 +1,1 @@
388 392 +bar
389 393 3 bleh (foo)
390 394 diff --git a/foo b/barney
391 395 rename from foo
392 396 rename to barney
393 397 diff --git a/fred b/fred
394 398 new file mode 100644
395 399 --- /dev/null
396 400 +++ b/fred
397 401 @@ -0,0 +1,1 @@
398 402 +bar
399 403 3 barney (foo)
400 404 % refresh omitting an added file
401 405 C newfile
402 406 A newfile
403 407 Now at: bar
404 408 % create a git patch
405 409 diff --git a/alexander b/alexander
406 410 % create a git binary patch
407 411 8ba2a2f3e77b55d03051ff9c24ad65e7 bucephalus
408 412 diff --git a/bucephalus b/bucephalus
409 413 % check binary patches can be popped and pushed
410 414 Now at: addalexander
411 415 applying addbucephalus
412 416 Now at: addbucephalus
413 417 8ba2a2f3e77b55d03051ff9c24ad65e7 bucephalus
414 418 % strip again
415 419 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
416 420 created new head
417 421 merging foo
418 422 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
419 423 (branch merge, don't forget to commit)
420 424 changeset: 3:99615015637b
421 425 tag: tip
422 426 parent: 2:20cbbe65cff7
423 427 parent: 1:d2871fc282d4
424 428 user: test
425 429 date: Thu Jan 01 00:00:00 1970 +0000
426 430 summary: merge
427 431
428 432 changeset: 2:20cbbe65cff7
429 433 parent: 0:53245c60e682
430 434 user: test
431 435 date: Thu Jan 01 00:00:00 1970 +0000
432 436 summary: change foo 2
433 437
434 438 changeset: 1:d2871fc282d4
435 439 user: test
436 440 date: Thu Jan 01 00:00:00 1970 +0000
437 441 summary: change foo 1
438 442
439 443 changeset: 0:53245c60e682
440 444 user: test
441 445 date: Thu Jan 01 00:00:00 1970 +0000
442 446 summary: add foo
443 447
444 448 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
445 449 saving bundle to
446 450 saving bundle to
447 451 adding branch
448 452 adding changesets
449 453 adding manifests
450 454 adding file changes
451 455 added 1 changesets with 1 changes to 1 files
452 456 changeset: 1:20cbbe65cff7
453 457 tag: tip
454 458 user: test
455 459 date: Thu Jan 01 00:00:00 1970 +0000
456 460 summary: change foo 2
457 461
458 462 changeset: 0:53245c60e682
459 463 user: test
460 464 date: Thu Jan 01 00:00:00 1970 +0000
461 465 summary: add foo
462 466
463 467 % qclone
464 468 abort: versioned patch repository not found (see qinit -c)
465 469 adding .hg/patches/patch1
466 470 main repo:
467 471 rev 1: change foo
468 472 rev 0: add foo
469 473 patch repo:
470 474 rev 0: checkpoint
471 475 updating working directory
472 476 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
473 477 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
474 478 main repo:
475 479 rev 0: add foo
476 480 patch repo:
477 481 rev 0: checkpoint
478 482 Patch queue now empty
479 483 main repo:
480 484 rev 0: add foo
481 485 patch repo:
482 486 rev 0: checkpoint
483 487 updating working directory
484 488 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
485 489 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
486 490 main repo:
487 491 rev 0: add foo
488 492 patch repo:
489 493 rev 0: checkpoint
490 494 % test applying on an empty file (issue 1033)
491 495 adding a
492 496 Patch queue now empty
493 497 applying changea
494 498 Now at: changea
495 499 % test qpush with --force, issue1087
496 500 adding bye.txt
497 501 adding hello.txt
498 502 Patch queue now empty
499 503 % qpush should fail, local changes
500 504 abort: local changes found, refresh first
501 505 % apply force, should not discard changes with empty patch
502 506 applying empty
503 507 patch: **** Only garbage was found in the patch input.
504 508 patch failed, unable to continue (try -v)
505 509 patch empty is empty
506 510 Now at: empty
507 511 diff -r bf5fc3f07a0a hello.txt
508 512 --- a/hello.txt
509 513 +++ b/hello.txt
510 514 @@ -1,1 +1,2 @@
511 515 hello
512 516 +world
513 517 diff -r 9ecee4f634e3 hello.txt
514 518 --- a/hello.txt
515 519 +++ b/hello.txt
516 520 @@ -1,1 +1,2 @@
517 521 hello
518 522 +world
519 523 changeset: 1:bf5fc3f07a0a
520 524 tag: qtip
521 525 tag: tip
522 526 tag: empty
523 527 tag: qbase
524 528 user: test
525 529 date: Thu Jan 01 00:00:00 1970 +0000
526 530 summary: imported patch empty
527 531
528 532
529 533 Patch queue now empty
530 534 % qpush should fail, local changes
531 535 abort: local changes found, refresh first
532 536 % apply force, should discard changes in hello, but not bye
533 537 applying empty
534 538 Now at: empty
535 539 M bye.txt
536 540 diff -r ba252371dbc1 bye.txt
537 541 --- a/bye.txt
538 542 +++ b/bye.txt
539 543 @@ -1,1 +1,2 @@
540 544 bye
541 545 +universe
542 546 diff -r 9ecee4f634e3 bye.txt
543 547 --- a/bye.txt
544 548 +++ b/bye.txt
545 549 @@ -1,1 +1,2 @@
546 550 bye
547 551 +universe
548 552 diff -r 9ecee4f634e3 hello.txt
549 553 --- a/hello.txt
550 554 +++ b/hello.txt
551 555 @@ -1,1 +1,3 @@
552 556 hello
553 557 +world
554 558 +universe
@@ -1,107 +1,164
1 notify extension - No help text available
1 notify extension - hook extension to email notifications on commits/pushes
2
3 Subscriptions can be managed through hgrc. Default mode is to print
4 messages to stdout, for testing and configuring.
5
6 To use, configure notify extension and enable in hgrc like this:
7
8 [extensions]
9 hgext.notify =
10
11 [hooks]
12 # one email for each incoming changeset
13 incoming.notify = python:hgext.notify.hook
14 # batch emails when many changesets incoming at one time
15 changegroup.notify = python:hgext.notify.hook
16
17 [notify]
18 # config items go in here
19
20 config items:
21
22 REQUIRED:
23 config = /path/to/file # file containing subscriptions
24
25 OPTIONAL:
26 test = True # print messages to stdout for testing
27 strip = 3 # number of slashes to strip for url paths
28 domain = example.com # domain to use if committer missing domain
29 style = ... # style file to use when formatting email
30 template = ... # template to use when formatting email
31 incoming = ... # template to use when run as incoming hook
32 changegroup = ... # template when run as changegroup hook
33 maxdiff = 300 # max lines of diffs to include (0=none, -1=all)
34 maxsubject = 67 # truncate subject line longer than this
35 diffstat = True # add a diffstat before the diff content
36 sources = serve # notify if source of incoming changes in this list
37 # (serve == ssh or http, push, pull, bundle)
38 [email]
39 from = user@host.com # email address to send as if none given
40 [web]
41 baseurl = http://hgserver/... # root of hg web site for browsing commits
42
43 notify config file has same format as regular hgrc. it has two
44 sections so you can express subscriptions in whatever way is handier
45 for you.
46
47 [usersubs]
48 # key is subscriber email, value is ","-separated list of glob patterns
49 user@host = pattern
50
51 [reposubs]
52 # key is glob pattern, value is ","-separated list of subscriber emails
53 pattern = user@host
54
55 glob patterns are matched against path to repo root.
56
57 if you like, you can put notify config file in repo that users can
58 push changes to, they can manage their own subscriptions.
2 59
3 60 no commands defined
4 61 % commit
5 62 adding a
6 63 % clone
7 64 updating working directory
8 65 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
9 66 % commit
10 67 % pull (minimal config)
11 68 pulling from ../a
12 69 searching for changes
13 70 adding changesets
14 71 adding manifests
15 72 adding file changes
16 73 added 1 changesets with 1 changes to 1 files
17 74 Content-Type: text/plain; charset="us-ascii"
18 75 MIME-Version: 1.0
19 76 Content-Transfer-Encoding: 7bit
20 77 Date:
21 78 Subject: changeset in test-notify/b: b
22 79 From: test
23 80 X-Hg-Notification: changeset 0647d048b600
24 81 Message-Id:
25 82 To: baz, foo@bar
26 83
27 84 changeset 0647d048b600 in test-notify/b
28 85 details: test-notify/b?cmd=changeset;node=0647d048b600
29 86 description: b
30 87
31 88 diffs (6 lines):
32 89
33 90 diff -r cb9a9f314b8b -r 0647d048b600 a
34 91 --- a/a Thu Jan 01 00:00:00 1970 +0000
35 92 +++ b/a Thu Jan 01 00:00:01 1970 +0000
36 93 @@ -1,1 +1,2 @@
37 94 a
38 95 +a
39 96 (run 'hg update' to get a working copy)
40 97 % fail for config file is missing
41 98 rolling back last transaction
42 99 pull failed
43 100 % pull
44 101 rolling back last transaction
45 102 pulling from ../a
46 103 searching for changes
47 104 adding changesets
48 105 adding manifests
49 106 adding file changes
50 107 added 1 changesets with 1 changes to 1 files
51 108 Content-Type: text/plain; charset="us-ascii"
52 109 MIME-Version: 1.0
53 110 Content-Transfer-Encoding: 7bit
54 111 Date:
55 112 Subject: b
56 113 From: test@test.com
57 114 X-Hg-Notification: changeset 0647d048b600
58 115 Message-Id:
59 116 To: baz@test.com, foo@bar
60 117
61 118 changeset 0647d048b600
62 119 description:
63 120 b
64 121 diffs (6 lines):
65 122
66 123 diff -r cb9a9f314b8b -r 0647d048b600 a
67 124 --- a/a Thu Jan 01 00:00:00 1970 +0000
68 125 +++ b/a Thu Jan 01 00:00:01 1970 +0000
69 126 @@ -1,1 +1,2 @@
70 127 a
71 128 +a
72 129 (run 'hg update' to get a working copy)
73 130 % pull
74 131 rolling back last transaction
75 132 pulling from ../a
76 133 searching for changes
77 134 adding changesets
78 135 adding manifests
79 136 adding file changes
80 137 added 1 changesets with 1 changes to 1 files
81 138 Content-Type: text/plain; charset="us-ascii"
82 139 MIME-Version: 1.0
83 140 Content-Transfer-Encoding: 7bit
84 141 Date:
85 142 Subject: b
86 143 From: test@test.com
87 144 X-Hg-Notification: changeset 0647d048b600
88 145 Message-Id:
89 146 To: baz@test.com, foo@bar
90 147
91 148 changeset 0647d048b600
92 149 description:
93 150 b
94 151 diffstat:
95 152
96 153 1 file changed, 1 insertion(+)
97 154 a | 1 +
98 155
99 156 diffs (6 lines):
100 157
101 158 diff -r cb9a9f314b8b -r 0647d048b600 a
102 159 --- a/a Thu Jan 01 00:00:00 1970 +0000
103 160 +++ b/a Thu Jan 01 00:00:01 1970 +0000
104 161 @@ -1,1 +1,2 @@
105 162 a
106 163 +a
107 164 (run 'hg update' to get a working copy)
@@ -1,207 +1,211
1 1 % help (no mq, so no qrecord)
2 2 hg: unknown command 'qrecord'
3 3 Mercurial Distributed SCM
4 4
5 5 basic commands:
6 6
7 7 add add the specified files on the next commit
8 8 annotate show changeset information per file line
9 9 clone make a copy of an existing repository
10 10 commit commit the specified files or all outstanding changes
11 11 diff diff repository (or selected files)
12 12 export dump the header and diffs for one or more changesets
13 13 init create a new repository in the given directory
14 14 log show revision history of entire repository or files
15 15 merge merge working directory with another revision
16 16 parents show the parents of the working dir or revision
17 17 pull pull changes from the specified source
18 18 push push changes to the specified destination
19 19 remove remove the specified files on the next commit
20 20 serve export the repository via HTTP
21 21 status show changed files in the working directory
22 22 update update working directory
23 23
24 enabled extensions:
25
26 record interactive change selection during commit or qrefresh
27
24 28 use "hg help" for the full list of commands or "hg -v" for details
25 29 % help (mq present)
26 30 hg qrecord [OPTION]... PATCH [FILE]...
27 31
28 32 interactively record a new patch
29 33
30 34 see 'hg help qnew' & 'hg help record' for more information and usage
31 35
32 36 options:
33 37
34 38 -e --edit edit commit message
35 39 -g --git use git extended diff format
36 40 -U --currentuser add "From: <current user>" to patch
37 41 -u --user add "From: <given user>" to patch
38 42 -D --currentdate add "Date: <current date>" to patch
39 43 -d --date add "Date: <given date>" to patch
40 44 -I --include include names matching the given patterns
41 45 -X --exclude exclude names matching the given patterns
42 46 -m --message use <text> as commit message
43 47 -l --logfile read commit message from <file>
44 48
45 49 use "hg -v help qrecord" to show global options
46 50 % base commit
47 51 % changing files
48 52 % whole diff
49 53 diff -r 1057167b20ef 1.txt
50 54 --- a/1.txt
51 55 +++ b/1.txt
52 56 @@ -1,5 +1,5 @@
53 57 1
54 58 -2
55 59 +2 2
56 60 3
57 61 -4
58 62 +4 4
59 63 5
60 64 diff -r 1057167b20ef 2.txt
61 65 --- a/2.txt
62 66 +++ b/2.txt
63 67 @@ -1,5 +1,5 @@
64 68 a
65 69 -b
66 70 +b b
67 71 c
68 72 d
69 73 e
70 74 diff -r 1057167b20ef dir/a.txt
71 75 --- a/dir/a.txt
72 76 +++ b/dir/a.txt
73 77 @@ -1,4 +1,4 @@
74 78 -hello world
75 79 +hello world!
76 80
77 81 someone
78 82 up
79 83 % qrecord a.patch
80 84 diff --git a/1.txt b/1.txt
81 85 2 hunks, 4 lines changed
82 86 examine changes to '1.txt'? [Ynsfdaq?] @@ -1,3 +1,3 @@
83 87 1
84 88 -2
85 89 +2 2
86 90 3
87 91 record this change to '1.txt'? [Ynsfdaq?] @@ -3,3 +3,3 @@
88 92 3
89 93 -4
90 94 +4 4
91 95 5
92 96 record this change to '1.txt'? [Ynsfdaq?] diff --git a/2.txt b/2.txt
93 97 1 hunks, 2 lines changed
94 98 examine changes to '2.txt'? [Ynsfdaq?] @@ -1,5 +1,5 @@
95 99 a
96 100 -b
97 101 +b b
98 102 c
99 103 d
100 104 e
101 105 record this change to '2.txt'? [Ynsfdaq?] diff --git a/dir/a.txt b/dir/a.txt
102 106 1 hunks, 2 lines changed
103 107 examine changes to 'dir/a.txt'? [Ynsfdaq?]
104 108 % after qrecord a.patch 'tip'
105 109 changeset: 1:5d1ca63427ee
106 110 tag: qtip
107 111 tag: tip
108 112 tag: a.patch
109 113 tag: qbase
110 114 user: test
111 115 date: Thu Jan 01 00:00:00 1970 +0000
112 116 summary: aaa
113 117
114 118 diff -r 1057167b20ef -r 5d1ca63427ee 1.txt
115 119 --- a/1.txt Thu Jan 01 00:00:00 1970 +0000
116 120 +++ b/1.txt Thu Jan 01 00:00:00 1970 +0000
117 121 @@ -1,5 +1,5 @@
118 122 1
119 123 -2
120 124 +2 2
121 125 3
122 126 4
123 127 5
124 128 diff -r 1057167b20ef -r 5d1ca63427ee 2.txt
125 129 --- a/2.txt Thu Jan 01 00:00:00 1970 +0000
126 130 +++ b/2.txt Thu Jan 01 00:00:00 1970 +0000
127 131 @@ -1,5 +1,5 @@
128 132 a
129 133 -b
130 134 +b b
131 135 c
132 136 d
133 137 e
134 138
135 139
136 140 % after qrecord a.patch 'diff'
137 141 diff -r 5d1ca63427ee 1.txt
138 142 --- a/1.txt
139 143 +++ b/1.txt
140 144 @@ -1,5 +1,5 @@
141 145 1
142 146 2 2
143 147 3
144 148 -4
145 149 +4 4
146 150 5
147 151 diff -r 5d1ca63427ee dir/a.txt
148 152 --- a/dir/a.txt
149 153 +++ b/dir/a.txt
150 154 @@ -1,4 +1,4 @@
151 155 -hello world
152 156 +hello world!
153 157
154 158 someone
155 159 up
156 160 % qrecord b.patch
157 161 diff --git a/1.txt b/1.txt
158 162 1 hunks, 2 lines changed
159 163 examine changes to '1.txt'? [Ynsfdaq?] @@ -1,5 +1,5 @@
160 164 1
161 165 2 2
162 166 3
163 167 -4
164 168 +4 4
165 169 5
166 170 record this change to '1.txt'? [Ynsfdaq?] diff --git a/dir/a.txt b/dir/a.txt
167 171 1 hunks, 2 lines changed
168 172 examine changes to 'dir/a.txt'? [Ynsfdaq?] @@ -1,4 +1,4 @@
169 173 -hello world
170 174 +hello world!
171 175
172 176 someone
173 177 up
174 178 record this change to 'dir/a.txt'? [Ynsfdaq?]
175 179 % after qrecord b.patch 'tip'
176 180 changeset: 2:b056198bf878
177 181 tag: qtip
178 182 tag: tip
179 183 tag: b.patch
180 184 user: test
181 185 date: Thu Jan 01 00:00:00 1970 +0000
182 186 summary: bbb
183 187
184 188 diff -r 5d1ca63427ee -r b056198bf878 1.txt
185 189 --- a/1.txt Thu Jan 01 00:00:00 1970 +0000
186 190 +++ b/1.txt Thu Jan 01 00:00:00 1970 +0000
187 191 @@ -1,5 +1,5 @@
188 192 1
189 193 2 2
190 194 3
191 195 -4
192 196 +4 4
193 197 5
194 198 diff -r 5d1ca63427ee -r b056198bf878 dir/a.txt
195 199 --- a/dir/a.txt Thu Jan 01 00:00:00 1970 +0000
196 200 +++ b/dir/a.txt Thu Jan 01 00:00:00 1970 +0000
197 201 @@ -1,4 +1,4 @@
198 202 -hello world
199 203 +hello world!
200 204
201 205 someone
202 206 up
203 207
204 208
205 209 % after qrecord b.patch 'diff'
206 210
207 211 % --- end ---
@@ -1,105 +1,108
1 1 #!/bin/sh
2 2
3 3 echo "[extensions]" >> $HGRCPATH
4 4 echo "rebase=" >> $HGRCPATH
5 5
6 6 addcommit () {
7 7 echo $1 > $1
8 8 hg add $1
9 9 hg commit -d "${2} 0" -u test -m $1
10 10 }
11 11
12 12 commit () {
13 13 hg commit -d "${2} 0" -u test -m $1
14 14 }
15 15
16 16 createrepo () {
17 17 hg init a
18 18 cd a
19 19 addcommit "c1" 0
20 20 addcommit "c2" 1
21 21 addcommit "c3" 2
22 22
23 23 hg update -C 1
24 24 addcommit "l1" 3
25 25 addcommit "l2" 4
26 26 addcommit "l3" 5
27 27
28 28 hg update -C 2
29 29 addcommit "r1" 6
30 30 addcommit "r2" 7
31 31 }
32 32
33 33 createrepo > /dev/null 2>&1
34 34 echo "% These fail"
35 35 echo
36 36 echo "% Use continue and abort"
37 37 hg rebase --continue --abort
38 38
39 39 echo
40 40 echo "% Use continue and collapse"
41 41 hg rebase --continue --collapse
42 42
43 43 echo
44 44 echo "% Use continue/abort and dest/source"
45 45 hg rebase --continue --dest 4
46 46
47 47 echo
48 48 echo "% Use source and base"
49 49 hg rebase --base 5 --source 4
50 50
51 51 echo
52 52 echo "% Rebase with no arguments - from current"
53 53 hg rebase
54 54
55 55 echo
56 56 echo "% Rebase with no arguments - from the current branch"
57 57 hg update 6
58 58 hg rebase
59 59
60 60 echo "% ----------"
61 61 echo "% These work"
62 62 echo
63 63 echo "% Rebase with no arguments (from 3 onto 7)"
64 64 hg update -C 5
65 65 hg rebase 2>&1 | sed 's/\(saving bundle to \).*/\1/'
66 66
67 echo "% Try to rollback after a rebase (fail)"
68 hg rollback
69
67 70 createrepo > /dev/null 2>&1
68 71 echo
69 72 echo "% Rebase with base == '.' => same as no arguments (from 3 onto 7)"
70 73 hg update -C 5
71 74 hg rebase --base . 2>&1 | sed 's/\(saving bundle to \).*/\1/'
72 75
73 76 createrepo > /dev/null 2>&1
74 77 echo
75 78 echo "% Rebase with dest == `hg branch` => same as no arguments (from 3 onto 7)"
76 79 hg update -C 5
77 80 hg rebase --dest `hg branch` 2>&1 | sed 's/\(saving bundle to \).*/\1/'
78 81
79 82 createrepo > /dev/null 2>&1
80 83 echo
81 84 echo "% Specify only source (from 4 onto 7)"
82 85 hg rebase --source 4 2>&1 | sed 's/\(saving bundle to \).*/\1/'
83 86
84 87 createrepo > /dev/null 2>&1
85 88 echo
86 89 echo "% Specify only dest (from 3 onto 6)"
87 90 hg update -C 5
88 91 hg rebase --dest 6 2>&1 | sed 's/\(saving bundle to \).*/\1/'
89 92
90 93 createrepo > /dev/null 2>&1
91 94 echo
92 95 echo "% Specify only base (from 3 onto 7)"
93 96 hg rebase --base 5 2>&1 | sed 's/\(saving bundle to \).*/\1/'
94 97
95 98 createrepo > /dev/null 2>&1
96 99 echo
97 100 echo "% Specify source and dest (from 4 onto 6)"
98 101 hg rebase --source 4 --dest 6 2>&1 | sed 's/\(saving bundle to \).*/\1/'
99 102
100 103 createrepo > /dev/null 2>&1
101 104 echo
102 105 echo "% Specify base and dest (from 3 onto 6)"
103 106 hg rebase --base 4 --dest 6 2>&1 | sed 's/\(saving bundle to \).*/\1/'
104 107
105 108 exit 0
@@ -1,194 +1,196
1 1 % These fail
2 2
3 3 % Use continue and abort
4 4 hg rebase: cannot use both abort and continue
5 5 hg rebase [-s rev | -b rev] [-d rev] [--collapse] | [-c] | [-a] | [--keep]
6 6
7 7 move changeset (and descendants) to a different branch
8 8
9 9 Rebase uses repeated merging to graft changesets from one part of history
10 10 onto another. This can be useful for linearizing local changes relative to
11 11 a master development tree.
12 12
13 13 If a rebase is interrupted to manually resolve a merge, it can be continued
14 14 with --continue or aborted with --abort.
15 15
16 16 options:
17 17
18 18 --keep keep original revisions
19 19 -s --source rebase from a given revision
20 20 -b --base rebase from the base of a given revision
21 21 -d --dest rebase onto a given revision
22 22 --collapse collapse the rebased revisions
23 23 -c --continue continue an interrupted rebase
24 24 -a --abort abort an interrupted rebase
25 25 --style display using template map file
26 26 --template display with template
27 27
28 28 use "hg -v help rebase" to show global options
29 29
30 30 % Use continue and collapse
31 31 hg rebase: cannot use collapse with continue or abort
32 32 hg rebase [-s rev | -b rev] [-d rev] [--collapse] | [-c] | [-a] | [--keep]
33 33
34 34 move changeset (and descendants) to a different branch
35 35
36 36 Rebase uses repeated merging to graft changesets from one part of history
37 37 onto another. This can be useful for linearizing local changes relative to
38 38 a master development tree.
39 39
40 40 If a rebase is interrupted to manually resolve a merge, it can be continued
41 41 with --continue or aborted with --abort.
42 42
43 43 options:
44 44
45 45 --keep keep original revisions
46 46 -s --source rebase from a given revision
47 47 -b --base rebase from the base of a given revision
48 48 -d --dest rebase onto a given revision
49 49 --collapse collapse the rebased revisions
50 50 -c --continue continue an interrupted rebase
51 51 -a --abort abort an interrupted rebase
52 52 --style display using template map file
53 53 --template display with template
54 54
55 55 use "hg -v help rebase" to show global options
56 56
57 57 % Use continue/abort and dest/source
58 58 hg rebase: abort and continue do not allow specifying revisions
59 59 hg rebase [-s rev | -b rev] [-d rev] [--collapse] | [-c] | [-a] | [--keep]
60 60
61 61 move changeset (and descendants) to a different branch
62 62
63 63 Rebase uses repeated merging to graft changesets from one part of history
64 64 onto another. This can be useful for linearizing local changes relative to
65 65 a master development tree.
66 66
67 67 If a rebase is interrupted to manually resolve a merge, it can be continued
68 68 with --continue or aborted with --abort.
69 69
70 70 options:
71 71
72 72 --keep keep original revisions
73 73 -s --source rebase from a given revision
74 74 -b --base rebase from the base of a given revision
75 75 -d --dest rebase onto a given revision
76 76 --collapse collapse the rebased revisions
77 77 -c --continue continue an interrupted rebase
78 78 -a --abort abort an interrupted rebase
79 79 --style display using template map file
80 80 --template display with template
81 81
82 82 use "hg -v help rebase" to show global options
83 83
84 84 % Use source and base
85 85 hg rebase: cannot specify both a revision and a base
86 86 hg rebase [-s rev | -b rev] [-d rev] [--collapse] | [-c] | [-a] | [--keep]
87 87
88 88 move changeset (and descendants) to a different branch
89 89
90 90 Rebase uses repeated merging to graft changesets from one part of history
91 91 onto another. This can be useful for linearizing local changes relative to
92 92 a master development tree.
93 93
94 94 If a rebase is interrupted to manually resolve a merge, it can be continued
95 95 with --continue or aborted with --abort.
96 96
97 97 options:
98 98
99 99 --keep keep original revisions
100 100 -s --source rebase from a given revision
101 101 -b --base rebase from the base of a given revision
102 102 -d --dest rebase onto a given revision
103 103 --collapse collapse the rebased revisions
104 104 -c --continue continue an interrupted rebase
105 105 -a --abort abort an interrupted rebase
106 106 --style display using template map file
107 107 --template display with template
108 108
109 109 use "hg -v help rebase" to show global options
110 110
111 111 % Rebase with no arguments - from current
112 112 nothing to rebase
113 113
114 114 % Rebase with no arguments - from the current branch
115 115 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
116 116 nothing to rebase
117 117 % ----------
118 118 % These work
119 119
120 120 % Rebase with no arguments (from 3 onto 7)
121 121 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
122 122 saving bundle to
123 123 adding branch
124 124 adding changesets
125 125 adding manifests
126 126 adding file changes
127 127 added 5 changesets with 5 changes to 5 files
128 128 rebase completed
129 % Try to rollback after a rebase (fail)
130 no rollback information available
129 131
130 132 % Rebase with base == '.' => same as no arguments (from 3 onto 7)
131 133 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
132 134 saving bundle to
133 135 adding branch
134 136 adding changesets
135 137 adding manifests
136 138 adding file changes
137 139 added 5 changesets with 5 changes to 5 files
138 140 rebase completed
139 141
140 142 % Rebase with dest == default => same as no arguments (from 3 onto 7)
141 143 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
142 144 saving bundle to
143 145 adding branch
144 146 adding changesets
145 147 adding manifests
146 148 adding file changes
147 149 added 5 changesets with 5 changes to 5 files
148 150 rebase completed
149 151
150 152 % Specify only source (from 4 onto 7)
151 153 saving bundle to
152 154 adding branch
153 155 adding changesets
154 156 adding manifests
155 157 adding file changes
156 158 added 4 changesets with 4 changes to 4 files (-1 heads)
157 159 rebase completed
158 160
159 161 % Specify only dest (from 3 onto 6)
160 162 3 files updated, 0 files merged, 3 files removed, 0 files unresolved
161 163 saving bundle to
162 164 adding branch
163 165 adding changesets
164 166 adding manifests
165 167 adding file changes
166 168 added 5 changesets with 5 changes to 5 files (+1 heads)
167 169 rebase completed
168 170
169 171 % Specify only base (from 3 onto 7)
170 172 saving bundle to
171 173 adding branch
172 174 adding changesets
173 175 adding manifests
174 176 adding file changes
175 177 added 5 changesets with 5 changes to 5 files
176 178 rebase completed
177 179
178 180 % Specify source and dest (from 4 onto 6)
179 181 saving bundle to
180 182 adding branch
181 183 adding changesets
182 184 adding manifests
183 185 adding file changes
184 186 added 4 changesets with 4 changes to 4 files
185 187 rebase completed
186 188
187 189 % Specify base and dest (from 3 onto 6)
188 190 saving bundle to
189 191 adding branch
190 192 adding changesets
191 193 adding manifests
192 194 adding file changes
193 195 added 5 changesets with 5 changes to 5 files (+1 heads)
194 196 rebase completed
General Comments 0
You need to be logged in to leave comments. Login now