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